suite/common/bindings/notification.xml
author Neil Rashbrook <neil@parkwaycc.co.uk>
Sun, 14 Apr 2013 23:24:06 +0100
changeset 13679 12ea7b098021d9dcf23b2d9109c807806091452e
parent 13670 89781801c00c21f64a2b4556535daed5a0a0d972
child 13931 9cf36ae1f02ac263bd210cba0e619f8f8a1e0022
permissions -rw-r--r--
Bug 860537 Some parts of click-to-play don't work if doorhangers are disabled r=Ratty a=IanN

<?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>

      <method name="onDocumentChange">
        <body>
          <![CDATA[
            this.missingPlugins = null;
            this.crashNotified = false;
            this.clickToPlayPromptShown = false;
            this.clickToPlayPluginsActivated = false;
            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 = lastState;
            const nsIWebProgressListener = Components.interfaces.nsIWebProgressListener;
            var pref = "security.warn_leaving_secure";
            var message = "EnterInsecureMessage";
            var priority = this.PRIORITY_WARNING_LOW;
            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;
              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_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("ssl_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.originatingWindow.top == browser.contentWindow)
                  this.addonInstallBlocked(installInfo);
                break;

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

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

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

              case "addon-install-started":
                var installInfo = aSubject.QueryInterface(Components.interfaces.amIWebInstallInfo);
                if (installInfo.originatingWindow.top == browser.contentWindow)
                  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 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;

                try {
                  var hostport = this.activeBrowser.currentURI.hostPort;
                } catch (ex) {
                  // we can't do much without a hostport here
                  return;
                }
                var host = '.' + permission.host;
                hostport = '.' + hostport;

                if (host == hostport.slice(-host.length)) {
                  var popupNotification = this.getNotificationWithValue("popup-blocked");
                  if (popupNotification)
                    this.removeNotification(popupNotification);
                  this.popupCount = 0;
                  this.notifyPopupCountChanged();
                }
                break;
            }
          ]]>
        </body>
      </method>

      <field name="_missingPlugins">null</field>
      <property name="missingPlugins"
                onget="return this._missingPlugins || (this._missingPlugins = new Map());"
                onset="return this._missingPlugins = val;"/>

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

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

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

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

      <method name="getPluginInfo">
        <parameter name="pluginElement"/>
        <body>
          <![CDATA[
            var mimetype;
            var pluginsPage;
            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;
              }
            }

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

      <method name="pluginUnavailable">
        <parameter name="aPlugin"/>
        <parameter name="aNotification"/>
        <parameter name="aMessage"/>
        <parameter name="aButtons"/>
        <parameter name="aPref"/>
        <body>
          <![CDATA[
            // Save information on the plugin to give to the plugin finder.
            var pluginInfo = this.getPluginInfo(aPlugin);
            this.missingPlugins.set(pluginInfo.mimetype, pluginInfo);

            // Hide the in-content UI if it's too big. The crashed plugin handler already does this.
            var doc = aPlugin.ownerDocument;
            var overlay = doc.getAnonymousElementByAttribute(aPlugin, "class", "mainBox");
            if (this.isTooSmall(aPlugin, overlay))
              overlay.style.visibility = "hidden";

            if (this._prefs.getBoolPref(aPref || "plugins.hide_infobar_for_missing_plugin"))
              return;

            var notification;
            var notifications = [
              "blocked-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="objLoadingContent"/>
        <body>
          <![CDATA[
            const nsIObjectLoadingContent = Components.interfaces.nsIObjectLoadingContent;
            var actualType = objLoadingContent.actualType;
            if (objLoadingContent.getContentTypeForMIMEType(actualType) != nsIObjectLoadingContent.TYPE_PLUGIN ||
                objLoadingContent.activated ||
                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);
            var plugins = this.contentWindowUtils.plugins;
            for (var plugin of plugins) {
              var objLoadingContent = plugin.QueryInterface(Components.interfaces.nsIObjectLoadingContent);
              if (this.canActivatePlugin(objLoadingContent)) {
                var permissionString = ph.getPermissionStringForType(objLoadingContent.actualType);
                pm.add(this.activeBrowser.currentURI, permissionString, aPermission);
              }
            }
          ]]>
        </body>
      </method>

      <method name="activatePlugins">
        <body>
          <![CDATA[
            this.clickToPlayPluginsActivated = true;
            var plugins = this.contentWindowUtils.plugins;
            for (let plugin of plugins) {
              let objLoadingContent = plugin.QueryInterface(Components.interfaces.nsIObjectLoadingContent);
              if (this.canActivatePlugin(objLoadingContent))
                objLoadingContent.playPlugin();
            }
            this.removePluginClickToPlayPrompt();
          ]]>
        </body>
      </method>

      <method name="activateSinglePlugin">
        <parameter name="aPlugin"/>
        <body>
          <![CDATA[
            var objLoadingContent = aPlugin.QueryInterface(Components.interfaces.nsIObjectLoadingContent);
            if (this.canActivatePlugin(objLoadingContent))
              objLoadingContent.playPlugin();

            var haveUnplayedPlugins = this.contentWindowUtils.plugins.some(function(plugin) {
              var hupObjLoadingContent = plugin.QueryInterface(Components.interfaces.nsIObjectLoadingContent);
              return plugin != aPlugin && this.canActivatePlugin(hupObjLoadingContent);
            }, this);
            if (!haveUnplayedPlugins) {
              this.removePluginClickToPlayPrompt();
              this.clickToPlayPromptShown = false;
            }
          ]]>
        </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="makeNicePluginName">
        <parameter name="aName"/>
        <parameter name="aFilename"/>
        <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"
            var newName = aName.replace(/\bplug-?in\b/i, "").replace(/[\s\d\.\-\_\(\)]+$/, "");
            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="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"/>
        <body>
          <![CDATA[
            this.CrashSubmit.submit(pluginDumpID);
            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;

          if (aEvent.type != "MozPlayPlugin")
            return;

          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)
            previewContent.removeChild(iframe);
          ]]>
        </body>
      </method>

      <method name="installMissingPlugins">
        <body>
          <![CDATA[
            window.openDialog("chrome://mozapps/content/plugins/pluginInstallerWizard.xul",
                              "", "chrome,centerscreen,resizable=yes",
                              {plugins: this.missingPlugins, browser: this.activeBrowser});
          ]]>
        </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.allow"),
                accessKey: this._stringBundle.GetStringFromName("offlineApps.allow.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.deny"),
                accessKey: this._stringBundle.GetStringFromName("offlineApps.deny.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("offlineApps.permissions", [host], 1);
              var priority = this.PRIORITY_INFO_LOW;
              notification = this.appendNotification(messageString, notificationName,
                                                     null, priority, 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">
        <body>
          <![CDATA[
            this.clickToPlayPromptShown = true;

            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();
              }).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.removeClickToPlayOverlays();
              }).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="removeClickToPlayOverlays">
        <body>
          <![CDATA[
            var plugins = this.contentWindowUtils.plugins;
            for (let plugin of plugins)
              this.hideClickToPlayOverlay(plugin);
          ]]>
        </body>
      </method>

      <method name="hideClickToPlayOverlay">
        <parameter name="pluginElement"/>
        <body>
          <![CDATA[
            var doc = pluginElement.ownerDocument;
            var overlay = doc.getAnonymousElementByAttribute(pluginElement, "class", "mainBox");
            if (overlay) // no overlay if plugin has been activated
              overlay.style.visibility = "hidden";
          ]]>
        </body>
      </method>

      <method name="setupPluginClickToPlay">
        <parameter name="pluginElement"/>
        <body>
          <![CDATA[
            var doc = pluginElement.ownerDocument;
            var overlay = doc.getAnonymousElementByAttribute(pluginElement, "class", "mainBox");

            var objLoadingContent = pluginElement.QueryInterface(Components.interfaces.nsIObjectLoadingContent);
            if (!this.canActivatePlugin(objLoadingContent)) {
              overlay.style.visibility = "hidden";
              return;
            }

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

            if (!this.clickToPlayPromptShown)
              this.showPluginClickToPlayPrompt();

            if (this.isTooSmall(pluginElement, overlay)) {
              overlay.style.visibility = "hidden";
              return;
            }

            this.addLinkClickCallback(overlay, this.activateSinglePlugin, pluginElement);

            var closeIcon = doc.getAnonymousElementByAttribute(pluginElement, "anonid", "closeIcon");
            this.addLinkClickCallback(closeIcon, this.hideClickToPlayOverlay, pluginElement);
          ]]>
        </body>
      </method>

      <method name="handlePlayPreviewEvent">
        <parameter name="pluginElement"/>
        <body>
          <![CDATA[
            var doc = pluginElement.ownerDocument;
            var previewContent = doc.getAnonymousElementByAttribute(pluginElement, "class", "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 + ".dontShareLocation"),
              accessKey: this._stringBundle.GetStringFromName(type + ".dontShareLocation.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 };
            if (site) {
              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="promptIndexedDB">
        <parameter name="aRequestor"/>
        <parameter name="aWindow"/>
        <parameter name="aTopic"/>
        <parameter name="aData"/>
        <body>
          <![CDATA[
            var host = aWindow.document.documentURIObject.asciiHost;
            var message = this._stringBundle.formatStringFromName("offlineApps." + aTopic, [host, aData], 2);
            var observer = aRequestor.getInterface(Components.interfaces.nsIObserver);
            var nsIPermissionManager = Components.interfaces.nsIPermissionManager;
            var buttons = [{
              label: this._stringBundle.GetStringFromName("offlineApps.allow"),
              accessKey: this._stringBundle.GetStringFromName("offlineApps.allow.accesskey"),
              popup: null,
              callback: function allowIndexedDB() {
                clearTimeout(box.timeout);
                observer.observe(null, "indexedDB-" + aTopic + "-response",
                                 nsIPermissionManager.ALLOW_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);
              }
            }, {
              label: this._stringBundle.GetStringFromName("offlineApps.deny"),
              accessKey: this._stringBundle.GetStringFromName("offlineApps.deny.accesskey"),
              popup: null,
              callback: function denyIndexedDB() {
                clearTimeout(box.timeout);
                observer.observe(null, "indexedDB-" + aTopic + "-response",
                                 nsIPermissionManager.DENY_ACTION);
              }
            }];
            var box = this.appendNotification(message, "indexedDB-" + aTopic + "-prompt", null,
                                              this.PRIOITY_INFO_HIGH, 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"/>
        <parameter name="sourceURI"/>
        <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.originatingWindow, sourceURI,
                                                      installInfo.installs, installInfo.installs.length)) {
                  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(function(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;

            var sourceURI = installInfo.originatingWindow.document.documentURIObject;
            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, sourceURI)
            }];
            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="aIsMalware"/>
        <body>
          <![CDATA[
            var uri = this.activeBrowser.currentURI;
            var asciiSpec = uri.asciiSpec;
            var flag = Components.interfaces.nsIWebNavigation.LOAD_FLAGS_BYPASS_CLASSIFIER;
            this.activeBrowser.loadURIWithFlags(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;
            if (aIsMalware) {
              title = "safebrowsing.reportedAttackSite";
              label = "safebrowsing.notAnAttackButton.label";
              accessKey = "safebrowsing.notAnAttackButton.accessKey";
              reportName = "MalwareError";
            }
            else {
              title = "safebrowsing.reportedWebForgery";
              label = "safebrowsing.notAForgeryButton.label";
              accessKey = "safebrowsing.notAForgeryButton.accessKey";
              reportName = "Error";
            }
            title = this._stringBundle.GetStringFromName(title);

            var tmp = {};
            Components.utils.import("resource://gre/modules/SafeBrowsing.jsm", tmp);
            var reportUrl = tmp.SafeBrowsing.getReportURL(reportName);
            reportUrl += "&url=" + encodeURIComponent(asciiSpec);

            var buttons = [{
              label: this._stringBundle.GetStringFromName("safebrowsing.getMeOutOfHereButton.label"),
              accessKey: this._stringBundle.GetStringFromName("safebrowsing.getMeOutOfHereButton.accessKey"),
              callback: getMeOutOfHere
            }, {
              label: this._stringBundle.GetStringFromName(label),
              accessKey: this._stringBundle.GetStringFromName(accessKey),
              callback: function () { 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.pageReport)
            return;

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

          // Check for duplicates and remove the old occurence of this url,
          // to update the features.
          var lastItemPlace = browser.pageReport.length - 1;
          var lastItem = browser.pageReport[lastItemPlace];
          for (var i = 0; i < lastItemPlace; i++) {
            if (browser.pageReport[i].popupWindowURI.equals(lastItem.popupWindowURI)) {
              browser.pageReport.splice(i, 1);
              break;
            }
          }

          // Limit the length of the menu to some reasonable size.
          // We only add one item every time in browser.xml, so no need for more complex stuff.
          if (browser.pageReport.length > 100)
            browser.pageReport.shift();

          if (this._prefs.getBoolPref("privacy.popups.showBrowserMessage"))
          {
            var brandShortName = this._brandStringBundle.GetStringFromName("brandShortName");
            var message;
            if (this.popupCount > 1)
              message = this._stringBundle.formatStringFromName("popupWarningMultiple", [brandShortName, this.popupCount], 2);
            else
              message = this._stringBundle.formatStringFromName("popupWarning", [brandShortName], 1);

            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 notification, message, buttons, pref;
          var doc = plugin.ownerDocument;
          switch (plugin.pluginFallbackType) {
            case nsIObjectLoadingContent.PLUGIN_UNSUPPORTED:
              // For broken non-object plugin tags, register a click handler so
              // that the user can click the plugin replacement to get the new
              // plugin. Object tags can, and often do, deal with that
              // themselves, so don't stomp on the page developer's toes.
              if (!(plugin instanceof HTMLObjectElement)) {
                // We don't yet check to see if there's actually an installer available.
                var installStatus = doc.getAnonymousElementByAttribute(plugin, "class", "installStatus");
                installStatus.setAttribute("status", "ready");
                var iconStatus = doc.getAnonymousElementByAttribute(plugin, "class", "icon");
                iconStatus.setAttribute("status", "ready");
                var installLink = doc.getAnonymousElementByAttribute(plugin, "class", "installPluginLink");
                this.addLinkClickCallback(installLink, installMissingPlugins);
              }

              notification = "missing-plugins";
              message = this._stringBundle.GetStringFromName("missingpluginsMessage.title");
              buttons = [{
                label: this._stringBundle.GetStringFromName("missingpluginsMessage.button.label"),
                accessKey: this._stringBundle.GetStringFromName("missingpluginsMessage.button.accesskey"),
                popup: null,
                callback: this.installMissingPlugins.bind(this)
              }];
              break;

            case nsIObjectLoadingContent.PLUGIN_DISABLED:
              if ("toEM" in window) {
                var manageLink = doc.getAnonymousElementByAttribute(plugin, "class", "managePluginsLink");
                this.addLinkClickCallback(manageLink, window.toEM, "addons://list/plugin");
              }
              return;

            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")
              }, {
                label: this._stringBundle.GetStringFromName("blockedpluginsMessage.searchButton.label"),
                accessKey: this._stringBundle.GetStringFromName("blockedpluginsMessage.searchButton.accesskey"),
                popup: null,
                callback: this.installMissingPlugins.bind(this)
              }];
              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 = doc.getAnonymousElementByAttribute(plugin, "class", "checkForUpdatesLink");
              this.addLinkClickCallback(updateLink, this.openURLPref, "plugins.update.url");
              // fall through
            case nsIObjectLoadingContent.PLUGIN_VULNERABLE_NO_UPDATE:
              // fall through
            case nsIObjectLoadingContent.PLUGIN_CLICK_TO_PLAY:
              this.setupPluginClickToPlay(plugin);
              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[
          var plugin = event.target;

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

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

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

          // Configure the crashed-plugin placeholder.
          var doc = plugin.ownerDocument;
          var overlay = doc.getAnonymousElementByAttribute(plugin, "class", "mainBox");

          var status;
          var statusDiv = doc.getAnonymousElementByAttribute(plugin, "class", "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";
              // XXX can we make the link target actually be blank?
              let pleaseLink = doc.getAnonymousElementByAttribute(
                                    plugin, "class", "pleaseSubmitLink");
              this.addLinkClickCallback(pleaseLink, this.submitReport,
                                        pluginDumpID, browserDumpID);
            }

            // 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 bottomLinks = doc.getAnonymousElementByAttribute(plugin, "class", "msg msgBottomLinks");
          bottomLinks.style.display = "block";
          var helpIcon = doc.getAnonymousElementByAttribute(plugin, "class", "helpIcon");
          this.addLinkClickCallback(helpIcon, this.openHelpPage);

          var messageString = this._stringBundle.formatStringFromName("crashedpluginsMessage.title", [pluginName], 1);
          var crashText = doc.getAnonymousElementByAttribute(plugin, "class", "msg msgCrashed");
          crashText.textContent = messageString;

          var link = doc.getAnonymousElementByAttribute(plugin, "class", "reloadLink");
          this.addLinkClickCallback(link, this.reloadPage);

          // Is the <object>'s size too small to hold what we want to show?
          if (this.isTooSmall(plugin, overlay)) {
            // Hide the overlay's contents. Use visibility style, so that it
            // doesn't collapse down to 0x0.
            overlay.style.visibility = "hidden";
            // 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);
          }
          else {
            // 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;
          }
        ]]>
      </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="NewPluginInstalled" phase="capturing">
        <![CDATA[
          this.missingPlugins = null;

          // clean up the UI after a new plugin has been installed.
          var notification = this.getNotificationWithValue("missing-plugins");
          if (notification)
            this.removeNotification(notification);

          // reload the browser to make the new plugin show.
          this.activeBrowser.reload();
        ]]>
      </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 || !this._prefs.getBoolPref("plugins.click_to_play"))
            return;

          var pluginNeedsActivation = this.contentWindowUtils.plugins.some(function(aPlugin) {
            var objLoadingContent = aPlugin.QueryInterface(Components.interfaces.nsIObjectLoadingContent);
            return this.canActivatePlugin(objLoadingContent);
          }, this);
          if (pluginNeedsActivation)
            this.showPluginClickToPlayPrompt();
        ]]>
      </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="removePluginClickToPlayPrompt">
        <body>
          <![CDATA[
            var notification = PopupNotifications.getNotification("click-to-play-plugins",
                                                                  this.activeBrowser);
            if (notification)
              notification.remove();
          ]]>
        </body>
      </method>

      <method name="showPluginClickToPlayPrompt">
        <body>
          <![CDATA[
            this.clickToPlayPromptShown = true;
            const nsIPermissionManager = Components.interfaces.nsIPermissionManager;
            var messageString = this._stringBundle.GetStringFromName("activatepluginsMessage.title");
            var mainAction = {
              label: this._stringBundle.GetStringFromName("activatepluginsMessage.activate.label"),
              accessKey: this._stringBundle.GetStringFromName("activatepluginsMessage.activate.accesskey"),
              callback: this.activatePlugins.bind(this)
            };
            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();
              }).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.removeClickToPlayOverlays();
              }).bind(this)
            }];
            var options = {
              dismissed: true
            };
            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 + ".dontShareLocation"),
              accessKey: this._stringBundle.GetStringFromName(type + ".dontShareLocation.accesskey"),
              callback: function (aNotification) {
                cancelCallback(false);
              }
            }, {
              label: this._stringBundle.GetStringFromName(type + ".neverShareLocation"),
              accessKey: this._stringBundle.GetStringFromName(type + ".neverShareLocation.accesskey"),
              callback: function (aNotification) {
                cancelCallback(true);
              }
            }, {
              label: this._stringBundle.GetStringFromName(type + ".alwaysShareLocation"),
              accessKey: this._stringBundle.GetStringFromName(type + ".alwaysShareLocation.accesskey"),
              callback: function (aNotification) {
                allowCallback(true);
              }
            }];

            var message;
            if (site) {
              message = this._stringBundle
                            .formatStringFromName(type + ".siteWantsToKnow",
                                                  [site], 1);
            } else {
              message = this._stringBundle
                            .formatStringFromName(type + ".fileWantsToKnow",
                                                  [file], 1);
              secondaryActions.length = 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="promptIndexedDB">
        <parameter name="aRequestor"/>
        <parameter name="aWindow"/>
        <parameter name="aTopic"/>
        <parameter name="aData"/>
        <body>
          <![CDATA[
            var host = aWindow.document.documentURIObject.asciiHost;
            var message = this._stringBundle.formatStringFromName("offlineApps." + aTopic, [host, aData], 2);
            var observer = aRequestor.getInterface(Components.interfaces.nsIObserver);
            var nsIPermissionManager = Components.interfaces.nsIPermissionManager;
            var mainAction = {
              label: this._stringBundle.GetStringFromName("offlineApps.allow"),
              accessKey: this._stringBundle.GetStringFromName("offlineApps.allow.accesskey"),
              callback: function allowIndexedDB() {
                clearTimeout(notification.timeout);
                observer.observe(null, "indexedDB-" + aTopic + "-response",
                                 nsIPermissionManager.ALLOW_ACTION);
              }
            };
            var secondaryActions = [{
              label: this._stringBundle.GetStringFromName("offlineApps.deny"),
              accessKey: this._stringBundle.GetStringFromName("offlineApps.deny.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",
                                        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"/>
        <parameter name="sourceURI"/>
        <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.originatingWindow, sourceURI,
                                                      installInfo.installs, installInfo.installs.length)) {
                  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(function(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;

            var sourceURI = installInfo.originatingWindow.document.documentURIObject;
            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, sourceURI)
            };
            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 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 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>
</bindings>