<?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 % notificationDTD SYSTEM "chrome://global/locale/notification.dtd">
%notificationDTD;
]>

<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[
          Components.classes["@mozilla.org/intl/stringbundle;1"]
                    .getService(Components.interfaces.nsIStringBundleService)
                    .createBundle("chrome://communicator/locale/notification.properties");
        ]]>
      </field>

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

      <field name="_prefs" readonly="true">
        <![CDATA[
          Components.classes["@mozilla.org/preferences-service;1"]
                    .getService(Components.interfaces.nsIPrefBranch);
        ]]>
      </field>

      <field name="_urlFormatter" readonly="true">
        <![CDATA[
          Components.classes["@mozilla.org/toolkit/URLFormatterService;1"]
                    .getService(Components.interfaces.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(Components.interfaces.nsIInterfaceRequestor)
                              .getInterface(Components.interfaces.nsIDOMWindowUtils);
            return this._cwu;
          ]]>
        </getter>
      </property>

      <field name="usePrivateBrowsing" readonly="true">
        window.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
              .getInterface(Components.interfaces.nsIWebNavigation)
              .QueryInterface(Components.interfaces.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, Components.interfaces.nsIWebProgress.NOTIFY_SECURITY |
                                             Components.interfaces.nsIWebProgress.NOTIFY_LOCATION |
                                             Components.interfaces.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 = Components.interfaces.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 &&
                this._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 &&
                this._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 = Components.interfaces.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 &&
                this._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 &&
                this._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 = Components.interfaces.nsIPermissionManager;
                    Components.classes["@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 &&
                this._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 &&
                this._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 (!this._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 = Components.interfaces.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 (this._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(Components.interfaces.nsIRefreshURI);
                    refreshURI.forceRefreshURI(aNotification.uri,
                                               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 (!this._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 = Components.classes["@mozilla.org/embedcomp/prompt-service;1"].getService(Components.interfaces.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)
              this._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(Components.interfaces.nsIInterfaceRequestor);
                var contentWindow = requestor.getInterface(Components.interfaces.nsIDOMWindow);
                if (contentWindow.top == browser.contentWindow)
                  this.promptIndexedDB(requestor, contentWindow, "permissions");
                break;

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

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

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

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

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

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

              case "addon-install-started":
                var installInfo = aSubject.QueryInterface(Components.interfaces.amIWebInstallInfo);
                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 ioService = Components.classes["@mozilla.org/network/io-service;1"]
                                            .getService(Components.interfaces.nsIIOService);
                  var manifestURI = ioService.newURI(manifest, doc.characterSet, doc.documentURIObject);
                  var cacheUpdate = aSubject.QueryInterface(Components.interfaces.nsIOfflineCacheUpdate);
                  if (manifestURI.equals(cacheUpdate.manifestURI))
                    this.checkUsage(manifestURI);
                } catch (e) {
                  alert(e);
                }
                break;

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

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

                if (aData == "plugins.hide_infobar_for_outdated_plugin") {
                  if (!this._prefs.getBoolPref(aData))
                    return;

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

                if (aData == "plugins.hide_infobar_for_carbon_failure_plugin") {
                  if (!this._prefs.getBoolPref(aData))
                    return;

                  var carbonfailureNotification = this.getNotificationWithValue("carbon-failure-plugins");
                  if (carbonfailureNotification)
                    this.removeNotification(carbonfailureNotification);
                }

                if (aData == "plugins.hide_infobar_for_missing_plugin") {
                  if (!this._prefs.getBoolPref(aData))
                    return;

                  var missingNotification = this.getNotificationWithValue("missing-plugins");
                  if (missingNotification)
                    this.removeNotification(missingNotification);

                  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 (this._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(Components.interfaces.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 HTMLAppletElement) {
              mimetype = "application/x-java-vm";
            } else {
              if (pluginElement instanceof HTMLObjectElement) {
                pluginsPage = pluginElement.codebase;
              } else {
                pluginsPage = pluginElement.getAttribute("pluginspage");
                if (pluginsPage) {
                  var doc = pluginElement.ownerDocument;
                  var ioService = Components.classes["@mozilla.org/network/io-service;1"]
                                            .getService(Components.interfaces.nsIIOService);
                  pluginsPage = ioService.newURI(pluginsPage, doc.characterSet, doc.documentURIObject).spec;
                }
              }

              mimetype = pluginElement.QueryInterface(Components.interfaces.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 (this._prefs.getBoolPref(aPref || "plugins.hide_infobar_for_missing_plugin"))
              return;

            var notification;
            var notifications = [
              "blocked-plugins",
              "disabled-plugins",
              "missing-plugins",
              "carbon-failure-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[
            const nsIObjectLoadingContent = Components.interfaces.nsIObjectLoadingContent;
            var objLoadingContent = aPlugin.QueryInterface(nsIObjectLoadingContent);
            var actualType = objLoadingContent.actualType;
            if (objLoadingContent.getContentTypeForMIMEType(actualType) != nsIObjectLoadingContent.TYPE_PLUGIN ||
                objLoadingContent.activated ||
                (!aSkipFallbackType &&
                 (objLoadingContent.pluginFallbackType < nsIObjectLoadingContent.PLUGIN_CLICK_TO_PLAY ||
                  objLoadingContent.pluginFallbackType > nsIObjectLoadingContent.PLUGIN_VULNERABLE_NO_UPDATE)))
              return false;

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

      <method name="setPermissionForPlugins">
        <parameter name="aPermission"/>
        <body>
          <![CDATA[
            var ph = Components.classes["@mozilla.org/plugin/host;1"]
                               .getService(Components.interfaces.nsIPluginHost);
            const nsIPermissionManager = Components.interfaces.nsIPermissionManager;
            var pm = Components.classes["@mozilla.org/permissionmanager;1"]
                               .getService(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(Components.interfaces.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 = Components.interfaces.nsIBrowserDOMWindow;

            var browserWin;
            var whereToOpen = this._prefs.getIntPref("browser.link.open_external");

            if (whereToOpen != nsIBrowserDOMWindow.OPEN_NEWWINDOW) {
              var windowManager = Components.classes['@mozilla.org/appshell/window-mediator;1']
                                            .getService(Components.interfaces.nsIWindowMediator);
              browserWin = windowManager.getMostRecentWindow("navigator:browser");
            }

            if (!browserWin) {
              var browserURL = "chrome://navigator/content/navigator.xul";
              try {
                browserURL = this._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 Components.interfaces.nsIObjectLoadingContent))
              return null;

            switch (aPlugin.pluginFallbackType) {
              case Components.interfaces.nsIObjectLoadingContent.PLUGIN_UNSUPPORTED:
                return "PluginNotFound";
              case Components.interfaces.nsIObjectLoadingContent.PLUGIN_DISABLED:
                return "PluginDisabled";
              case Components.interfaces.nsIObjectLoadingContent.PLUGIN_BLOCKLISTED:
                return "PluginBlocklisted";
              case Components.interfaces.nsIObjectLoadingContent.PLUGIN_OUTDATED:
                return "PluginOutdated";
              case Components.interfaces.nsIObjectLoadingContent.PLUGIN_CLICK_TO_PLAY:
                return "PluginClickToPlay";
              case Components.interfaces.nsIObjectLoadingContent.PLUGIN_VULNERABLE_UPDATABLE:
                return "PluginVulnerableUpdatable";
              case Components.interfaces.nsIObjectLoadingContent.PLUGIN_VULNERABLE_NO_UPDATE:
                return "PluginVulnerableNoUpdate";
              case Components.interfaces.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.slice(arguments, 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),
                                      false);

            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),
                                      false);
          ]]>
        </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 = this._prefs.getBoolPref("privacy.popups.sound_enabled");

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

              var soundType = this._prefs.getIntPref("privacy.popups.sound_type");
              if (soundType == kCustomSound) {
                var soundUrlSpec = this._prefs.getCharPref("privacy.popups.sound_url");
                var fileHandler = Components.classes["@mozilla.org/network/protocol;1?name=file"]
                                            .getService(Components.interfaces.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 = Components.interfaces.nsIPermissionManager;
            var uri = this.activeBrowser.currentURI;
            var pm = Components.classes["@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 = Components.interfaces.nsIPermissionManager;
            var pm = Components.classes["@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 ioService = Components.classes["@mozilla.org/network/io-service;1"]
                                            .getService(Components.interfaces.nsIIOService);
                  var updateService = Components.classes["@mozilla.org/offlinecacheupdate-service;1"]
                                                .getService(Components.interfaces.nsIOfflineCacheUpdateService);
                  notification.documents.forEach(function(aDocument) {
                    if (!aDocument.documentElement)
                      return;
                    var manifest = aDocument.documentElement.getAttribute("manifest");
                    if (!manifest)
                      return;

                    try {
                      var manifestURI = ioService.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 = Components.interfaces.nsIPermissionManager;
            var pm = Components.classes["@mozilla.org/permissionmanager;1"]
                               .getService(nsIPermissionManager);
            if (pm.testExactPermission(aURI, "offline-app") ==
                Components.interfaces.nsIOfflineCacheUpdateService.ALLOW_NO_WARN)
              return;

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

            var ioService = Components.classes["@mozilla.org/network/io-service;1"]
                                      .getService(Components.interfaces.nsIIOService);
            var cacheService = Components.classes["@mozilla.org/network/application-cache-service;1"]
                                         .getService(Components.interfaces.nsIApplicationCacheService);
            cacheService.getGroups().forEach(function(aGroup) {
              var uri = ioService.newURI(aGroup, null, null);
              if (uri.asciiHost == host)
                usage += cacheService.getActiveCache(aGroup).usage;
            });
            var warnQuota = this._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",
                   Components.interfaces.nsIOfflineCacheUpdateService.ALLOW_NO_WARN);
          ]]>
        </body>
      </method>

      <method name="showRightsNotification">
        <body>
          <![CDATA[
            var rightsBundle = Components.classes["@mozilla.org/intl/stringbundle;1"]
                                         .getService(Components.interfaces.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("notifyRightsText", [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._stringBundle.formatStringFromName("lockPrompt.text", [brandShortName], 1);
            var buttons = [{
              label: this._stringBundle.GetStringFromName("lockPromptInfoButton.label"),
              accessKey: this._stringBundle.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() {
                Components.classes["@mozilla.org/updates/update-prompt;1"]
                          .createInstance(Components.interfaces.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() {
                Application.restart();
              }
            }];
            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 = Components.interfaces.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.indexOf(aPlugin) < 0 &&
                     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="showGeolocationPrompt">
        <parameter name="file"/>
        <parameter name="site"/>
        <parameter name="allowCallback"/>
        <parameter name="cancelCallback"/>
        <body>
          <![CDATA[
            var type = "geolocation";
            if (this.getNotificationWithValue(type))
              return;

            var buttons = [{
              label: this._stringBundle.GetStringFromName(type + ".shareLocation"),
              accessKey: this._stringBundle.GetStringFromName(type + ".shareLocation.accesskey"),
              popup: null,
              callback: function (aNotificationBox, aButton) {
                allowCallback(aNotificationBox.checkbox.checked);
              }
            }, {
              label: this._stringBundle.GetStringFromName(type + ".dontShareThisRequest"),
              accessKey: this._stringBundle.GetStringFromName(type + ".dontShareThisRequest.accesskey"),
              popup: null,
              callback: function (aNotificationBox, aButton) {
                cancelCallback(aNotificationBox.checkbox.checked);
              }
            }];
            var message;
            if (site)
              message = this._stringBundle
                            .formatStringFromName(type + ".siteWantsToKnow",
                                                  [site], 1);
            else
              message = this._stringBundle
                            .formatStringFromName(type + ".fileWantsToKnow",
                                                  [file], 1);
            var box = this.appendNotification(message, type, null,
                                              this.PRIORITY_INFO_HIGH, buttons);
            // Force a style flush, so that we ensure the binding is attached.
            box.clientTop;

            // Create a dummy checkbox so file requests don't try to remember.
            box.checkbox = { checked: false };

            // Don't offer action in Private Browsing mode if the action
            // remembers permission for more than a session.
            if (site && !this.usePrivateBrowsing) {
              box.checkbox = document.createElement("checkbox");
              box.checkbox.className = "rememberChoice";
              box.checkbox.setAttribute("label", this._stringBundle.GetStringFromName(type + ".remember"));
              box.appendChild(box.checkbox);
            }

            var link = document.createElement("label");
            link.className = "text-link";
            link.setAttribute("value", this._stringBundle.GetStringFromName(type + ".learnMore"));
            link.setAttribute("href", this._urlFormatter.formatURLPref("browser." + type + ".warning.infoURL"));
            document.getAnonymousElementByAttribute(box, "anonid", "messageText").appendChild(link);
          ]]>
        </body>
      </method>

      <method name="showWebNotificationPrompt">
        <parameter name="site"/>
        <parameter name="allowCallback"/>
        <parameter name="cancelCallback"/>
        <body>
          <![CDATA[
            const nsIPermissionManager = Components.interfaces.nsIPermissionManager;
            var type = "webNotifications";
            if (this.getNotificationWithValue(type))
              return;

            var buttons = [{
              label: this._stringBundle.GetStringFromName(type + ".showForSession"),
              accessKey: this._stringBundle.GetStringFromName(type + ".showForSession.accesskey"),
              popup: null,
              callback: function (aNotificationBox, aButton) {
                var expireType = aNotificationBox.checkbox.checked ?
                                 nsIPermissionManager.EXPIRE_NEVER :
                                 nsIPermissionManager.EXPIRE_SESSION;
                allowCallback(true, expireType);
              }
            }, {
              label: this._stringBundle.GetStringFromName(type + ".dontShowThisSession"),
              accessKey: this._stringBundle.GetStringFromName(type + ".dontShowThisSession.accesskey"),
              popup: null,
              callback: function (aNotificationBox, aButton) {
                var expireType = aNotificationBox.checkbox.checked ?
                                 nsIPermissionManager.EXPIRE_NEVER :
                                 nsIPermissionManager.EXPIRE_SESSION;
                cancelCallback(true, expireType);
              }
            }];
            var message = this._stringBundle
                              .formatStringFromName(type + ".showFromSite",
                                                    [site], 1);

            var box = this.appendNotification(message, type, null,
                                              this.PRIORITY_INFO_HIGH, buttons);
            // Force a style flush, so that we ensure the binding is attached.
            box.clientTop;

            // Create a dummy checkbox so private requests don't try to remember.
            box.checkbox = { checked: false };

            // Don't offer action in Private Browsing mode if the action
            // remembers permission for more than a session.
            if (!this.usePrivateBrowsing) {
              box.checkbox = document.createElement("checkbox");
              box.checkbox.className = "rememberChoice";
              box.checkbox.setAttribute("label", this._stringBundle.GetStringFromName(type + ".remember"));
              box.appendChild(box.checkbox);
            }
          ]]>
        </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(Components.interfaces.nsIObserver);
            var nsIPermissionManager = Components.interfaces.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(Components.interfaces.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 = {};
            Components.utils.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 = Components.classes["@mozilla.org/addons/web-install-listener;1"]
                                            .getService(Components.interfaces.amIWebInstallListener);
                if (weblistener.onWebInstallRequested(installInfo.browser, installInfo.originatingURI,
                                                      installInfo.installs, installInfo.installs.length)) {
                  installInfo.installs.forEach(function(aInstall) {
                    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 = {};
            Components.utils.import("resource://gre/modules/AddonManager.jsm", tmp);
            Components.utils.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 () {
                  Application.restart();
                }
              });
            } 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, buttons;
            var prefBranch = this._prefs; // used by editPrefs callback

            var notificationName = "addon-install-disabled";
            if (prefBranch.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() {
                  prefBranch.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 ==
                    Components.interfaces.nsIBlocklistService.STATE_BLOCKED) {
                  name = install.name;
                  error = "addonErrorBlocklisted";
                }
                return false;
              });

              var brandShortName = this._brandStringBundle.GetStringFromName("brandShortName");
              var version = Components.classes["@mozilla.org/xre/app-info;1"]
                                      .getService(Components.interfaces.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 = {};
            Components.utils.import("resource://gre/modules/AddonManager.jsm", tmp);
            if (installInfo.installs.every(function(aInstall) {
                  return aInstall.state == tmp.AddonManager.STATE_DOWNLOADED;
                }))
              return;

            Components.utils.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"/>
        <body>
          <![CDATA[
            var uri = this.activeBrowser.currentURI;
            var flag = Components.interfaces.nsIWebNavigation.LOAD_FLAGS_BYPASS_CLASSIFIER;
            this.activeBrowser.loadURIWithFlags(uri.asciiSpec, flag,
                                                null, null, null);

            const nsIPermissionManager = Components.interfaces.nsIPermissionManager;
            var pm = Components.classes["@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 forbidden sites
             // or 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 = {};
              Components.utils.import("resource://gre/modules/SafeBrowsing.jsm", tmp);
              var reportUrl = tmp.SafeBrowsing.getReportURL(reportName, uri);

              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 os = Components.classes["@mozilla.org/observer-service;1"]
                             .getService(Components.interfaces.nsIObserverService);
          os.addObserver(this, "indexedDB-permissions-prompt", false);
          os.addObserver(this, "indexedDB-quota-prompt", false);
          os.addObserver(this, "indexedDB-quota-cancel", false);
          os.addObserver(this, "addon-install-blocked", false);
          os.addObserver(this, "addon-install-complete", false);
          os.addObserver(this, "addon-install-disabled", false);
          os.addObserver(this, "addon-install-failed", false);
          os.addObserver(this, "addon-install-started", false);
          os.addObserver(this, "offline-cache-update-completed", false);
          os.addObserver(this, "perm-changed", false);
          os.addObserver(this, "formsubmit", false);

          this._prefs.addObserver("plugins.hide_infobar_for_outdated_plugin", this, false);
          this._prefs.addObserver("plugins.hide_infobar_for_carbon_failure_plugin", this, false);
          this._prefs.addObserver("plugins.hide_infobar_for_missing_plugin", this, false);
          this._prefs.addObserver("privacy.popups.showBrowserMessage", this, false);
          this._prefs.addObserver("dom.disable_open_during_load", this, false);

          this.addProgressListener();

          if ("nsICrashReporter" in Components.interfaces)
            Components.utils.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;
            var os = Components.classes["@mozilla.org/observer-service;1"]
                                .getService(Components.interfaces.nsIObserverService);
            try {
              os.removeObserver(this, "indexedDB-permissions-prompt");
            } catch (ex) {}
            try {
              os.removeObserver(this, "indexedDB-quota-prompt");
            } catch (ex) {}
            try {
              os.removeObserver(this, "indexedDB-quota-cancel");
            } catch (ex) {}
            try {
              os.removeObserver(this, "addon-install-blocked");
            } catch (ex) {}
            try {
              os.removeObserver(this, "addon-install-complete");
            } catch (ex) {}
            try {
              os.removeObserver(this, "addon-install-disabled");
            } catch (ex) {}
            try {
              os.removeObserver(this, "addon-install-failed");
            } catch (ex) {}
            try {
              os.removeObserver(this, "addon-install-started");
            } catch (ex) {}
            try {
              os.removeObserver(this, "offline-cache-update-completed");
            } catch (ex) {}
            try {
              os.removeObserver(this, "perm-changed");
            } catch (ex) {}
            try {
              os.removeObserver(this, "formsubmit");
            } catch (ex) {}

            try {
              this._prefs.removeObserver("plugins.hide_infobar_for_outdated_plugin", this);
            } catch (ex) {}
            try {
              this._prefs.removeObserver("plugins.hide_infobar_for_carbon_failure_plugin", this);
            } catch (ex) {}
            try {
              this._prefs.removeObserver("plugins.hide_infobar_for_missing_plugin", this);
            } catch (ex) {}
            try {
              this._prefs.removeObserver("privacy.popups.showBrowserMessage", this);
            } catch (ex) {}
            try {
              this._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")
                Components.classes["@mozilla.org/network/io-service;1"]
                          .getService(Components.interfaces.nsIIOService)
                          .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 = {};
          Components.utils.import("resource://gre/modules/PluralForm.jsm", tmp);
          if (this._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 nsIObjectLoadingContent = Components.interfaces.nsIObjectLoadingContent;
          var plugin = event.target;
          // Since we are expecting also untrusted events, make sure
          // that the target is a plugin
          if (!(plugin instanceof nsIObjectLoadingContent))
            return;

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

          var notification, message, buttons, pref;
          switch (plugin.pluginFallbackType) {
            case nsIObjectLoadingContent.PLUGIN_UNSUPPORTED:
              notification = "missing-plugins";
              message = this._stringBundle.GetStringFromName("missingpluginsMessage.title");
              if ("goPreferences" in window) {
                buttons = [{
                  label: this._stringBundle.GetStringFromName("missingpluginsMessage.button.label"),
                  accessKey: this._stringBundle.GetStringFromName("missingpluginsMessage.button.accesskey"),
                  popup: null,
                  callback: function() {
                    goPreferences("scripts_pane");
                    return true;
                  }
                }];
              }
              break;

            case 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 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 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: this.openURLPref.bind(this, "plugins.update.url")
              }];
              pref = "plugins.hide_infobar_for_outdated_plugin";
              break;

            case nsIObjectLoadingContent.PLUGIN_VULNERABLE_UPDATABLE:
              var updateLink = this.getPluginUI(plugin, "checkForUpdatesLink");
              this.addLinkClickCallback(updateLink, this.openURLPref, "plugins.update.url");
              // fall through
            case 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 nsIObjectLoadingContent.PLUGIN_CLICK_TO_PLAY:
              this.setupPluginClickToPlay(plugin, true);
              return;

            case 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 Components.interfaces.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);
                  this._prefs.setBoolPref("dom.ipc.plugins.reportCrashURL", optInCB.checked);
                }.bind(this));
                let optInCB = this.getPluginUI(plugin, "submitURLOptIn");
                optInCB.checked = this._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([Components.interfaces.nsIObserver,
                                                       Components.interfaces.nsISupportsWeakReference]),
                observe: function(subject, topic, data) {
                  let propertyBag = subject;
                  if (!(propertyBag instanceof Components.interfaces.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...
              Components.classes["@mozilla.org/observer-service;1"]
                        .getService(Components.interfaces.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, false);
            }
          }

          // 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="npapi-carbon-event-model-failure" phase="capturing">
        <![CDATA[
          var plugin = event.target;
          // Force a style flush, so that we ensure our binding is attached.
          plugin.clientTop;

          function callback() {
            // Notify all windows that an application quit has been requested.
            var cancelQuit = Components.classes["@mozilla.org/supports-PRBool;1"]
                                       .createInstance(Components.interfaces.nsISupportsPRBool);
            var os = Components.classes["@mozilla.org/observer-service;1"]
                               .getService(Components.interfaces.nsIObserverService);
            os.notifyObservers(cancelQuit, "quit-application-requested", null);

            // Something aborted the quit process.
            if (cancelQuit.data)
              return;

            var nsIAppStartup = Components.interfaces.nsIAppStartup;
            Components.classes["@mozilla.org/toolkit/app-startup;1"]
                      .getService(nsIAppStartup)
                      .quit(nsIAppStartup.eAttemptQuit |
                            nsIAppStartup.eRestart |
                            nsIAppStartup.eRestarti386);
          }

          var notification = "carbon-failure-plugins";
          var pref = "plugins.hide_infobar_for_carbon_failure_plugin";
          if ("@mozilla.org/xpcom/mac-utils;1" in Components.classes &&
              !Components.classes["@mozilla.org/xpcom/mac-utils;1"]
                         .getService(Components.interfaces.nsIMacUtils)
                         .isUniversalBinary) {
            pref = null;
            notification = "missing-plugins";
            callback = this.openURLPref.bind(this, "plugins.update.url");
          }

          var prefix = notification.replace(/-/g, "");
          var message = this._stringBundle.GetStringFromName(prefix + "Message.title");
          var buttons = [{
            label: this._stringBundle.GetStringFromName(prefix + "Message.button.label"),
            accessKey: this._stringBundle.GetStringFromName(prefix + "Message.button.accesskey"),
            popup: null,
            callback: callback
          }];

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

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

          try {
            if (this._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 = Components.interfaces.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() {
                Application.restart();
              }
            };
            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 ==
                      Components.interfaces.nsIObjectLoadingContent.PLUGIN_VULNERABLE_UPDATABLE) {
                  updateLink = this._urlFormatter.formatURLPref("plugins.update.url");
                  warn = true;
                  warningText = this._stringBundle.GetStringFromName("vulnerableUpdatablePluginWarning");
                }
                else if (plugin.pluginFallbackType ==
                           Components.interfaces.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 = Components.interfaces.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 ==
                       Components.interfaces.nsIObjectLoadingContent.PLUGIN_VULNERABLE_UPDATABLE ||
                       plugin.pluginFallbackType ==
                       Components.interfaces.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="showGeolocationPrompt">
        <parameter name="file"/>
        <parameter name="site"/>
        <parameter name="allowCallback"/>
        <parameter name="cancelCallback"/>
        <body>
          <![CDATA[
            var type = "geolocation";
            var mainAction = {
              label: this._stringBundle.GetStringFromName(type + ".shareLocation"),
              accessKey: this._stringBundle.GetStringFromName(type + ".shareLocation.accesskey"),
              callback: function(aNotification) {
                allowCallback(false);
              }
            };

            var secondaryActions = [{
              label: this._stringBundle.GetStringFromName(type + ".dontShareThisRequest"),
              accessKey: this._stringBundle.GetStringFromName(type + ".dontShareThisRequest.accesskey"),
              callback: function (aNotification) {
                cancelCallback(false);
              }
            }, {
              label: this._stringBundle.GetStringFromName(type + ".alwaysShareForSite"),
              accessKey: this._stringBundle.GetStringFromName(type + ".alwaysShareForSite.accesskey"),
              callback: function (aNotification) {
                allowCallback(true);
              }
            }, {
              label: this._stringBundle.GetStringFromName(type + ".neverShareForSite"),
              accessKey: this._stringBundle.GetStringFromName(type + ".neverShareForSite.accesskey"),
              callback: function (aNotification) {
                cancelCallback(true);
              }
            }];

            // Don't offer action in Private Browsing mode if the action
            // remembers permission for more than a session.
            if (file || this.usePrivateBrowsing)
              secondaryActions.length = 1;

            var message;
            if (site) {
              message = this._stringBundle
                            .formatStringFromName(type + ".siteWantsToKnow",
                                                  [site], 1);
            } else {
              message = this._stringBundle
                            .formatStringFromName(type + ".fileWantsToKnow",
                                                  [file], 1);
            }

            var options = {
              value: this._stringBundle.GetStringFromName(type + ".learnMore"),
              href: this._urlFormatter.formatURLPref("browser." + type + ".warning.infoURL")
            };

            PopupNotifications.show(this.activeBrowser,
                                    "geolocation", message,
                                    "geo-notification-icon", mainAction,
                                    secondaryActions, options);
          ]]>
        </body>
      </method>

      <method name="showWebNotificationPrompt">
        <parameter name="site"/>
        <parameter name="allowCallback"/>
        <parameter name="cancelCallback"/>
        <body>
          <![CDATA[
            const nsIPermissionManager = Components.interfaces.nsIPermissionManager;
            var type = "webNotifications";
            var mainAction = {
              label: this._stringBundle.GetStringFromName(type + ".showForSession"),
              accessKey: this._stringBundle.GetStringFromName(type + ".showForSession.accesskey"),
              callback: function(aNotification) {
                allowCallback(true, nsIPermissionManager.EXPIRE_SESSION);
              }
            };

            var secondaryActions = [{
              label: this._stringBundle.GetStringFromName(type + ".dontShowThisSession"),
              accessKey: this._stringBundle.GetStringFromName(type + ".dontShowThisSession.accesskey"),
              callback: function (aNotification) {
                cancelCallback(true, nsIPermissionManager.EXPIRE_SESSION);
              }
            }, {
              label: this._stringBundle.GetStringFromName(type + ".alwaysShowForSite"),
              accessKey: this._stringBundle.GetStringFromName(type + ".alwaysShowForSite.accesskey"),
              callback: function (aNotification) {
                allowCallback(true, nsIPermissionManager.EXPIRE_NEVER);
              }
            }, {
              label: this._stringBundle.GetStringFromName(type + ".neverShowForSite"),
              accessKey: this._stringBundle.GetStringFromName(type + ".neverShowForSite.accesskey"),
              callback: function (aNotification) {
                cancelCallback(true, nsIPermissionManager.EXPIRE_NEVER);
              }
            }];

            // Don't offer action in Private Browsing mode if the action
            // remembers permission for more than a session.
            if (this.usePrivateBrowsing)
              secondaryActions.length = 1;

            var message = this._stringBundle
                              .formatStringFromName(type + ".showFromSite",
                                                    [site], 1);

            PopupNotifications.show(this.activeBrowser,
                                    "web-notifications", message,
                                    "web-notifications-notification-icon",
                                    mainAction, secondaryActions);
          ]]>
        </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(Components.interfaces.nsIObserver);
            var nsIPermissionManager = Components.interfaces.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(Components.interfaces.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 = {};
            Components.utils.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 = Components.classes["@mozilla.org/addons/web-install-listener;1"]
                                            .getService(Components.interfaces.amIWebInstallListener);
                if (weblistener.onWebInstallRequested(installInfo.browser, installInfo.originatingURI,
                                                      installInfo.installs, installInfo.installs.length)) {
                  installInfo.installs.forEach(function(aInstall) {
                    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 = {};
            Components.utils.import("resource://gre/modules/AddonManager.jsm", tmp);
            Components.utils.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 () {
                  Application.restart();
                }
              };
            } 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;
            var prefBranch = this._prefs; // used by editPrefs callback

            if (prefBranch.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() {
                  prefBranch.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 ==
                  Components.interfaces.nsIBlocklistService.STATE_BLOCKED) {
                name = install.name;
                error = "addonErrorBlocklisted";
              }
              return false;
            });

            var brandShortName = this._brandStringBundle.GetStringFromName("brandShortName");
            var version = Components.classes["@mozilla.org/xre/app-info;1"]
                                    .getService(Components.interfaces.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 = {};
            Components.utils.import("resource://gre/modules/AddonManager.jsm", tmp);
            if (installInfo.installs.every(function(aInstall) {
                  return aInstall.state == tmp.AddonManager.STATE_DOWNLOADED;
                }))
              return;

            Components.utils.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>

    </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 = {};
            Components.utils.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;
              Components.utils.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="geolocation-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"
                         xbl:inherits="xbl:text=label"/>
        <xul:hbox class="popup-notification-button-container" align="center">
          <xul:label class="text-link" xbl:inherits="value,href"/>
          <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="value=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.setAttribute("value", this.notification.options.value);
          this.setAttribute("href", this.notification.options.href);
        ]]>
      </constructor>
    </implementation>
  </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 = {};
            Components.utils.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;
              Components.utils.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>
