suite/components/bindings/notification.xml
author Rob Lemley <rob@thunderbird.net>
Wed, 08 Apr 2020 18:29:48 -0400
changeset 38748 6d4c1e45518002fd062d717ccaa226fa9350d34c
parent 38616 6d347949d057b7efe4efe049f210a24bacf6ef80
permissions -rw-r--r--
Bug 1621787 - Additional license notice for Bzip2. r=kaie Bzip2 sources will be added to third_party/bzip2. This is the license text from the current version.

<?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 bindings [
<!ENTITY % commNotificationDTD SYSTEM "chrome://communicator/locale/notification.dtd">
 %commNotificationDTD;
<!ENTITY % globalNotificationDTD SYSTEM "chrome://global/locale/notification.dtd">
 %globalNotificationDTD;
]>

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

  <binding id="browser-notificationbox"
           extends="chrome://global/content/bindings/notification.xml#notificationbox">
    <implementation implements="nsIObserver, nsIFormSubmitObserver, nsIWebProgressListener, nsIWebProgressListener2, nsIDOMEventListener">
      <field name="_stringBundle" readonly="true">
        <![CDATA[
          Cc["@mozilla.org/intl/stringbundle;1"]
            .getService(Ci.nsIStringBundleService)
            .createBundle("chrome://communicator/locale/notification.properties");
        ]]>
      </field>

      <field name="_brandStringBundle" readonly="true">
        <![CDATA[
          Cc["@mozilla.org/intl/stringbundle;1"]
            .getService(Ci.nsIStringBundleService)
            .createBundle("chrome://branding/locale/brand.properties");
        ]]>
      </field>

      <field name="_placesBundle" readonly="true">
        <![CDATA[
         Services.strings.createBundle("chrome://communicator/locale/places/places.properties");
        ]]>
      </field>

      <field name="_urlFormatter" readonly="true">
        <![CDATA[
          Cc["@mozilla.org/toolkit/URLFormatterService;1"]
            .getService(Ci.nsIURLFormatter);
        ]]>
      </field>

      <field name="wrappedJSObject">this</field>

      <field name="_activeBrowser">null</field>

      <property name="activeBrowser" readonly="true">
        <getter>
          <![CDATA[
            if (!this._activeBrowser) {
              const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
              var browsers = this.getElementsByTagNameNS(XUL_NS, "browser");
              for (var i = 0; this._activeBrowser = browsers.item(i); i++)
                if (!this._activeBrowser.hidden)
                  break;
            }
            return this._activeBrowser;
          ]]>
        </getter>
      </property>

      <field name="_cwu">null</field>

      <property name="contentWindowUtils" readonly="true">
        <getter>
          <![CDATA[
            if (!this._cwu)
              this._cwu = this.activeBrowser.contentWindow
                              .QueryInterface(Ci.nsIInterfaceRequestor)
                              .getInterface(Ci.nsIDOMWindowUtils);
            return this._cwu;
          ]]>
        </getter>
      </property>

      <field name="usePrivateBrowsing" readonly="true">
        window.QueryInterface(Ci.nsIInterfaceRequestor)
              .getInterface(Ci.nsIWebNavigation)
              .QueryInterface(Ci.nsILoadContext)
              .usePrivateBrowsing
      </field>

      <method name="onDocumentChange">
        <body>
          <![CDATA[
            this.crashNotified = false;
            this.clickToPlayPluginsActivated = false;
            this.clickToPlayTypes = new Set();
            if (this.popupCount) {
              this.popupCount = 0;
              this.notifyPopupCountChanged();
            }
            this.removeTransientNotifications();
          ]]>
        </body>
      </method>

      <method name="addProgressListener">
        <body>
          <![CDATA[
            if (this.activeBrowser && !this._addedProgressListener) {
              this.activeBrowser.webProgress
                  .addProgressListener(this, Ci.nsIWebProgress.NOTIFY_SECURITY |
                                             Ci.nsIWebProgress.NOTIFY_LOCATION |
                                             Ci.nsIWebProgress.NOTIFY_REFRESH);
              this._addedProgressListener = true;
            }
          ]]>
        </body>
      </method>

      <field name="lastMessage">"EnterInsecureMessage"</field>
      <field name="lastState">0</field>

      <method name="onSecurityChange">
        <parameter name="aWebProgress"/>
        <parameter name="aRequest"/>
        <parameter name="aState"/>
        <body>
          <![CDATA[
            if (aState < 0)
              aState = this.lastState;
            const nsIWebProgressListener = Ci.nsIWebProgressListener;
            var pref = "security.warn_leaving_secure";
            var message = "EnterInsecureMessage";
            var priority = this.PRIORITY_WARNING_LOW;
            var pane = "ssl_pane";
            var buttons = [];
            if (aState & nsIWebProgressListener.STATE_IS_SECURE) {
              pref = "security.warn_entering_secure";
              message = "EnterSecureMessage";
              priority = this.PRIORITY_INFO_LOW;
            } else if (aState & nsIWebProgressListener.STATE_IS_BROKEN) {
              pref = "security.warn_viewing_mixed";
              message = "MixedContentMessage";
              priority = this.PRIORITY_CRITICAL_LOW;
            }

            if (aState & nsIWebProgressListener.STATE_LOADED_MIXED_ACTIVE_CONTENT &&
                Services.prefs.getBoolPref("security.warn_mixed_active_content")) {
              pref = "security.warn_mixed_active_content";
              message = "MixedActiveContentMessage";
              priority = this.PRIORITY_CRITICAL_LOW;
            } else if (aState & nsIWebProgressListener.STATE_BLOCKED_MIXED_ACTIVE_CONTENT &&
                Services.prefs.getBoolPref("security.warn_mixed_active_content")) {
              pref = "security.warn_mixed_active_content";
              message = "BlockedActiveContentMessage";
              priority = this.PRIORITY_INFO_LOW;
              this.lastState = aState & ~nsIWebProgressListener.STATE_BLOCKED_MIXED_ACTIVE_CONTENT;
              const nsIWebNavigation = Ci.nsIWebNavigation;
              buttons = [{
                label: this._stringBundle.GetStringFromName("SecurityKeepBlocking.label"),
                accessKey: this._stringBundle.GetStringFromName("SecurityKeepBlocking.accesskey"),
                callback: this.onSecurityChange.bind(this, null, null, -1)
              }, {
                label: this._stringBundle.GetStringFromName("SecurityUnblock.label"),
                accessKey: this._stringBundle.GetStringFromName("SecurityUnblock.accesskey"),
                callback: this.reloadPage.bind(this,
                              nsIWebNavigation.LOAD_FLAGS_ALLOW_MIXED_CONTENT |
                              nsIWebNavigation.LOAD_FLAGS_BYPASS_CACHE)
              }];
            } else if (aState & nsIWebProgressListener.STATE_LOADED_TRACKING_CONTENT &&
                Services.prefs.getBoolPref("privacy.warn_tracking_content")) {
              pref = "privacy.warn_tracking_content";
              message = "TrackingContentMessage";
              priority = this.PRIORITY_WARNING_LOW;
              pane = "security_pane";
            } else if (aState & nsIWebProgressListener.STATE_BLOCKED_TRACKING_CONTENT &&
                Services.prefs.getBoolPref("privacy.warn_tracking_content")) {
              pref = "privacy.warn_tracking_content";
              message = "BlockedTrackingContentMessage";
              priority = this.PRIORITY_INFO_LOW;
              pane = "security_pane";
              if (!this.usePrivateBrowsing) {
                this.lastState = aState & ~nsIWebProgressListener.STATE_BLOCKED_TRACKING_CONTENT;
                buttons = [{
                  label: this._stringBundle.GetStringFromName("SecurityKeepBlocking.label"),
                  accessKey: this._stringBundle.GetStringFromName("SecurityKeepBlocking.accesskey"),
                  callback: this.onSecurityChange.bind(this, null, null, -1)
                }, {
                  label: this._stringBundle.GetStringFromName("SecurityUnblock.label"),
                  accessKey: this._stringBundle.GetStringFromName("SecurityUnblock.accesskey"),
                  callback: () => {
                    const nsIPermissionManager = Ci.nsIPermissionManager;
                    Cc["@mozilla.org/permissionmanager;1"]
                      .getService(nsIPermissionManager)
                      .add(this.activeBrowser.currentURI,
                                   "trackingprotection",
                                   nsIPermissionManager.ALLOW_ACTION);
                    this.reloadPage();
                  }
                }];
              }
            } else if (aState & nsIWebProgressListener.STATE_LOADED_MIXED_DISPLAY_CONTENT &&
                Services.prefs.getBoolPref("security.warn_mixed_display_content")) {
              pref = "security.warn_mixed_display_content";
              message = "MixedDisplayContentMessage";
              priority = this.PRIORITY_WARNING_LOW;
            } else if (aState & nsIWebProgressListener.STATE_BLOCKED_MIXED_DISPLAY_CONTENT &&
                Services.prefs.getBoolPref("security.warn_mixed_display_content")) {
              pref = "security.warn_mixed_display_content";
              message = "BlockedDisplayContentMessage";
              priority = this.PRIORITY_INFO_LOW;
            }

            if (this.lastMessage == message)
              return false;

            var box = this.getNotificationWithValue(this.lastMessage);
            if (box)
              box.close();

            this.lastMessage = message;

            if (!Services.prefs.getBoolPref(pref))
              return true;

            if ("goPreferences" in window) {
              buttons.push({
                label: this._stringBundle.GetStringFromName("SecurityPreferences.label"),
                accessKey: this._stringBundle.GetStringFromName("SecurityPreferences.accesskey"),
                callback: function() {
                  goPreferences(pane);
                  return true;
                }
              });
            }
            var text = this._stringBundle.GetStringFromName(message);
            box = this.appendNotification(text, message, null, priority, buttons);
            box.persistence = 1;
            box.timeout = Date.now() + 20000; // 20 seconds
            return true;
          ]]>
        </body>
      </method>

      <method name="onLocationChange">
        <parameter name="aWebProgress" />
        <parameter name="aRequest" />
        <parameter name="aLocation" />
        <parameter name="aFlags" />
        <body>
          <![CDATA[
            const nsIWebProgressListener = Ci.nsIWebProgressListener;
            if (aWebProgress.DOMWindow == this.activeBrowser.contentWindow &&
                !(aFlags & nsIWebProgressListener.LOCATION_CHANGE_SAME_DOCUMENT))
              this.onDocumentChange();
          ]]>
        </body>
      </method>

      <method name="onRefreshAttempted">
        <parameter name="aWebProgress" />
        <parameter name="aURI" />
        <parameter name="aDelay" />
        <parameter name="aSameURI" />
        <body>
          <![CDATA[
            if (Services.prefs.getBoolPref("accessibility.blockautorefresh")) {
              let brandShortName = this._brandStringBundle.GetStringFromName("brandShortName");
              let refreshButtonText =
                this._stringBundle.GetStringFromName("refreshBlocked.goButton");
              let refreshButtonAccesskey =
                this._stringBundle.GetStringFromName("refreshBlocked.goButton.accesskey");
              let message =
                this._stringBundle.formatStringFromName(aSameURI ? "refreshBlocked.refreshLabel"
                                                                 : "refreshBlocked.redirectLabel",
                                                        [brandShortName], 1);
              let notification = this.getNotificationWithValue("refresh-blocked");
              if (notification) {
                notification.label = message;
              } else {
                let buttons = [{
                  label: refreshButtonText,
                  accessKey: refreshButtonAccesskey,
                  callback: function (aNotification, aButton) {
                    var refreshURI = aNotification.webProgress
                                                  .QueryInterface(Ci.nsIRefreshURI);
                    refreshURI.forceRefreshURI(aNotification.uri, null,
                                               aNotification.delay, true);
                  }
                }];
                notification =
                  this.appendNotification(message, "refresh-blocked", null,
                                          this.PRIORITY_INFO_MEDIUM, buttons);
              }
              // In the case of a meta refresh, the location has already
              // changed. But in the case of an HTTP header refresh, the
              // location changes synchronously after this call returns.
              // Set the persistence to 1 temporarily to stop this from
              // immediately clobbering the location bar (bug 516441),
              // but set the persistence back to 0 as soon as possible.
              setTimeout(function() { notification.persistence = 0; }, 0);
              notification.persistence = 1;
              notification.webProgress = aWebProgress;
              notification.uri = aURI;
              notification.delay = aDelay;
              return false;
            }
            return true;
          ]]>
        </body>
      </method>

      <method name="notify">
        <parameter name="aFormElement"/>
        <parameter name="aWindow"/>
        <parameter name="aActionURI"/>
        <parameter name="aCancel"/>
        <body>
          <![CDATA[
            aCancel.value = false;
            if (!aFormElement || !aWindow || !aActionURI)
              return;

            var browser = this.activeBrowser;

            // inactive sidebar panel:
            if (!browser || !browser.docShell || !browser.docShell.securityUI)
              return;

            // not our window:
            if (aWindow.top != browser.contentWindow)
              return;

            // pref disabled:
            if (!Services.prefs.getBoolPref("security.warn_submit_insecure"))
              return;

            // HTTPS uninteresting:
            if (aActionURI.schemeIs("https"))
              return;

            // javascript doesn't hit the network:
            if (aActionURI.schemeIs("javascript"))
              return;

            // PSM handles HTTPS source:
            var uri;
            try {
              uri = aFormElement.nodePrincipal.URI;
            } catch (e) {}
            if (!uri)
              uri = aFormElement.ownerDocument.documentURIObject;
            if (uri.schemeIs("https"))
              return;

            var warn = { value: true };
            var prompt = Cc["@mozilla.org/embedcomp/prompt-service;1"].getService(Ci.nsIPromptService);
            aCancel.value = prompt.confirmEx(
              aWindow,
              this._stringBundle.GetStringFromName("SecurityTitle"),
              this._stringBundle.GetStringFromName("PostToInsecureFromInsecureMessage"),
              prompt.BUTTON_TITLE_IS_STRING * prompt.BUTTON_POS_0 +
              prompt.BUTTON_TITLE_CANCEL * prompt.BUTTON_POS_1,
              this._stringBundle.GetStringFromName("PostToInsecureContinue"),
              null, null,
              this._stringBundle.GetStringFromName("PostToInsecureFromInsecureShowAgain"),
              warn) != 0;
            if (!aCancel.value)
              Services.prefs.setBoolPref("security.warn_submit_insecure", warn.value);
          ]]>
        </body>
      </method>

      <method name="observe">
        <parameter name="aSubject" />
        <parameter name="aTopic" />
        <parameter name="aData" />
        <body>
          <![CDATA[
            var browser = this.activeBrowser;

            // inactive sidebar panel:
            if (!browser || !browser.docShell)
              return;

            switch (aTopic) {
              case "indexedDB-permissions-prompt":
                var requestor = aSubject.QueryInterface(Ci.nsIInterfaceRequestor);
                var contentWindow = requestor.getInterface(Ci.nsIDOMWindow);
                if (contentWindow.top == browser.contentWindow)
                  this.promptIndexedDB(requestor, contentWindow, "permissions");
                break;

              case "indexedDB-quota-prompt":
                var requestor = aSubject.QueryInterface(Ci.nsIInterfaceRequestor);
                var contentWindow = requestor.getInterface(Ci.nsIDOMWindow);
                if (contentWindow.top == browser.contentWindow)
                  this.promptIndexedDB(requestor, contentWindow, "quota", aData);
                break;

              case "indexedDB-quota-cancel":
                var requestor = aSubject.QueryInterface(Ci.nsIInterfaceRequestor);
                var contentWindow = requestor.getInterface(Ci.nsIDOMWindow);
                if (contentWindow.top == browser.contentWindow)
                  this.cancelIndexedDB(requestor, contentWindow, "quota", aData);
                break;

              case "addon-install-blocked":
                var installInfo = aSubject.wrappedJSObject;
                if (installInfo.browser == browser)
                  this.addonInstallBlocked(installInfo);
                break;

              case "addon-install-complete":
                var installInfo = aSubject.wrappedJSObject;
                if (installInfo.browser == browser)
                  this.addonInstallComplete(installInfo);
                break;

              case "addon-install-disabled":
                var installInfo = aSubject.wrappedJSObject;
                if (installInfo.browser == browser)
                  this.addonInstallDisabled(installInfo);
                break;

              case "addon-install-failed":
                var installInfo = aSubject.wrappedJSObject;
                if (installInfo.browser == browser)
                  this.addonInstallFailed(installInfo);
                break;

              case "addon-install-started":
                var installInfo = aSubject.wrappedJSObject;
                if (installInfo.browser == browser)
                  this.addonInstallStarted(installInfo);
                break;

              case "offline-cache-update-completed":
                var doc = browser.contentDocument;
                if (!doc.documentElement)
                  break;

                var manifest = doc.documentElement.getAttribute("manifest");
                if (!manifest)
                  break;

                try {
                  var manifestURI = Services.io.newURI(manifest,
                                                       doc.characterSet,
                                                       doc.documentURIObject);
                  var cacheUpdate = 
                    aSubject.QueryInterface(Ci.nsIOfflineCacheUpdate);
                  if (manifestURI.equals(cacheUpdate.manifestURI))
                    this.checkUsage(manifestURI);
                } catch (e) {
                  alert(e);
                }
                break;

              case "nsPref:changed":
                if (aData == "privacy.popups.showBrowserMessage") {
                  if (Services.prefs.getBoolPref(aData))
                    return;

                  var popupNotification = this.getNotificationWithValue("popup-blocked");
                  if (popupNotification)
                    this.removeNotification(popupNotification);
                }

                if (aData == "plugins.hide_infobar_for_outdated_plugin") {
                  if (!Services.prefs.getBoolPref(aData))
                    return;

                  var outdatedNotification = this.getNotificationWithValue("outdated-plugins");
                  if (outdatedNotification)
                    this.removeNotification(outdatedNotification);
                }

                if (aData == "plugins.hide_infobar_for_missing_plugin") {
                  if (!Services.prefs.getBoolPref(aData))
                    return;

                  var disabledNotification = this.getNotificationWithValue("disabled-plugins");
                  if (disabledNotification)
                    this.removeNotification(disabledNotification);

                  var blockedNotification = this.getNotificationWithValue("blocked-plugins");
                  if (blockedNotification)
                    this.removeNotification(blockedNotification);
                }

                if (aData == "dom.disable_open_during_load") {
                  // remove notifications when popup blocking has been turned off
                  if (Services.prefs.getBoolPref(aData) || !this.popupCount)
                    return;

                  var popupNotification = this.getNotificationWithValue("popup-blocked");
                  if (popupNotification)
                    this.removeNotification(popupNotification);
                  this.popupCount = 0;
                  this.notifyPopupCountChanged();
                }
                break;

              case "perm-changed":
                var permission = aSubject.QueryInterface(Ci.nsIPermission);
                if (permission.type != "popup" || aData != "added" || !this.popupCount)
                  return;

                if (permission.matchesURI(this.activeBrowser.currentURI, false)) {
                  var popupNotification = this.getNotificationWithValue("popup-blocked");
                  if (popupNotification)
                    this.removeNotification(popupNotification);
                  this.popupCount = 0;
                  this.notifyPopupCountChanged();
                }
                break;
            }
          ]]>
        </body>
      </method>

      <field name="CrashSubmit">null</field>

      <field name="crashNotified">false</field>

      <field name="clickToPlayPluginsActivated">false</field>

      <field name="clickToPlayTypes">new Set()</field>

      <method name="getPluginInfo">
        <parameter name="pluginElement"/>
        <body>
          <![CDATA[
            var mimetype;
            var pluginsPage;
            var pluginName = this._stringBundle.GetStringFromName("pluginInfo.unknownPlugin");

            if (pluginElement instanceof HTMLObjectElement) {
              pluginsPage = pluginElement.codebase;
            } else {
              pluginsPage = pluginElement.getAttribute("pluginspage");
              if (pluginsPage) {
                var doc = pluginElement.ownerDocument;
                pluginsPage = Services.io.newURI(pluginsPage,
                                                 doc.characterSet,
                                                 doc.documentURIObject).spec;
              }
            }

            mimetype = pluginElement.QueryInterface(Ci.nsIObjectLoadingContent)
                                    .actualType;

            if (!mimetype) {
              mimetype = pluginElement.type;
            }

            var navMimeType = navigator.mimeTypes.namedItem(mimetype);
            if (navMimeType && navMimeType.enabledPlugin)
              pluginName = this.makeNicePluginName(navMimeType.enabledPlugin.name);

            return {
              mimetype: mimetype,
              pluginsPage: pluginsPage,
              pluginName: pluginName
            };
          ]]>
        </body>
      </method>

      <method name="pluginUnavailable">
        <parameter name="aPlugin"/>
        <parameter name="aNotification"/>
        <parameter name="aMessage"/>
        <parameter name="aButtons"/>
        <parameter name="aPref"/>
        <body>
          <![CDATA[
            // Show the in-content UI if it's not too big. The crashed plugin handler already does this.
            var overlay = this.getPluginUI(aPlugin, "main");
            aPlugin.addEventListener("overflow", this);
            aPlugin.addEventListener("underflow", this);
            overlay.classList.toggle("visible",
                                     !this.isTooSmall(aPlugin, overlay));

            if (Services.prefs.getBoolPref(aPref || "plugins.hide_infobar_for_missing_plugin"))
              return;

            var notification;
            var notifications = [
              "blocked-plugins",
              "disabled-plugins",
              "outdated-plugins",
            ];

            // Remove lower priority notifications.
            for (var i = 0; notifications[i] != aNotification; i++) {
              notification = this.getNotificationWithValue(notifications[i]);
              if (notification)
                notification.close();
            }

            // Skip if an equal or higher priority notification exists.
            while (i < notifications.length) {
              notification = this.getNotificationWithValue(notifications[i++]);
              if (notification)
                return;
            }

            // Now we can display our notification.
            const priority = this.PRIORITY_WARNING_MEDIUM;
            this.appendNotification(aMessage, aNotification,
                                    null, priority, aButtons);
          ]]>
        </body>
      </method>

      <method name="canActivatePlugin">
        <parameter name="aPlugin"/>
        <parameter name="aSkipFallbackType"/>
        <body>
          <![CDATA[
            var objLoadingContent = aPlugin.QueryInterface(Ci.nsIObjectLoadingContent);
            var actualType = objLoadingContent.actualType;
            if (objLoadingContent.getContentTypeForMIMEType(actualType) != Ci.nsIObjectLoadingContent.TYPE_PLUGIN ||
                objLoadingContent.activated ||
                (!aSkipFallbackType &&
                 (objLoadingContent.pluginFallbackType < Ci.nsIObjectLoadingContent.PLUGIN_CLICK_TO_PLAY ||
                  objLoadingContent.pluginFallbackType > Ci.nsIObjectLoadingContent.PLUGIN_VULNERABLE_NO_UPDATE)))
              return false;

            var ph = Cc["@mozilla.org/plugin/host;1"]
                       .getService(Ci.nsIPluginHost);
            var permissionString = ph.getPermissionStringForType(actualType);
            var pm = Cc["@mozilla.org/permissionmanager;1"]
                       .getService(Ci.nsIPermissionManager);
            var pluginPermission = pm.testPermission(this.activeBrowser.currentURI, permissionString);
            return pluginPermission != Ci.nsIPermissionManager.DENY_ACTION;
          ]]>
        </body>
      </method>

      <method name="setPermissionForPlugins">
        <parameter name="aPermission"/>
        <body>
          <![CDATA[
            var ph = Cc["@mozilla.org/plugin/host;1"]
                       .getService(Ci.nsIPluginHost);
            var pm = Cc["@mozilla.org/permissionmanager;1"]
                       .getService(Ci.nsIPermissionManager);
            for (var type of this.clickToPlayTypes) {
              var permissionString = ph.getPermissionStringForType(type);
              pm.add(this.activeBrowser.currentURI, permissionString, aPermission);
            }
          ]]>
        </body>
      </method>

      <method name="activatePlugins">
        <parameter name="aCheckForReload"/>
        <body>
          <![CDATA[
            this.clickToPlayPluginsActivated = true;
            var plugins = this.contentWindowUtils.plugins;
            for (let plugin of plugins) {
              if (this.canActivatePlugin(plugin))
                this.clickToPlayTypes.delete(plugin.actualType);
            }
            if (aCheckForReload && this.clickToPlayTypes.size)
              this.reloadPage();
            else {
              for (let plugin of plugins) {
                if (this.canActivatePlugin(plugin))
                  plugin.playPlugin();
              }
              if (!this.clickToPlayTypes.size)
                this.removePluginClickToPlayPrompt();
            }
          ]]>
        </body>
      </method>

      <method name="activateSinglePlugin">
        <parameter name="aPlugin"/>
        <body>
          <![CDATA[
            if (this.canActivatePlugin(aPlugin))
              aPlugin.playPlugin();

            var pluginNeedsActivation = this.pluginNeedsActivationExceptThese([aPlugin]);

            if (pluginNeedsActivation)
              this.showPluginClickToPlayPrompt();
            else
              this.removePluginClickToPlayPrompt();
          ]]>
        </body>
      </method>

      <method name="stopPlayPreview">
        <parameter name="aPlugin"/>
        <parameter name="aPlayPlugin"/>
        <body>
          <![CDATA[
            var objLoadingContent = aPlugin.QueryInterface(Ci.nsIObjectLoadingContent);
            if (objLoadingContent.activated)
              return;

            if (aPlayPlugin)
              objLoadingContent.playPlugin();
            else
              objLoadingContent.cancelPlayPreview();
          ]]>
        </body>
      </method>

      <method name="openURLPref">
        <parameter name="aPref"/>
        <body>
          <![CDATA[
            var url = this._urlFormatter.formatURLPref(aPref);
            var nsIBrowserDOMWindow = Ci.nsIBrowserDOMWindow;

            var browserWin;
            var whereToOpen = Services.prefs.getIntPref("browser.link.open_external");

            if (whereToOpen != nsIBrowserDOMWindow.OPEN_NEWWINDOW) {
              browserWin = Services.wm.getMostRecentWindow("navigator:browser");
            }

            if (!browserWin) {
              var browserURL = "chrome://navigator/content/navigator.xul";
              try {
                browserURL = Services.prefs.getCharPref("browser.chromeURL");
              } catch (ex) {}

              window.openDialog(browserURL, "_blank", "chrome,all,dialog=no", url);
            } else {
              if (whereToOpen == nsIBrowserDOMWindow.OPEN_CURRENTWINDOW)
                browserWin.loadURI(url);
              else {
                // new tab
                var browser = browserWin.getBrowser();
                var newTab = browser.addTab(url);
                browser.selectedTab = newTab;
              }
              browserWin.content.focus();
            }
            return true;
          ]]>
        </body>
      </method>

      <method name="getBindingType">
        <parameter name="aPlugin"/>
        <body>
          <![CDATA[
            // Helper to get the binding handler type from a plugin object
            if (!(aPlugin instanceof Ci.nsIObjectLoadingContent))
              return null;

            switch (aPlugin.pluginFallbackType) {
              case Ci.nsIObjectLoadingContent.PLUGIN_UNSUPPORTED:
                return "PluginNotFound";
              case Ci.nsIObjectLoadingContent.PLUGIN_DISABLED:
                return "PluginDisabled";
              case Ci.nsIObjectLoadingContent.PLUGIN_BLOCKLISTED:
                return "PluginBlocklisted";
              case Ci.nsIObjectLoadingContent.PLUGIN_OUTDATED:
                return "PluginOutdated";
              case Ci.nsIObjectLoadingContent.PLUGIN_CLICK_TO_PLAY:
                return "PluginClickToPlay";
              case Ci.nsIObjectLoadingContent.PLUGIN_VULNERABLE_UPDATABLE:
                return "PluginVulnerableUpdatable";
              case Ci.nsIObjectLoadingContent.PLUGIN_VULNERABLE_NO_UPDATE:
                return "PluginVulnerableNoUpdate";
              case Ci.nsIObjectLoadingContent.PLUGIN_PLAY_PREVIEW:
                return "PluginPlayPreview";
              default:
                // Not all states map to a handler
                return null;
            }
          ]]>
        </body>
      </method>

      <method name="makeNicePluginName">
        <parameter name="aName"/>
        <body>
          <![CDATA[
            if (aName == "Shockwave Flash")
              return "Adobe Flash";

            // Clean up the plugin name by stripping off any trailing version
            // numbers or "plugin". EG, "Foo Bar Plugin 1.23_02" --> "Foo Bar"
            // Do this by first stripping the numbers, etc. off the end, and
            // then removing "Plugin" (and then trimming to get rid of any
            // whitespace). Otherwise, something like "Java(TM) Plug-in
            // 1.7.0_07" gets mangled.
            var newName = aName.replace(/[\s\d\.\-\_\(\)]+$/, "").replace(/\bplug-?in\b/i, "").trim();
            return newName;
          ]]>
        </body>
      </method>

      <method name="isTooSmall">
        <parameter name="aPlugin"/>
        <parameter name="aOverlay"/>
        <body>
          <![CDATA[
            // Is the <object>'s size too small to hold what we want to show?
            var pluginRect = aPlugin.getBoundingClientRect();
            // XXX bug 446693. The text-shadow on the submitted-report text at
            //     the bottom causes scrollHeight to be larger than it should be.
            return (aOverlay.scrollWidth > pluginRect.width) ||
                   (aOverlay.scrollHeight - 5 > pluginRect.height);
          ]]>
        </body>
      </method>

      <method name="getPluginUI">
        <parameter name="aPlugin"/>
        <parameter name="aAnonId"/>
        <body>
          <![CDATA[
            return aPlugin.ownerDocument.getAnonymousElementByAttribute(aPlugin, "anonid", aAnonId);
          ]]>
        </body>
      </method>

      <method name="addLinkClickCallback">
        <parameter name="linkNode"/>
        <parameter name="callback"/>
        <body>
          <![CDATA[
            // XXX just doing (callback)(arg) was giving a same-origin error. bug?

            /* We use event bubbling for the event listeners so that inside a
               click-to-play plugin we don't activate the plugin when the user
               clicks on a link in the click-to-play UI (e.g. to check for
               plugin updates)
             */
            let callbackArgs = Array.from(arguments).slice(2);
            linkNode.addEventListener("click",
                                      function(aEvent) {
                                        if (!aEvent.isTrusted)
                                          return;
                                        if (aEvent.button != 0)
                                          return;
                                        aEvent.preventDefault();
                                        aEvent.stopPropagation();
                                        if (callbackArgs.length == 0)
                                          callbackArgs = [ aEvent ];
                                        callback.apply(this, callbackArgs);
                                      }.bind(this));

            linkNode.addEventListener("keydown",
                                      function(aEvent) {
                                        if (!aEvent.isTrusted)
                                          return;
                                        if (aEvent.keyCode != aEvent.DOM_VK_RETURN)
                                          return;
                                        aEvent.preventDefault();
                                        aEvent.stopPropagation();
                                        if (callbackArgs.length == 0)
                                          callbackArgs = [ aEvent ];
                                        callback.apply(this, callbackArgs);
                                      }.bind(this));
          ]]>
        </body>
      </method>

      <!-- Callback for user clicking "submit a report" link -->
      <method name="submitReport">
        <parameter name="pluginDumpID"/>
        <parameter name="browserDumpID"/>
        <parameter name="plugin"/>
        <body>
          <![CDATA[
            var keyVals = {};
            if (plugin) {
              let userComment = this.getPluginUI(plugin, "submitComment").value.trim();
              if (userComment)
                keyVals.PluginUserComment = userComment;
              if (this.getPluginUI(plugin, "submitURLOptIn").checked)
                keyVals.PluginContentURL = plugin.ownerDocument.URL;
            }
            this.CrashSubmit.submit(pluginDumpID, { extraExtraKeyVals: keyVals,
                                                    recordSubmission: true });
            if (browserDumpID)
              this.CrashSubmit.submit(browserDumpID);
          ]]>
        </body>
      </method>

      <!-- Callback for user clicking a "reload page" link -->
      <method name="reloadPage">
        <parameter name="flags"/>
        <body>
          <![CDATA[
            this.activeBrowser.reloadWithFlags(flags);
          ]]>
        </body>
      </method>

      <!-- Callback for user clicking the help icon -->
      <method name="openHelpPage">
        <body>
          <![CDATA[
            //XXX Ratty need in app help here.
            openHelp("plugin-crashed", "chrome://communicator/locale/help/suitehelp.rdf");
          ]]>
        </body>
      </method>

      <method name="showPluginCrashedNotification">
        <parameter name="pluginDumpID"/>
        <parameter name="browserDumpID"/>
        <parameter name="messageString"/>
        <body>
          <![CDATA[
            // If there's already an existing notification bar, don't do anything.
            var notification = this.getNotificationWithValue("plugin-crashed");
            if (notification)
              return;

            // Configure the notification bar
            var priority = this.PRIORITY_WARNING_MEDIUM;
            var reloadLabel = this._stringBundle.GetStringFromName("crashedpluginsMessage.reloadButton.label");
            var reloadKey   = this._stringBundle.GetStringFromName("crashedpluginsMessage.reloadButton.accesskey");
            var submitLabel = this._stringBundle.GetStringFromName("crashedpluginsMessage.submitButton.label");
            var submitKey   = this._stringBundle.GetStringFromName("crashedpluginsMessage.submitButton.accesskey");

            var buttons = [{
              label: reloadLabel,
              accessKey: reloadKey,
              popup: null,
              callback: this.reloadPage.bind(this)
            }];

            if (this.CrashSubmit && pluginDumpID) {
              let submitButton = {
                label: submitLabel,
                accessKey: submitKey,
                popup: null,
                callback: this.submitReport.bind(this, pluginDumpID, browserDumpID)
              };
              buttons.push(submitButton);
            }

            var notification = this.appendNotification(messageString, "plugin-crashed",
                                                       null, priority, buttons);

            // Add the "learn more" link.
            var XULNS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
            var link = notification.ownerDocument.createElementNS(XULNS, "label");
            link.className = "text-link";
            link.setAttribute("value", this._stringBundle.GetStringFromName("crashedpluginsMessage.learnMore"));
            this.addLinkClickCallback(link, this.openHelpPage);
            var description = notification.ownerDocument.getAnonymousElementByAttribute(notification, "anonid", "messageText");
            description.appendChild(link);
          ]]>
        </body>
      </method>

      <method name="handleEvent">
        <parameter name="aEvent"/>
        <body>
          <![CDATA[
            if (!aEvent.isTrusted)
              return;

            switch (aEvent.type) {
              case "overflow":
              case "underflow":
                var plugin = aEvent.target;
                var overlay = this.getPluginUI(plugin, "main");
                if (overlay)
                  overlay.classList.toggle("visible",
                                           !this.isTooSmall(plugin, overlay));
                break;

              case "MozPlayPlugin":
                var previewContent = aEvent.currentTarget;
                previewContent.removeEventListener("MozPlayPlugin", this, true);

                var pluginElement = previewContent.ownerDocument.getBindingParent(previewContent);
                var playPlugin = !aEvent.detail;
                this.stopPlayPreview(pluginElement, playPlugin);

                // cleaning up: removes overlay iframe from the DOM
                var iframe = previewContent.getElementsByClassName("previewPluginContentFrame")[0];
                if (iframe)
                  iframe.remove();
                break;
            }
          ]]>
        </body>
      </method>

      <method name="playSoundForBlockedPopup">
        <body>
          <![CDATA[
            const kCustomSound = 1;
            var playSound = Services.prefs.getBoolPref("privacy.popups.sound_enabled");

            if (playSound) {
              var sound = Cc["@mozilla.org/sound;1"]
                            .createInstance(Ci.nsISound);

              var soundType = Services.prefs.getIntPref("privacy.popups.sound_type");
              if (soundType == kCustomSound) {
                var soundUrlSpec = Services.prefs.getCharPref("privacy.popups.sound_url");
                var fileHandler = Cc["@mozilla.org/network/protocol;1?name=file"]
                                    .getService(Ci.nsIFileProtocolHandler);
                var file = fileHandler.getFileFromURLSpec(soundUrlSpec);
                if (file.exists()) {
                  var soundUrl = fileHandler.newFileURI(file);
                  sound.play(soundUrl);
                  return;
                }
              }

              // Either a custom sound is selected which does not exist
              // or the system beep was selected, so make the system beep.
              sound.beep();
            }
          ]]>
        </body>
      </method>

      <field name="popupCount">0</field>

      <method name="notifyPopupCountChanged">
        <body>
          <![CDATA[
            this.dispatchEvent(new Event("PopupCountChanged",
              { bubbles: true, cancelable: true }));
          ]]>
        </body>
      </method>

      <method name="allowPopupsForSite">
        <parameter name="aEvent"/>
        <body>
          <![CDATA[
            const nsIPermissionManager = Ci.nsIPermissionManager;
            var uri = this.activeBrowser.currentURI;
            var pm = Cc["@mozilla.org/permissionmanager;1"]
                       .getService(nsIPermissionManager);
            pm.add(uri, "popup", nsIPermissionManager.ALLOW_ACTION);

            this.removeCurrentNotification();
          ]]>
        </body>
      </method>

      <method name="offlineAppRequested">
        <parameter name="aDocument"/>
        <body>
          <![CDATA[
            var documentURI = aDocument.documentURIObject;
            const nsIPermissionManager = Ci.nsIPermissionManager;
            var pm = Cc["@mozilla.org/permissionmanager;1"]
                       .getService(nsIPermissionManager);

            if (pm.testExactPermission(documentURI, "offline-app") !=
                nsIPermissionManager.UNKNOWN_ACTION)
              return;

            var host = documentURI.asciiHost;
            var notificationName = "offline-app-requested-" + host;
            var notification = this.getNotificationWithValue(notificationName);
            if (notification)
              notification.documents.push(aDocument);
            else {
              var buttons = [{
                label: this._stringBundle.GetStringFromName("offlineApps.always"),
                accessKey: this._stringBundle.GetStringFromName("offlineApps.always.accesskey"),
                callback: function() {
                  pm.add(documentURI, "offline-app", nsIPermissionManager.ALLOW_ACTION);
                  var updateService = Cc["@mozilla.org/offlinecacheupdate-service;1"]
                                        .getService(Ci.nsIOfflineCacheUpdateService);
                  notification.documents.forEach(function(aDocument) {
                    if (!aDocument.documentElement)
                      return;
                    var manifest = aDocument.documentElement.getAttribute("manifest");
                    if (!manifest)
                      return;

                    try {
                      var manifestURI =
                        Services.io.newURI(manifest,
                                           aDocument.characterSet,
                                           aDocument.documentURIObject);
                      updateService.scheduleUpdate(manifestURI,
                                                   aDocument.documentURIObject,
                                                   window);
                    } catch (e) {
                    }
                  });
                }
              }, {
                label: this._stringBundle.GetStringFromName("offlineApps.never"),
                accessKey: this._stringBundle.GetStringFromName("offlineApps.never.accesskey"),
                callback: function() {
                  pm.add(documentURI, "offline-app", nsIPermissionManager.DENY_ACTION);
                }
              }, {
                label: this._stringBundle.GetStringFromName("offlineApps.later"),
                accessKey: this._stringBundle.GetStringFromName("offlineApps.later.accesskey"),
                callback: function() { /* no-op */ }
              }];

              var messageString = this._stringBundle.formatStringFromName(this.usePrivateBrowsing ?
                "offlineApps.private" : "offlineApps.permissions", [host], 1);
              var priority = this.PRIORITY_INFO_LOW;
              notification = this.appendNotification(messageString, notificationName,
                                                     null, priority,
                                                     this.usePrivateBrowsing ?
                                                       null : buttons);
              notification.documents = [aDocument];
            }
          ]]>
        </body>
      </method>

      <method name="checkUsage">
        <parameter name="aURI"/>
        <body>
          <![CDATA[
            const nsIPermissionManager = Ci.nsIPermissionManager;
            var pm = Cc["@mozilla.org/permissionmanager;1"]
                       .getService(nsIPermissionManager);
            if (pm.testExactPermission(aURI, "offline-app") ==
                Ci.nsIOfflineCacheUpdateService.ALLOW_NO_WARN)
              return;

            var host = aURI.asciiHost;
            var usage = 0;

            var cacheService = Cc["@mozilla.org/network/application-cache-service;1"]
                                 .getService(Ci.nsIApplicationCacheService);
            cacheService.getGroups().forEach(function(aGroup) {
              var uri = Services.io.newURI(aGroup);
              if (uri.asciiHost == host)
                usage += cacheService.getActiveCache(aGroup).usage;
            });
            var warnQuota = Services.prefs.getIntPref("offline-apps.quota.warn");
            if (usage < warnQuota * 1024)
              return;

            var message = this._stringBundle.formatStringFromName("offlineApps.quota", [host, warnQuota / 1024], 2);
            var priority = this.PRIORITY_WARNING_MEDIUM;
            this.appendNotification(message, "offline-app-usage", null,
                                    priority, null);
            pm.add(aURI, "offline-app",
                   Ci.nsIOfflineCacheUpdateService.ALLOW_NO_WARN);
          ]]>
        </body>
      </method>

      <method name="showRightsNotification">
        <body>
          <![CDATA[
            var rightsBundle = Cc["@mozilla.org/intl/stringbundle;1"]
                                 .getService(Ci.nsIStringBundleService)
                                 .createBundle("chrome://branding/locale/aboutRights.properties");
            var buttonLabel = rightsBundle.GetStringFromName("buttonLabel");
            var buttonAccessKey = rightsBundle.GetStringFromName("buttonAccessKey");
            var productName = this._brandStringBundle.GetStringFromName("brandFullName");
            var notifyRightsText = rightsBundle.formatStringFromName("notifyRightsText2", [productName], 1);

            var buttons = [{
              label: buttonLabel,
              accessKey: buttonAccessKey,
              popup: null,
              callback: function (aNotificationBox, aButton) {
                var browser = document.getBindingParent(aNotificationBox);
                browser.addTab("about:rights", { focusNewTab: true });
              }
            }];
            var box = this.appendNotification(notifyRightsText, "about-rights",
                                              null, this.PRIORITY_INFO_LOW,
                                              buttons);
            box.persistence = 3; // arbitrary number, just so bar sticks around for a bit
          ]]>
        </body>
      </method>

      <method name="showPlacesLockedWarning">
        <body>
          <![CDATA[
            var brandShortName = this._brandStringBundle.GetStringFromName("brandShortName");
            var message = this._placesBundle.formatStringFromName("lockPrompt.text", [brandShortName], 1);
            var buttons = [{
              label: this._placesBundle.GetStringFromName("lockPromptInfoButton.label"),
              accessKey: this._placesBundle.GetStringFromName("lockPromptInfoButton.accesskey"),
              popup: null,
              callback: function() {
                openHelp("places-locked", "chrome://communicator/locale/help/suitehelp.rdf");
              }
            }];
            var box = this.appendNotification(message, "places-locked", null,
                                              this.PRIORITY_CRITICAL_MEDIUM,
                                              buttons);
            box.persistence = -1; // until user closes it
          ]]>
        </body>
      </method>

      <method name="showUpdateWarning">
        <body>
          <![CDATA[
            var brandShortName = this._brandStringBundle.GetStringFromName("brandShortName");
            var message = this._stringBundle.formatStringFromName("updatePrompt.text", [brandShortName], 1);
            var buttons = [{
              label: this._stringBundle.GetStringFromName("updatePromptCheckButton.label"),
              accessKey: this._stringBundle.GetStringFromName("updatePromptCheckButton.accesskey"),
              popup: null,
              callback: function() {
                Cc["@mozilla.org/updates/update-prompt;1"]
                  .createInstance(Ci.nsIUpdatePrompt)
                  .checkForUpdates();
              }
            }];
            var box = this.appendNotification(message, "update-warning", null,
                                              this.PRIORITY_CRITICAL_MEDIUM,
                                              buttons);
            box.persistence = -1; // until user closes it
          ]]>
        </body>
      </method>

      <method name="removeNotifications">
        <parameter name="aNotifications"/>
        <body>
          <![CDATA[
            aNotifications.forEach(function(value) {
              var box = this.getNotificationWithValue(value);
              if (box)
                this.removeNotification(box);
            }, this);
          ]]>
        </body>
      </method>

      <method name="lwthemeInstallRequest">
        <parameter name="aHost"/>
        <parameter name="aCallback"/>
        <body>
          <![CDATA[
            var message = this._stringBundle.formatStringFromName("lwthemeInstallRequest.message", [aHost], 1);
            var buttons = [{
              label: this._stringBundle.GetStringFromName("lwthemeInstallRequest.allowButton"),
              accessKey: this._stringBundle.GetStringFromName("lwthemeInstallRequest.allowButton.accesskey"),
              popup: null,
              callback: aCallback
            }];
            var box = this.appendNotification(message,
                                              "lwtheme-install-request", null,
                                              this.PRIORITY_INFO_MEDIUM,
                                              buttons);
            box.persistence = 1;
          ]]>
        </body>
      </method>

      <method name="lwthemeInstallNotification">
        <parameter name="aCallback"/>
        <body>
          <![CDATA[
            var message = this._stringBundle.GetStringFromName("lwthemeInstallNotification.message");
            var buttons = [{
              label: this._stringBundle.GetStringFromName("lwthemeInstallNotification.undoButton"),
              accessKey: this._stringBundle.GetStringFromName("lwthemeInstallNotification.undoButton.accesskey"),
              callback: aCallback
            }, {
              label: this._stringBundle.GetStringFromName("lwthemeInstallNotification.manageButton"),
              accessKey: this._stringBundle.GetStringFromName("lwthemeInstallNotification.manageButton.accesskey"),
              callback: function() {
                window.toEM("addons://list/theme");
              }
            }];
            var box = this.appendNotification(message,
                                              "lwtheme-install-notification",
                                              null, this.PRIORITY_INFO_MEDIUM,
                                              buttons);
            box.persistence = 1;
            box.timeout = Date.now() + 20000; // 20 seconds
          ]]>
        </body>
      </method>

      <method name="lwthemeNeedsRestart">
        <parameter name="aNewThemeName"/>
        <body>
          <![CDATA[
            var message = this._stringBundle.formatStringFromName("lwthemeNeedsRestart.message", [aNewThemeName], 1);
            var buttons = [{
              label: this._stringBundle.GetStringFromName("lwthemeNeedsRestart.restartButton"),
              accessKey: this._stringBundle.GetStringFromName("lwthemeNeedsRestart.restartButton.accesskey"),
              popup: null,
              callback: function() {
                BrowserUtils.restartApplication();
              }
            }];
            var box = this.appendNotification(message,
                                              "lwtheme-install-notification",
                                               null,this.PRIORITY_INFO_MEDIUM,
                                              buttons);
            box.persistence = 1;
            box.timeout = Date.now() + 20000; // 20 seconds
          ]]>
        </body>
      </method>

      <method name="removePluginClickToPlayPrompt">
        <body>
          <![CDATA[
            var notification = this.getNotificationWithValue("click-to-play-plugins");
            if (notification)
              this.removeNotification(notification);
          ]]>
        </body>
      </method>

      <method name="showPluginClickToPlayPrompt">
        <parameter name="aTriggeredByNewPlugin"/>
        <body>
          <![CDATA[
            // If there's already an existing notification bar, don't do anything.
            // Also don't display a notification bar unless showPluginClickToPlayPrompt
            // has been triggered by a new plugin object on the website; the other code
            // path to this function here is by clicking on the click-to-play overlay on
            // a plugin object. We don't want to re-show the notification bar in that
            // case
            var notification = this.getNotificationWithValue("click-to-play-plugins");
            if (notification || !aTriggeredByNewPlugin)
              return;

            var checkbox = document.createElement("checkbox");
            checkbox.className = "rememberChoice";
            checkbox.setAttribute("label", this._stringBundle.GetStringFromName("activatepluginsMessage.remember"));

            const nsIPermissionManager = Ci.nsIPermissionManager;
            var messageString = this._stringBundle.GetStringFromName("activatepluginsMessage.title");
            var buttons = [{
              label: this._stringBundle.GetStringFromName("activatepluginsMessage.activate.label"),
              accessKey: this._stringBundle.GetStringFromName("activatepluginsMessage.activate.accesskey"),
              callback: (function () {
                if (checkbox.checked)
                  this.setPermissionForPlugins(nsIPermissionManager.ALLOW_ACTION);
                this.activatePlugins(checkbox.checked);
              }).bind(this)
            }, {
              label: this._stringBundle.GetStringFromName("activatepluginsMessage.dismiss.label"),
              accessKey: this._stringBundle.GetStringFromName("activatepluginsMessage.dismiss.accesskey"),
              callback: (function () {
                if (checkbox.checked)
                  this.setPermissionForPlugins(nsIPermissionManager.DENY_ACTION);
                this.hideAllPluginOverlays();
              }).bind(this)
            }];
            var box = this.appendNotification(messageString,
                                              "click-to-play-plugins",
                                              null,
                                              this.PRIORITY_INFO_HIGH,
                                              buttons);
            // Force a style flush, so that we ensure the binding is attached.
            box.clientTop;
            box.appendChild(checkbox);
          ]]>
        </body>
      </method>

      <method name="hideAllPluginOverlays">
        <body>
          <![CDATA[
            var plugins = this.contentWindowUtils.plugins;
            for (let plugin of plugins)
              this.hidePluginOverlay(plugin);
          ]]>
        </body>
      </method>

      <method name="hidePluginOverlay">
        <parameter name="pluginElement"/>
        <body>
          <![CDATA[
            var overlay = this.getPluginUI(pluginElement, "main");
            if (overlay) // no overlay if plugin has been activated
              overlay.classList.remove("visible");
          ]]>
        </body>
      </method>

      <method name="pluginNeedsActivationExceptThese">
        <parameter name="aExceptThese"/>
        <body>
          <![CDATA[
            var pluginNeedsActivation = this.contentWindowUtils
                                            .plugins.some(function(aPlugin) {
              return !aExceptThese.includes(aPlugin) &&
                     this.canActivatePlugin(aPlugin);
            }, this);

            return pluginNeedsActivation;
          ]]>
        </body>
      </method>

      <method name="setupPluginClickToPlay">
        <parameter name="pluginElement"/>
        <parameter name="aTriggeredByNewPlugin"/>
        <body>
          <![CDATA[
            var overlay = this.getPluginUI(pluginElement, "main");

            if (!this.canActivatePlugin(pluginElement)) {
              overlay.classList.remove("visible");
              return;
            }

            if (this.clickToPlayPluginsActivated) {
              pluginElement.playPlugin();
              return;
            }

            this.clickToPlayTypes.add(pluginElement.actualType);
            this.showPluginClickToPlayPrompt(aTriggeredByNewPlugin);

            // Show the in-content UI if it's not too big. The crashed plugin handler already does this.
            pluginElement.addEventListener("overflow", this);
            pluginElement.addEventListener("underflow", this);
            overlay.classList.toggle("visible",
                                     !this.isTooSmall(pluginElement, overlay));

            var pluginName = this.getPluginInfo(pluginElement).pluginName;
            var messageString = this._stringBundle.formatStringFromName("PluginClickToActivate", [pluginName], 1);
            var overlayText = this.getPluginUI(pluginElement, "clickToPlay");
            overlayText.textContent = messageString;
            this.addLinkClickCallback(overlay, this.activateSinglePlugin, pluginElement);
          ]]>
        </body>
      </method>

      <method name="handlePlayPreviewEvent">
        <parameter name="pluginElement"/>
        <body>
          <![CDATA[
            var doc = pluginElement.ownerDocument;
            var previewContent = this.getPluginUI(pluginElement, "previewPluginContent");

            var iframe = previewContent.getElementsByClassName("previewPluginContentFrame")[0];
            if (!iframe) {
              // lazy initialization of the iframe
              iframe = doc.createElementNS("http://www.w3.org/1999/xhtml", "iframe");
              iframe.className = "previewPluginContentFrame";
              previewContent.appendChild(iframe);
            }

            var pluginInfo = this.getPluginInfo(pluginElement);
            var playPreviewUri = "data:application/x-moz-playpreview;," + pluginInfo.mimetype;
            iframe.src = playPreviewUri;

            // The MozPlayPlugin event can be dispatched from the extension chrome
            // code to replace the preview content with the native plugin.
            previewContent.addEventListener("MozPlayPlugin", this, true);
          ]]>
        </body>
      </method>

      <method name="promptIndexedDB">
        <parameter name="aRequestor"/>
        <parameter name="aWindow"/>
        <parameter name="aTopic"/>
        <parameter name="aData"/>
        <body>
          <![CDATA[
            var host = aWindow.document.documentURIObject.asciiHost;
            var property = this.usePrivateBrowsing ? "offlineApps.private" :
                                                     "offlineApps." + aTopic;
            var message = this._stringBundle.formatStringFromName(property,
                                                                  [host, aData], 2);
            var observer = aRequestor.getInterface(Ci.nsIObserver);
            var nsIPermissionManager = Ci.nsIPermissionManager;
            var buttons = [{
              label: this._stringBundle.GetStringFromName("offlineApps.always"),
              accessKey: this._stringBundle.GetStringFromName("offlineApps.always.accesskey"),
              popup: null,
              callback: function allowIndexedDB() {
                clearTimeout(box.timeout);
                observer.observe(null, "indexedDB-" + aTopic + "-response",
                                 nsIPermissionManager.ALLOW_ACTION);
              }
            }, {
              label: this._stringBundle.GetStringFromName("offlineApps.never"),
              accessKey: this._stringBundle.GetStringFromName("offlineApps.never.accesskey"),
              popup: null,
              callback: function denyIndexedDB() {
                clearTimeout(box.timeout);
                observer.observe(null, "indexedDB-" + aTopic + "-response",
                                 nsIPermissionManager.DENY_ACTION);
              }
            }, {
              label: this._stringBundle.GetStringFromName("offlineApps.later"),
              accessKey: this._stringBundle.GetStringFromName("offlineApps.later.accesskey"),
              popup: null,
              callback: function laterIndexedDB() {
                clearTimeout(box.timeout);
                observer.observe(null, "indexedDB-" + aTopic + "-response",
                                 nsIPermissionManager.UNKNOWN_ACTION);
              }
            }];
            var box = this.appendNotification(message,
                                              "indexedDB-" + aTopic + "-prompt",
                                              null, this.PRIORITY_INFO_LOW,
                                              this.usePrivateBrowsing ?
                                                null : buttons);
            box.timeout = setTimeout(function() {
              observer.observe(null, "indexedDB-" + aTopic + "-response",
                               nsIPermissionManager.UNKNOWN_ACTION);
              if (box.parentNode)
                box.parentNode.removeNotification(box);
            }, 300000); // 5 minutes
          ]]>
        </body>
      </method>

      <method name="cancelIndexedDB">
        <parameter name="aRequestor"/>
        <parameter name="aWindow"/>
        <parameter name="aTopic"/>
        <parameter name="aData"/>
        <body>
          <![CDATA[
            var popupNotification = this.getNotificationWithValue("indexedDB-" + aTopic + "-prompt");
            if (popupNotification) {
              clearTimeout(popupNotification.timeout);
              this.removeNotification(popupNotification);
            }

            var observer = aRequestor.getInterface(Ci.nsIObserver);
            observer.observe(null, "indexedDB-" + aTopic + "-response",
                             nsIPermissionManager.UNKNOWN_ACTION);
          ]]>
        </body>
      </method>

      <method name="addonInstallBlocked">
        <parameter name="installInfo"/>
        <body>
          <![CDATA[
            var host;
            try {
              // this fails with nsSimpleURIs like data: URIs
              host = installInfo.originatingURI.host;
            } catch (ex) {
              host = this._stringBundle.GetStringFromName("xpinstallHostNotAvailable");
            }
            var notificationName = "addon-install-blocked";
            var brandShortName = this._brandStringBundle.GetStringFromName("brandShortName");
            var messageString = this._stringBundle.formatStringFromName("xpinstallPromptWarning",
                                                                        [brandShortName, host], 2);
            var buttons = [{
              label: this._stringBundle.GetStringFromName("xpinstallPromptInstallButton"),
              accessKey: this._stringBundle.GetStringFromName("xpinstallPromptInstallButton.accesskey"),
              popup: null,
              callback: function allowInstall() {
                installInfo.install();
                return false;
              }
            }];

            if (!this.getNotificationWithValue(notificationName)) {
              var priority = this.PRIORITY_WARNING_MEDIUM;
              this.appendNotification(messageString, notificationName,
                                      null, priority, buttons);
            }
          ]]>
        </body>
      </method>

      <method name="addonInstallCancelled">
        <parameter name="installInfo"/>
        <body>
          <![CDATA[
            var tmp = {};
            ChromeUtils.import("resource://gre/modules/PluralForm.jsm", tmp);
            var notificationName = "addon-install-cancelled";
            var messageString = this._stringBundle.GetStringFromName("addonDownloadCancelled");
            messageString = tmp.PluralForm.get(installInfo.installs.length, messageString);
            var buttons = [{
              label: this._stringBundle.GetStringFromName("addonDownloadRestartButton"),
              accessKey: this._stringBundle.GetStringFromName("addonDownloadRestartButton.accesskey"),
              popup: null,
              callback: function() {
                var weblistener = Cc["@mozilla.org/addons/web-install-listener;1"]
                                    .getService(Ci.amIWebInstallListener);
                if (weblistener.onWebInstallRequested(installInfo.browser, installInfo.originatingURI,
                                                      [aInstall], 1)) {
                  aInstall.install();
                }
              }
            }];
            var priority = this.PRIORITY_INFO_MEDIUM;
            this.appendNotification(messageString, notificationName,
                                    null, priority, buttons);

            installInfo.installs.every(function(aInstall) {
              aInstall.cancel();
            });
            return true; // the downloading notification closed automatically
          ]]>
        </body>
      </method>

      <method name="addonInstallComplete">
        <parameter name="installInfo"/>
        <body>
          <![CDATA[
            var tmp = {};
            ChromeUtils.import("resource://gre/modules/AddonManager.jsm", tmp);
            ChromeUtils.import("resource://gre/modules/PluralForm.jsm", tmp);

            var notificationName = "addon-install-complete"
            var addonNotification = this.getNotificationWithValue(notificationName);
            if (addonNotification)
              this.removeNotification(addonNotification);

            var buttons = [];
            var messageString;
            if (installInfo.installs.some(install =>
                  install.addon.pendingOperations &
                  tmp.AddonManager.PENDING_INSTALL)) {
              messageString = this._stringBundle.GetStringFromName("addonsInstalledNeedsRestart");
              buttons.push({
                label: this._stringBundle.GetStringFromName("addonInstallRestartButton"),
                accessKey: this._stringBundle.GetStringFromName("addonInstallRestartButton.accesskey"),
                callback: function () {
                  BrowserUtils.restartApplication();
                }
              });
            } else {
              messageString = this._stringBundle.GetStringFromName("addonsInstalled");
            }

            if ("toEM" in window) {
              buttons.push({
                label: this._stringBundle.GetStringFromName("addonInstallManageButton"),
                accessKey: this._stringBundle.GetStringFromName("addonInstallManageButton.accesskey"),
                callback: function() {
                  window.toEM("addons://list/extension");
                }
              });
            }

            var brandShortName = this._brandStringBundle.GetStringFromName("brandShortName");
            messageString = tmp.PluralForm.get(installInfo.installs.length, messageString)
                                          .replace("#1", installInfo.installs[0].name)
                                          .replace("#2", installInfo.installs.length)
                                          .replace("#3", brandShortName);
            var priority = this.PRIORITY_WARNING_MEDIUM;
            this.appendNotification(messageString, notificationName,
                                    null, priority, buttons);
          ]]>
        </body>
      </method>

      <method name="addonInstallDisabled">
        <parameter name="installInfo"/>
        <body>
          <![CDATA[
            var messageString;
            var buttons;

            var notificationName = "addon-install-disabled";
            if (Services.prefs.prefIsLocked("xpinstall.enabled")) {
              messageString = this._stringBundle.GetStringFromName("xpinstallDisabledMessageLocked");
              buttons = [];
            } else {
              messageString = this._stringBundle.GetStringFromName("xpinstallDisabledMessage");
              buttons = [{
                label: this._stringBundle.GetStringFromName("xpinstallDisabledButton"),
                accessKey: this._stringBundle.GetStringFromName("xpinstallDisabledButton.accesskey"),
                popup: null,
                callback: function editPrefs() {
                  Services.prefs.setBoolPref("xpinstall.enabled", true);
                  return false;
                }
              }];
            }

            if (!this.getNotificationWithValue(notificationName)) {
              var priority = this.PRIORITY_WARNING_MEDIUM;
              this.appendNotification(messageString, notificationName,
                                      null, priority, buttons);
            }
          ]]>
        </body>
      </method>

      <method name="addonInstallFailed">
        <parameter name="installInfo"/>
        <body>
          <![CDATA[
            var notificationName = "addon-install-failed";
            if (!this.getNotificationWithValue(notificationName)) {
              var host;
              try {
                // this fails with nsSimpleURIs like data: URIs
                host = installInfo.originatingURI.host;
              } catch (ex) {
                host = this._stringBundle.GetStringFromName("xpinstallHostNotAvailable");
              }

              var error = "addonErrorIncompatible";
              var name = installInfo.installs[0].name;
              installInfo.installs.some(function(install) {
                if (install.error) {
                  name = install.name;
                  error = "addonError" + install.error;
                  return true;
                }
                if (install.addon.blocklistState ==
                    Ci.nsIBlocklistService.STATE_BLOCKED) {
                  name = install.name;
                  error = "addonErrorBlocklisted";
                }
                return false;
              });

              var brandShortName = this._brandStringBundle.GetStringFromName("brandShortName");
              var version = Cc["@mozilla.org/xre/app-info;1"]
                              .getService(Ci.nsIXULAppInfo).version;
              var messageString = this._stringBundle.GetStringFromName(error)
                                                    .replace("#1", name)
                                                    .replace("#2", host)
                                                    .replace("#3", brandShortName)
                                                    .replace("#4", version);

              var priority = this.PRIORITY_WARNING_MEDIUM;
              this.appendNotification(messageString, notificationName,
                                      null, priority, []);
            }
          ]]>
        </body>
      </method>

      <method name="addonInstallStarted">
        <parameter name="installInfo"/>
        <body>
          <![CDATA[
            var tmp = {};
            ChromeUtils.import("resource://gre/modules/AddonManager.jsm", tmp);
            if (installInfo.installs.every(function(aInstall) {
                  return aInstall.state == tmp.AddonManager.STATE_DOWNLOADED;
                }))
              return;

            ChromeUtils.import("resource://gre/modules/PluralForm.jsm", tmp);
            var notificationName = "addon-install-started";
            var messageString = this._stringBundle.GetStringFromName("addonDownloading");
            messageString = tmp.PluralForm.get(installInfo.installs.length, messageString);
            var buttons = [{
              label: this._stringBundle.GetStringFromName("addonDownloadCancelButton"),
              accessKey: this._stringBundle.GetStringFromName("addonDownloadCancelButton.accesskey"),
              popup: null,
              callback: this.addonInstallCancelled.bind(this, installInfo)
            }];
            var priority = this.PRIORITY_INFO_MEDIUM;
            var box = this.appendNotification(messageString, notificationName,
                                              null, priority, buttons);
            box.installInfo = installInfo;
            installInfo.installs.forEach(function(aInstall) {
              aInstall.addListener(box);
            });
          ]]>
        </body>
      </method>

      <method name="ignoreSafeBrowsingWarning">
        <parameter name="aReason"/>
        <parameter name="aBlockedInfo"/>

        <body>
          <![CDATA[
            var uri = this.activeBrowser.currentURI;
            var flag = Ci.nsIWebNavigation.LOAD_FLAGS_BYPASS_CLASSIFIER;
            this.activeBrowser.loadURIWithFlags(uri.asciiSpec, flag,
                                                null, null, null);

            const nsIPermissionManager = Ci.nsIPermissionManager;
            var pm = Cc["@mozilla.org/permissionmanager;1"]
                       .getService(nsIPermissionManager);
            pm.add(uri, "safe-browsing", nsIPermissionManager.ALLOW_ACTION,
                                         nsIPermissionManager.EXPIRE_SESSION);

            var title, label, accessKey, reportName, buttons;

            switch (aReason) {
             case "phishing":
               title = "safebrowsing.deceptiveSite";
               label = "safebrowsing.notADeceptiveSiteButton.label";
               accessKey = "safebrowsing.notADeceptiveSiteButton.accessKey";
               reportName = "PhishMistake";
               break;
             case "malware":
               title = "safebrowsing.reportedAttackSite";
               label = "safebrowsing.notAnAttackButton.label";
               accessKey = "safebrowsing.notAnAttackButton.accessKey";
               reportName = "MalwareMistake";
               break;
             case "unwanted":
               title = "safebrowsing.reportedUnwantedSite";
               break;
             // No notifications for unknown reasons.
             default:
               return;
           }

            title = this._stringBundle.GetStringFromName(title);

            buttons = [{
                label: this._stringBundle.GetStringFromName("safebrowsing.getMeOutOfHereButton.label"),
                accessKey: this._stringBundle.GetStringFromName("safebrowsing.getMeOutOfHereButton.accessKey"),
                callback: getMeOutOfHere
             }]

            if (reportName) {
              var tmp = {};
              ChromeUtils.import("resource://gre/modules/SafeBrowsing.jsm", tmp);
              var reportUrl = tmp.SafeBrowsing.getReportURL(reportName, aBlockedInfo);

              // There's no button if we can not get report url, for example
              // if the provider of blockedInfo is not Google.
              if (reportUrl) {
                buttons.push({
                  label: this._stringBundle.GetStringFromName(label),
                  accessKey: this._stringBundle.GetStringFromName(accessKey),
                  callback() { openUILinkIn(reportUrl, "tabfocused"); }
                });
              }
            }

            var type = "blocked-badware-page";
            var notification = this.getNotificationWithValue(type);
            if (notification)
              this.removeNotification(notification);

            var box = this.appendNotification(title, type, null,
                                              this.PRIORITY_CRITICAL_HIGH,
                                              buttons);

            // Persist the notification until the user removes so it
            // doesn't get removed on redirects.
            box.persistence = -1;
          ]]>
        </body>
      </method>
      <constructor>
        <![CDATA[
          var {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");
          ChromeUtils.import("resource://gre/modules/BrowserUtils.jsm");

          Services.obs.addObserver(this, "indexedDB-permissions-prompt");
          Services.obs.addObserver(this, "indexedDB-quota-prompt");
          Services.obs.addObserver(this, "indexedDB-quota-cancel");
          Services.obs.addObserver(this, "addon-install-blocked");
          Services.obs.addObserver(this, "addon-install-complete");
          Services.obs.addObserver(this, "addon-install-disabled");
          Services.obs.addObserver(this, "addon-install-failed");
          Services.obs.addObserver(this, "addon-install-started");
          Services.obs.addObserver(this, "offline-cache-update-completed");
          Services.obs.addObserver(this, "perm-changed");
          Services.obs.addObserver(this, "formsubmit");

          Services.prefs.addObserver("plugins.hide_infobar_for_outdated_plugin", this);
          Services.prefs.addObserver("plugins.hide_infobar_for_missing_plugin", this);
          Services.prefs.addObserver("privacy.popups.showBrowserMessage", this);
          Services.prefs.addObserver("dom.disable_open_during_load", this);

          this.addProgressListener();

          if ("nsICrashReporter" in Ci)
            ChromeUtils.import("resource://gre/modules/CrashSubmit.jsm", this);
        ]]>
      </constructor>

      <destructor>
        <![CDATA[
          this.destroy();
        ]]>
      </destructor>

      <field name="mDestroyed">false</field>

      <!-- This is necessary because the destructor doesn't always get called when
            we are removed from a tabbrowser. This will be explicitly called by tabbrowser -->
      <method name="destroy">
        <body>
          <![CDATA[
            if (this.mDestroyed)
              return;
            this.mDestroyed = true;

            if (this._addedProgressListener) {
              this.activeBrowser.webProgress.removeProgressListener(this);
              this._addedProgressListener = false;
            }

            this._activeBrowser = null;
            try {
              Services.obs.removeObserver(this, "indexedDB-permissions-prompt");
            } catch (ex) {}
            try {
              Services.obs.removeObserver(this, "indexedDB-quota-prompt");
            } catch (ex) {}
            try {
              Services.obs.removeObserver(this, "indexedDB-quota-cancel");
            } catch (ex) {}
            try {
              Services.obs.removeObserver(this, "addon-install-blocked");
            } catch (ex) {}
            try {
              Services.obs.removeObserver(this, "addon-install-complete");
            } catch (ex) {}
            try {
              Services.obs.removeObserver(this, "addon-install-disabled");
            } catch (ex) {}
            try {
              Services.obs.removeObserver(this, "addon-install-failed");
            } catch (ex) {}
            try {
              Services.obs.removeObserver(this, "addon-install-started");
            } catch (ex) {}
            try {
              Services.obs.removeObserver(this, "offline-cache-update-completed");
            } catch (ex) {}
            try {
              Services.obs.removeObserver(this, "perm-changed");
            } catch (ex) {}
            try {
              Services.obs.removeObserver(this, "formsubmit");
            } catch (ex) {}

            try {
              Services.prefs.removeObserver("plugins.hide_infobar_for_outdated_plugin", this);
            } catch (ex) {}
            try {
              Services.prefs.removeObserver("plugins.hide_infobar_for_missing_plugin", this);
            } catch (ex) {}
            try {
              Services.prefs.removeObserver("privacy.popups.showBrowserMessage", this);
            } catch (ex) {}
            try {
              Services.prefs.removeObserver("dom.disable_open_during_load", this);
            } catch (ex) {}
          ]]>
        </body>
      </method>
    </implementation>

    <handlers>
      <handler event="DOMContentLoaded" phase="capturing">
        <![CDATA[
          if (/^about:neterror\?e=netOffline/.test(event.target.documentURI))
            event.target.addEventListener("click", function tryAgain(event) {
              if (event.target.id == "errorTryAgain")
                Services.io.offline = false;
            }, true);
        ]]>
      </handler>

      <handler event="DOMUpdatePageReport" phase="capturing">
        <![CDATA[
          var browser = this.activeBrowser;
          if (!browser.blockedPopups || browser.blockedPopups.reported != false)
            return;

          // this.popupCount can be 0, while browser.blockedPopups has not been cleared.
          if (!this.popupCount && browser.blockedPopups.length > 1) {
            this.popupCount = browser.blockedPopups.length;
          } else {
            this.popupCount++;
          }
          this.playSoundForBlockedPopup();
          this.notifyPopupCountChanged();

          var tmp = {};
          ChromeUtils.import("resource://gre/modules/PluralForm.jsm", tmp);
          if (Services.prefs.getBoolPref("privacy.popups.showBrowserMessage"))
          {
            var brandShortName = this._brandStringBundle.GetStringFromName("brandShortName");
            var message = this._stringBundle.GetStringFromName("popupWarning.message");
            message = tmp.PluralForm.get(this.popupCount, message)
                                    .replace("#1", brandShortName)
                                    .replace("#2", this.popupCount);

            var notification = this.getNotificationWithValue("popup-blocked");
            if (notification) {
              notification.label = message;
            } else {
              var popupButtonText = this._stringBundle.GetStringFromName("popupWarningButton");
              var popupButtonAccesskey = this._stringBundle.GetStringFromName("popupWarningButton.accesskey");
              var buttons = [{
                label: popupButtonText,
                accessKey: popupButtonAccesskey,
                popup: "popupNotificationMenu",
                callback: null
              }];

              const priority = this.PRIORITY_WARNING_MEDIUM;
              this.appendNotification(message, "popup-blocked",
                                      null, priority, buttons);
            }
          }
        ]]>
      </handler>

      <handler event="PluginBindingAttached" phase="capturing" allowuntrusted="true">
        <![CDATA[
          var plugin = event.target;
          // Since we are expecting also untrusted events, make sure
          // that the target is a plugin
          if (!(plugin instanceof Ci.nsIObjectLoadingContent))
            return;

          var closeIcon = this.getPluginUI(plugin, "closeIcon");
          this.addLinkClickCallback(closeIcon, this.hidePluginOverlay, plugin);

          var notification, message, buttons, pref;
          switch (plugin.pluginFallbackType) {
            case Ci.nsIObjectLoadingContent.PLUGIN_UNSUPPORTED:
              // NOP. Plugin finder service (PFS) is dead.
              return;

            case Ci.nsIObjectLoadingContent.PLUGIN_DISABLED:
              if (!this.canActivatePlugin(plugin, true))
                return;

              notification = "disabled-plugins";
              message = this._stringBundle.GetStringFromName("missingpluginsMessage.title");
              if ("toEM" in window) {
                buttons = [{
                  label: this._stringBundle.GetStringFromName("addonInstallManageButton"),
                  accessKey: this._stringBundle.GetStringFromName("addonInstallManageButton.accesskey"),
                  popup: null,
                  callback: function() {
                    window.toEM("addons://list/plugin");
                  }
                }];

                var manageLink = this.getPluginUI(plugin, "managePluginsLink");
                this.addLinkClickCallback(manageLink, window.toEM, "addons://list/plugin");
              }
              break;

            case Ci.nsIObjectLoadingContent.PLUGIN_BLOCKLISTED:
              notification = "blocked-plugins";
              message = this._stringBundle.GetStringFromName("blockedpluginsMessage.title");
              buttons = [{
                label: this._stringBundle.GetStringFromName("blockedpluginsMessage.infoButton.label"),
                accessKey: this._stringBundle.GetStringFromName("blockedpluginsMessage.infoButton.accesskey"),
                popup: null,
                callback: this.openURLPref.bind(this, "extensions.blocklist.detailsURL")
              }];
              break;

            case Ci.nsIObjectLoadingContent.PLUGIN_OUTDATED:
              notification = "outdated-plugins";
              message = this._stringBundle.GetStringFromName("outdatedpluginsMessage.title");
              buttons = [{
                label: this._stringBundle.GetStringFromName("outdatedpluginsMessage.button.label"),
                accessKey: this._stringBundle.GetStringFromName("outdatedpluginsMessage.button.accesskey"),
                popup: null,
                callback: null,
              }];
              pref = "plugins.hide_infobar_for_outdated_plugin";
              break;

            case Ci.nsIObjectLoadingContent.PLUGIN_VULNERABLE_UPDATABLE:
              // fall through
            case Ci.nsIObjectLoadingContent.PLUGIN_VULNERABLE_NO_UPDATE:
              var eventType = this.getBindingType(plugin);
              // When we got here, we deal with a vulnerable plugin object, so setup UI elements for this case
              var vulnerabilityString = this._stringBundle.GetStringFromName(eventType);
              var vulnerabilityText = this.getPluginUI(plugin, "vulnerabilityStatus");
              vulnerabilityText.textContent = vulnerabilityString;
              // fall through
            case Ci.nsIObjectLoadingContent.PLUGIN_CLICK_TO_PLAY:
              this.setupPluginClickToPlay(plugin, true);
              return;

            case Ci.nsIObjectLoadingContent.PLUGIN_PLAY_PREVIEW:
              this.handlePlayPreviewEvent(plugin);
              return;

            default:
              return;
          }
          this.pluginUnavailable(plugin, notification, message, buttons, pref);
        ]]>
      </handler>

      <handler event="PluginCrashed" phase="capturing">
        <![CDATA[
          // Ensure the plugin and event are of the right type.
          var plugin = event.target;
          var detail = event.detail;
          if (!(detail instanceof Ci.nsIPropertyBag2))
            return;

          var submittedReport = detail.getPropertyAsBool("submittedCrashReport");
          var doPrompt        = true; // XXX followup for .getPropertyAsBool("doPrompt");
          var submitReports   = true; // XXX followup for .getPropertyAsBool("submitReports");
          var pluginName      = detail.getPropertyAsAString("pluginName");
          var pluginFilename  = detail.getPropertyAsAString("pluginFilename");
          var pluginDumpID    = detail.getPropertyAsAString("pluginDumpID");
          var browserDumpID   = detail.getPropertyAsAString("browserDumpID");

          // Remap the plugin name to a more user-presentable form.
          pluginName = this.makeNicePluginName(pluginName);

          // Force a style flush, so that we ensure our binding is attached.
          plugin.clientTop;

          // Configure the crashed-plugin placeholder.
          var overlay = this.getPluginUI(plugin, "main");

          var status;
          var statusDiv = this.getPluginUI(plugin, "submitStatus");

          if (this.CrashSubmit) {
            // Determine which message to show regarding crash reports.
            if (submittedReport) { // submitReports && !doPrompt, handled in observer
              status = "submitted";
            }
            else if (!submitReports && !doPrompt) {
              status = "noSubmit";
            }
            else { // doPrompt
              status = "please";
              this.getPluginUI(plugin, "submitButton").addEventListener("click",
                function (event) {
                  if (event.button != 0 || !event.isTrusted)
                    return;
                  this.submitReport(pluginDumpID, browserDumpID, plugin);
                  Services.prefs.setBoolPref("dom.ipc.plugins.reportCrashURL", optInCB.checked);
                }.bind(this));
                let optInCB = this.getPluginUI(plugin, "submitURLOptIn");
                optInCB.checked = Services.prefs.getBoolPref("dom.ipc.plugins.reportCrashURL");
            }

            // If we're showing the link to manually trigger report submission, we'll
            // want to be able to update all the instances of the UI for this crash to
            // show an updated message when a report is submitted.
            if (doPrompt) {
              let observer = {
                QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver,
                                                       Ci.nsISupportsWeakReference]),
                observe: function(subject, topic, data) {
                  let propertyBag = subject;
                  if (!(propertyBag instanceof Ci.nsIPropertyBag2))
                    return;
                  // Ignore notifications for other crashes.
                  if (propertyBag.get("minidumpID") != pluginDumpID)
                    return;
                  statusDiv.setAttribute("status", data);
                },

                handleEvent : function(event) {
                  // Not expected to be called, just here for the closure.
                }
              }

              // Use a weak reference, so we don't have to remove it...
              Cc["@mozilla.org/observer-service;1"]
                .getService(Ci.nsIObserverService)
                .addObserver(observer, "crash-report-status", true);
              // ...alas, now we need something to hold a strong reference to prevent
              // it from being GC. But I don't want to manually manage the reference's
              // lifetime (which should be no greater than the page).
              // Clever solution? Use a closure with an event listener on the statusDiv.
              // When it goes away, so do the listener references and the closure.
              statusDiv.addEventListener("mozCleverClosureHack", observer);
            }
          }

          // If we don't have a minidumpID, we can't (or didn't) submit anything.
          // This can happen if the plugin is killed from the task manager.
          if (!pluginDumpID) {
            status = "noReport";
          }

          statusDiv.setAttribute("status", status);

          var helpIcon = this.getPluginUI(plugin, "helpIcon");
          this.addLinkClickCallback(helpIcon, this.openHelpPage);

          var messageString = this._stringBundle.formatStringFromName("crashedpluginsMessage.title", [pluginName], 1);
          var crashText = this.getPluginUI(plugin, "crashedText");
          crashText.textContent = messageString;

          var link = this.getPluginUI(plugin, "reloadLink");
          this.addLinkClickCallback(link, this.reloadPage);

          var isShowing = true;

          // Is the <object>'s size too small to hold what we want to show?
          if (this.isTooSmall(plugin, overlay)) {
            // Try hiding the comment box and related report submission UI.
            statusDiv.removeAttribute("status");

            if (this.isTooSmall(plugin, overlay))
              isShowing = false;
          }

          if (isShowing) {
            overlay.classList.add("visible");
            // If a previous plugin on the page was too small and resulted in
            // adding a notification bar, then remove it because this plugin
            // instance it big enough to serve as in-content notification.
            var notification = this.getNotificationWithValue("plugin-crashed");
            if (notification)
              this.removeNotification(notification, true);
            this.crashNotified = true;
          } else {
            overlay.classList.remove("visible");
            // If another plugin on the page was large enough to show our UI, we don't
            // want to show a notification bar.
            if (!this.crashNotified)
              this.showPluginCrashedNotification(pluginDumpID, browserDumpID, messageString);
          }
        ]]>
      </handler>

      <handler event="MozApplicationManifest" phase="capturing">
        <![CDATA[
          if (!Services.prefs.getBoolPref("browser.offline-apps.notify"))
            return;

          try {
            if (Services.prefs.getBoolPref("offline-apps.allow_by_default"))
              return;
          } catch (e) {
          }

          this.offlineAppRequested(event.originalTarget);
        ]]>
      </handler>

      <handler event="pageshow" phase="capturing">
        <![CDATA[
          // The PluginClickToPlay events are not fired when navigating using the
          // BF cache. |event.persisted| is true when the page is loaded from the
          // BF cache, so this code reshows the notification if necessary.
          if (!event.persisted)
            return;

          if (pluginNeedsActivationExceptThese([]))
            this.showPluginClickToPlayPrompt(true);
        ]]>
      </handler>
    </handlers>
  </binding>

  <binding id="popup-notification"
           extends="chrome://communicator/content/bindings/notification.xml#browser-notificationbox">
    <implementation>
      <method name="onLocationChange">
        <parameter name="aWebProgress" />
        <parameter name="aRequest" />
        <parameter name="aLocation" />
        <parameter name="aFlags" />
        <body>
          <![CDATA[
            const nsIWebProgressListener = Ci.nsIWebProgressListener;
            if (aWebProgress.DOMWindow == this.activeBrowser.contentWindow &&
                !(aFlags & nsIWebProgressListener.LOCATION_CHANGE_SAME_DOCUMENT)) {
              this.onDocumentChange();
              PopupNotifications.locationChange(this.activeBrowser);
            }
          ]]>
        </body>
      </method>

      <method name="removeNotifications">
        <parameter name="aNotifications"/>
        <body>
          <![CDATA[
            aNotifications.forEach(function(value) {
              var notification = PopupNotifications.getNotification(value);
              if (notification)
                PopupNotifications.remove(notification);
            });
          ]]>
        </body>
      </method>

      <method name="lwthemeInstallRequest">
        <parameter name="aHost"/>
        <parameter name="aCallback"/>
        <body>
          <![CDATA[
            var message = this._stringBundle.formatStringFromName("lwthemeInstallRequest.message", [aHost], 1);
            var action = {
              label: this._stringBundle.GetStringFromName("lwthemeInstallRequest.allowButton"),
              accessKey: this._stringBundle.GetStringFromName("lwthemeInstallRequest.allowButton.accesskey"),
              callback: aCallback
            };
            PopupNotifications.show(this.activeBrowser,
                                    "lwtheme-install-request", message,
                                    "addons-notification-icon", action);
          ]]>
        </body>
      </method>

      <method name="lwthemeInstallNotification">
        <parameter name="aCallback"/>
        <body>
          <![CDATA[
            var message = this._stringBundle.GetStringFromName("lwthemeInstallNotification.message");
            var mainAction = {
              label: this._stringBundle.GetStringFromName("lwthemeInstallNotification.undoButton"),
              accessKey: this._stringBundle.GetStringFromName("lwthemeInstallNotification.undoButton.accesskey"),
              callback: aCallback
            };
            var secondaryActions = [{
              label: this._stringBundle.GetStringFromName("lwthemeInstallNotification.manageButton"),
              accessKey: this._stringBundle.GetStringFromName("lwthemeInstallNotification.manageButton.accesskey"),
              callback: function() {
                window.toEM("addons://list/theme");
              }
            }];
            var options = {
              timeout: Date.now() + 20000 // 20 seconds
            };
            PopupNotifications.show(this.activeBrowser,
                                    "lwtheme-install-notification", message,
                                    "addons-notification-icon", mainAction,
                                    secondaryActions, options);
          ]]>
        </body>
      </method>

      <method name="lwthemeNeedsRestart">
        <parameter name="aNewThemeName"/>
        <body>
          <![CDATA[
            var message = this._stringBundle.formatStringFromName("lwthemeNeedsRestart.message", [aNewThemeName], 1);
            var action = {
              label: this._stringBundle.GetStringFromName("lwthemeNeedsRestart.restartButton"),
              accessKey: this._stringBundle.GetStringFromName("lwthemeNeedsRestart.restartButton.accesskey"),
              callback: function() {
                BrowserUtils.restartApplication();
              }
            };
            var options = {
              timeout: Date.now() + 20000 // 20 seconds
            };
            PopupNotifications.show(this.activeBrowser,
                                    "lwtheme-install-notification", message,
                                    "addons-notification-icon", action, null,
                                    options);
          ]]>
        </body>
      </method>

      <method name="makeCenterActions">
        <body>
          <![CDATA[
            let pluginsDictionary = new Map();
            for (let plugin of this.contentWindowUtils.plugins) {
              if (this.canActivatePlugin(plugin)) {
                let pluginName = this.getPluginInfo(plugin).pluginName;
                if (!pluginsDictionary.has(pluginName)) {
                  pluginsDictionary.set(pluginName, []);
                }
                pluginsDictionary.get(pluginName).push(plugin);
              }
            }

            let centerActions = [];
            for (let [pluginName, namedPluginArray] of pluginsDictionary) {
              let plugin = namedPluginArray[0];
              let warn = false;
              let warningText = "";
              let updateLink = "";

              if (plugin.pluginFallbackType) {
                if (plugin.pluginFallbackType ==
                      Ci.nsIObjectLoadingContent.PLUGIN_VULNERABLE_UPDATABLE ||
                    plugin.pluginFallbackType ==
                      Ci.nsIObjectLoadingContent.PLUGIN_VULNERABLE_NO_UPDATE) {
                  warn = true;
                  warningText = this._stringBundle.GetStringFromName("vulnerableNoUpdatePluginWarning");
                }
              }

              let action = {
                message: pluginName,
                warn: warn,
                warningText: warningText,
                updateLink: updateLink,
                label: this._stringBundle.GetStringFromName("activateSinglePlugin"),
                callback: (function(aName) {
                  /* Play all plugins currently in the page of the given name
                     and, if needed, remove the notification */
                  for (let plugin of this.contentWindowUtils.plugins) {
                    if (this.canActivatePlugin(plugin) &&
                        this.getPluginInfo(plugin).pluginName == aName)
                      plugin.playPlugin();
                  }

                  let notification = PopupNotifications.getNotification("click-to-play-plugins", this.activeBrowser)
;
                  if (notification &&
                      !this.pluginNeedsActivationExceptThese([]))
                    notification.remove();
                }).bind(this, pluginName)
              };
              centerActions.push(action);
            }

            return centerActions;
          ]]>
        </body>
      </method>


      <method name="removePluginClickToPlayPrompt">
        <body>
          <![CDATA[
            var notification = PopupNotifications.getNotification("click-to-play-plugins",
                                                                  this.activeBrowser);
            if (notification)
              notification.remove();
          ]]>
        </body>
      </method>

      <method name="showPluginClickToPlayPrompt">
        <body>
          <![CDATA[
            const nsIPermissionManager = Ci.nsIPermissionManager;
            var mainAction = {
              label: this._stringBundle.GetStringFromName("activatepluginsMessage.activate.label"),
              accessKey: this._stringBundle.GetStringFromName("activatepluginsMessage.activate.accesskey"),
              callback: this.activatePlugins.bind(this)
            };
            var centerActions = this.makeCenterActions();
            var haveVulnerablePlugin = this.contentWindowUtils.plugins.some(function(plugin) {
              return (this.canActivatePlugin(plugin) &&
                      (plugin.pluginFallbackType ==
                       Ci.nsIObjectLoadingContent.PLUGIN_VULNERABLE_UPDATABLE ||
                       plugin.pluginFallbackType ==
                       Ci.nsIObjectLoadingContent.PLUGIN_VULNERABLE_NO_UPDATE));
            }, this);
            var messageString = this._stringBundle.GetStringFromName(haveVulnerablePlugin ?
                                                                     "vulnerablePluginsMessage" :
                                                                     "activatepluginsMessage.title");
            var secondaryActions = [{
              label: this._stringBundle.GetStringFromName("activatepluginsMessage.always.label"),
              accessKey: this._stringBundle.GetStringFromName("activatepluginsMessage.always.accesskey"),
              callback: (function () {
                this.setPermissionForPlugins(nsIPermissionManager.ALLOW_ACTION);
                this.activatePlugins(true);
              }).bind(this)
            }, {
              label: this._stringBundle.GetStringFromName("activatepluginsMessage.never.label"),
              accessKey: this._stringBundle.GetStringFromName("activatepluginsMessage.never.accesskey"),
              callback: (function () {
                this.setPermissionForPlugins(nsIPermissionManager.DENY_ACTION);
                var notification = PopupNotifications.getNotification("click-to-play-plugins",
                                                                      this.activeBrowser);
                if (notification)
                  notification.remove();
                this.hideAllPluginOverlays();
              }).bind(this)
            }];
            var options = {
              dismissed: true,
              centerActions: centerActions
            };
            PopupNotifications.show(this.activeBrowser, "click-to-play-plugins",
                                    messageString, "plugins-notification-icon",
                                    mainAction, secondaryActions, options);
           ]]>
        </body>
      </method>

      <method name="promptIndexedDB">
        <parameter name="aRequestor"/>
        <parameter name="aWindow"/>
        <parameter name="aTopic"/>
        <parameter name="aData"/>
        <body>
          <![CDATA[
            var host = aWindow.document.documentURIObject.asciiHost;
            var property = this.usePrivateBrowsing ? "offlineApps.private" :
                                                     "offlineApps." + aTopic;
            var message = this._stringBundle.formatStringFromName(property,
                                                                  [host, aData], 2);
            var observer = aRequestor.getInterface(Ci.nsIObserver);
            var nsIPermissionManager = Ci.nsIPermissionManager;
            var mainAction = {
              label: this._stringBundle.GetStringFromName("offlineApps.always"),
              accessKey: this._stringBundle.GetStringFromName("offlineApps.always.accesskey"),
              callback: function allowIndexedDB() {
                clearTimeout(notification.timeout);
                observer.observe(null, "indexedDB-" + aTopic + "-response",
                                 nsIPermissionManager.ALLOW_ACTION);
              }
            };
            var secondaryActions = [{
              label: this._stringBundle.GetStringFromName("offlineApps.never"),
              accessKey: this._stringBundle.GetStringFromName("offlineApps.never.accesskey"),
              callback: function denyIndexedDB() {
                clearTimeout(notification.timeout);
                observer.observe(null, "indexedDB-" + aTopic + "-response",
                                 nsIPermissionManager.DENY_ACTION);
              }
            }];
            function notificationTimedOut() {
              observer.observe(null, "indexedDB-" + aTopic + "-response",
                               nsIPermissionManager.UNKNOWN_ACTION);
              notification.remove();
            }
            var options = {
              eventCallback: function(state) {
                if (notification) {
                  // Always clear the timeout up front. If the doorhanger was
                  // temporarily dismissed, we'll set a new 30 second timeout
                  // to automatically cancel the request. If the doorhanger
                  // gets redisplayed we don't want it to time out unless it
                  // gets dismissed again. And if the doorhanger gets removed
                  // then we aren't interested in it any more.
                  clearTimeout(timeout);
                  if (state == "dismissed")
                    timeout = setTimeout(notificationTimedOut, 30000);
                }
              }
            };
            var notification =
                PopupNotifications.show(this.activeBrowser,
                                        "indexedDB-" + aTopic + "-prompt",
                                        message, "indexedDB-notification-icon",
                                        this.usePrivateBrowsing ? null : mainAction,
                                        secondaryActions, options);
            var timeout = setTimeout(notificationTimedOut, 300000); // 5 minutes
          ]]>
        </body>
      </method>

      <method name="cancelIndexedDB">
        <parameter name="aRequestor"/>
        <parameter name="aWindow"/>
        <parameter name="aTopic"/>
        <parameter name="aData"/>
        <body>
          <![CDATA[
            var popupNotification = PopupNotifications.getNotification("indexedDB-" + aTopic + "-prompt", this.activeBrowser);
            if (popupNotification)
              popupNotification.remove(); // eventCallback clears the timeout

            var observer = aRequestor.getInterface(Ci.nsIObserver);
            observer.observe(null, "indexedDB-" + aTopic + "-response",
                             nsIPermissionManager.UNKNOWN_ACTION);
          ]]>
        </body>
      </method>

      <method name="addonInstallBlocked">
        <parameter name="installInfo"/>
        <body>
          <![CDATA[
            var host;
            try {
              // this fails with nsSimpleURIs like data: URIs
              host = installInfo.originatingURI.host;
            } catch (ex) {
              host = this._stringBundle.GetStringFromName("xpinstallHostNotAvailable");
            }
            var brandShortName = this._brandStringBundle.GetStringFromName("brandShortName");
            var messageString = this._stringBundle.formatStringFromName("xpinstallPromptWarning",
                                                                        [brandShortName, host], 2);
            var action = {
              label: this._stringBundle.GetStringFromName("xpinstallPromptInstallButton"),
              accessKey: this._stringBundle.GetStringFromName("xpinstallPromptInstallButton.accesskey"),
              callback: function allowInstall() {
                installInfo.install();
                return false;
              }
            };

            // Make notifications persist a minimum of 30 seconds
            var options = {
              timeout: Date.now() + 30000
            };
            PopupNotifications.show(this.activeBrowser,
                                    "addon-install-blocked", messageString,
                                    "addons-notification-icon", action,
                                    null, options);
          ]]>
        </body>
      </method>

      <method name="addonInstallCancelled">
        <parameter name="installInfo"/>
        <body>
          <![CDATA[
            var tmp = {};
            ChromeUtils.import("resource://gre/modules/PluralForm.jsm", tmp);
            var messageString = this._stringBundle.GetStringFromName("addonDownloadCancelled");
            messageString = tmp.PluralForm.get(installInfo.installs.length, messageString);
            var action = {
              label: this._stringBundle.GetStringFromName("addonDownloadRestartButton"),
              accessKey: this._stringBundle.GetStringFromName("addonDownloadRestartButton.accesskey"),
              popup: null,
              callback: function() {
                var weblistener = Cc["@mozilla.org/addons/web-install-listener;1"]
                                    .getService(Ci.amIWebInstallListener);
                if (weblistener.onWebInstallRequested(installInfo.browser, installInfo.originatingURI,
                                                      [aInstall], 1)) {
                  aInstall.install();
                }
              }
            };
            PopupNotifications.show(this.activeBrowser,
                                    "addon-install-cancelled", messageString,
                                    "addons-notification-icon", action);

            installInfo.installs.every(function(aInstall) {
              aInstall.cancel();
            });
          ]]>
        </body>
      </method>

      <method name="addonInstallComplete">
        <parameter name="installInfo"/>
        <body>
          <![CDATA[
            var tmp = {};
            ChromeUtils.import("resource://gre/modules/AddonManager.jsm", tmp);
            ChromeUtils.import("resource://gre/modules/PluralForm.jsm", tmp);

            var messageString;
            var mainAction = null;
            var secondaryActions = null;

            if ("toEM" in window) {
              mainAction = {
                label: this._stringBundle.GetStringFromName("addonInstallManageButton"),
                accessKey: this._stringBundle.GetStringFromName("addonInstallManageButton.accesskey"),
                callback: function() {
                  window.toEM("addons://list/extension");
                }
              };
            }

            if (installInfo.installs.some(install =>
                  install.addon.pendingOperations &
                  tmp.AddonManager.PENDING_INSTALL)) {
              messageString = this._stringBundle.GetStringFromName("addonsInstalledNeedsRestart");
              if (mainAction)
                secondaryActions = [mainAction];
              mainAction = {
                label: this._stringBundle.GetStringFromName("addonInstallRestartButton"),
                accessKey: this._stringBundle.GetStringFromName("addonInstallRestartButton.accesskey"),
                callback: function () {
                  BrowserUtils.restartApplication();
                }
              };
            } else {
              messageString = this._stringBundle.GetStringFromName("addonsInstalled");
            }

            var brandShortName = this._brandStringBundle.GetStringFromName("brandShortName");
            messageString = tmp.PluralForm.get(installInfo.installs.length, messageString)
                                          .replace("#1", installInfo.installs[0].name)
                                          .replace("#2", installInfo.installs.length)
                                          .replace("#3", brandShortName);

            // Make notifications persist a minimum of 30 seconds
            var options = {
              timeout: Date.now() + 30000
            };
            PopupNotifications.show(this.activeBrowser,
                                    "addon-install-complete", messageString,
                                    "addons-notification-icon", mainAction,
                                    secondaryActions, options);
          ]]>
        </body>
      </method>

      <method name="addonInstallDisabled">
        <parameter name="installInfo"/>
        <body>
          <![CDATA[
            var messageString;
            var action = null;

            if (Services.prefs.prefIsLocked("xpinstall.enabled"))
              messageString = this._stringBundle.GetStringFromName("xpinstallDisabledMessageLocked");
            else {
              messageString = this._stringBundle.GetStringFromName("xpinstallDisabledMessage");
              action = {
                label: this._stringBundle.GetStringFromName("xpinstallDisabledButton"),
                accessKey: this._stringBundle.GetStringFromName("xpinstallDisabledButton.accesskey"),
                callback: function editPrefs() {
                  Services.prefs.setBoolPref("xpinstall.enabled", true);
                }
              };
            }

            // Make notifications persist a minimum of 30 seconds
            var options = {
              timeout: Date.now() + 30000
            };
            PopupNotifications.show(this.activeBrowser,
                                    "addon-install-disabled", messageString,
                                    "addons-notification-icon", action,
                                    null, options);
          ]]>
        </body>
      </method>

      <method name="addonInstallFailed">
        <parameter name="installInfo"/>
        <body>
          <![CDATA[
            var host;
            try {
              // this fails with nsSimpleURIs like data: URIs
              host = installInfo.originatingURI.host;
            } catch (ex) {
              host = this._stringBundle.GetStringFromName("xpinstallHostNotAvailable");
            }

            var error = "addonErrorIncompatible";
            var name = installInfo.installs[0].name;
            installInfo.installs.some(function(install) {
              if (install.error) {
                name = install.name;
                error = "addonError" + install.error;
                return true;
              }
              if (install.addon.blocklistState ==
                  Ci.nsIBlocklistService.STATE_BLOCKED) {
                name = install.name;
                error = "addonErrorBlocklisted";
              }
              return false;
            });

            var brandShortName = this._brandStringBundle.GetStringFromName("brandShortName");
            var version = Cc["@mozilla.org/xre/app-info;1"]
                            .getService(Ci.nsIXULAppInfo).version;
            var messageString = this._stringBundle.GetStringFromName(error)
                                                  .replace("#1", name)
                                                  .replace("#2", host)
                                                  .replace("#3", brandShortName)
                                                  .replace("#4", version);

            // Make notifications persist a minimum of 30 seconds
            var options = {
              timeout: Date.now() + 30000
            };
            PopupNotifications.show(this.activeBrowser,
                                    "addon-install-failed", messageString,
                                    "addons-notification-icon", null,
                                    null, options);
          ]]>
        </body>
      </method>

      <method name="addonInstallStarted">
        <parameter name="installInfo"/>
        <body>
          <![CDATA[
            var tmp = {};
            ChromeUtils.import("resource://gre/modules/AddonManager.jsm", tmp);
            if (installInfo.installs.every(function(aInstall) {
                  return aInstall.state == tmp.AddonManager.STATE_DOWNLOADED;
                }))
              return;

            ChromeUtils.import("resource://gre/modules/PluralForm.jsm", tmp);
            var messageString = this._stringBundle.GetStringFromName("addonDownloading");
            messageString = tmp.PluralForm.get(installInfo.installs.length, messageString);
            var action = {
              label: this._stringBundle.GetStringFromName("addonDownloadCancelButton"),
              accessKey: this._stringBundle.GetStringFromName("addonDownloadCancelButton.accesskey"),
              callback: this.addonInstallCancelled.bind(this, installInfo)
            };
            var options = {
              installInfo: installInfo
            };
            PopupNotifications.show(this.activeBrowser,
                                    "addon-install-started", messageString,
                                    "addons-notification-icon", action,
                                    null, options);
          ]]>
        </body>
      </method>
      <constructor>
        <![CDATA[
          ChromeUtils.import("resource://gre/modules/BrowserUtils.jsm");
        ]]>
      </constructor>
    </implementation>
  </binding>

  <binding id="addon-progress-notification"
           extends="chrome://global/content/bindings/notification.xml#notification">
    <content>
      <xul:hbox class="notification-inner outset" flex="1" xbl:inherits="type">
        <xul:hbox anonid="details" align="center" flex="1"
                  oncommand="this.parentNode.parentNode._doButtonCommand(event);">
          <xul:image anonid="messageImage" class="messageImage" xbl:inherits="src=image,type,value"/>
          <xul:description anonid="messageText" class="messageText" xbl:inherits="xbl:text=label"/>
          <xul:progressmeter mode="undetermined" xbl:inherits="mode,value=progress"/>
          <xul:label flex="1" xbl:inherits="value=status"/>
          <children/>
        </xul:hbox>
        <xul:toolbarbutton ondblclick="event.stopPropagation();"
                           class="messageCloseButton tabbable"
                           xbl:inherits="hidden=hideclose"
                           tooltiptext="&closeNotification.tooltip;"
                           oncommand="document.getBindingParent(this).close();"/>
      </xul:hbox>
    </content>

    <implementation>
      <destructor>
        <![CDATA[
          this.installInfo.installs.forEach(function(aInstall) {
            aInstall.removeListener(this);
          }, this);
        ]]>
      </destructor>

      <method name="updateProgress">
        <body>
          <![CDATA[
            var count = 0;
            var progress = 0;
            var max = 0;

            var tmp = {};
            ChromeUtils.import("resource://gre/modules/AddonManager.jsm", tmp);
            this.installInfo.installs.forEach(function(aInstall) {
              if (aInstall.maxProgress < 0)
                max = -1;
              else if (max >= 0)
                max += aInstall.maxProgress;
              progress += aInstall.progress;
              if (aInstall.state < tmp.AddonManager.STATE_DOWNLOADED)
                count++;
            });

            if (max < 0)
              this.setAttribute("mode", "undetermined");
            else {
              this.setAttribute("mode", "determined");
              this.setAttribute("progress", progress * 100 / max);
            }

            var now = Date.now();
            if (!this.startTime) {
              this.startTime = now;
              this.lastUpdate = now - 750;
              this.lastSeconds = null;
            }

            if (progress == max || now - this.lastUpdate >= 750) {
              this.lastUpdate = now;
              var elapsed = (now - this.startTime) / 1000;
              var rate = elapsed && progress / elapsed;
              ChromeUtils.import("resource://gre/modules/DownloadUtils.jsm", tmp);
              var status;
              [status, this.lastSeconds] = tmp.DownloadUtils.getDownloadStatus(progress, max, rate, this.lastSeconds);
              this.setAttribute("status", status);
            }

            if (!count)
              this.close();
          ]]>
        </body>
      </method>

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

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

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

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

  <binding id="sidebar-notification"
           extends="chrome://global/content/bindings/notification.xml#notification">
    <content>
      <xul:vbox class="notification-inner outset" flex="1" xbl:inherits="type">
        <xul:hbox align="center">
          <xul:image anonid="messageImage" class="messageImage" xbl:inherits="src=image,type,value"/>
          <xul:arrowscrollbox orient="horizontal" flex="1" pack="end"
                              oncommand="document.getBindingParent(this)._doButtonCommand(event);">
            <children/>
          </xul:arrowscrollbox>
          <xul:toolbarbutton ondblclick="event.stopPropagation();"
                             class="messageCloseButton tabbable"
                             xbl:inherits="hidden=hideclose"
                             tooltiptext="&closeNotification.tooltip;"
                             oncommand="document.getBindingParent(this).close();"/>
        </xul:hbox>
        <xul:description anonid="messageText" class="messageText" flex="1" xbl:inherits="xbl:text=label"/>
      </xul:vbox>
    </content>
  </binding>

  <binding id="sidebar-addon-progress-notification"
           extends="chrome://communicator/content/bindings/notification.xml#addon-progress-notification">
    <content>
      <xul:vbox class="notification-inner outset" flex="1" xbl:inherits="type">
        <xul:hbox align="center">
          <xul:image anonid="messageImage" class="messageImage" xbl:inherits="src=image,type,value"/>
          <xul:arrowscrollbox orient="horizontal" flex="1" pack="end"
                              oncommand="document.getBindingParent(this)._doButtonCommand(event);">
            <children/>
          </xul:arrowscrollbox>
          <xul:toolbarbutton ondblclick="event.stopPropagation();"
                             class="messageCloseButton tabbable"
                             xbl:inherits="hidden=hideclose"
                             tooltiptext="&closeNotification.tooltip;"
                             oncommand="document.getBindingParent(this).close();"/>
        </xul:hbox>
        <xul:description anonid="messageText" class="messageText" flex="1" xbl:inherits="xbl:text=label"/>
        <xul:progressmeter mode="undetermined" xbl:inherits="mode,value=progress"/>
        <xul:label xbl:inherits="value=status"/>
      </xul:vbox>
    </content>
  </binding>

  <binding id="addon-progress-popup-notification" extends="chrome://global/content/bindings/notification.xml#popup-notification">
    <content align="start">
      <xul:image class="popup-notification-icon" xbl:inherits="popupid"/>
      <xul:vbox flex="1">
        <xul:description class="popup-notification-description addon-progress-description"
                         xbl:inherits="xbl:text=label"/>
        <xul:hbox class="popup-notification-button-container" align="center">
          <xul:progressmeter mode="undetermined"
                             xbl:inherits="mode,value=progress"/>
          <xul:spacer flex="1"/>
          <xul:button anonid="button"
                      class="popup-notification-menubutton"
                      type="menu-button"
                      xbl:inherits="oncommand=buttoncommand,label=buttonlabel,accesskey=buttonaccesskey">
            <xul:menupopup anonid="menupopup"
                           xbl:inherits="oncommand=menucommand">
              <children/>
              <xul:menuitem class="menuitem-iconic popup-notification-closeitem"
                            label="&closeNotificationItem.label;"
                            xbl:inherits="oncommand=closeitemcommand"/>
            </xul:menupopup>
          </xul:button>
        </xul:hbox>
        <xul:label xbl:inherits="xbl:text=status"/>
      </xul:vbox>
      <xul:vbox pack="start">
        <xul:toolbarbutton anonid="closebutton"
                           class="messageCloseButton close-icon popup-notification-closebutton tabbable"
                           xbl:inherits="oncommand=closebuttoncommand"
                           tooltiptext="&closeNotification.tooltip;"/>
      </xul:vbox>
    </content>

    <implementation>
      <constructor>
        <![CDATA[
          this.installInfo = this.notification.options.installInfo;
          this.installInfo.installs.forEach(function(aInstall) {
            aInstall.addListener(this);
          }, this);
        ]]>
      </constructor>

      <destructor>
        <![CDATA[
          this.installInfo.installs.forEach(function(aInstall) {
            aInstall.removeListener(this);
          }, this);
        ]]>
      </destructor>

      <method name="updateProgress">
        <body>
          <![CDATA[
            var count = 0;
            var progress = 0;
            var max = 0;

            var tmp = {};
            ChromeUtils.import("resource://gre/modules/AddonManager.jsm", tmp);
            this.installInfo.installs.forEach(function(aInstall) {
              if (aInstall.maxProgress < 0)
                max = -1;
              else if (max >= 0)
                max += aInstall.maxProgress;
              progress += aInstall.progress;
              if (aInstall.state < tmp.AddonManager.STATE_DOWNLOADED)
                count++;
            });

            if (max < 0)
              this.setAttribute("mode", "undetermined");
            else {
              this.setAttribute("mode", "determined");
              this.setAttribute("progress", progress * 100 / max);
            }

            var now = Date.now();
            if (!this.startTime) {
              this.startTime = now;
              this.lastUpdate = now - 750;
              this.lastSeconds = null;
            }

            if (progress == max || now - this.lastUpdate >= 750) {
              this.lastUpdate = now;
              var elapsed = (now - this.startTime) / 1000;
              var rate = elapsed && progress / elapsed;
              ChromeUtils.import("resource://gre/modules/DownloadUtils.jsm", tmp);
              var status;
              [status, this.lastSeconds] = tmp.DownloadUtils.getDownloadStatus(progress, max, rate, this.lastSeconds);
              this.setAttribute("status", status);
            }

            if (!count)
              PopupNotifications.remove(this.notification);
          ]]>
        </body>
      </method>

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

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

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

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

  <binding id="center-item">
    <content>
      <xul:vbox flex="1" class="center-item-box"
                xbl:inherits="warn,showseparator,padbottom">
        <xul:hbox align="center">
          <xul:image class="center-item-icon"
                     xbl:inherits="src=itemicon"/>
          <xul:description class="center-item-label"
                           xbl:inherits="xbl:text=itemtext"/>
          <xul:spacer flex="1"/>
          <xul:button class="popup-notification-menubutton center-item-button"
                      oncommand="document.getBindingParent(this).runCallback();"
                      xbl:inherits="label=buttonlabel"/>
        </xul:hbox>
        <xul:hbox align="center" class="center-item-warning">
          <xul:image class="center-item-warning-icon"/>
          <xul:label class="center-item-warning-description" xbl:inherits="xbl:text=warningText"/>
          <xul:label xbl:inherits="href=updateLink" value="&checkForUpdates;" class="text-link"/>
        </xul:hbox>
      </xul:vbox>
    </content>
    <resources>
      <stylesheet src="chrome://global/skin/notification.css"/>
    </resources>
    <implementation>
      <field name="action"></field>
      <method name="runCallback">
        <body><![CDATA[
          let action = this.action;
          action.callback();
          let cas = action.popupnotification.notification.options.centerActions;
          cas.splice(cas.indexOf(action), 1);
          PopupNotifications._dismiss();
        ]]></body>
      </method>
    </implementation>
  </binding>

  <binding id="click-to-play-plugins-notification" extends="chrome://global/content/bindings/notification.xml#popup-notification">
    <content class="click-to-play-plugins-notification-content">
      <xul:hbox flex="1">
        <xul:vbox class="click-to-play-plugins-notification-icon-box">
          <xul:image class="popup-notification-icon"
                     xbl:inherits="popupid,src=icon"/>
        </xul:vbox>
        <xul:spacer class="click-to-play-plugins-notification-separator"/>
        <xul:vbox flex="1" class="popup-notification-main-box"
                  xbl:inherits="popupid">
          <xul:box class="click-to-play-plugins-notification-description-box" flex="1">
            <xul:description xbl:inherits="xbl:text=label"/>
          </xul:box>
          <xul:spacer class="click-to-play-plugins-notification-separator"/>
          <xul:vbox class="click-to-play-plugins-notification-center-box">
            <children includes="popupnotification-centeritem"/>
          </xul:vbox>
          <xul:spacer class="click-to-play-plugins-notification-separator"/>
          <xul:hbox class="click-to-play-plugins-notification-button-container"
                    pack="end" align="center">
            <xul:button anonid="button"
                        class="popup-notification-menubutton"
                        type="menu-button"
                        xbl:inherits="oncommand=buttoncommand,label=buttonlabel,accesskey=buttonaccesskey">
              <xul:menupopup anonid="menupopup"
                             xbl:inherits="oncommand=menucommand">
                <children/>
                <xul:menuitem class="menuitem-iconic popup-notification-closeitem"
                              label="&closeNotificationItem.label;"
                              xbl:inherits="oncommand=closeitemcommand"/>
              </xul:menupopup>
            </xul:button>
          </xul:hbox>
        </xul:vbox>
      </xul:hbox>
    </content>
    <resources>
      <stylesheet src="chrome://global/skin/notification.css"/>
    </resources>
    <implementation>
      <field name="button" readonly="true">
        document.getAnonymousElementByAttribute(this, "anonid", "button");
      </field>
      <field name="menupopup" readonly="true">
        document.getAnonymousElementByAttribute(this, "anonid", "menupopup");
      </field>
      <constructor><![CDATA[
        const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
        let popupnotification = this;
        let item = null;
        let prev = null;
        this.notification.options.centerActions.forEach(function(action) {
          action.popupnotification = popupnotification;
          item = document.createElementNS(XUL_NS, "popupnotification-centeritem");
          item.action = action;
          item.setAttribute("itemtext", action.message);
          item.setAttribute("buttonlabel", action.label);
          item.setAttribute("warn", action.warn);
          item.setAttribute("warningText", action.warningText);
          item.setAttribute("updateLink", action.updateLink);
          if (prev &&
              (prev.getAttribute("warn") == "true" ||
               item.getAttribute("warn") == "true")) {
            item.setAttribute("showseparator", true);
            if (prev.getAttribute("warn") != "true") {
              prev.setAttribute("padbottom", true);
            }
          }
          popupnotification.appendChild(item);
          prev = item;
        });
        if (item) {
          item.setAttribute("padbottom", "true");
        }
      ]]></constructor>
    </implementation>
  </binding>

</bindings>