toolkit/mozapps/extensions/content/extensions.xml
author Dorel Luca <dluca@mozilla.com>
Mon, 22 Oct 2018 20:57:41 +0300
changeset 490790 c141c2c31ca1eaacdb62b0be052f742b084b9e56
parent 490781 2d0174d68cec998537cb529f688c96cfa729a892
child 491062 3377d76041ed9940b811024745441d7b8846b29a
permissions -rw-r--r--
Backed out changeset 2d0174d68cec (bug 1497087) for mochitest failure on caps/tests/mochitest/test_bug292789.html

<?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", Cr.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>

  <!-- 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" crop="end" 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:box anonid="progress" class="download-progress"/>
      <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="_installRemote">
        document.getAnonymousElementByAttribute(this, "anonid",
                                                "install-remote-btn");
      </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;

          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;
            }

          }

          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="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");
          let prompt = Services.prefs.getBoolPref("extensions.webextPermissionPrompts", false);
          if (prompt) {
            this.mInstall.promptHandler = info => new Promise((resolve, reject) => {
              // Skip prompts for non-webextensions
              if (!info.addon.userPermissions) {
                resolve();
                return;
              }
              let subject = {
                wrappedJSObject: {
                  target: window.docShell.chromeEventHandler,
                  info: {
                    addon: info.addon,
                    source: "AMO",
                    icon: info.addon.iconURL,
                    permissions: info.addon.userPermissions,
                    resolve,
                    reject,
                  },
                },
              };
              Services.obs.notifyObservers(subject, "webextension-permission-prompt");
            });
          }
          this.mInstall.install();
        ]]></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>
      <property name="isLegacy" readonly="true">
        <getter><![CDATA[
          if (this.mAddon.install) {
            return false;
          }
          return isLegacyExtension(this.mAddon);
        ]]></getter>
      </property>

      <method name="hasPermission">
        <parameter name="aPerm"/>
        <body><![CDATA[
          var perm = AddonManager["PERM_CAN_" + aPerm.toUpperCase()];
          return !!(this.mAddon.permissions & perm);
        ]]></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.remove();
        ]]></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="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:image class="card-heading-image" anonid="theme-screenshot" hidden="true"/>

      <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="xbl:text=name"/>
                <xul:label anonid="legacy" class="legacy-warning text-link" value="&addon.legacy.label;"/>
                <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="advancedinfo-container" flex="1">
            <xul:vbox class="description-outer-container" flex="1">
              <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 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: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" flex="1">
            <xul:button anonid="preferences-btn"
                        class="addon-control preferences"
#ifdef XP_WIN
                        label="&cmd.showPreferencesWin.label;"
                        tooltiptext="&cmd.showPreferencesWin.tooltip;"
#else
                        label="&cmd.showPreferencesUnix.label;"
                        tooltiptext="&cmd.showPreferencesUnix.tooltip;"
#endif
                        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="replacement-btn" class="addon-control replacement"
                        label="&cmd.findReplacement.label;"
                        oncommand="document.getBindingParent(this).findReplacement();"/>
            <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"
                          flex="1"
                          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="_icon">
        document.getAnonymousElementByAttribute(this, "anonid", "icon");
      </field>
      <field name="_dateUpdated">
        document.getAnonymousElementByAttribute(this, "anonid",
                                                "date-updated");
      </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[
          if (val === true) {
            gViewController.commands.cmd_disableItem.doCommand(this.mAddon);
          } else if (val === false) {
            gViewController.commands.cmd_enableItem.doCommand(this.mAddon);
          } else {
            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, 32, window);
          if (iconURL)
            this._icon.src = iconURL;
          else
            this._icon.src = "";

          let legacyWarning = legacyExtensionsEnabled && !this.mAddon.install &&
            isLegacyExtension(this.mAddon);
          this.setAttribute("legacy", legacyWarning);
          document.getAnonymousElementByAttribute(this, "anonid", "legacy").href = SUPPORT_URL + "webextensions";

          if (!("applyBackgroundUpdates" in this.mAddon) ||
              (this.mAddon.applyBackgroundUpdates == AddonManager.AUTOUPDATE_DISABLE ||
               (this.mAddon.applyBackgroundUpdates == AddonManager.AUTOUPDATE_DEFAULT &&
                !AddonManager.autoUpdateDefault))) {
            AddonManager.getAllInstalls().then(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 dtOptions = { year: "numeric", month: "long", day: "numeric" };
            return aDate.toLocaleDateString(undefined, 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_UNINSTALL) {
            this.removeAttribute("notification");

            // We don't care about pending operations other than uninstall.
            // They're transient, and cannot be undone.
            this.setAttribute("pending", "uninstall");
            this._pending.textContent = gStrings.ext.formatStringFromName(
              "notification.restartless-uninstall",
              [this.mAddon.name], 1);
          } 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.mAddon.getBlocklistURL().then(url => {
                this._errorLink.href = url;
                this._errorLink.hidden = false;
              });
            } else if (!isUpgrade && isDisabledUnsigned(this.mAddon)) {
              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 = SUPPORT_URL + "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 = SUPPORT_URL + "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.mAddon.getBlocklistURL().then(url => {
                this._warningLink.href = url;
                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.mAddon.getBlocklistURL().then(url => {
                this._warningLink.href = url;
                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.mAddon.getBlocklistURL().then(url => {
                this._errorLink.href = url;
                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.mAddon.getBlocklistURL().then(url => {
                this._errorLink.href = url;
                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.optionsType && this.mAddon.type != "plugin";

          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.install &&
                              this.mAddon.install.state != AddonManager.STATE_INSTALLED);
          this._showStatus(showProgress ? "progress" : "none");
        ]]></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 = new XSLTProcessor();
            processor.flags |= XSLTProcessor.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.includes("text/html")) &&
                req.responseXML &&
                req.responseXML.documentElement.namespaceURI != XMLURI_PARSE_ERROR) {
              if (req == dataReq)
                relNotesData = req.responseXML;
              else
                transformData = req.responseXML;
              showRelNotes();
            } else {
              handleError();
            }
          }

          var dataReq = new XMLHttpRequest();
          dataReq.open("GET", aURI.spec, true);
          dataReq.responseType = "document";
          dataReq.addEventListener("load", handleResponse);
          dataReq.addEventListener("error", handleError);
          dataReq.send(null);

          var styleReq = new XMLHttpRequest();
          styleReq.open("GET", UPDATES_RELEASENOTES_TRANSFORMFILE, true);
          styleReq.responseType = "document";
          styleReq.addEventListener("load", handleResponse);
          styleReq.addEventListener("error", handleError);
          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="undo">
        <body><![CDATA[
          gViewController.commands.cmd_cancelOperation.doCommand(this.mAddon);
        ]]></body>
      </method>

      <method name="uninstall">
        <body><![CDATA[
          // If 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.typeHasFlag("SUPPORTS_UNDO_RESTARTLESS_UNINSTALL")) {
            this.mAddon.uninstall(true);
          } else {
            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.disable().then(() => {
              // This won't update any other add-on manager views (bug 582002)
              this.setAttribute("pending", "uninstall");
            });
          }
        ]]></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="findReplacement">
        <body><![CDATA[
          let url = (this.mAddon.type == "theme") ?
            SUPPORT_URL + "complete-themes" :
            `https://addons.mozilla.org/find-replacement/?guid=${this.mAddon.id}`;
            openURL(url);
        ]]></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">
        <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.includes("appDisabled") ||
              aProperties.includes("signedState") ||
              aProperties.includes("userDisabled"))
            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"/>
        <body><![CDATA[
          if (aExistingAddon.id != this.mAddon.id)
            return;

          this._initWithAddon(aAddon);
        ]]></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[
          this._initWithAddon(aAddon);
        ]]></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[
        if (!["button", "checkbox", "menulist", "menuitem"].includes(event.originalTarget.localName) &&
            !event.originalTarget.classList.contains("text-link") &&
            // Treat the relnotes container as embedded text instead of a click target.
            !event.originalTarget.closest(".relnotes-container")) {
          this.showInDetailView();
        }
      ]]></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="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);

        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>

      <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.enable();

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

      <method name="onExternalInstall">
        <parameter name="aAddon"/>
        <parameter name="aExistingAddon"/>
        <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")) {
            if (this.getAttribute("wasDisabled") == "true")
              aAddon.disable();
            else
              aAddon.enable();
          }

          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")) {
            if (this.getAttribute("wasDisabled") == "true")
              aInstall.addon.disable();
            else
              aInstall.addon.enable();
          }
        ]]></body>
      </method>

      <method name="onInstallEnded">
        <parameter name="aInstall"/>
        <parameter name="aAddon"/>
        <body><![CDATA[
          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 = Cc["@mozilla.org/network/standard-url-mutator;1"]
                          .createInstance(Ci.nsIStandardURLMutator)
                          .init(Ci.nsIStandardURL.URLTYPE_STANDARD,
                                80, this.mInstall.sourceURI.spec,
                                null, null)
                          .finalize()
                          .QueryInterface(Ci.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>