browser/base/content/browser-addons.js

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

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

mercurial