forked from mirrors/gecko-dev
		
	MozReview-Commit-ID: 3W7zEemDOTa --HG-- extra : rebase_source : e6dd9ab946ab365b3633b9df5158da5a5cd58d38
		
			
				
	
	
		
			869 lines
		
	
	
	
		
			30 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			869 lines
		
	
	
	
		
			30 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
 | 
						|
 * 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/. */
 | 
						|
 | 
						|
// Removes a doorhanger notification if all of the installs it was notifying
 | 
						|
// about have ended in some way.
 | 
						|
function removeNotificationOnEnd(notification, installs) {
 | 
						|
  let count = installs.length;
 | 
						|
 | 
						|
  function maybeRemove(install) {
 | 
						|
    install.removeListener(this);
 | 
						|
 | 
						|
    if (--count == 0) {
 | 
						|
      // Check that the notification is still showing
 | 
						|
      let current = PopupNotifications.getNotification(notification.id, notification.browser);
 | 
						|
      if (current === notification)
 | 
						|
        notification.remove();
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  for (let install of installs) {
 | 
						|
    install.addListener({
 | 
						|
      onDownloadCancelled: maybeRemove,
 | 
						|
      onDownloadFailed: maybeRemove,
 | 
						|
      onInstallFailed: maybeRemove,
 | 
						|
      onInstallEnded: maybeRemove
 | 
						|
    });
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
const gXPInstallObserver = {
 | 
						|
  _findChildShell(aDocShell, aSoughtShell) {
 | 
						|
    if (aDocShell == aSoughtShell)
 | 
						|
      return aDocShell;
 | 
						|
 | 
						|
    var node = aDocShell.QueryInterface(Components.interfaces.nsIDocShellTreeItem);
 | 
						|
    for (var i = 0; i < node.childCount; ++i) {
 | 
						|
      var docShell = node.getChildAt(i);
 | 
						|
      docShell = this._findChildShell(docShell, aSoughtShell);
 | 
						|
      if (docShell == aSoughtShell)
 | 
						|
        return docShell;
 | 
						|
    }
 | 
						|
    return null;
 | 
						|
  },
 | 
						|
 | 
						|
  _getBrowser(aDocShell) {
 | 
						|
    for (let browser of gBrowser.browsers) {
 | 
						|
      if (this._findChildShell(browser.docShell, aDocShell))
 | 
						|
        return browser;
 | 
						|
    }
 | 
						|
    return null;
 | 
						|
  },
 | 
						|
 | 
						|
  pendingInstalls: new WeakMap(),
 | 
						|
 | 
						|
  showInstallConfirmation(browser, installInfo, height = undefined) {
 | 
						|
    // If the confirmation notification is already open cache the installInfo
 | 
						|
    // and the new confirmation will be shown later
 | 
						|
    if (PopupNotifications.getNotification("addon-install-confirmation", browser)) {
 | 
						|
      let pending = this.pendingInstalls.get(browser);
 | 
						|
      if (pending) {
 | 
						|
        pending.push(installInfo);
 | 
						|
      } else {
 | 
						|
        this.pendingInstalls.set(browser, [installInfo]);
 | 
						|
      }
 | 
						|
      return;
 | 
						|
    }
 | 
						|
 | 
						|
    let showNextConfirmation = () => {
 | 
						|
      // Make sure the browser is still alive.
 | 
						|
      if (gBrowser.browsers.indexOf(browser) == -1)
 | 
						|
        return;
 | 
						|
 | 
						|
      let pending = this.pendingInstalls.get(browser);
 | 
						|
      if (pending && pending.length)
 | 
						|
        this.showInstallConfirmation(browser, pending.shift());
 | 
						|
    }
 | 
						|
 | 
						|
    // If all installs have already been cancelled in some way then just show
 | 
						|
    // the next confirmation
 | 
						|
    if (installInfo.installs.every(i => i.state != AddonManager.STATE_DOWNLOADED)) {
 | 
						|
      showNextConfirmation();
 | 
						|
      return;
 | 
						|
    }
 | 
						|
 | 
						|
    const anchorID = "addons-notification-icon";
 | 
						|
 | 
						|
    // Make notifications persistent
 | 
						|
    var options = {
 | 
						|
      displayURI: installInfo.originatingURI,
 | 
						|
      persistent: true,
 | 
						|
    };
 | 
						|
 | 
						|
    let acceptInstallation = () => {
 | 
						|
      for (let install of installInfo.installs)
 | 
						|
        install.install();
 | 
						|
      installInfo = null;
 | 
						|
 | 
						|
      Services.telemetry
 | 
						|
              .getHistogramById("SECURITY_UI")
 | 
						|
              .add(Ci.nsISecurityUITelemetry.WARNING_CONFIRM_ADDON_INSTALL_CLICK_THROUGH);
 | 
						|
    };
 | 
						|
 | 
						|
    let cancelInstallation = () => {
 | 
						|
      if (installInfo) {
 | 
						|
        for (let install of installInfo.installs) {
 | 
						|
          // The notification may have been closed because the add-ons got
 | 
						|
          // cancelled elsewhere, only try to cancel those that are still
 | 
						|
          // pending install.
 | 
						|
          if (install.state != AddonManager.STATE_CANCELLED)
 | 
						|
            install.cancel();
 | 
						|
        }
 | 
						|
      }
 | 
						|
 | 
						|
      showNextConfirmation();
 | 
						|
    };
 | 
						|
 | 
						|
    let unsigned = installInfo.installs.filter(i => i.addon.signedState <= AddonManager.SIGNEDSTATE_MISSING);
 | 
						|
    let someUnsigned = unsigned.length > 0 && unsigned.length < installInfo.installs.length;
 | 
						|
 | 
						|
    options.eventCallback = (aEvent) => {
 | 
						|
      switch (aEvent) {
 | 
						|
        case "removed":
 | 
						|
          cancelInstallation();
 | 
						|
          break;
 | 
						|
        case "shown":
 | 
						|
          let addonList = document.getElementById("addon-install-confirmation-content");
 | 
						|
          while (addonList.firstChild)
 | 
						|
            addonList.firstChild.remove();
 | 
						|
 | 
						|
          for (let install of installInfo.installs) {
 | 
						|
            let container = document.createElement("hbox");
 | 
						|
 | 
						|
            let name = document.createElement("label");
 | 
						|
            name.setAttribute("value", install.addon.name);
 | 
						|
            name.setAttribute("class", "addon-install-confirmation-name");
 | 
						|
            container.appendChild(name);
 | 
						|
 | 
						|
            if (someUnsigned && install.addon.signedState <= AddonManager.SIGNEDSTATE_MISSING) {
 | 
						|
              let unsignedLabel = document.createElement("label");
 | 
						|
              unsignedLabel.setAttribute("value",
 | 
						|
                gNavigatorBundle.getString("addonInstall.unsigned"));
 | 
						|
              unsignedLabel.setAttribute("class",
 | 
						|
                "addon-install-confirmation-unsigned");
 | 
						|
              container.appendChild(unsignedLabel);
 | 
						|
            }
 | 
						|
 | 
						|
            addonList.appendChild(container);
 | 
						|
          }
 | 
						|
          break;
 | 
						|
      }
 | 
						|
    };
 | 
						|
 | 
						|
    options.learnMoreURL = Services.urlFormatter.formatURLPref("app.support.baseURL");
 | 
						|
 | 
						|
    let messageString;
 | 
						|
    let notification = document.getElementById("addon-install-confirmation-notification");
 | 
						|
    if (unsigned.length == installInfo.installs.length) {
 | 
						|
      // None of the add-ons are verified
 | 
						|
      messageString = gNavigatorBundle.getString("addonConfirmInstallUnsigned.message");
 | 
						|
      notification.setAttribute("warning", "true");
 | 
						|
      options.learnMoreURL += "unsigned-addons";
 | 
						|
    } else if (unsigned.length == 0) {
 | 
						|
      // All add-ons are verified or don't need to be verified
 | 
						|
      messageString = gNavigatorBundle.getString("addonConfirmInstall.message");
 | 
						|
      notification.removeAttribute("warning");
 | 
						|
      options.learnMoreURL += "find-and-install-add-ons";
 | 
						|
    } else {
 | 
						|
      // Some of the add-ons are unverified, the list of names will indicate
 | 
						|
      // which
 | 
						|
      messageString = gNavigatorBundle.getString("addonConfirmInstallSomeUnsigned.message");
 | 
						|
      notification.setAttribute("warning", "true");
 | 
						|
      options.learnMoreURL += "unsigned-addons";
 | 
						|
    }
 | 
						|
 | 
						|
    let brandBundle = document.getElementById("bundle_brand");
 | 
						|
    let brandShortName = brandBundle.getString("brandShortName");
 | 
						|
 | 
						|
    messageString = PluralForm.get(installInfo.installs.length, messageString);
 | 
						|
    messageString = messageString.replace("#1", brandShortName);
 | 
						|
    messageString = messageString.replace("#2", installInfo.installs.length);
 | 
						|
 | 
						|
    let action = {
 | 
						|
      label: gNavigatorBundle.getString("addonInstall.acceptButton.label"),
 | 
						|
      accessKey: gNavigatorBundle.getString("addonInstall.acceptButton.accesskey"),
 | 
						|
      callback: acceptInstallation,
 | 
						|
    };
 | 
						|
 | 
						|
    let secondaryAction = {
 | 
						|
      label: gNavigatorBundle.getString("addonInstall.cancelButton.label"),
 | 
						|
      accessKey: gNavigatorBundle.getString("addonInstall.cancelButton.accesskey"),
 | 
						|
      callback: () => {},
 | 
						|
    };
 | 
						|
 | 
						|
    if (height) {
 | 
						|
      notification.style.minHeight = height + "px";
 | 
						|
    }
 | 
						|
 | 
						|
    let tab = gBrowser.getTabForBrowser(browser);
 | 
						|
    if (tab) {
 | 
						|
      gBrowser.selectedTab = tab;
 | 
						|
    }
 | 
						|
 | 
						|
    let popup = PopupNotifications.show(browser, "addon-install-confirmation",
 | 
						|
                                        messageString, anchorID, action,
 | 
						|
                                        [secondaryAction], options);
 | 
						|
 | 
						|
    removeNotificationOnEnd(popup, installInfo.installs);
 | 
						|
 | 
						|
    Services.telemetry
 | 
						|
            .getHistogramById("SECURITY_UI")
 | 
						|
            .add(Ci.nsISecurityUITelemetry.WARNING_CONFIRM_ADDON_INSTALL);
 | 
						|
  },
 | 
						|
 | 
						|
  observe(aSubject, aTopic, aData) {
 | 
						|
    var brandBundle = document.getElementById("bundle_brand");
 | 
						|
    var installInfo = aSubject.wrappedJSObject;
 | 
						|
    var browser = installInfo.browser;
 | 
						|
 | 
						|
    // Make sure the browser is still alive.
 | 
						|
    if (!browser || gBrowser.browsers.indexOf(browser) == -1)
 | 
						|
      return;
 | 
						|
 | 
						|
    const anchorID = "addons-notification-icon";
 | 
						|
    var messageString, action;
 | 
						|
    var brandShortName = brandBundle.getString("brandShortName");
 | 
						|
 | 
						|
    var notificationID = aTopic;
 | 
						|
    // Make notifications persistent
 | 
						|
    var options = {
 | 
						|
      displayURI: installInfo.originatingURI,
 | 
						|
      persistent: true,
 | 
						|
      hideClose: true,
 | 
						|
      timeout: Date.now() + 30000,
 | 
						|
    };
 | 
						|
 | 
						|
    switch (aTopic) {
 | 
						|
    case "addon-install-disabled": {
 | 
						|
      notificationID = "xpinstall-disabled";
 | 
						|
      let secondaryActions = null;
 | 
						|
 | 
						|
      if (gPrefService.prefIsLocked("xpinstall.enabled")) {
 | 
						|
        messageString = gNavigatorBundle.getString("xpinstallDisabledMessageLocked");
 | 
						|
        buttons = [];
 | 
						|
      } else {
 | 
						|
        messageString = gNavigatorBundle.getString("xpinstallDisabledMessage");
 | 
						|
 | 
						|
        action = {
 | 
						|
          label: gNavigatorBundle.getString("xpinstallDisabledButton"),
 | 
						|
          accessKey: gNavigatorBundle.getString("xpinstallDisabledButton.accesskey"),
 | 
						|
          callback: function editPrefs() {
 | 
						|
            gPrefService.setBoolPref("xpinstall.enabled", true);
 | 
						|
          }
 | 
						|
        };
 | 
						|
 | 
						|
        secondaryActions = [{
 | 
						|
          label: gNavigatorBundle.getString("addonInstall.cancelButton.label"),
 | 
						|
          accessKey: gNavigatorBundle.getString("addonInstall.cancelButton.accesskey"),
 | 
						|
          callback: () => {},
 | 
						|
        }];
 | 
						|
      }
 | 
						|
 | 
						|
      PopupNotifications.show(browser, notificationID, messageString, anchorID,
 | 
						|
                              action, secondaryActions, options);
 | 
						|
      break; }
 | 
						|
    case "addon-install-origin-blocked": {
 | 
						|
      messageString = gNavigatorBundle.getFormattedString("xpinstallPromptMessage",
 | 
						|
                        [brandShortName]);
 | 
						|
 | 
						|
      options.removeOnDismissal = true;
 | 
						|
      options.persistent = false;
 | 
						|
 | 
						|
      let secHistogram = Components.classes["@mozilla.org/base/telemetry;1"].getService(Ci.nsITelemetry).getHistogramById("SECURITY_UI");
 | 
						|
      secHistogram.add(Ci.nsISecurityUITelemetry.WARNING_ADDON_ASKING_PREVENTED);
 | 
						|
      let popup = PopupNotifications.show(browser, notificationID,
 | 
						|
                                          messageString, anchorID,
 | 
						|
                                          null, null, options);
 | 
						|
      removeNotificationOnEnd(popup, installInfo.installs);
 | 
						|
      break; }
 | 
						|
    case "addon-install-blocked": {
 | 
						|
      messageString = gNavigatorBundle.getFormattedString("xpinstallPromptMessage",
 | 
						|
                        [brandShortName]);
 | 
						|
 | 
						|
      let secHistogram = Components.classes["@mozilla.org/base/telemetry;1"].getService(Ci.nsITelemetry).getHistogramById("SECURITY_UI");
 | 
						|
      action = {
 | 
						|
        label: gNavigatorBundle.getString("xpinstallPromptAllowButton"),
 | 
						|
        accessKey: gNavigatorBundle.getString("xpinstallPromptAllowButton.accesskey"),
 | 
						|
        callback() {
 | 
						|
          secHistogram.add(Ci.nsISecurityUITelemetry.WARNING_ADDON_ASKING_PREVENTED_CLICK_THROUGH);
 | 
						|
          installInfo.install();
 | 
						|
        }
 | 
						|
      };
 | 
						|
      let secondaryAction = {
 | 
						|
        label: gNavigatorBundle.getString("xpinstallPromptMessage.dontAllow"),
 | 
						|
        accessKey: gNavigatorBundle.getString("xpinstallPromptMessage.dontAllow.accesskey"),
 | 
						|
        callback: () => {},
 | 
						|
      };
 | 
						|
 | 
						|
      secHistogram.add(Ci.nsISecurityUITelemetry.WARNING_ADDON_ASKING_PREVENTED);
 | 
						|
      let popup = PopupNotifications.show(browser, notificationID,
 | 
						|
                                          messageString, anchorID,
 | 
						|
                                          action, [secondaryAction], options);
 | 
						|
      removeNotificationOnEnd(popup, installInfo.installs);
 | 
						|
      break; }
 | 
						|
    case "addon-install-started": {
 | 
						|
      let needsDownload = function needsDownload(aInstall) {
 | 
						|
        return aInstall.state != AddonManager.STATE_DOWNLOADED;
 | 
						|
      }
 | 
						|
      // If all installs have already been downloaded then there is no need to
 | 
						|
      // show the download progress
 | 
						|
      if (!installInfo.installs.some(needsDownload))
 | 
						|
        return;
 | 
						|
      notificationID = "addon-progress";
 | 
						|
      messageString = gNavigatorBundle.getString("addonDownloadingAndVerifying");
 | 
						|
      messageString = PluralForm.get(installInfo.installs.length, messageString);
 | 
						|
      messageString = messageString.replace("#1", installInfo.installs.length);
 | 
						|
      options.installs = installInfo.installs;
 | 
						|
      options.contentWindow = browser.contentWindow;
 | 
						|
      options.sourceURI = browser.currentURI;
 | 
						|
      options.eventCallback = function(aEvent) {
 | 
						|
        switch (aEvent) {
 | 
						|
          case "shown":
 | 
						|
            let notificationElement = [...this.owner.panel.childNodes]
 | 
						|
                                      .find(n => n.notification == this);
 | 
						|
            if (notificationElement) {
 | 
						|
              if (Preferences.get("xpinstall.customConfirmationUI", false)) {
 | 
						|
                notificationElement.setAttribute("mainactiondisabled", "true");
 | 
						|
              } else {
 | 
						|
                notificationElement.button.hidden = true;
 | 
						|
              }
 | 
						|
            }
 | 
						|
            break;
 | 
						|
          case "removed":
 | 
						|
            options.contentWindow = null;
 | 
						|
            options.sourceURI = null;
 | 
						|
            break;
 | 
						|
        }
 | 
						|
      };
 | 
						|
      action = {
 | 
						|
        label: gNavigatorBundle.getString("addonInstall.acceptButton.label"),
 | 
						|
        accessKey: gNavigatorBundle.getString("addonInstall.acceptButton.accesskey"),
 | 
						|
        callback: () => {},
 | 
						|
      };
 | 
						|
      let secondaryAction = {
 | 
						|
        label: gNavigatorBundle.getString("addonInstall.cancelButton.label"),
 | 
						|
        accessKey: gNavigatorBundle.getString("addonInstall.cancelButton.accesskey"),
 | 
						|
        callback: () => {
 | 
						|
          for (let install of installInfo.installs) {
 | 
						|
            if (install.state != AddonManager.STATE_CANCELLED) {
 | 
						|
              install.cancel();
 | 
						|
            }
 | 
						|
          }
 | 
						|
        },
 | 
						|
      };
 | 
						|
      let notification = PopupNotifications.show(browser, notificationID, messageString,
 | 
						|
                                                 anchorID, action,
 | 
						|
                                                 [secondaryAction], options);
 | 
						|
      notification._startTime = Date.now();
 | 
						|
 | 
						|
      break; }
 | 
						|
    case "addon-install-failed": {
 | 
						|
      options.removeOnDismissal = true;
 | 
						|
      options.persistent = false;
 | 
						|
 | 
						|
      // TODO This isn't terribly ideal for the multiple failure case
 | 
						|
      for (let install of installInfo.installs) {
 | 
						|
        let host;
 | 
						|
        try {
 | 
						|
          host  = options.displayURI.host;
 | 
						|
        } catch (e) {
 | 
						|
          // displayURI might be missing or 'host' might throw for non-nsStandardURL nsIURIs.
 | 
						|
        }
 | 
						|
 | 
						|
        if (!host)
 | 
						|
          host = (install.sourceURI instanceof Ci.nsIStandardURL) &&
 | 
						|
                 install.sourceURI.host;
 | 
						|
 | 
						|
        let error = (host || install.error == 0) ? "addonInstallError" : "addonLocalInstallError";
 | 
						|
        let args;
 | 
						|
        if (install.error < 0) {
 | 
						|
          error += install.error;
 | 
						|
          args = [brandShortName, install.name];
 | 
						|
        } else if (install.addon.blocklistState == Ci.nsIBlocklistService.STATE_BLOCKED) {
 | 
						|
          error += "Blocklisted";
 | 
						|
          args = [install.name];
 | 
						|
        } else {
 | 
						|
          error += "Incompatible";
 | 
						|
          args = [brandShortName, Services.appinfo.version, install.name];
 | 
						|
        }
 | 
						|
 | 
						|
        // Add Learn More link when refusing to install an unsigned add-on
 | 
						|
        if (install.error == AddonManager.ERROR_SIGNEDSTATE_REQUIRED) {
 | 
						|
          options.learnMoreURL = Services.urlFormatter.formatURLPref("app.support.baseURL") + "unsigned-addons";
 | 
						|
        }
 | 
						|
 | 
						|
        messageString = gNavigatorBundle.getFormattedString(error, args);
 | 
						|
 | 
						|
        PopupNotifications.show(browser, notificationID, messageString, anchorID,
 | 
						|
                                action, null, options);
 | 
						|
 | 
						|
        // Can't have multiple notifications with the same ID, so stop here.
 | 
						|
        break;
 | 
						|
      }
 | 
						|
      this._removeProgressNotification(browser);
 | 
						|
      break; }
 | 
						|
    case "addon-install-confirmation": {
 | 
						|
      let showNotification = () => {
 | 
						|
        let height = undefined;
 | 
						|
 | 
						|
        if (PopupNotifications.isPanelOpen) {
 | 
						|
          let rect = document.getElementById("addon-progress-notification").getBoundingClientRect();
 | 
						|
          height = rect.height;
 | 
						|
        }
 | 
						|
 | 
						|
        this._removeProgressNotification(browser);
 | 
						|
        this.showInstallConfirmation(browser, installInfo, height);
 | 
						|
      };
 | 
						|
 | 
						|
      let progressNotification = PopupNotifications.getNotification("addon-progress", browser);
 | 
						|
      if (progressNotification) {
 | 
						|
        let downloadDuration = Date.now() - progressNotification._startTime;
 | 
						|
        let securityDelay = Services.prefs.getIntPref("security.dialog_enable_delay") - downloadDuration;
 | 
						|
        if (securityDelay > 0) {
 | 
						|
          setTimeout(() => {
 | 
						|
            // The download may have been cancelled during the security delay
 | 
						|
            if (PopupNotifications.getNotification("addon-progress", browser))
 | 
						|
              showNotification();
 | 
						|
          }, securityDelay);
 | 
						|
          break;
 | 
						|
        }
 | 
						|
      }
 | 
						|
      showNotification();
 | 
						|
      break; }
 | 
						|
    case "addon-install-complete": {
 | 
						|
      let needsRestart = installInfo.installs.some(function(i) {
 | 
						|
        return i.addon.pendingOperations != AddonManager.PENDING_NONE;
 | 
						|
      });
 | 
						|
 | 
						|
      let secondaryActions = null;
 | 
						|
 | 
						|
      if (needsRestart) {
 | 
						|
        notificationID = "addon-install-restart";
 | 
						|
        messageString = gNavigatorBundle.getString("addonsInstalledNeedsRestart");
 | 
						|
        action = {
 | 
						|
          label: gNavigatorBundle.getString("addonInstallRestartButton"),
 | 
						|
          accessKey: gNavigatorBundle.getString("addonInstallRestartButton.accesskey"),
 | 
						|
          callback() {
 | 
						|
            BrowserUtils.restartApplication();
 | 
						|
          }
 | 
						|
        };
 | 
						|
        secondaryActions = [{
 | 
						|
          label: gNavigatorBundle.getString("addonInstallRestartIgnoreButton"),
 | 
						|
          accessKey: gNavigatorBundle.getString("addonInstallRestartIgnoreButton.accesskey"),
 | 
						|
          callback: () => {},
 | 
						|
        }];
 | 
						|
      } else {
 | 
						|
        messageString = gNavigatorBundle.getString("addonsInstalled");
 | 
						|
        action = null;
 | 
						|
      }
 | 
						|
 | 
						|
      messageString = PluralForm.get(installInfo.installs.length, messageString);
 | 
						|
      messageString = messageString.replace("#1", installInfo.installs[0].name);
 | 
						|
      messageString = messageString.replace("#2", installInfo.installs.length);
 | 
						|
      messageString = messageString.replace("#3", brandShortName);
 | 
						|
 | 
						|
      // Remove notification on dismissal, since it's possible to cancel the
 | 
						|
      // install through the addons manager UI, making the "restart" prompt
 | 
						|
      // irrelevant.
 | 
						|
      options.removeOnDismissal = true;
 | 
						|
      options.persistent = false;
 | 
						|
 | 
						|
      PopupNotifications.show(browser, notificationID, messageString, anchorID,
 | 
						|
                              action, secondaryActions, options);
 | 
						|
      break; }
 | 
						|
    }
 | 
						|
  },
 | 
						|
  _removeProgressNotification(aBrowser) {
 | 
						|
    let notification = PopupNotifications.getNotification("addon-progress", aBrowser);
 | 
						|
    if (notification)
 | 
						|
      notification.remove();
 | 
						|
  }
 | 
						|
};
 | 
						|
 | 
						|
const gExtensionsNotifications = {
 | 
						|
  initialized: false,
 | 
						|
  init() {
 | 
						|
    this.updateAlerts();
 | 
						|
    this.boundUpdate = this.updateAlerts.bind(this);
 | 
						|
    ExtensionsUI.on("change", this.boundUpdate);
 | 
						|
    this.initialized = true;
 | 
						|
  },
 | 
						|
 | 
						|
  uninit() {
 | 
						|
    // uninit() can race ahead of init() in some cases, if that happens,
 | 
						|
    // we have no handler to remove.
 | 
						|
    if (!this.initialized) {
 | 
						|
      return;
 | 
						|
    }
 | 
						|
    ExtensionsUI.off("change", this.boundUpdate);
 | 
						|
  },
 | 
						|
 | 
						|
  updateAlerts() {
 | 
						|
    let sideloaded = ExtensionsUI.sideloaded;
 | 
						|
    let updates = ExtensionsUI.updates;
 | 
						|
    if (sideloaded.size + updates.size == 0) {
 | 
						|
      gMenuButtonBadgeManager.removeBadge(gMenuButtonBadgeManager.BADGEID_ADDONS);
 | 
						|
    } else {
 | 
						|
      gMenuButtonBadgeManager.addBadge(gMenuButtonBadgeManager.BADGEID_ADDONS,
 | 
						|
                                       "addon-alert");
 | 
						|
    }
 | 
						|
 | 
						|
    let container = document.getElementById("PanelUI-footer-addons");
 | 
						|
 | 
						|
    while (container.firstChild) {
 | 
						|
      container.firstChild.remove();
 | 
						|
    }
 | 
						|
 | 
						|
    const DEFAULT_EXTENSION_ICON =
 | 
						|
      "chrome://mozapps/skin/extensions/extensionGeneric.svg";
 | 
						|
    let items = 0;
 | 
						|
    for (let update of updates) {
 | 
						|
      if (++items > 4) {
 | 
						|
        break;
 | 
						|
      }
 | 
						|
 | 
						|
      let button = document.createElement("toolbarbutton");
 | 
						|
      let text = gNavigatorBundle.getFormattedString("webextPerms.updateMenuItem", [update.addon.name]);
 | 
						|
      button.setAttribute("label", text);
 | 
						|
 | 
						|
      let icon = update.addon.iconURL || DEFAULT_EXTENSION_ICON;
 | 
						|
      button.setAttribute("image", icon);
 | 
						|
 | 
						|
      button.addEventListener("click", evt => {
 | 
						|
        ExtensionsUI.showUpdate(gBrowser, update);
 | 
						|
      });
 | 
						|
 | 
						|
      container.appendChild(button);
 | 
						|
    }
 | 
						|
 | 
						|
    let appName;
 | 
						|
    for (let addon of sideloaded) {
 | 
						|
      if (++items > 4) {
 | 
						|
        break;
 | 
						|
      }
 | 
						|
      if (!appName) {
 | 
						|
        let brandBundle = document.getElementById("bundle_brand");
 | 
						|
        appName = brandBundle.getString("brandShortName");
 | 
						|
      }
 | 
						|
 | 
						|
      let button = document.createElement("toolbarbutton");
 | 
						|
      let text = gNavigatorBundle.getFormattedString("webextPerms.sideloadMenuItem", [addon.name, appName]);
 | 
						|
      button.setAttribute("label", text);
 | 
						|
 | 
						|
      let icon = addon.iconURL || DEFAULT_EXTENSION_ICON;
 | 
						|
      button.setAttribute("image", icon);
 | 
						|
 | 
						|
      button.addEventListener("click", evt => {
 | 
						|
        ExtensionsUI.showSideloaded(gBrowser, addon);
 | 
						|
      });
 | 
						|
 | 
						|
      container.appendChild(button);
 | 
						|
    }
 | 
						|
  },
 | 
						|
};
 | 
						|
 | 
						|
var LightWeightThemeWebInstaller = {
 | 
						|
  init() {
 | 
						|
    let mm = window.messageManager;
 | 
						|
    mm.addMessageListener("LightWeightThemeWebInstaller:Install", this);
 | 
						|
    mm.addMessageListener("LightWeightThemeWebInstaller:Preview", this);
 | 
						|
    mm.addMessageListener("LightWeightThemeWebInstaller:ResetPreview", this);
 | 
						|
  },
 | 
						|
 | 
						|
  receiveMessage(message) {
 | 
						|
    // ignore requests from background tabs
 | 
						|
    if (message.target != gBrowser.selectedBrowser) {
 | 
						|
      return;
 | 
						|
    }
 | 
						|
 | 
						|
    let data = message.data;
 | 
						|
 | 
						|
    switch (message.name) {
 | 
						|
      case "LightWeightThemeWebInstaller:Install": {
 | 
						|
        this._installRequest(data.themeData, data.baseURI);
 | 
						|
        break;
 | 
						|
      }
 | 
						|
      case "LightWeightThemeWebInstaller:Preview": {
 | 
						|
        this._preview(data.themeData, data.baseURI);
 | 
						|
        break;
 | 
						|
      }
 | 
						|
      case "LightWeightThemeWebInstaller:ResetPreview": {
 | 
						|
        this._resetPreview(data && data.baseURI);
 | 
						|
        break;
 | 
						|
      }
 | 
						|
    }
 | 
						|
  },
 | 
						|
 | 
						|
  handleEvent(event) {
 | 
						|
    switch (event.type) {
 | 
						|
      case "TabSelect": {
 | 
						|
        this._resetPreview();
 | 
						|
        break;
 | 
						|
      }
 | 
						|
    }
 | 
						|
  },
 | 
						|
 | 
						|
  get _manager() {
 | 
						|
    let temp = {};
 | 
						|
    Cu.import("resource://gre/modules/LightweightThemeManager.jsm", temp);
 | 
						|
    delete this._manager;
 | 
						|
    return this._manager = temp.LightweightThemeManager;
 | 
						|
  },
 | 
						|
 | 
						|
  _installRequest(dataString, baseURI) {
 | 
						|
    let data = this._manager.parseTheme(dataString, baseURI);
 | 
						|
 | 
						|
    if (!data) {
 | 
						|
      return;
 | 
						|
    }
 | 
						|
 | 
						|
    let uri = makeURI(baseURI);
 | 
						|
 | 
						|
    // A notification bar with the option to undo is normally shown after a
 | 
						|
    // theme is installed.  But the discovery pane served from the url(s)
 | 
						|
    // below has its own toggle switch for quick undos, so don't show the
 | 
						|
    // notification in that case.
 | 
						|
    let notify = uri.prePath != "https://discovery.addons.mozilla.org";
 | 
						|
    if (notify) {
 | 
						|
      try {
 | 
						|
        if (Services.prefs.getBoolPref("extensions.webapi.testing")
 | 
						|
            && (uri.prePath == "https://discovery.addons.allizom.org"
 | 
						|
                || uri.prePath == "https://discovery.addons-dev.allizom.org")) {
 | 
						|
          notify = false;
 | 
						|
        }
 | 
						|
      } catch (e) {
 | 
						|
        // getBoolPref() throws if the testing pref isn't set.  ignore it.
 | 
						|
      }
 | 
						|
    }
 | 
						|
 | 
						|
    if (this._isAllowed(baseURI)) {
 | 
						|
      this._install(data, notify);
 | 
						|
      return;
 | 
						|
    }
 | 
						|
 | 
						|
    let allowButtonText =
 | 
						|
      gNavigatorBundle.getString("lwthemeInstallRequest.allowButton");
 | 
						|
    let allowButtonAccesskey =
 | 
						|
      gNavigatorBundle.getString("lwthemeInstallRequest.allowButton.accesskey");
 | 
						|
    let message =
 | 
						|
      gNavigatorBundle.getFormattedString("lwthemeInstallRequest.message",
 | 
						|
                                          [uri.host]);
 | 
						|
    let buttons = [{
 | 
						|
      label: allowButtonText,
 | 
						|
      accessKey: allowButtonAccesskey,
 | 
						|
      callback() {
 | 
						|
        LightWeightThemeWebInstaller._install(data, notify);
 | 
						|
      }
 | 
						|
    }];
 | 
						|
 | 
						|
    this._removePreviousNotifications();
 | 
						|
 | 
						|
    let notificationBox = gBrowser.getNotificationBox();
 | 
						|
    let notificationBar =
 | 
						|
      notificationBox.appendNotification(message, "lwtheme-install-request", "",
 | 
						|
                                         notificationBox.PRIORITY_INFO_MEDIUM,
 | 
						|
                                         buttons);
 | 
						|
    notificationBar.persistence = 1;
 | 
						|
  },
 | 
						|
 | 
						|
  _install(newLWTheme, notify) {
 | 
						|
    let previousLWTheme = this._manager.currentTheme;
 | 
						|
 | 
						|
    let listener = {
 | 
						|
      onEnabling(aAddon, aRequiresRestart) {
 | 
						|
        if (!aRequiresRestart) {
 | 
						|
          return;
 | 
						|
        }
 | 
						|
 | 
						|
        let messageString = gNavigatorBundle.getFormattedString("lwthemeNeedsRestart.message",
 | 
						|
          [aAddon.name], 1);
 | 
						|
 | 
						|
        let action = {
 | 
						|
          label: gNavigatorBundle.getString("lwthemeNeedsRestart.button"),
 | 
						|
          accessKey: gNavigatorBundle.getString("lwthemeNeedsRestart.accesskey"),
 | 
						|
          callback() {
 | 
						|
            BrowserUtils.restartApplication();
 | 
						|
          }
 | 
						|
        };
 | 
						|
 | 
						|
        let options = {
 | 
						|
          persistent: true
 | 
						|
        };
 | 
						|
 | 
						|
        PopupNotifications.show(gBrowser.selectedBrowser, "addon-theme-change",
 | 
						|
                                messageString, "addons-notification-icon",
 | 
						|
                                action, null, options);
 | 
						|
      },
 | 
						|
 | 
						|
      onEnabled(aAddon) {
 | 
						|
        if (notify) {
 | 
						|
          LightWeightThemeWebInstaller._postInstallNotification(newLWTheme, previousLWTheme);
 | 
						|
        }
 | 
						|
      }
 | 
						|
    };
 | 
						|
 | 
						|
    AddonManager.addAddonListener(listener);
 | 
						|
    this._manager.currentTheme = newLWTheme;
 | 
						|
    AddonManager.removeAddonListener(listener);
 | 
						|
  },
 | 
						|
 | 
						|
  _postInstallNotification(newTheme, previousTheme) {
 | 
						|
    function text(id) {
 | 
						|
      return gNavigatorBundle.getString("lwthemePostInstallNotification." + id);
 | 
						|
    }
 | 
						|
 | 
						|
    let buttons = [{
 | 
						|
      label: text("undoButton"),
 | 
						|
      accessKey: text("undoButton.accesskey"),
 | 
						|
      callback() {
 | 
						|
        LightWeightThemeWebInstaller._manager.forgetUsedTheme(newTheme.id);
 | 
						|
        LightWeightThemeWebInstaller._manager.currentTheme = previousTheme;
 | 
						|
      }
 | 
						|
    }, {
 | 
						|
      label: text("manageButton"),
 | 
						|
      accessKey: text("manageButton.accesskey"),
 | 
						|
      callback() {
 | 
						|
        BrowserOpenAddonsMgr("addons://list/theme");
 | 
						|
      }
 | 
						|
    }];
 | 
						|
 | 
						|
    this._removePreviousNotifications();
 | 
						|
 | 
						|
    let notificationBox = gBrowser.getNotificationBox();
 | 
						|
    let notificationBar =
 | 
						|
      notificationBox.appendNotification(text("message"),
 | 
						|
                                         "lwtheme-install-notification", "",
 | 
						|
                                         notificationBox.PRIORITY_INFO_MEDIUM,
 | 
						|
                                         buttons);
 | 
						|
    notificationBar.persistence = 1;
 | 
						|
    notificationBar.timeout = Date.now() + 20000; // 20 seconds
 | 
						|
  },
 | 
						|
 | 
						|
  _removePreviousNotifications() {
 | 
						|
    let box = gBrowser.getNotificationBox();
 | 
						|
 | 
						|
    ["lwtheme-install-request",
 | 
						|
     "lwtheme-install-notification"].forEach(function(value) {
 | 
						|
        let notification = box.getNotificationWithValue(value);
 | 
						|
        if (notification)
 | 
						|
          box.removeNotification(notification);
 | 
						|
      });
 | 
						|
  },
 | 
						|
 | 
						|
  _preview(dataString, baseURI) {
 | 
						|
    if (!this._isAllowed(baseURI))
 | 
						|
      return;
 | 
						|
 | 
						|
    let data = this._manager.parseTheme(dataString, baseURI);
 | 
						|
    if (!data)
 | 
						|
      return;
 | 
						|
 | 
						|
    this._resetPreview();
 | 
						|
    gBrowser.tabContainer.addEventListener("TabSelect", this);
 | 
						|
    this._manager.previewTheme(data);
 | 
						|
  },
 | 
						|
 | 
						|
  _resetPreview(baseURI) {
 | 
						|
    if (baseURI && !this._isAllowed(baseURI))
 | 
						|
      return;
 | 
						|
    gBrowser.tabContainer.removeEventListener("TabSelect", this);
 | 
						|
    this._manager.resetPreview();
 | 
						|
  },
 | 
						|
 | 
						|
  _isAllowed(srcURIString) {
 | 
						|
    let uri;
 | 
						|
    try {
 | 
						|
      uri = makeURI(srcURIString);
 | 
						|
    } catch (e) {
 | 
						|
      // makeURI fails if srcURIString is a nonsense URI
 | 
						|
      return false;
 | 
						|
    }
 | 
						|
 | 
						|
    if (!uri.schemeIs("https")) {
 | 
						|
      return false;
 | 
						|
    }
 | 
						|
 | 
						|
    let pm = Services.perms;
 | 
						|
    return pm.testPermission(uri, "install") == pm.ALLOW_ACTION;
 | 
						|
  }
 | 
						|
};
 | 
						|
 | 
						|
/*
 | 
						|
 * Listen for Lightweight Theme styling changes and update the browser's theme accordingly.
 | 
						|
 */
 | 
						|
var LightweightThemeListener = {
 | 
						|
  _modifiedStyles: [],
 | 
						|
 | 
						|
  init() {
 | 
						|
    XPCOMUtils.defineLazyGetter(this, "styleSheet", function() {
 | 
						|
      for (let i = document.styleSheets.length - 1; i >= 0; i--) {
 | 
						|
        let sheet = document.styleSheets[i];
 | 
						|
        if (sheet.href == "chrome://browser/skin/browser-lightweightTheme.css")
 | 
						|
          return sheet;
 | 
						|
      }
 | 
						|
      return undefined;
 | 
						|
    });
 | 
						|
 | 
						|
    Services.obs.addObserver(this, "lightweight-theme-styling-update", false);
 | 
						|
    Services.obs.addObserver(this, "lightweight-theme-optimized", false);
 | 
						|
    if (document.documentElement.hasAttribute("lwtheme"))
 | 
						|
      this.updateStyleSheet(document.documentElement.style.backgroundImage);
 | 
						|
  },
 | 
						|
 | 
						|
  uninit() {
 | 
						|
    Services.obs.removeObserver(this, "lightweight-theme-styling-update");
 | 
						|
    Services.obs.removeObserver(this, "lightweight-theme-optimized");
 | 
						|
  },
 | 
						|
 | 
						|
  /**
 | 
						|
   * Append the headerImage to the background-image property of all rulesets in
 | 
						|
   * browser-lightweightTheme.css.
 | 
						|
   *
 | 
						|
   * @param headerImage - a string containing a CSS image for the lightweight theme header.
 | 
						|
   */
 | 
						|
  updateStyleSheet(headerImage) {
 | 
						|
    if (!this.styleSheet)
 | 
						|
      return;
 | 
						|
    this.substituteRules(this.styleSheet.cssRules, headerImage);
 | 
						|
  },
 | 
						|
 | 
						|
  substituteRules(ruleList, headerImage, existingStyleRulesModified = 0) {
 | 
						|
    let styleRulesModified = 0;
 | 
						|
    for (let i = 0; i < ruleList.length; i++) {
 | 
						|
      let rule = ruleList[i];
 | 
						|
      if (rule instanceof Ci.nsIDOMCSSGroupingRule) {
 | 
						|
        // Add the number of modified sub-rules to the modified count
 | 
						|
        styleRulesModified += this.substituteRules(rule.cssRules, headerImage, existingStyleRulesModified + styleRulesModified);
 | 
						|
      } else if (rule instanceof Ci.nsIDOMCSSStyleRule) {
 | 
						|
        if (!rule.style.backgroundImage)
 | 
						|
          continue;
 | 
						|
        let modifiedIndex = existingStyleRulesModified + styleRulesModified;
 | 
						|
        if (!this._modifiedStyles[modifiedIndex])
 | 
						|
          this._modifiedStyles[modifiedIndex] = { backgroundImage: rule.style.backgroundImage };
 | 
						|
 | 
						|
        rule.style.backgroundImage = this._modifiedStyles[modifiedIndex].backgroundImage + ", " + headerImage;
 | 
						|
        styleRulesModified++;
 | 
						|
      } else {
 | 
						|
        Cu.reportError("Unsupported rule encountered");
 | 
						|
      }
 | 
						|
    }
 | 
						|
    return styleRulesModified;
 | 
						|
  },
 | 
						|
 | 
						|
  // nsIObserver
 | 
						|
  observe(aSubject, aTopic, aData) {
 | 
						|
    if ((aTopic != "lightweight-theme-styling-update" && aTopic != "lightweight-theme-optimized") ||
 | 
						|
          !this.styleSheet)
 | 
						|
      return;
 | 
						|
 | 
						|
    if (aTopic == "lightweight-theme-optimized" && aSubject != window)
 | 
						|
      return;
 | 
						|
 | 
						|
    let themeData = JSON.parse(aData);
 | 
						|
    if (!themeData)
 | 
						|
      return;
 | 
						|
    this.updateStyleSheet("url(" + themeData.headerURL + ")");
 | 
						|
  },
 | 
						|
};
 |