<?xml version="1.0"?>
<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->


<!DOCTYPE page [
<!ENTITY % extensionsDTD SYSTEM "chrome://mozapps/locale/extensions/extensions.dtd">
%extensionsDTD;
]>

<!-- import-globals-from extensions.js -->

<bindings id="addonBindings"
          xmlns="http://www.mozilla.org/xbl"
          xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
          xmlns:xbl="http://www.mozilla.org/xbl">


  <!-- Rating - displays current/average rating, allows setting user rating -->
  <binding id="rating">
    <content>
      <xul:image class="star"
                 onmouseover="document.getBindingParent(this)._hover(1);"
                 onclick="document.getBindingParent(this).userRating = 1;"/>
      <xul:image class="star"
                 onmouseover="document.getBindingParent(this)._hover(2);"
                 onclick="document.getBindingParent(this).userRating = 2;"/>
      <xul:image class="star"
                 onmouseover="document.getBindingParent(this)._hover(3);"
                 onclick="document.getBindingParent(this).userRating = 3;"/>
      <xul:image class="star"
                 onmouseover="document.getBindingParent(this)._hover(4);"
                 onclick="document.getBindingParent(this).userRating = 4;"/>
      <xul:image class="star"
                 onmouseover="document.getBindingParent(this)._hover(5);"
                 onclick="document.getBindingParent(this).userRating = 5;"/>
    </content>

    <implementation>
      <constructor><![CDATA[
        this._updateStars();
      ]]></constructor>

      <property name="stars" readonly="true">
        <getter><![CDATA[
          return document.getAnonymousNodes(this);
        ]]></getter>
      </property>

      <property name="averageRating">
        <getter><![CDATA[
          if (this.hasAttribute("averagerating"))
            return this.getAttribute("averagerating");
          return -1;
        ]]></getter>
        <setter><![CDATA[
          this.setAttribute("averagerating", val);
          if (this.showRating == "average")
            this._updateStars();
        ]]></setter>
      </property>

      <property name="userRating">
        <getter><![CDATA[
          if (this.hasAttribute("userrating"))
            return this.getAttribute("userrating");
          return -1;
        ]]></getter>
        <setter><![CDATA[
          if (this.showRating != "user")
            return;
          this.setAttribute("userrating", val);
          if (this.showRating == "user")
            this._updateStars();
        ]]></setter>
      </property>

      <property name="showRating">
        <getter><![CDATA[
          if (this.hasAttribute("showrating"))
            return this.getAttribute("showrating");
          return "average";
        ]]></getter>
        <setter><![CDATA[
          if (val != "average" || val != "user")
            throw Components.Exception("Invalid value", Components.results.NS_ERROR_ILLEGAL_VALUE);
          this.setAttribute("showrating", val);
          this._updateStars();
        ]]></setter>
      </property>

      <method name="_updateStars">
        <body><![CDATA[
          var stars = this.stars;
          var rating = this[this.showRating + "Rating"];
          // average ratings can be non-whole numbers, round them so they
          // match to their closest star
          rating = Math.round(rating);
          for (let i = 0; i < stars.length; i++)
            stars[i].setAttribute("on", rating > i);
        ]]></body>
      </method>

      <method name="_hover">
        <parameter name="aScore"/>
        <body><![CDATA[
          if (this.showRating != "user")
            return;
          var stars = this.stars;
          for (let i = 0; i < stars.length; i++)
            stars[i].setAttribute("on", i <= (aScore -1));
        ]]></body>
      </method>

    </implementation>

    <handlers>
      <handler event="mouseout">
        this._updateStars();
      </handler>
    </handlers>
  </binding>

  <!-- Download progress - shows graphical progress of download and any
       related status message. -->
  <binding id="download-progress">
    <content>
      <xul:stack flex="1">
        <xul:hbox flex="1">
          <xul:hbox class="start-cap"/>
          <xul:progressmeter anonid="progress" class="progress" flex="1"
                             min="0" max="100"/>
          <xul:hbox class="end-cap"/>
        </xul:hbox>
        <xul:hbox class="status-container">
          <xul:spacer flex="1"/>
          <xul:label anonid="status" class="status"/>
          <xul:spacer flex="1"/>
          <xul:button anonid="cancel-btn" class="cancel"
                      tooltiptext="&progress.cancel.tooltip;"
                      oncommand="document.getBindingParent(this).cancel();"/>
        </xul:hbox>
      </xul:stack>
    </content>

    <implementation>
      <constructor><![CDATA[
        var progress = 0;
        if (this.hasAttribute("progress"))
          progress = parseInt(this.getAttribute("progress"));
        this.progress = progress;
      ]]></constructor>

      <field name="_progress">
        document.getAnonymousElementByAttribute(this, "anonid", "progress");
      </field>
      <field name="_cancel">
        document.getAnonymousElementByAttribute(this, "anonid", "cancel-btn");
      </field>
      <field name="_status">
        document.getAnonymousElementByAttribute(this, "anonid", "status");
      </field>

      <property name="progress">
        <getter><![CDATA[
          return this._progress.value;
        ]]></getter>
        <setter><![CDATA[
          this._progress.value = val;
          if (val == this._progress.max)
            this.setAttribute("complete", true);
          else
            this.removeAttribute("complete");
        ]]></setter>
      </property>

      <property name="maxProgress">
        <getter><![CDATA[
          return this._progress.max;
        ]]></getter>
        <setter><![CDATA[
          if (val == -1) {
            this._progress.mode = "undetermined";
          } else {
            this._progress.mode = "determined";
            this._progress.max = val;
          }
          this.setAttribute("mode", this._progress.mode);
        ]]></setter>
      </property>

      <property name="status">
        <getter><![CDATA[
          return this._status.value;
        ]]></getter>
        <setter><![CDATA[
          this._status.value = val;
        ]]></setter>
      </property>

      <method name="cancel">
        <body><![CDATA[
          this.mInstall.cancel();
        ]]></body>
      </method>
    </implementation>
  </binding>


  <!-- Sorters - displays and controls the sort state of a list. -->
  <binding id="sorters">
    <content orient="horizontal">
      <xul:button anonid="name-btn" class="sorter"
                  label="&sort.name.label;" tooltiptext="&sort.name.tooltip;"
                  oncommand="this.parentNode._handleChange('name');"/>
      <xul:button anonid="date-btn" class="sorter"
                  label="&sort.dateUpdated.label;"
                  tooltiptext="&sort.dateUpdated.tooltip;"
                  oncommand="this.parentNode._handleChange('updateDate');"/>
      <xul:button anonid="price-btn" class="sorter" hidden="true"
                  label="&sort.price.label;"
                  tooltiptext="&sort.price.tooltip;"
                  oncommand="this.parentNode._handleChange('purchaseAmount');"/>
      <xul:button anonid="relevance-btn" class="sorter" hidden="true"
                  label="&sort.relevance.label;"
                  tooltiptext="&sort.relevance.tooltip;"
                  oncommand="this.parentNode._handleChange('relevancescore');"/>
    </content>

    <implementation>
      <constructor><![CDATA[
        if (!this.hasAttribute("sortby"))
          this.setAttribute("sortby", "name");

        if (this.getAttribute("showrelevance") == "true")
          this._btnRelevance.hidden = false;

        if (this.getAttribute("showprice") == "true")
          this._btnPrice.hidden = false;

        this._refreshState();
      ]]></constructor>

      <field name="handler">null</field>
      <field name="_btnName">
        document.getAnonymousElementByAttribute(this, "anonid", "name-btn");
      </field>
      <field name="_btnDate">
        document.getAnonymousElementByAttribute(this, "anonid", "date-btn");
      </field>
      <field name="_btnPrice">
        document.getAnonymousElementByAttribute(this, "anonid", "price-btn");
      </field>
      <field name="_btnRelevance">
        document.getAnonymousElementByAttribute(this, "anonid", "relevance-btn");
      </field>

      <property name="sortBy">
        <getter><![CDATA[
          return this.getAttribute("sortby");
        ]]></getter>
        <setter><![CDATA[
          if (val != this.sortBy) {
            this.setAttribute("sortBy", val);
            this._refreshState();
          }
        ]]></setter>
      </property>

      <property name="ascending">
        <getter><![CDATA[
          return (this.getAttribute("ascending") == "true");
        ]]></getter>
        <setter><![CDATA[
          val = !!val;
          if (val != this.ascending) {
            this.setAttribute("ascending", val);
            this._refreshState();
          }
        ]]></setter>
      </property>

      <property name="showrelevance">
        <getter><![CDATA[
          return (this.getAttribute("showrelevance") == "true");
        ]]></getter>
        <setter><![CDATA[
          val = !!val;
          this.setAttribute("showrelevance", val);
          this._btnRelevance.hidden = !val;
        ]]></setter>
      </property>

      <property name="showprice">
        <getter><![CDATA[
          return (this.getAttribute("showprice") == "true");
        ]]></getter>
        <setter><![CDATA[
          val = !!val;
          this.setAttribute("showprice", val);
          this._btnPrice.hidden = !val;
        ]]></setter>
      </property>

      <method name="setSort">
        <parameter name="aSort"/>
        <parameter name="aAscending"/>
        <body><![CDATA[
          var sortChanged = false;
          if (aSort != this.sortBy) {
            this.setAttribute("sortby", aSort);
            sortChanged = true;
          }

          aAscending = !!aAscending;
          if (this.ascending != aAscending) {
            this.setAttribute("ascending", aAscending);
            sortChanged = true;
          }

          if (sortChanged)
            this._refreshState();
        ]]></body>
      </method>

      <method name="_handleChange">
        <parameter name="aSort"/>
        <body><![CDATA[
          const ASCENDING_SORT_FIELDS = ["name", "purchaseAmount"];

          // Toggle ascending if sort by is not changing, otherwise
          // name sorting defaults to ascending, others to descending
          if (aSort == this.sortBy)
            this.ascending = !this.ascending;
          else
            this.setSort(aSort, ASCENDING_SORT_FIELDS.indexOf(aSort) >= 0);
        ]]></body>
      </method>

      <method name="_refreshState">
        <body><![CDATA[
          var sortBy = this.sortBy;
          var checkState = this.ascending ? 2 : 1;

          if (sortBy == "name") {
            this._btnName.checkState = checkState;
            this._btnName.checked = true;
          } else {
            this._btnName.checkState = 0;
            this._btnName.checked = false;
          }

          if (sortBy == "updateDate") {
            this._btnDate.checkState = checkState;
            this._btnDate.checked = true;
          } else {
            this._btnDate.checkState = 0;
            this._btnDate.checked = false;
          }

          if (sortBy == "purchaseAmount") {
            this._btnPrice.checkState = checkState;
            this._btnPrice.checked = true;
          } else {
            this._btnPrice.checkState = 0;
            this._btnPrice.checked = false;
          }

          if (sortBy == "relevancescore") {
            this._btnRelevance.checkState = checkState;
            this._btnRelevance.checked = true;
          } else {
            this._btnRelevance.checkState = 0;
            this._btnRelevance.checked = false;
          }

          if (this.handler && "onSortChanged" in this.handler)
            this.handler.onSortChanged(sortBy, this.ascending);
        ]]></body>
      </method>
    </implementation>
  </binding>


  <!-- Categories list - displays the list of categories on the left pane. -->
  <binding id="categories-list"
           extends="chrome://global/content/bindings/richlistbox.xml#richlistbox">
    <implementation>
      <!-- This needs to be overridden to allow the fancy animation while not
           allowing that item to be selected when hiding.  -->
      <method name="_canUserSelect">
        <parameter name="aItem"/>
        <body>
        <![CDATA[
          if (aItem.hasAttribute("disabled") &&
              aItem.getAttribute("disabled") == "true")
            return false;
          var style = document.defaultView.getComputedStyle(aItem, "");
          return style.display != "none" && style.visibility == "visible";
        ]]>
        </body>
      </method>
    </implementation>
  </binding>


  <!-- Category item - an item in the category list. -->
  <binding id="category"
           extends="chrome://global/content/bindings/richlistbox.xml#richlistitem">
    <content align="center">
      <xul:image anonid="icon" class="category-icon"/>
      <xul:label anonid="name" class="category-name" flex="1" xbl:inherits="value=name"/>
      <xul:label anonid="badge" class="category-badge" xbl:inherits="value=count"/>
    </content>

    <implementation>
      <constructor><![CDATA[
        if (!this.hasAttribute("count"))
          this.setAttribute("count", 0);
      ]]></constructor>

      <property name="badgeCount">
        <getter><![CDATA[
          return this.getAttribute("count");
        ]]></getter>
        <setter><![CDATA[
          if (this.getAttribute("count") == val)
            return;

          this.setAttribute("count", val);
          var event = document.createEvent("Events");
          event.initEvent("CategoryBadgeUpdated", true, true);
          this.dispatchEvent(event);
        ]]></setter>
      </property>
    </implementation>
  </binding>


  <!-- Creator link - Name of a user/developer, providing a link if relevant. -->
  <binding id="creator-link">
    <content>
      <xul:label anonid="label" value="&addon.createdBy.label;"/>
      <xul:label anonid="creator-link" class="creator-link text-link"/>
      <xul:label anonid="creator-name" class="creator-name"/>
    </content>

    <implementation>
      <constructor><![CDATA[
        if (this.hasAttribute("nameonly") &&
            this.getAttribute("nameonly") == "true") {
          this._label.hidden = true;
        }
      ]]></constructor>

      <field name="_label">
        document.getAnonymousElementByAttribute(this, "anonid", "label");
      </field>
      <field name="_creatorLink">
        document.getAnonymousElementByAttribute(this, "anonid", "creator-link");
      </field>
      <field name="_creatorName">
        document.getAnonymousElementByAttribute(this, "anonid", "creator-name");
      </field>

      <method name="setCreator">
        <parameter name="aCreator"/>
        <parameter name="aHomepageURL"/>
        <body><![CDATA[
          if (!aCreator) {
            this.collapsed = true;
            return;
          }
          this.collapsed = false;
          var url = aCreator.url || aHomepageURL;
          var showLink = !!url;
          if (showLink) {
            this._creatorLink.value = aCreator.name;
            this._creatorLink.href = url;
          } else {
            this._creatorName.value = aCreator.name;
          }
          this._creatorLink.hidden = !showLink;
          this._creatorName.hidden = showLink;
        ]]></body>
      </method>
    </implementation>
  </binding>


  <!-- Install status - Displays the status of an install/upgrade. -->
  <binding id="install-status">
    <content>
      <xul:label anonid="message"/>
      <xul:progressmeter anonid="progress" class="download-progress"/>
      <xul:button anonid="purchase-remote-btn" hidden="true"
                  class="addon-control"
                  oncommand="document.getBindingParent(this).purchaseRemote();"/>
      <xul:button anonid="install-remote-btn" hidden="true"
                  class="addon-control install" label="&addon.install.label;"
                  tooltiptext="&addon.install.tooltip;"
                  oncommand="document.getBindingParent(this).installRemote();"/>
    </content>

    <implementation>
      <constructor><![CDATA[
        if (this.mInstall)
          this.initWithInstall(this.mInstall);
        else if (this.mControl.mAddon.install)
          this.initWithInstall(this.mControl.mAddon.install);
        else
          this.refreshState();
      ]]></constructor>

      <destructor><![CDATA[
        if (this.mInstall)
          this.mInstall.removeListener(this);
      ]]></destructor>

      <field name="_message">
        document.getAnonymousElementByAttribute(this, "anonid", "message");
      </field>
      <field name="_progress">
        document.getAnonymousElementByAttribute(this, "anonid", "progress");
      </field>
      <field name="_purchaseRemote">
        document.getAnonymousElementByAttribute(this, "anonid",
                                                "purchase-remote-btn");
      </field>
      <field name="_installRemote">
        document.getAnonymousElementByAttribute(this, "anonid",
                                                "install-remote-btn");
      </field>
      <field name="_restartNeeded">
        document.getAnonymousElementByAttribute(this, "anonid",
                                                "restart-needed");
      </field>
      <field name="_undo">
        document.getAnonymousElementByAttribute(this, "anonid",
                                                "undo-btn");
      </field>

      <method name="initWithInstall">
        <parameter name="aInstall"/>
        <body><![CDATA[
          if (this.mInstall) {
            this.mInstall.removeListener(this);
            this.mInstall = null;
          }
          this.mInstall = aInstall;
          this._progress.mInstall = aInstall;
          this.refreshState();
          this.mInstall.addListener(this);
        ]]></body>
      </method>

      <method name="refreshState">
        <body><![CDATA[
          var showInstallRemote = false;
          var showPurchase = false;

          if (this.mInstall) {

            switch (this.mInstall.state) {
              case AddonManager.STATE_AVAILABLE:
                if (this.mControl.getAttribute("remote") != "true")
                  break;

                this._progress.hidden = true;
                showInstallRemote = true;
                break;
              case AddonManager.STATE_DOWNLOADING:
                this.showMessage("installDownloading");
                break;
              case AddonManager.STATE_CHECKING:
                this.showMessage("installVerifying");
                break;
              case AddonManager.STATE_DOWNLOADED:
                this.showMessage("installDownloaded");
                break;
              case AddonManager.STATE_DOWNLOAD_FAILED:
                // XXXunf expose what error occured (bug 553487)
                this.showMessage("installDownloadFailed", true);
                break;
              case AddonManager.STATE_INSTALLING:
                this.showMessage("installInstalling");
                break;
              case AddonManager.STATE_INSTALL_FAILED:
                // XXXunf expose what error occured (bug 553487)
                this.showMessage("installFailed", true);
                break;
              case AddonManager.STATE_CANCELLED:
                this.showMessage("installCancelled", true);
                break;
            }

          } else if (this.mControl.mAddon.purchaseURL) {
            this._progress.hidden = true;
            showPurchase = true;
            this._purchaseRemote.label =
              gStrings.ext.formatStringFromName("addon.purchase.label",
                [this.mControl.mAddon.purchaseDisplayAmount], 1);
            this._purchaseRemote.tooltiptext =
              gStrings.ext.GetStringFromName("addon.purchase.tooltip");
          }

          this._purchaseRemote.hidden = !showPurchase;
          this._installRemote.hidden = !showInstallRemote;

          if ("refreshInfo" in this.mControl)
            this.mControl.refreshInfo();
        ]]></body>
      </method>

      <method name="showMessage">
        <parameter name="aMsgId"/>
        <parameter name="aHideProgress"/>
        <body><![CDATA[
          this._message.setAttribute("hidden", !aHideProgress);
          this._progress.setAttribute("hidden", !!aHideProgress);

          var msg = gStrings.ext.GetStringFromName(aMsgId);
          if (aHideProgress)
            this._message.value = msg;
          else
            this._progress.status = msg;
        ]]></body>
      </method>

      <method name="purchaseRemote">
        <body><![CDATA[
          openURL(this.mControl.mAddon.purchaseURL);
        ]]></body>
      </method>

      <method name="installRemote">
        <body><![CDATA[
          if (this.mControl.getAttribute("remote") != "true")
            return;

          if (this.mControl.mAddon.eula) {
            var data = {
              addon: this.mControl.mAddon,
              accepted: false
            };
            window.openDialog("chrome://mozapps/content/extensions/eula.xul", "_blank",
                              "chrome,dialog,modal,centerscreen,resizable=no", data);
            if (!data.accepted)
              return;
          }

          delete this.mControl.mAddon;
          this.mControl.mInstall = this.mInstall;
          this.mControl.setAttribute("status", "installing");
          this.mInstall.install();
        ]]></body>
      </method>

      <method name="undoAction">
        <body><![CDATA[
          if (!this.mAddon)
            return;
          var pending = this.mAddon.pendingOperations;
          if (pending & AddonManager.PENDING_ENABLE)
            this.mAddon.userDisabled = true;
          else if (pending & AddonManager.PENDING_DISABLE)
            this.mAddon.userDisabled = false;
          this.refreshState();
        ]]></body>
      </method>

      <method name="onDownloadStarted">
        <body><![CDATA[
          this.refreshState();
        ]]></body>
      </method>

      <method name="onDownloadEnded">
        <body><![CDATA[
          this.refreshState();
        ]]></body>
      </method>

      <method name="onDownloadFailed">
        <body><![CDATA[
          this.refreshState();
        ]]></body>
      </method>

      <method name="onDownloadProgress">
        <body><![CDATA[
          this._progress.maxProgress = this.mInstall.maxProgress;
          this._progress.progress = this.mInstall.progress;
        ]]></body>
      </method>

      <method name="onInstallStarted">
        <body><![CDATA[
          this._progress.progress = 0;
          this.refreshState();
        ]]></body>
      </method>

      <method name="onInstallEnded">
        <body><![CDATA[
          this.refreshState();
          if ("onInstallCompleted" in this.mControl)
            this.mControl.onInstallCompleted();
        ]]></body>
      </method>

      <method name="onInstallFailed">
        <body><![CDATA[
          this.refreshState();
        ]]></body>
      </method>
    </implementation>
  </binding>


  <!-- Addon - base - parent binding of any item representing an addon. -->
  <binding id="addon-base"
           extends="chrome://global/content/bindings/richlistbox.xml#richlistitem">
    <implementation>
      <method name="hasPermission">
        <parameter name="aPerm"/>
        <body><![CDATA[
          var perm = AddonManager["PERM_CAN_" + aPerm.toUpperCase()];
          return !!(this.mAddon.permissions & perm);
        ]]></body>
      </method>

      <method name="opRequiresRestart">
        <parameter name="aOperation"/>
        <body><![CDATA[
          var operation = AddonManager["OP_NEEDS_RESTART_" + aOperation.toUpperCase()];
          return !!(this.mAddon.operationsRequiringRestart & operation);
        ]]></body>
      </method>

      <method name="isPending">
        <parameter name="aAction"/>
        <body><![CDATA[
          var action = AddonManager["PENDING_" + aAction.toUpperCase()];
          return !!(this.mAddon.pendingOperations & action);
        ]]></body>
      </method>

      <method name="typeHasFlag">
        <parameter name="aFlag"/>
        <body><![CDATA[
          let flag = AddonManager["TYPE_" + aFlag];
          let type = AddonManager.addonTypes[this.mAddon.type];

          return !!(type.flags & flag);
        ]]></body>
      </method>

      <method name="onUninstalled">
        <body><![CDATA[
          this.parentNode.removeChild(this);
        ]]></body>
      </method>
    </implementation>
  </binding>


  <!-- Addon - generic - A normal addon item, or an update to one -->
  <binding id="addon-generic"
           extends="chrome://mozapps/content/extensions/extensions.xml#addon-base">
    <content>
      <xul:hbox anonid="warning-container"
                class="warning">
        <xul:image class="warning-icon"/>
        <xul:label anonid="warning" flex="1"/>
        <xul:label anonid="warning-link" class="text-link"/>
        <xul:button anonid="warning-btn" class="button-link" hidden="true"/>
        <xul:spacer flex="5000"/> <!-- Necessary to allow the message to wrap -->
      </xul:hbox>
      <xul:hbox anonid="error-container"
                class="error">
        <xul:image class="error-icon"/>
        <xul:label anonid="error" flex="1"/>
        <xul:label anonid="error-link" class="text-link" hidden="true"/>
        <xul:spacer flex="5000"/> <!-- Necessary to allow the message to wrap -->
      </xul:hbox>
      <xul:hbox anonid="pending-container"
                class="pending">
        <xul:image class="pending-icon"/>
        <xul:label anonid="pending" flex="1"/>
        <xul:button anonid="restart-btn" class="button-link"
                    label="&addon.restartNow.label;"
                    oncommand="document.getBindingParent(this).restart();"/>
        <xul:button anonid="undo-btn" class="button-link"
                    label="&addon.undoAction.label;"
                    tooltipText="&addon.undoAction.tooltip;"
                    oncommand="document.getBindingParent(this).undo();"/>
        <xul:spacer flex="5000"/> <!-- Necessary to allow the message to wrap -->
      </xul:hbox>

      <xul:hbox class="content-container" align="center">
        <xul:vbox class="icon-container">
          <xul:image anonid="icon" class="icon"/>
        </xul:vbox>
        <xul:vbox class="content-inner-container" flex="1">
          <xul:hbox class="basicinfo-container">
              <xul:hbox class="name-container">
                <xul:label anonid="name" class="name" crop="end" flex="1"
                           tooltip="addonitem-tooltip" xbl:inherits="value=name"/>
                <xul:label class="disabled-postfix" value="&addon.disabled.postfix;"/>
                <xul:label class="update-postfix" value="&addon.update.postfix;"/>
                <xul:spacer flex="5000"/> <!-- Necessary to make the name crop -->
              </xul:hbox>
            <xul:label anonid="date-updated" class="date-updated"
                       unknown="&addon.unknownDate;"/>
          </xul:hbox>
          <xul:hbox class="experiment-container">
            <svg width="6" height="6" viewBox="0 0 6 6" version="1.1"
                 xmlns="http://www.w3.org/2000/svg"
                 class="experiment-bullet-container">
              <circle cx="3" cy="3" r="3" class="experiment-bullet"/>
            </svg>
            <xul:label anonid="experiment-state" class="experiment-state"/>
            <xul:label anonid="experiment-time" class="experiment-time"/>
          </xul:hbox>

          <xul:hbox class="advancedinfo-container" flex="1">
            <xul:vbox class="description-outer-container" flex="1">
              <xul:hbox class="description-container">
                <xul:label anonid="description" class="description" crop="end" flex="1"/>
                <xul:button anonid="details-btn" class="details button-link"
                            label="&addon.details.label;"
                            tooltiptext="&addon.details.tooltip;"
                            oncommand="document.getBindingParent(this).showInDetailView();"/>
                <xul:spacer flex="5000"/> <!-- Necessary to make the description crop -->
              </xul:hbox>
              <xul:vbox anonid="relnotes-container" class="relnotes-container">
                <xul:label class="relnotes-header" value="&addon.releaseNotes.label;"/>
                <xul:label anonid="relnotes-loading" value="&addon.loadingReleaseNotes.label;"/>
                <xul:label anonid="relnotes-error" hidden="true"
                           value="&addon.errorLoadingReleaseNotes.label;"/>
                <xul:vbox anonid="relnotes" class="relnotes"/>
              </xul:vbox>
              <xul:hbox class="relnotes-toggle-container">
                <xul:button anonid="relnotes-toggle-btn" class="relnotes-toggle"
                            hidden="true" label="&cmd.showReleaseNotes.label;"
                            tooltiptext="&cmd.showReleaseNotes.tooltip;"
                            showlabel="&cmd.showReleaseNotes.label;"
                            showtooltip="&cmd.showReleaseNotes.tooltip;"
                            hidelabel="&cmd.hideReleaseNotes.label;"
                            hidetooltip="&cmd.hideReleaseNotes.tooltip;"
                            oncommand="document.getBindingParent(this).toggleReleaseNotes();"/>
              </xul:hbox>
            </xul:vbox>
          </xul:hbox>
        </xul:vbox>
        <xul:vbox class="status-control-wrapper">
          <xul:hbox class="status-container">
            <xul:hbox anonid="checking-update" hidden="true">
              <xul:image class="spinner"/>
              <xul:label value="&addon.checkingForUpdates.label;"/>
            </xul:hbox>
            <xul:vbox anonid="update-available" class="update-available"
                      hidden="true">
              <xul:checkbox anonid="include-update" class="include-update"
                            label="&addon.includeUpdate.label;" checked="true"
                            oncommand="document.getBindingParent(this).onIncludeUpdateChanged();"/>
              <xul:hbox class="update-info-container">
                <xul:label class="update-available-notice"
                           value="&addon.updateAvailable.label;"/>
                <xul:button anonid="update-btn" class="addon-control update"
                            label="&addon.updateNow.label;"
                            tooltiptext="&addon.updateNow.tooltip;"
                            oncommand="document.getBindingParent(this).upgrade();"/>
              </xul:hbox>
            </xul:vbox>
            <xul:hbox anonid="install-status" class="install-status"
                      hidden="true"/>
          </xul:hbox>
          <xul:hbox anonid="control-container" class="control-container">
            <xul:button anonid="preferences-btn"
                        class="addon-control preferences"
                        label="&cmd.showPreferencesUnix.label;"
                        tooltiptext="&cmd.showPreferencesUnix.tooltip;"
                        oncommand="document.getBindingParent(this).showPreferences();"/>
            <xul:button anonid="enable-btn"  class="addon-control enable"
                        label="&cmd.enableAddon.label;"
                        oncommand="document.getBindingParent(this).userDisabled = false;"/>
            <xul:button anonid="disable-btn" class="addon-control disable"
                        label="&cmd.disableAddon.label;"
                        oncommand="document.getBindingParent(this).userDisabled = true;"/>
            <xul:button anonid="remove-btn" class="addon-control remove"
                        label="&cmd.uninstallAddon.label;"
                        oncommand="document.getBindingParent(this).uninstall();"/>
            <xul:menulist anonid="state-menulist"
                          class="addon-control state"
                          tooltiptext="&cmd.stateMenu.tooltip;">
              <xul:menupopup>
                <xul:menuitem anonid="ask-to-activate-menuitem"
                              class="addon-control"
                              label="&cmd.askToActivate.label;"
                              tooltiptext="&cmd.askToActivate.tooltip;"
                              oncommand="document.getBindingParent(this).userDisabled = AddonManager.STATE_ASK_TO_ACTIVATE;"/>
                <xul:menuitem anonid="always-activate-menuitem"
                              class="addon-control"
                              label="&cmd.alwaysActivate.label;"
                              tooltiptext="&cmd.alwaysActivate.tooltip;"
                              oncommand="document.getBindingParent(this).userDisabled = false;"/>
                <xul:menuitem anonid="never-activate-menuitem"
                              class="addon-control"
                              label="&cmd.neverActivate.label;"
                              tooltiptext="&cmd.neverActivate.tooltip;"
                              oncommand="document.getBindingParent(this).userDisabled = true;"/>
              </xul:menupopup>
            </xul:menulist>
          </xul:hbox>
        </xul:vbox>
      </xul:hbox>
    </content>

    <implementation>
      <constructor><![CDATA[
        this._installStatus = document.getAnonymousElementByAttribute(this, "anonid", "install-status");
        this._installStatus.mControl = this;

        this.setAttribute("contextmenu", "addonitem-popup");

        this._showStatus("none");

        this._initWithAddon(this.mAddon);

        gEventManager.registerAddonListener(this, this.mAddon.id);
      ]]></constructor>

      <destructor><![CDATA[
        gEventManager.unregisterAddonListener(this, this.mAddon.id);
      ]]></destructor>

      <field name="_warningContainer">
        document.getAnonymousElementByAttribute(this, "anonid",
                                                "warning-container");
      </field>
      <field name="_warning">
        document.getAnonymousElementByAttribute(this, "anonid",
                                                "warning");
      </field>
      <field name="_warningLink">
        document.getAnonymousElementByAttribute(this, "anonid",
                                                "warning-link");
      </field>
      <field name="_warningBtn">
        document.getAnonymousElementByAttribute(this, "anonid",
                                                "warning-btn");
      </field>
      <field name="_errorContainer">
        document.getAnonymousElementByAttribute(this, "anonid",
                                                "error-container");
      </field>
      <field name="_error">
        document.getAnonymousElementByAttribute(this, "anonid",
                                                "error");
      </field>
      <field name="_errorLink">
        document.getAnonymousElementByAttribute(this, "anonid",
                                                "error-link");
      </field>
      <field name="_pendingContainer">
        document.getAnonymousElementByAttribute(this, "anonid",
                                                "pending-container");
      </field>
      <field name="_pending">
        document.getAnonymousElementByAttribute(this, "anonid",
                                                "pending");
      </field>
      <field name="_infoContainer">
        document.getAnonymousElementByAttribute(this, "anonid",
                                                "info-container");
      </field>
      <field name="_info">
        document.getAnonymousElementByAttribute(this, "anonid",
                                                "info");
      </field>
      <field name="_experimentState">
        document.getAnonymousElementByAttribute(this, "anonid", "experiment-state");
      </field>
      <field name="_experimentTime">
        document.getAnonymousElementByAttribute(this, "anonid", "experiment-time");
      </field>
      <field name="_icon">
        document.getAnonymousElementByAttribute(this, "anonid", "icon");
      </field>
      <field name="_dateUpdated">
        document.getAnonymousElementByAttribute(this, "anonid",
                                                "date-updated");
      </field>
      <field name="_description">
        document.getAnonymousElementByAttribute(this, "anonid",
                                                "description");
      </field>
      <field name="_stateMenulist">
        document.getAnonymousElementByAttribute(this, "anonid",
                                                "state-menulist");
      </field>
      <field name="_askToActivateMenuitem">
        document.getAnonymousElementByAttribute(this, "anonid",
                                                "ask-to-activate-menuitem");
      </field>
      <field name="_alwaysActivateMenuitem">
        document.getAnonymousElementByAttribute(this, "anonid",
                                                "always-activate-menuitem");
      </field>
      <field name="_neverActivateMenuitem">
        document.getAnonymousElementByAttribute(this, "anonid",
                                                "never-activate-menuitem");
      </field>
      <field name="_preferencesBtn">
        document.getAnonymousElementByAttribute(this, "anonid",
                                                "preferences-btn");
      </field>
      <field name="_enableBtn">
        document.getAnonymousElementByAttribute(this, "anonid",
                                                "enable-btn");
      </field>
      <field name="_disableBtn">
        document.getAnonymousElementByAttribute(this, "anonid",
                                                "disable-btn");
      </field>
      <field name="_removeBtn">
        document.getAnonymousElementByAttribute(this, "anonid",
                                                "remove-btn");
      </field>
      <field name="_updateBtn">
        document.getAnonymousElementByAttribute(this, "anonid",
                                                "update-btn");
      </field>
      <field name="_controlContainer">
        document.getAnonymousElementByAttribute(this, "anonid",
                                                "control-container");
      </field>
      <field name="_installStatus">
        document.getAnonymousElementByAttribute(this, "anonid",
                                                "install-status");
      </field>
      <field name="_checkingUpdate">
        document.getAnonymousElementByAttribute(this, "anonid",
                                                "checking-update");
      </field>
      <field name="_updateAvailable">
        document.getAnonymousElementByAttribute(this, "anonid",
                                                "update-available");
      </field>
      <field name="_includeUpdate">
        document.getAnonymousElementByAttribute(this, "anonid",
                                                "include-update");
      </field>
      <field name="_relNotesLoaded">false</field>
      <field name="_relNotesToggle">
        document.getAnonymousElementByAttribute(this, "anonid",
                                                "relnotes-toggle-btn");
      </field>
      <field name="_relNotesLoading">
        document.getAnonymousElementByAttribute(this, "anonid",
                                                "relnotes-loading");
      </field>
      <field name="_relNotesError">
        document.getAnonymousElementByAttribute(this, "anonid",
                                                "relnotes-error");
      </field>
      <field name="_relNotesContainer">
        document.getAnonymousElementByAttribute(this, "anonid",
                                                "relnotes-container");
      </field>
      <field name="_relNotes">
        document.getAnonymousElementByAttribute(this, "anonid",
                                                "relnotes");
      </field>

      <property name="userDisabled">
        <getter><![CDATA[
          return this.mAddon.userDisabled;
        ]]></getter>
        <setter><![CDATA[
          this.mAddon.userDisabled = val;
        ]]></setter>
      </property>

      <property name="includeUpdate">
        <getter><![CDATA[
          return this._includeUpdate.checked && !!this.mManualUpdate;
        ]]></getter>
        <setter><![CDATA[
          // XXXunf Eventually, we'll want to persist this for individual
          //        updates - see bug 594619.
          this._includeUpdate.checked = !!val;
        ]]></setter>
      </property>

      <method name="_initWithAddon">
        <parameter name="aAddon"/>
        <body><![CDATA[
          this.mAddon = aAddon;

          this._installStatus.mAddon = this.mAddon;
          this._updateDates();
          this._updateState();

          this.setAttribute("name", aAddon.name);

          var iconURL = AddonManager.getPreferredIconURL(aAddon, 48, window);
          if (iconURL)
            this._icon.src = iconURL;
          else
            this._icon.src = "";

          if (this.mAddon.description)
            this._description.value = this.mAddon.description;
          else
            this._description.hidden = true;

          if (!("applyBackgroundUpdates" in this.mAddon) ||
              (this.mAddon.applyBackgroundUpdates == AddonManager.AUTOUPDATE_DISABLE ||
               (this.mAddon.applyBackgroundUpdates == AddonManager.AUTOUPDATE_DEFAULT &&
                !AddonManager.autoUpdateDefault))) {
            AddonManager.getAllInstalls(aInstallsList => {
              // This can return after the binding has been destroyed,
              // so try to detect that and return early
              if (!("onNewInstall" in this))
                return;
              for (let install of aInstallsList) {
                if (install.existingAddon &&
                    install.existingAddon.id == this.mAddon.id &&
                    install.state == AddonManager.STATE_AVAILABLE) {
                  this.onNewInstall(install);
                  this.onIncludeUpdateChanged();
                }
              }
            });
          }
        ]]></body>
      </method>

      <method name="_showStatus">
        <parameter name="aType"/>
        <body><![CDATA[
          this._controlContainer.hidden = aType != "none" &&
                                          !(aType == "update-available" && !this.hasAttribute("upgrade"));

          this._installStatus.hidden = aType != "progress";
          if (aType == "progress")
            this._installStatus.refreshState();
          this._checkingUpdate.hidden = aType != "checking-update";
          this._updateAvailable.hidden = aType != "update-available";
          this._relNotesToggle.hidden = !(this.mManualUpdate ?
                                          this.mManualUpdate.releaseNotesURI :
                                          this.mAddon.releaseNotesURI);
        ]]></body>
      </method>

      <method name="_updateDates">
        <body><![CDATA[
          function formatDate(aDate) {
            const locale = Components.classes["@mozilla.org/chrome/chrome-registry;1"]
                           .getService(Components.interfaces.nsIXULChromeRegistry)
                           .getSelectedLocale("global", true);
            const dtOptions = { year: 'numeric', month: 'long', day: 'numeric' };
            return aDate.toLocaleDateString(locale, dtOptions);
          }

          if (this.mAddon.updateDate)
            this._dateUpdated.value = formatDate(this.mAddon.updateDate);
          else
            this._dateUpdated.value = this._dateUpdated.getAttribute("unknown");
        ]]></body>
      </method>

      <method name="_updateState">
        <body><![CDATA[
          if (this.parentNode.selectedItem == this)
            gViewController.updateCommands();

          var pending = this.mAddon.pendingOperations;
          if (pending != AddonManager.PENDING_NONE) {
            this.removeAttribute("notification");

            pending = null;
            const PENDING_OPERATIONS = ["enable", "disable", "install",
                                        "uninstall", "upgrade"];
            for (let op of PENDING_OPERATIONS) {
              if (this.isPending(op))
                pending = op;
            }

            this.setAttribute("pending", pending);
            this._pending.textContent = gStrings.ext.formatStringFromName(
              "notification." + pending,
              [this.mAddon.name, gStrings.brandShortName], 2
            );
          } else {
            this.removeAttribute("pending");

            var isUpgrade = this.hasAttribute("upgrade");
            var install = this._installStatus.mInstall;

            if (install && install.state == AddonManager.STATE_DOWNLOAD_FAILED) {
              this.setAttribute("notification", "warning");
              this._warning.textContent = gStrings.ext.formatStringFromName(
                "notification.downloadError",
                [this.mAddon.name], 1
              );
              this._warningBtn.label = gStrings.ext.GetStringFromName("notification.downloadError.retry");
              this._warningBtn.tooltipText = gStrings.ext.GetStringFromName("notification.downloadError.retry.tooltip");
              this._warningBtn.setAttribute("oncommand", "document.getBindingParent(this).retryInstall();");
              this._warningBtn.hidden = false;
              this._warningLink.hidden = true;
            } else if (install && install.state == AddonManager.STATE_INSTALL_FAILED) {
              this.setAttribute("notification", "warning");
              this._warning.textContent = gStrings.ext.formatStringFromName(
                "notification.installError",
                [this.mAddon.name], 1
              );
              this._warningBtn.label = gStrings.ext.GetStringFromName("notification.installError.retry");
              this._warningBtn.tooltipText = gStrings.ext.GetStringFromName("notification.downloadError.retry.tooltip");
              this._warningBtn.setAttribute("oncommand", "document.getBindingParent(this).retryInstall();");
              this._warningBtn.hidden = false;
              this._warningLink.hidden = true;
            } else if (!isUpgrade && this.mAddon.blocklistState == Ci.nsIBlocklistService.STATE_BLOCKED) {
              this.setAttribute("notification", "error");
              this._error.textContent = gStrings.ext.formatStringFromName(
                "notification.blocked",
                [this.mAddon.name], 1
              );
              this._errorLink.value = gStrings.ext.GetStringFromName("notification.blocked.link");
              this._errorLink.href = this.mAddon.blocklistURL;
              this._errorLink.hidden = false;
            } else if (!isUpgrade && !isCorrectlySigned(this.mAddon) && SIGNING_REQUIRED) {
              this.setAttribute("notification", "error");
              this._error.textContent = gStrings.ext.formatStringFromName(
                "notification.unsignedAndDisabled", [this.mAddon.name, gStrings.brandShortName], 2
              );
              this._errorLink.value = gStrings.ext.GetStringFromName("notification.unsigned.link");
              this._errorLink.href = Services.urlFormatter.formatURLPref("app.support.baseURL") + "unsigned-addons";
              this._errorLink.hidden = false;
            } else if ((!isUpgrade && !this.mAddon.isCompatible) && (AddonManager.checkCompatibility
            || (this.mAddon.blocklistState != Ci.nsIBlocklistService.STATE_SOFTBLOCKED))) {
              this.setAttribute("notification", "warning");
              this._warning.textContent = gStrings.ext.formatStringFromName(
                "notification.incompatible",
                [this.mAddon.name, gStrings.brandShortName, gStrings.appVersion], 3
              );
              this._warningLink.hidden = true;
              this._warningBtn.hidden = true;
            } else if (!isUpgrade && !isCorrectlySigned(this.mAddon)) {
              this.setAttribute("notification", "warning");
              this._warning.textContent = gStrings.ext.formatStringFromName(
                "notification.unsigned", [this.mAddon.name, gStrings.brandShortName], 2
              );
              this._warningLink.value = gStrings.ext.GetStringFromName("notification.unsigned.link");
              this._warningLink.href = Services.urlFormatter.formatURLPref("app.support.baseURL") + "unsigned-addons";
              this._warningLink.hidden = false;
            } else if (!isUpgrade && this.mAddon.blocklistState == Ci.nsIBlocklistService.STATE_SOFTBLOCKED) {
              this.setAttribute("notification", "warning");
              this._warning.textContent = gStrings.ext.formatStringFromName(
                "notification.softblocked",
                [this.mAddon.name], 1
              );
              this._warningLink.value = gStrings.ext.GetStringFromName("notification.softblocked.link");
              this._warningLink.href = this.mAddon.blocklistURL;
              this._warningLink.hidden = false;
              this._warningBtn.hidden = true;
            } else if (!isUpgrade && this.mAddon.blocklistState == Ci.nsIBlocklistService.STATE_OUTDATED) {
              this.setAttribute("notification", "warning");
              this._warning.textContent = gStrings.ext.formatStringFromName(
                "notification.outdated",
                [this.mAddon.name], 1
              );
              this._warningLink.value = gStrings.ext.GetStringFromName("notification.outdated.link");
              this._warningLink.href = this.mAddon.blocklistURL;
              this._warningLink.hidden = false;
              this._warningBtn.hidden = true;
            } else if (!isUpgrade && this.mAddon.blocklistState == Ci.nsIBlocklistService.STATE_VULNERABLE_UPDATE_AVAILABLE) {
              this.setAttribute("notification", "error");
              this._error.textContent = gStrings.ext.formatStringFromName(
                "notification.vulnerableUpdatable",
                [this.mAddon.name], 1
              );
              this._errorLink.value = gStrings.ext.GetStringFromName("notification.vulnerableUpdatable.link");
              this._errorLink.href = this.mAddon.blocklistURL;
              this._errorLink.hidden = false;
            } else if (!isUpgrade && this.mAddon.blocklistState == Ci.nsIBlocklistService.STATE_VULNERABLE_NO_UPDATE) {
              this.setAttribute("notification", "error");
              this._error.textContent = gStrings.ext.formatStringFromName(
                "notification.vulnerableNoUpdate",
                [this.mAddon.name], 1
              );
              this._errorLink.value = gStrings.ext.GetStringFromName("notification.vulnerableNoUpdate.link");
              this._errorLink.href = this.mAddon.blocklistURL;
              this._errorLink.hidden = false;
            } else if (this.mAddon.isGMPlugin && !this.mAddon.isInstalled &&
                       this.mAddon.isActive) {
              this.setAttribute("notification", "warning");
              this._warning.textContent =
                gStrings.ext.formatStringFromName("notification.gmpPending",
                                                  [this.mAddon.name], 1);
            } else {
              this.removeAttribute("notification");
            }
          }

          this._preferencesBtn.hidden = (!this.mAddon.optionsURL) ||
                                        this.mAddon.optionsType == AddonManager.OPTIONS_TYPE_INLINE_INFO;

          if (this.typeHasFlag("SUPPORTS_ASK_TO_ACTIVATE")) {
            this._enableBtn.disabled = true;
            this._disableBtn.disabled = true;
            this._askToActivateMenuitem.disabled = !this.hasPermission("ask_to_activate");
            this._alwaysActivateMenuitem.disabled = !this.hasPermission("enable");
            this._neverActivateMenuitem.disabled = !this.hasPermission("disable");
            if (!this.mAddon.isActive) {
              this._stateMenulist.selectedItem = this._neverActivateMenuitem;
            } else if (this.mAddon.userDisabled == AddonManager.STATE_ASK_TO_ACTIVATE) {
              this._stateMenulist.selectedItem = this._askToActivateMenuitem;
            } else {
              this._stateMenulist.selectedItem = this._alwaysActivateMenuitem;
            }
            let hasActivatePermission =
              ["ask_to_activate", "enable", "disable"].some(perm => this.hasPermission(perm));
            this._stateMenulist.disabled = !hasActivatePermission;
            this._stateMenulist.hidden = false;
            this._stateMenulist.classList.add('no-auto-hide');
          } else {
            this._stateMenulist.hidden = true;

            let enableTooltip = gViewController.commands["cmd_enableItem"]
                                               .getTooltip(this.mAddon);
            this._enableBtn.setAttribute("tooltiptext", enableTooltip);
            if (this.hasPermission("enable")) {
              this._enableBtn.hidden = false;
            } else {
              this._enableBtn.hidden = true;
            }

            let disableTooltip = gViewController.commands["cmd_disableItem"]
                                                .getTooltip(this.mAddon);
            this._disableBtn.setAttribute("tooltiptext", disableTooltip);
            if (this.hasPermission("disable")) {
              this._disableBtn.hidden = false;
            } else {
              this._disableBtn.hidden = true;
            }
          }

          let uninstallTooltip = gViewController.commands["cmd_uninstallItem"]
                                                .getTooltip(this.mAddon);
          this._removeBtn.setAttribute("tooltiptext", uninstallTooltip);
          if (this.hasPermission("uninstall")) {
            this._removeBtn.hidden = false;
          } else {
            this._removeBtn.hidden = true;
          }

          this.setAttribute("active", this.mAddon.isActive);

          var showProgress = this.mAddon.purchaseURL || (this.mAddon.install &&
                             this.mAddon.install.state != AddonManager.STATE_INSTALLED);
          this._showStatus(showProgress ? "progress" : "none");

          if (this.mAddon.type == "experiment") {
            this.removeAttribute("notification");
            let prefix = "experiment.";
            let active = this.mAddon.isActive;

            if (!showProgress) {
              let stateKey = prefix + "state." + (active ? "active" : "complete");
              this._experimentState.value = gStrings.ext.GetStringFromName(stateKey);

              let now = Date.now();
              let end = this.endDate;
              let days = Math.abs(end - now) / (24 * 60 * 60 * 1000);

              let timeKey = prefix + "time.";
              let timeMessage;

              if (days < 1) {
                timeKey += (active ? "endsToday" : "endedToday");
                timeMessage = gStrings.ext.GetStringFromName(timeKey);
              } else {
                timeKey += (active ? "daysRemaining" : "daysPassed");
                days = Math.round(days);
                let timeString = gStrings.ext.GetStringFromName(timeKey);
                timeMessage = PluralForm.get(days, timeString)
                                        .replace("#1", days);
              }

              this._experimentTime.value = timeMessage;
            }
          }
        ]]></body>
      </method>

      <method name="_fetchReleaseNotes">
        <parameter name="aURI"/>
        <body><![CDATA[
          if (!aURI || this._relNotesLoaded) {
            sendToggleEvent();
            return;
          }

          var relNotesData = null, transformData = null;

          this._relNotesLoaded = true;
          this._relNotesLoading.hidden = false;
          this._relNotesError.hidden = true;

          let sendToggleEvent = () => {
            var event = document.createEvent("Events");
            event.initEvent("RelNotesToggle", true, true);
            this.dispatchEvent(event);
          }

          let showRelNotes = () => {
            if (!relNotesData || !transformData)
              return;

            this._relNotesLoading.hidden = true;

            var processor = Components.classes["@mozilla.org/document-transformer;1?type=xslt"]
                                      .createInstance(Components.interfaces.nsIXSLTProcessor);
            processor.flags |= Components.interfaces.nsIXSLTProcessorPrivate.DISABLE_ALL_LOADS;

            processor.importStylesheet(transformData);
            var fragment = processor.transformToFragment(relNotesData, document);
            this._relNotes.appendChild(fragment);
            if (this.hasAttribute("show-relnotes")) {
              var container = this._relNotesContainer;
              container.style.height = container.scrollHeight + "px";
            }
            sendToggleEvent();
          }

          let handleError = () => {
            dataReq.abort();
            styleReq.abort();
            this._relNotesLoading.hidden = true;
            this._relNotesError.hidden = false;
            this._relNotesLoaded = false; // allow loading to be re-tried
            sendToggleEvent();
          }

          function handleResponse(aEvent) {
            var req = aEvent.target;
            var ct = req.getResponseHeader("content-type");
            if ((!ct || ct.indexOf("text/html") < 0) &&
                req.responseXML &&
                req.responseXML.documentElement.namespaceURI != XMLURI_PARSE_ERROR) {
              if (req == dataReq)
                relNotesData = req.responseXML;
              else
                transformData = req.responseXML;
              showRelNotes();
            } else {
              handleError();
            }
          }

          var dataReq = Components.classes["@mozilla.org/xmlextras/xmlhttprequest;1"]
                              .createInstance(Components.interfaces.nsIXMLHttpRequest);
          dataReq.open("GET", aURI.spec, true);
          dataReq.responseType = "document";
          dataReq.addEventListener("load", handleResponse, false);
          dataReq.addEventListener("error", handleError, false);
          dataReq.send(null);

          var styleReq = Components.classes["@mozilla.org/xmlextras/xmlhttprequest;1"]
                              .createInstance(Components.interfaces.nsIXMLHttpRequest);
          styleReq.open("GET", UPDATES_RELEASENOTES_TRANSFORMFILE, true);
          styleReq.responseType = "document";
          styleReq.addEventListener("load", handleResponse, false);
          styleReq.addEventListener("error", handleError, false);
          styleReq.send(null);
        ]]></body>
      </method>

      <method name="toggleReleaseNotes">
        <body><![CDATA[
          if (this.hasAttribute("show-relnotes")) {
            this._relNotesContainer.style.height = "0px";
            this.removeAttribute("show-relnotes");
            this._relNotesToggle.setAttribute(
              "label",
              this._relNotesToggle.getAttribute("showlabel")
            );
            this._relNotesToggle.setAttribute(
              "tooltiptext",
              this._relNotesToggle.getAttribute("showtooltip")
            );
            var event = document.createEvent("Events");
            event.initEvent("RelNotesToggle", true, true);
            this.dispatchEvent(event);
          } else {
            this._relNotesContainer.style.height = this._relNotesContainer.scrollHeight +
                                                   "px";
            this.setAttribute("show-relnotes", true);
            this._relNotesToggle.setAttribute(
              "label",
              this._relNotesToggle.getAttribute("hidelabel")
            );
            this._relNotesToggle.setAttribute(
              "tooltiptext",
              this._relNotesToggle.getAttribute("hidetooltip")
            );
            var uri = this.mManualUpdate ?
                      this.mManualUpdate.releaseNotesURI :
                      this.mAddon.releaseNotesURI;
            this._fetchReleaseNotes(uri);
          }
        ]]></body>
      </method>

      <method name="restart">
        <body><![CDATA[
          gViewController.commands["cmd_restartApp"].doCommand();
        ]]></body>
      </method>

      <method name="undo">
        <body><![CDATA[
          gViewController.commands["cmd_cancelOperation"].doCommand(this.mAddon);
        ]]></body>
      </method>

      <method name="uninstall">
        <body><![CDATA[
          // If uninstalling does not require a restart and the type doesn't
          // support undoing of restartless uninstalls, then we fake it by
          // just disabling it it, and doing the real uninstall later.
          if (!this.opRequiresRestart("uninstall") &&
              !this.typeHasFlag("SUPPORTS_UNDO_RESTARTLESS_UNINSTALL")) {
            this.setAttribute("wasDisabled", this.mAddon.userDisabled);

            // We must set userDisabled to true first, this will call
            // _updateState which will clear any pending attribute set.
            this.mAddon.userDisabled = true;

            // This won't update any other add-on manager views (bug 582002)
            this.setAttribute("pending", "uninstall");
          } else {
            this.mAddon.uninstall(true);
          }
        ]]></body>
      </method>

      <method name="showPreferences">
        <body><![CDATA[
          gViewController.doCommand("cmd_showItemPreferences", this.mAddon);
        ]]></body>
      </method>

      <method name="upgrade">
        <body><![CDATA[
          var install = this.mManualUpdate;
          delete this.mManualUpdate;
          install.install();
        ]]></body>
      </method>

      <method name="retryInstall">
        <body><![CDATA[
          var install = this._installStatus.mInstall;
          if (!install)
            return;
          if (install.state != AddonManager.STATE_DOWNLOAD_FAILED &&
              install.state != AddonManager.STATE_INSTALL_FAILED)
            return;
          install.install();
        ]]></body>
      </method>

      <method name="showInDetailView">
        <body><![CDATA[
          gViewController.loadView("addons://detail/" +
                                   encodeURIComponent(this.mAddon.id));
        ]]></body>
      </method>

      <method name="onIncludeUpdateChanged">
        <body><![CDATA[
          var event = document.createEvent("Events");
          event.initEvent("IncludeUpdateChanged", true, true);
          this.dispatchEvent(event);
        ]]></body>
      </method>

      <method name="onEnabling">
        <body><![CDATA[
          this._updateState();
        ]]></body>
      </method>

      <method name="onEnabled">
        <body><![CDATA[
          this._updateState();
        ]]></body>
      </method>

      <method name="onDisabling">
        <body><![CDATA[
          this._updateState();
        ]]></body>
      </method>

      <method name="onDisabled">
        <body><![CDATA[
          this._updateState();
        ]]></body>
      </method>

      <method name="onUninstalling">
        <parameter name="aRestartRequired"/>
        <body><![CDATA[
          this._updateState();
        ]]></body>
      </method>

      <method name="onOperationCancelled">
        <body><![CDATA[
          this._updateState();
        ]]></body>
      </method>

      <method name="onPropertyChanged">
        <parameter name="aProperties"/>
        <body><![CDATA[
          if (aProperties.indexOf("appDisabled") != -1 ||
              aProperties.indexOf("signedState") != -1 ||
              aProperties.indexOf("userDisabled") != -1)
            this._updateState();
        ]]></body>
      </method>

      <method name="onNoUpdateAvailable">
        <body><![CDATA[
          this._showStatus("none");
        ]]></body>
      </method>

      <method name="onCheckingUpdate">
        <body><![CDATA[
          this._showStatus("checking-update");
        ]]></body>
      </method>

      <method name="onCompatibilityUpdateAvailable">
        <body><![CDATA[
          this._updateState();
        ]]></body>
      </method>

      <method name="onExternalInstall">
        <parameter name="aAddon"/>
        <parameter name="aExistingAddon"/>
        <parameter name="aNeedsRestart"/>
        <body><![CDATA[
          if (aExistingAddon.id != this.mAddon.id)
            return;

          // If the install completed without needing a restart then switch to
          // using the new Addon
          if (!aNeedsRestart)
            this._initWithAddon(aAddon);
          else
            this._updateState();
        ]]></body>
      </method>

      <method name="onNewInstall">
        <parameter name="aInstall"/>
        <body><![CDATA[
          if (this.mAddon.applyBackgroundUpdates == AddonManager.AUTOUPDATE_ENABLE)
            return;
          if (this.mAddon.applyBackgroundUpdates == AddonManager.AUTOUPDATE_DEFAULT &&
              AddonManager.autoUpdateDefault)
            return;

          this.mManualUpdate = aInstall;
          this._showStatus("update-available");
        ]]></body>
      </method>

      <method name="onDownloadStarted">
        <parameter name="aInstall"/>
        <body><![CDATA[
          this._updateState();
          this._showStatus("progress");
          this._installStatus.initWithInstall(aInstall);
        ]]></body>
      </method>

      <method name="onInstallStarted">
        <parameter name="aInstall"/>
        <body><![CDATA[
          this._updateState();
          this._showStatus("progress");
          this._installStatus.initWithInstall(aInstall);
        ]]></body>
      </method>

      <method name="onInstallEnded">
        <parameter name="aInstall"/>
        <parameter name="aAddon"/>
        <body><![CDATA[
          // If the install completed without needing a restart then switch to
          // using the new Addon
          if (!(aAddon.pendingOperations & AddonManager.PENDING_INSTALL))
            this._initWithAddon(aAddon);
          else
            this._updateState();
        ]]></body>
      </method>

      <method name="onDownloadFailed">
        <body><![CDATA[
            this._updateState();
        ]]></body>
      </method>

      <method name="onInstallFailed">
        <body><![CDATA[
            this._updateState();
        ]]></body>
      </method>

      <method name="onInstallCancelled">
        <body><![CDATA[
            this._updateState();
        ]]></body>
      </method>
    </implementation>

    <handlers>
      <handler event="click" button="0"><![CDATA[
        switch (event.detail) {
        case 1:
          // Prevent double-click where the UI changes on the first click
          this._lastClickTarget = event.originalTarget;
          break;
        case 2:
          if (event.originalTarget.localName != 'button' &&
              !event.originalTarget.classList.contains('text-link') &&
              event.originalTarget == this._lastClickTarget) {
            this.showInDetailView();
          }
          break;
        }
      ]]></handler>
    </handlers>
  </binding>


  <!-- Addon - uninstalled - An uninstalled addon that can be re-installed. -->
  <binding id="addon-uninstalled"
           extends="chrome://mozapps/content/extensions/extensions.xml#addon-base">
    <content>
      <xul:hbox class="pending">
        <xul:image class="pending-icon"/>
        <xul:label anonid="notice" flex="1"/>
        <xul:button anonid="restart-btn" class="button-link"
                    label="&addon.restartNow.label;"
                    command="cmd_restartApp"/>
        <xul:button anonid="undo-btn" class="button-link"
                    label="&addon.undoRemove.label;"
                    tooltiptext="&addon.undoRemove.tooltip;"
                    oncommand="document.getBindingParent(this).cancelUninstall();"/>
        <xul:spacer flex="5000"/> <!-- Necessary to allow the message to wrap -->
      </xul:hbox>
    </content>

    <implementation>
      <constructor><![CDATA[
        this._notice.textContent = gStrings.ext.formatStringFromName("uninstallNotice",
                                                                     [this.mAddon.name],
                                                                     1);

        if (!this.opRequiresRestart("uninstall"))
          this._restartBtn.setAttribute("hidden", true);

        gEventManager.registerAddonListener(this, this.mAddon.id);
      ]]></constructor>

      <destructor><![CDATA[
        gEventManager.unregisterAddonListener(this, this.mAddon.id);
      ]]></destructor>

      <field name="_notice" readonly="true">
        document.getAnonymousElementByAttribute(this, "anonid", "notice");
      </field>
      <field name="_restartBtn" readonly="true">
        document.getAnonymousElementByAttribute(this, "anonid", "restart-btn");
      </field>

      <method name="cancelUninstall">
        <body><![CDATA[
          // This assumes that disabling does not require a restart when
          // uninstalling doesn't. Things will still work if not, the add-on
          // will just still be active until finally getting uninstalled.

          if (this.isPending("uninstall"))
            this.mAddon.cancelUninstall();
          else if (this.getAttribute("wasDisabled") != "true")
            this.mAddon.userDisabled = false;

          this.removeAttribute("pending");
        ]]></body>
      </method>

      <method name="onOperationCancelled">
        <body><![CDATA[
          if (!this.isPending("uninstall"))
            this.removeAttribute("pending");
        ]]></body>
      </method>

      <method name="onExternalInstall">
        <parameter name="aAddon"/>
        <parameter name="aExistingAddon"/>
        <parameter name="aNeedsRestart"/>
        <body><![CDATA[
          if (aExistingAddon.id != this.mAddon.id)
            return;

          // Make sure any newly installed add-on has the correct disabled state
          if (this.hasAttribute("wasDisabled"))
            aAddon.userDisabled = this.getAttribute("wasDisabled") == "true";

          // If the install completed without needing a restart then switch to
          // using the new Addon
          if (!aNeedsRestart)
            this.mAddon = aAddon;

          this.removeAttribute("pending");
        ]]></body>
      </method>

      <method name="onInstallStarted">
        <parameter name="aInstall"/>
        <body><![CDATA[
          // Make sure any newly installed add-on has the correct disabled state
          if (this.hasAttribute("wasDisabled"))
            aInstall.addon.userDisabled = this.getAttribute("wasDisabled") == "true";
        ]]></body>
      </method>

      <method name="onInstallEnded">
        <parameter name="aInstall"/>
        <parameter name="aAddon"/>
        <body><![CDATA[
          // If the install completed without needing a restart then switch to
          // using the new Addon
          if (!(aAddon.pendingOperations & AddonManager.PENDING_INSTALL))
            this.mAddon = aAddon;

          this.removeAttribute("pending");
        ]]></body>
      </method>
    </implementation>
  </binding>


  <!-- Addon - installing - an addon item that is currently being installed -->
  <binding id="addon-installing"
           extends="chrome://mozapps/content/extensions/extensions.xml#addon-base">
    <content>
      <xul:hbox anonid="warning-container" class="warning">
        <xul:image class="warning-icon"/>
        <xul:label anonid="warning" flex="1"/>
        <xul:button anonid="warning-link" class="button-link"
                   oncommand="document.getBindingParent(this).retryInstall();"/>
        <xul:spacer flex="5000"/> <!-- Necessary to allow the message to wrap -->
      </xul:hbox>
      <xul:hbox class="content-container">
        <xul:vbox class="icon-outer-container">
          <xul:vbox class="icon-container">
            <xul:image anonid="icon" class="icon"/>
          </xul:vbox>
        </xul:vbox>
        <xul:vbox class="fade name-outer-container" flex="1">
          <xul:hbox class="name-container">
            <xul:label anonid="name" class="name" crop="end" tooltip="addonitem-tooltip"/>
          </xul:hbox>
        </xul:vbox>
        <xul:vbox class="install-status-container">
          <xul:hbox anonid="install-status" class="install-status"/>
        </xul:vbox>
      </xul:hbox>
    </content>

    <implementation>
      <constructor><![CDATA[
        this._installStatus.mControl = this;
        this._installStatus.mInstall = this.mInstall;
        this.refreshInfo();
      ]]></constructor>

      <field name="_icon">
        document.getAnonymousElementByAttribute(this, "anonid", "icon");
      </field>
      <field name="_name">
        document.getAnonymousElementByAttribute(this, "anonid", "name");
      </field>
      <field name="_warning">
        document.getAnonymousElementByAttribute(this, "anonid", "warning");
      </field>
      <field name="_warningLink">
        document.getAnonymousElementByAttribute(this, "anonid", "warning-link");
      </field>
      <field name="_installStatus">
        document.getAnonymousElementByAttribute(this, "anonid",
                                                "install-status");
      </field>

      <method name="onInstallCompleted">
        <body><![CDATA[
          this.mAddon = this.mInstall.addon;
          this.setAttribute("name", this.mAddon.name);
          this.setAttribute("value", this.mAddon.id);
          this.setAttribute("status", "installed");
        ]]></body>
      </method>

      <method name="refreshInfo">
        <body><![CDATA[
          this.mAddon = this.mAddon || this.mInstall.addon;
          if (this.mAddon) {
            this._icon.src = this.mAddon.iconURL ||
                             (this.mInstall ? this.mInstall.iconURL : "");
            this._name.value = this.mAddon.name;
          } else {
            this._icon.src = this.mInstall.iconURL;
            // AddonInstall.name isn't always available - fallback to filename
            if (this.mInstall.name) {
              this._name.value = this.mInstall.name;
            } else if (this.mInstall.sourceURI) {
              var url = Components.classes["@mozilla.org/network/standard-url;1"]
                                  .createInstance(Components.interfaces.nsIStandardURL);
              url.init(url.URLTYPE_STANDARD, 80, this.mInstall.sourceURI.spec,
                       null, null);
              url.QueryInterface(Components.interfaces.nsIURL);
              this._name.value = url.fileName;
            }
          }

          if (this.mInstall.state == AddonManager.STATE_DOWNLOAD_FAILED) {
            this.setAttribute("notification", "warning");
            this._warning.textContent = gStrings.ext.formatStringFromName(
              "notification.downloadError",
              [this._name.value], 1
            );
            this._warningLink.label = gStrings.ext.GetStringFromName("notification.downloadError.retry");
            this._warningLink.tooltipText = gStrings.ext.GetStringFromName("notification.downloadError.retry.tooltip");
          } else if (this.mInstall.state == AddonManager.STATE_INSTALL_FAILED) {
            this.setAttribute("notification", "warning");
            this._warning.textContent = gStrings.ext.formatStringFromName(
              "notification.installError",
              [this._name.value], 1
            );
            this._warningLink.label = gStrings.ext.GetStringFromName("notification.installError.retry");
            this._warningLink.tooltipText = gStrings.ext.GetStringFromName("notification.downloadError.retry.tooltip");
          } else {
            this.removeAttribute("notification");
          }
        ]]></body>
      </method>

      <method name="retryInstall">
        <body><![CDATA[
          this.mInstall.install();
        ]]></body>
      </method>
    </implementation>
  </binding>

  <binding id="detail-row">
    <content>
      <xul:label class="detail-row-label" xbl:inherits="value=label"/>
      <xul:label class="detail-row-value" xbl:inherits="value"/>
    </content>

    <implementation>
      <property name="value">
        <getter><![CDATA[
          return this.getAttribute("value");
        ]]></getter>
        <setter><![CDATA[
          if (!val)
            this.removeAttribute("value");
          else
            this.setAttribute("value", val);
        ]]></setter>
      </property>
    </implementation>
  </binding>

</bindings>
