browser/base/content/browser-addons.js

Wed, 31 Dec 2014 13:27:57 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 13:27:57 +0100
branch
TOR_BUG_3246
changeset 6
8bccb770b82d
permissions
-rw-r--r--

Ignore runtime configuration files generated during quality assurance.

     1 # -*- Mode: javascript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
     2 # This Source Code Form is subject to the terms of the Mozilla Public
     3 # License, v. 2.0. If a copy of the MPL was not distributed with this
     4 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
     6 const gXPInstallObserver = {
     7   _findChildShell: function (aDocShell, aSoughtShell)
     8   {
     9     if (aDocShell == aSoughtShell)
    10       return aDocShell;
    12     var node = aDocShell.QueryInterface(Components.interfaces.nsIDocShellTreeItem);
    13     for (var i = 0; i < node.childCount; ++i) {
    14       var docShell = node.getChildAt(i);
    15       docShell = this._findChildShell(docShell, aSoughtShell);
    16       if (docShell == aSoughtShell)
    17         return docShell;
    18     }
    19     return null;
    20   },
    22   _getBrowser: function (aDocShell)
    23   {
    24     for (let browser of gBrowser.browsers) {
    25       if (this._findChildShell(browser.docShell, aDocShell))
    26         return browser;
    27     }
    28     return null;
    29   },
    31   observe: function (aSubject, aTopic, aData)
    32   {
    33     var brandBundle = document.getElementById("bundle_brand");
    34     var installInfo = aSubject.QueryInterface(Components.interfaces.amIWebInstallInfo);
    35     var win = installInfo.originatingWindow;
    36     var shell = win.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
    37                    .getInterface(Components.interfaces.nsIWebNavigation)
    38                    .QueryInterface(Components.interfaces.nsIDocShell);
    39     var browser = this._getBrowser(shell);
    40     if (!browser)
    41       return;
    42     const anchorID = "addons-notification-icon";
    43     var messageString, action;
    44     var brandShortName = brandBundle.getString("brandShortName");
    46     var notificationID = aTopic;
    47     // Make notifications persist a minimum of 30 seconds
    48     var options = {
    49       timeout: Date.now() + 30000
    50     };
    52     switch (aTopic) {
    53     case "addon-install-disabled":
    54       notificationID = "xpinstall-disabled"
    56       if (gPrefService.prefIsLocked("xpinstall.enabled")) {
    57         messageString = gNavigatorBundle.getString("xpinstallDisabledMessageLocked");
    58         buttons = [];
    59       }
    60       else {
    61         messageString = gNavigatorBundle.getString("xpinstallDisabledMessage");
    63         action = {
    64           label: gNavigatorBundle.getString("xpinstallDisabledButton"),
    65           accessKey: gNavigatorBundle.getString("xpinstallDisabledButton.accesskey"),
    66           callback: function editPrefs() {
    67             gPrefService.setBoolPref("xpinstall.enabled", true);
    68           }
    69         };
    70       }
    72       PopupNotifications.show(browser, notificationID, messageString, anchorID,
    73                               action, null, options);
    74       break;
    75     case "addon-install-blocked":
    76       messageString = gNavigatorBundle.getFormattedString("xpinstallPromptWarning",
    77                         [brandShortName, installInfo.originatingURI.host]);
    79       let secHistogram = Components.classes["@mozilla.org/base/telemetry;1"].getService(Ci.nsITelemetry).getHistogramById("SECURITY_UI");
    80       action = {
    81         label: gNavigatorBundle.getString("xpinstallPromptAllowButton"),
    82         accessKey: gNavigatorBundle.getString("xpinstallPromptAllowButton.accesskey"),
    83         callback: function() {
    84           secHistogram.add(Ci.nsISecurityUITelemetry.WARNING_ADDON_ASKING_PREVENTED_CLICK_THROUGH);
    85           installInfo.install();
    86         }
    87       };
    89       secHistogram.add(Ci.nsISecurityUITelemetry.WARNING_ADDON_ASKING_PREVENTED);
    90       PopupNotifications.show(browser, notificationID, messageString, anchorID,
    91                               action, null, options);
    92       break;
    93     case "addon-install-started":
    94       var needsDownload = function needsDownload(aInstall) {
    95         return aInstall.state != AddonManager.STATE_DOWNLOADED;
    96       }
    97       // If all installs have already been downloaded then there is no need to
    98       // show the download progress
    99       if (!installInfo.installs.some(needsDownload))
   100         return;
   101       notificationID = "addon-progress";
   102       messageString = gNavigatorBundle.getString("addonDownloading");
   103       messageString = PluralForm.get(installInfo.installs.length, messageString);
   104       options.installs = installInfo.installs;
   105       options.contentWindow = browser.contentWindow;
   106       options.sourceURI = browser.currentURI;
   107       options.eventCallback = function(aEvent) {
   108         if (aEvent != "removed")
   109           return;
   110         options.contentWindow = null;
   111         options.sourceURI = null;
   112       };
   113       PopupNotifications.show(browser, notificationID, messageString, anchorID,
   114                               null, null, options);
   115       break;
   116     case "addon-install-failed":
   117       // TODO This isn't terribly ideal for the multiple failure case
   118       for (let install of installInfo.installs) {
   119         let host = (installInfo.originatingURI instanceof Ci.nsIStandardURL) &&
   120                    installInfo.originatingURI.host;
   121         if (!host)
   122           host = (install.sourceURI instanceof Ci.nsIStandardURL) &&
   123                  install.sourceURI.host;
   125         let error = (host || install.error == 0) ? "addonError" : "addonLocalError";
   126         if (install.error != 0)
   127           error += install.error;
   128         else if (install.addon.blocklistState == Ci.nsIBlocklistService.STATE_BLOCKED)
   129           error += "Blocklisted";
   130         else
   131           error += "Incompatible";
   133         messageString = gNavigatorBundle.getString(error);
   134         messageString = messageString.replace("#1", install.name);
   135         if (host)
   136           messageString = messageString.replace("#2", host);
   137         messageString = messageString.replace("#3", brandShortName);
   138         messageString = messageString.replace("#4", Services.appinfo.version);
   140         PopupNotifications.show(browser, notificationID, messageString, anchorID,
   141                                 action, null, options);
   142       }
   143       break;
   144     case "addon-install-complete":
   145       var needsRestart = installInfo.installs.some(function(i) {
   146         return i.addon.pendingOperations != AddonManager.PENDING_NONE;
   147       });
   149       if (needsRestart) {
   150         messageString = gNavigatorBundle.getString("addonsInstalledNeedsRestart");
   151         action = {
   152           label: gNavigatorBundle.getString("addonInstallRestartButton"),
   153           accessKey: gNavigatorBundle.getString("addonInstallRestartButton.accesskey"),
   154           callback: function() {
   155             Application.restart();
   156           }
   157         };
   158       }
   159       else {
   160         messageString = gNavigatorBundle.getString("addonsInstalled");
   161         action = null;
   162       }
   164       messageString = PluralForm.get(installInfo.installs.length, messageString);
   165       messageString = messageString.replace("#1", installInfo.installs[0].name);
   166       messageString = messageString.replace("#2", installInfo.installs.length);
   167       messageString = messageString.replace("#3", brandShortName);
   169       // Remove notificaion on dismissal, since it's possible to cancel the
   170       // install through the addons manager UI, making the "restart" prompt
   171       // irrelevant.
   172       options.removeOnDismissal = true;
   174       PopupNotifications.show(browser, notificationID, messageString, anchorID,
   175                               action, null, options);
   176       break;
   177     }
   178   }
   179 };
   181 var LightWeightThemeWebInstaller = {
   182   handleEvent: function (event) {
   183     switch (event.type) {
   184       case "InstallBrowserTheme":
   185       case "PreviewBrowserTheme":
   186       case "ResetBrowserThemePreview":
   187         // ignore requests from background tabs
   188         if (event.target.ownerDocument.defaultView.top != content)
   189           return;
   190     }
   191     switch (event.type) {
   192       case "InstallBrowserTheme":
   193         this._installRequest(event);
   194         break;
   195       case "PreviewBrowserTheme":
   196         this._preview(event);
   197         break;
   198       case "ResetBrowserThemePreview":
   199         this._resetPreview(event);
   200         break;
   201       case "pagehide":
   202       case "TabSelect":
   203         this._resetPreview();
   204         break;
   205     }
   206   },
   208   get _manager () {
   209     var temp = {};
   210     Cu.import("resource://gre/modules/LightweightThemeManager.jsm", temp);
   211     delete this._manager;
   212     return this._manager = temp.LightweightThemeManager;
   213   },
   215   _installRequest: function (event) {
   216     var node = event.target;
   217     var data = this._getThemeFromNode(node);
   218     if (!data)
   219       return;
   221     if (this._isAllowed(node)) {
   222       this._install(data);
   223       return;
   224     }
   226     var allowButtonText =
   227       gNavigatorBundle.getString("lwthemeInstallRequest.allowButton");
   228     var allowButtonAccesskey =
   229       gNavigatorBundle.getString("lwthemeInstallRequest.allowButton.accesskey");
   230     var message =
   231       gNavigatorBundle.getFormattedString("lwthemeInstallRequest.message",
   232                                           [node.ownerDocument.location.host]);
   233     var buttons = [{
   234       label: allowButtonText,
   235       accessKey: allowButtonAccesskey,
   236       callback: function () {
   237         LightWeightThemeWebInstaller._install(data);
   238       }
   239     }];
   241     this._removePreviousNotifications();
   243     var notificationBox = gBrowser.getNotificationBox();
   244     var notificationBar =
   245       notificationBox.appendNotification(message, "lwtheme-install-request", "",
   246                                          notificationBox.PRIORITY_INFO_MEDIUM,
   247                                          buttons);
   248     notificationBar.persistence = 1;
   249   },
   251   _install: function (newLWTheme) {
   252     var previousLWTheme = this._manager.currentTheme;
   254     var listener = {
   255       onEnabling: function(aAddon, aRequiresRestart) {
   256         if (!aRequiresRestart)
   257           return;
   259         let messageString = gNavigatorBundle.getFormattedString("lwthemeNeedsRestart.message",
   260           [aAddon.name], 1);
   262         let action = {
   263           label: gNavigatorBundle.getString("lwthemeNeedsRestart.button"),
   264           accessKey: gNavigatorBundle.getString("lwthemeNeedsRestart.accesskey"),
   265           callback: function () {
   266             Application.restart();
   267           }
   268         };
   270         let options = {
   271           timeout: Date.now() + 30000
   272         };
   274         PopupNotifications.show(gBrowser.selectedBrowser, "addon-theme-change",
   275                                 messageString, "addons-notification-icon",
   276                                 action, null, options);
   277       },
   279       onEnabled: function(aAddon) {
   280         LightWeightThemeWebInstaller._postInstallNotification(newLWTheme, previousLWTheme);
   281       }
   282     };
   284     AddonManager.addAddonListener(listener);
   285     this._manager.currentTheme = newLWTheme;
   286     AddonManager.removeAddonListener(listener);
   287   },
   289   _postInstallNotification: function (newTheme, previousTheme) {
   290     function text(id) {
   291       return gNavigatorBundle.getString("lwthemePostInstallNotification." + id);
   292     }
   294     var buttons = [{
   295       label: text("undoButton"),
   296       accessKey: text("undoButton.accesskey"),
   297       callback: function () {
   298         LightWeightThemeWebInstaller._manager.forgetUsedTheme(newTheme.id);
   299         LightWeightThemeWebInstaller._manager.currentTheme = previousTheme;
   300       }
   301     }, {
   302       label: text("manageButton"),
   303       accessKey: text("manageButton.accesskey"),
   304       callback: function () {
   305         BrowserOpenAddonsMgr("addons://list/theme");
   306       }
   307     }];
   309     this._removePreviousNotifications();
   311     var notificationBox = gBrowser.getNotificationBox();
   312     var notificationBar =
   313       notificationBox.appendNotification(text("message"),
   314                                          "lwtheme-install-notification", "",
   315                                          notificationBox.PRIORITY_INFO_MEDIUM,
   316                                          buttons);
   317     notificationBar.persistence = 1;
   318     notificationBar.timeout = Date.now() + 20000; // 20 seconds
   319   },
   321   _removePreviousNotifications: function () {
   322     var box = gBrowser.getNotificationBox();
   324     ["lwtheme-install-request",
   325      "lwtheme-install-notification"].forEach(function (value) {
   326         var notification = box.getNotificationWithValue(value);
   327         if (notification)
   328           box.removeNotification(notification);
   329       });
   330   },
   332   _previewWindow: null,
   333   _preview: function (event) {
   334     if (!this._isAllowed(event.target))
   335       return;
   337     var data = this._getThemeFromNode(event.target);
   338     if (!data)
   339       return;
   341     this._resetPreview();
   343     this._previewWindow = event.target.ownerDocument.defaultView;
   344     this._previewWindow.addEventListener("pagehide", this, true);
   345     gBrowser.tabContainer.addEventListener("TabSelect", this, false);
   347     this._manager.previewTheme(data);
   348   },
   350   _resetPreview: function (event) {
   351     if (!this._previewWindow ||
   352         event && !this._isAllowed(event.target))
   353       return;
   355     this._previewWindow.removeEventListener("pagehide", this, true);
   356     this._previewWindow = null;
   357     gBrowser.tabContainer.removeEventListener("TabSelect", this, false);
   359     this._manager.resetPreview();
   360   },
   362   _isAllowed: function (node) {
   363     var pm = Services.perms;
   365     var uri = node.ownerDocument.documentURIObject;
   366     return pm.testPermission(uri, "install") == pm.ALLOW_ACTION;
   367   },
   369   _getThemeFromNode: function (node) {
   370     return this._manager.parseTheme(node.getAttribute("data-browsertheme"),
   371                                     node.baseURI);
   372   }
   373 }
   375 /*
   376  * Listen for Lightweight Theme styling changes and update the browser's theme accordingly.
   377  */
   378 let LightweightThemeListener = {
   379   _modifiedStyles: [],
   381   init: function () {
   382     XPCOMUtils.defineLazyGetter(this, "styleSheet", function() {
   383       for (let i = document.styleSheets.length - 1; i >= 0; i--) {
   384         let sheet = document.styleSheets[i];
   385         if (sheet.href == "chrome://browser/skin/browser-lightweightTheme.css")
   386           return sheet;
   387       }
   388     });
   390     Services.obs.addObserver(this, "lightweight-theme-styling-update", false);
   391     Services.obs.addObserver(this, "lightweight-theme-optimized", false);
   392     if (document.documentElement.hasAttribute("lwtheme"))
   393       this.updateStyleSheet(document.documentElement.style.backgroundImage);
   394   },
   396   uninit: function () {
   397     Services.obs.removeObserver(this, "lightweight-theme-styling-update");
   398     Services.obs.removeObserver(this, "lightweight-theme-optimized");
   399   },
   401   /**
   402    * Append the headerImage to the background-image property of all rulesets in
   403    * browser-lightweightTheme.css.
   404    *
   405    * @param headerImage - a string containing a CSS image for the lightweight theme header.
   406    */
   407   updateStyleSheet: function(headerImage) {
   408     if (!this.styleSheet)
   409       return;
   410     this.substituteRules(this.styleSheet.cssRules, headerImage);
   411   },
   413   substituteRules: function(ruleList, headerImage, existingStyleRulesModified = 0) {
   414     let styleRulesModified = 0;
   415     for (let i = 0; i < ruleList.length; i++) {
   416       let rule = ruleList[i];
   417       if (rule instanceof Ci.nsIDOMCSSGroupingRule) {
   418         // Add the number of modified sub-rules to the modified count
   419         styleRulesModified += this.substituteRules(rule.cssRules, headerImage, existingStyleRulesModified + styleRulesModified);
   420       } else if (rule instanceof Ci.nsIDOMCSSStyleRule) {
   421         if (!rule.style.backgroundImage)
   422           continue;
   423         let modifiedIndex = existingStyleRulesModified + styleRulesModified;
   424         if (!this._modifiedStyles[modifiedIndex])
   425           this._modifiedStyles[modifiedIndex] = { backgroundImage: rule.style.backgroundImage };
   427         rule.style.backgroundImage = this._modifiedStyles[modifiedIndex].backgroundImage + ", " + headerImage;
   428         styleRulesModified++;
   429       } else {
   430         Cu.reportError("Unsupported rule encountered");
   431       }
   432     }
   433     return styleRulesModified;
   434   },
   436   // nsIObserver
   437   observe: function (aSubject, aTopic, aData) {
   438     if ((aTopic != "lightweight-theme-styling-update" && aTopic != "lightweight-theme-optimized") ||
   439           !this.styleSheet)
   440       return;
   442     if (aTopic == "lightweight-theme-optimized" && aSubject != window)
   443       return;
   445     let themeData = JSON.parse(aData);
   446     if (!themeData)
   447       return;
   448     this.updateStyleSheet("url(" + themeData.headerURL + ")");
   449   },
   450 };

mercurial