1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/browser/base/content/browser-addons.js Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,450 @@ 1.4 +# -*- Mode: javascript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- 1.5 +# This Source Code Form is subject to the terms of the Mozilla Public 1.6 +# License, v. 2.0. If a copy of the MPL was not distributed with this 1.7 +# file, You can obtain one at http://mozilla.org/MPL/2.0/. 1.8 + 1.9 +const gXPInstallObserver = { 1.10 + _findChildShell: function (aDocShell, aSoughtShell) 1.11 + { 1.12 + if (aDocShell == aSoughtShell) 1.13 + return aDocShell; 1.14 + 1.15 + var node = aDocShell.QueryInterface(Components.interfaces.nsIDocShellTreeItem); 1.16 + for (var i = 0; i < node.childCount; ++i) { 1.17 + var docShell = node.getChildAt(i); 1.18 + docShell = this._findChildShell(docShell, aSoughtShell); 1.19 + if (docShell == aSoughtShell) 1.20 + return docShell; 1.21 + } 1.22 + return null; 1.23 + }, 1.24 + 1.25 + _getBrowser: function (aDocShell) 1.26 + { 1.27 + for (let browser of gBrowser.browsers) { 1.28 + if (this._findChildShell(browser.docShell, aDocShell)) 1.29 + return browser; 1.30 + } 1.31 + return null; 1.32 + }, 1.33 + 1.34 + observe: function (aSubject, aTopic, aData) 1.35 + { 1.36 + var brandBundle = document.getElementById("bundle_brand"); 1.37 + var installInfo = aSubject.QueryInterface(Components.interfaces.amIWebInstallInfo); 1.38 + var win = installInfo.originatingWindow; 1.39 + var shell = win.QueryInterface(Components.interfaces.nsIInterfaceRequestor) 1.40 + .getInterface(Components.interfaces.nsIWebNavigation) 1.41 + .QueryInterface(Components.interfaces.nsIDocShell); 1.42 + var browser = this._getBrowser(shell); 1.43 + if (!browser) 1.44 + return; 1.45 + const anchorID = "addons-notification-icon"; 1.46 + var messageString, action; 1.47 + var brandShortName = brandBundle.getString("brandShortName"); 1.48 + 1.49 + var notificationID = aTopic; 1.50 + // Make notifications persist a minimum of 30 seconds 1.51 + var options = { 1.52 + timeout: Date.now() + 30000 1.53 + }; 1.54 + 1.55 + switch (aTopic) { 1.56 + case "addon-install-disabled": 1.57 + notificationID = "xpinstall-disabled" 1.58 + 1.59 + if (gPrefService.prefIsLocked("xpinstall.enabled")) { 1.60 + messageString = gNavigatorBundle.getString("xpinstallDisabledMessageLocked"); 1.61 + buttons = []; 1.62 + } 1.63 + else { 1.64 + messageString = gNavigatorBundle.getString("xpinstallDisabledMessage"); 1.65 + 1.66 + action = { 1.67 + label: gNavigatorBundle.getString("xpinstallDisabledButton"), 1.68 + accessKey: gNavigatorBundle.getString("xpinstallDisabledButton.accesskey"), 1.69 + callback: function editPrefs() { 1.70 + gPrefService.setBoolPref("xpinstall.enabled", true); 1.71 + } 1.72 + }; 1.73 + } 1.74 + 1.75 + PopupNotifications.show(browser, notificationID, messageString, anchorID, 1.76 + action, null, options); 1.77 + break; 1.78 + case "addon-install-blocked": 1.79 + messageString = gNavigatorBundle.getFormattedString("xpinstallPromptWarning", 1.80 + [brandShortName, installInfo.originatingURI.host]); 1.81 + 1.82 + let secHistogram = Components.classes["@mozilla.org/base/telemetry;1"].getService(Ci.nsITelemetry).getHistogramById("SECURITY_UI"); 1.83 + action = { 1.84 + label: gNavigatorBundle.getString("xpinstallPromptAllowButton"), 1.85 + accessKey: gNavigatorBundle.getString("xpinstallPromptAllowButton.accesskey"), 1.86 + callback: function() { 1.87 + secHistogram.add(Ci.nsISecurityUITelemetry.WARNING_ADDON_ASKING_PREVENTED_CLICK_THROUGH); 1.88 + installInfo.install(); 1.89 + } 1.90 + }; 1.91 + 1.92 + secHistogram.add(Ci.nsISecurityUITelemetry.WARNING_ADDON_ASKING_PREVENTED); 1.93 + PopupNotifications.show(browser, notificationID, messageString, anchorID, 1.94 + action, null, options); 1.95 + break; 1.96 + case "addon-install-started": 1.97 + var needsDownload = function needsDownload(aInstall) { 1.98 + return aInstall.state != AddonManager.STATE_DOWNLOADED; 1.99 + } 1.100 + // If all installs have already been downloaded then there is no need to 1.101 + // show the download progress 1.102 + if (!installInfo.installs.some(needsDownload)) 1.103 + return; 1.104 + notificationID = "addon-progress"; 1.105 + messageString = gNavigatorBundle.getString("addonDownloading"); 1.106 + messageString = PluralForm.get(installInfo.installs.length, messageString); 1.107 + options.installs = installInfo.installs; 1.108 + options.contentWindow = browser.contentWindow; 1.109 + options.sourceURI = browser.currentURI; 1.110 + options.eventCallback = function(aEvent) { 1.111 + if (aEvent != "removed") 1.112 + return; 1.113 + options.contentWindow = null; 1.114 + options.sourceURI = null; 1.115 + }; 1.116 + PopupNotifications.show(browser, notificationID, messageString, anchorID, 1.117 + null, null, options); 1.118 + break; 1.119 + case "addon-install-failed": 1.120 + // TODO This isn't terribly ideal for the multiple failure case 1.121 + for (let install of installInfo.installs) { 1.122 + let host = (installInfo.originatingURI instanceof Ci.nsIStandardURL) && 1.123 + installInfo.originatingURI.host; 1.124 + if (!host) 1.125 + host = (install.sourceURI instanceof Ci.nsIStandardURL) && 1.126 + install.sourceURI.host; 1.127 + 1.128 + let error = (host || install.error == 0) ? "addonError" : "addonLocalError"; 1.129 + if (install.error != 0) 1.130 + error += install.error; 1.131 + else if (install.addon.blocklistState == Ci.nsIBlocklistService.STATE_BLOCKED) 1.132 + error += "Blocklisted"; 1.133 + else 1.134 + error += "Incompatible"; 1.135 + 1.136 + messageString = gNavigatorBundle.getString(error); 1.137 + messageString = messageString.replace("#1", install.name); 1.138 + if (host) 1.139 + messageString = messageString.replace("#2", host); 1.140 + messageString = messageString.replace("#3", brandShortName); 1.141 + messageString = messageString.replace("#4", Services.appinfo.version); 1.142 + 1.143 + PopupNotifications.show(browser, notificationID, messageString, anchorID, 1.144 + action, null, options); 1.145 + } 1.146 + break; 1.147 + case "addon-install-complete": 1.148 + var needsRestart = installInfo.installs.some(function(i) { 1.149 + return i.addon.pendingOperations != AddonManager.PENDING_NONE; 1.150 + }); 1.151 + 1.152 + if (needsRestart) { 1.153 + messageString = gNavigatorBundle.getString("addonsInstalledNeedsRestart"); 1.154 + action = { 1.155 + label: gNavigatorBundle.getString("addonInstallRestartButton"), 1.156 + accessKey: gNavigatorBundle.getString("addonInstallRestartButton.accesskey"), 1.157 + callback: function() { 1.158 + Application.restart(); 1.159 + } 1.160 + }; 1.161 + } 1.162 + else { 1.163 + messageString = gNavigatorBundle.getString("addonsInstalled"); 1.164 + action = null; 1.165 + } 1.166 + 1.167 + messageString = PluralForm.get(installInfo.installs.length, messageString); 1.168 + messageString = messageString.replace("#1", installInfo.installs[0].name); 1.169 + messageString = messageString.replace("#2", installInfo.installs.length); 1.170 + messageString = messageString.replace("#3", brandShortName); 1.171 + 1.172 + // Remove notificaion on dismissal, since it's possible to cancel the 1.173 + // install through the addons manager UI, making the "restart" prompt 1.174 + // irrelevant. 1.175 + options.removeOnDismissal = true; 1.176 + 1.177 + PopupNotifications.show(browser, notificationID, messageString, anchorID, 1.178 + action, null, options); 1.179 + break; 1.180 + } 1.181 + } 1.182 +}; 1.183 + 1.184 +var LightWeightThemeWebInstaller = { 1.185 + handleEvent: function (event) { 1.186 + switch (event.type) { 1.187 + case "InstallBrowserTheme": 1.188 + case "PreviewBrowserTheme": 1.189 + case "ResetBrowserThemePreview": 1.190 + // ignore requests from background tabs 1.191 + if (event.target.ownerDocument.defaultView.top != content) 1.192 + return; 1.193 + } 1.194 + switch (event.type) { 1.195 + case "InstallBrowserTheme": 1.196 + this._installRequest(event); 1.197 + break; 1.198 + case "PreviewBrowserTheme": 1.199 + this._preview(event); 1.200 + break; 1.201 + case "ResetBrowserThemePreview": 1.202 + this._resetPreview(event); 1.203 + break; 1.204 + case "pagehide": 1.205 + case "TabSelect": 1.206 + this._resetPreview(); 1.207 + break; 1.208 + } 1.209 + }, 1.210 + 1.211 + get _manager () { 1.212 + var temp = {}; 1.213 + Cu.import("resource://gre/modules/LightweightThemeManager.jsm", temp); 1.214 + delete this._manager; 1.215 + return this._manager = temp.LightweightThemeManager; 1.216 + }, 1.217 + 1.218 + _installRequest: function (event) { 1.219 + var node = event.target; 1.220 + var data = this._getThemeFromNode(node); 1.221 + if (!data) 1.222 + return; 1.223 + 1.224 + if (this._isAllowed(node)) { 1.225 + this._install(data); 1.226 + return; 1.227 + } 1.228 + 1.229 + var allowButtonText = 1.230 + gNavigatorBundle.getString("lwthemeInstallRequest.allowButton"); 1.231 + var allowButtonAccesskey = 1.232 + gNavigatorBundle.getString("lwthemeInstallRequest.allowButton.accesskey"); 1.233 + var message = 1.234 + gNavigatorBundle.getFormattedString("lwthemeInstallRequest.message", 1.235 + [node.ownerDocument.location.host]); 1.236 + var buttons = [{ 1.237 + label: allowButtonText, 1.238 + accessKey: allowButtonAccesskey, 1.239 + callback: function () { 1.240 + LightWeightThemeWebInstaller._install(data); 1.241 + } 1.242 + }]; 1.243 + 1.244 + this._removePreviousNotifications(); 1.245 + 1.246 + var notificationBox = gBrowser.getNotificationBox(); 1.247 + var notificationBar = 1.248 + notificationBox.appendNotification(message, "lwtheme-install-request", "", 1.249 + notificationBox.PRIORITY_INFO_MEDIUM, 1.250 + buttons); 1.251 + notificationBar.persistence = 1; 1.252 + }, 1.253 + 1.254 + _install: function (newLWTheme) { 1.255 + var previousLWTheme = this._manager.currentTheme; 1.256 + 1.257 + var listener = { 1.258 + onEnabling: function(aAddon, aRequiresRestart) { 1.259 + if (!aRequiresRestart) 1.260 + return; 1.261 + 1.262 + let messageString = gNavigatorBundle.getFormattedString("lwthemeNeedsRestart.message", 1.263 + [aAddon.name], 1); 1.264 + 1.265 + let action = { 1.266 + label: gNavigatorBundle.getString("lwthemeNeedsRestart.button"), 1.267 + accessKey: gNavigatorBundle.getString("lwthemeNeedsRestart.accesskey"), 1.268 + callback: function () { 1.269 + Application.restart(); 1.270 + } 1.271 + }; 1.272 + 1.273 + let options = { 1.274 + timeout: Date.now() + 30000 1.275 + }; 1.276 + 1.277 + PopupNotifications.show(gBrowser.selectedBrowser, "addon-theme-change", 1.278 + messageString, "addons-notification-icon", 1.279 + action, null, options); 1.280 + }, 1.281 + 1.282 + onEnabled: function(aAddon) { 1.283 + LightWeightThemeWebInstaller._postInstallNotification(newLWTheme, previousLWTheme); 1.284 + } 1.285 + }; 1.286 + 1.287 + AddonManager.addAddonListener(listener); 1.288 + this._manager.currentTheme = newLWTheme; 1.289 + AddonManager.removeAddonListener(listener); 1.290 + }, 1.291 + 1.292 + _postInstallNotification: function (newTheme, previousTheme) { 1.293 + function text(id) { 1.294 + return gNavigatorBundle.getString("lwthemePostInstallNotification." + id); 1.295 + } 1.296 + 1.297 + var buttons = [{ 1.298 + label: text("undoButton"), 1.299 + accessKey: text("undoButton.accesskey"), 1.300 + callback: function () { 1.301 + LightWeightThemeWebInstaller._manager.forgetUsedTheme(newTheme.id); 1.302 + LightWeightThemeWebInstaller._manager.currentTheme = previousTheme; 1.303 + } 1.304 + }, { 1.305 + label: text("manageButton"), 1.306 + accessKey: text("manageButton.accesskey"), 1.307 + callback: function () { 1.308 + BrowserOpenAddonsMgr("addons://list/theme"); 1.309 + } 1.310 + }]; 1.311 + 1.312 + this._removePreviousNotifications(); 1.313 + 1.314 + var notificationBox = gBrowser.getNotificationBox(); 1.315 + var notificationBar = 1.316 + notificationBox.appendNotification(text("message"), 1.317 + "lwtheme-install-notification", "", 1.318 + notificationBox.PRIORITY_INFO_MEDIUM, 1.319 + buttons); 1.320 + notificationBar.persistence = 1; 1.321 + notificationBar.timeout = Date.now() + 20000; // 20 seconds 1.322 + }, 1.323 + 1.324 + _removePreviousNotifications: function () { 1.325 + var box = gBrowser.getNotificationBox(); 1.326 + 1.327 + ["lwtheme-install-request", 1.328 + "lwtheme-install-notification"].forEach(function (value) { 1.329 + var notification = box.getNotificationWithValue(value); 1.330 + if (notification) 1.331 + box.removeNotification(notification); 1.332 + }); 1.333 + }, 1.334 + 1.335 + _previewWindow: null, 1.336 + _preview: function (event) { 1.337 + if (!this._isAllowed(event.target)) 1.338 + return; 1.339 + 1.340 + var data = this._getThemeFromNode(event.target); 1.341 + if (!data) 1.342 + return; 1.343 + 1.344 + this._resetPreview(); 1.345 + 1.346 + this._previewWindow = event.target.ownerDocument.defaultView; 1.347 + this._previewWindow.addEventListener("pagehide", this, true); 1.348 + gBrowser.tabContainer.addEventListener("TabSelect", this, false); 1.349 + 1.350 + this._manager.previewTheme(data); 1.351 + }, 1.352 + 1.353 + _resetPreview: function (event) { 1.354 + if (!this._previewWindow || 1.355 + event && !this._isAllowed(event.target)) 1.356 + return; 1.357 + 1.358 + this._previewWindow.removeEventListener("pagehide", this, true); 1.359 + this._previewWindow = null; 1.360 + gBrowser.tabContainer.removeEventListener("TabSelect", this, false); 1.361 + 1.362 + this._manager.resetPreview(); 1.363 + }, 1.364 + 1.365 + _isAllowed: function (node) { 1.366 + var pm = Services.perms; 1.367 + 1.368 + var uri = node.ownerDocument.documentURIObject; 1.369 + return pm.testPermission(uri, "install") == pm.ALLOW_ACTION; 1.370 + }, 1.371 + 1.372 + _getThemeFromNode: function (node) { 1.373 + return this._manager.parseTheme(node.getAttribute("data-browsertheme"), 1.374 + node.baseURI); 1.375 + } 1.376 +} 1.377 + 1.378 +/* 1.379 + * Listen for Lightweight Theme styling changes and update the browser's theme accordingly. 1.380 + */ 1.381 +let LightweightThemeListener = { 1.382 + _modifiedStyles: [], 1.383 + 1.384 + init: function () { 1.385 + XPCOMUtils.defineLazyGetter(this, "styleSheet", function() { 1.386 + for (let i = document.styleSheets.length - 1; i >= 0; i--) { 1.387 + let sheet = document.styleSheets[i]; 1.388 + if (sheet.href == "chrome://browser/skin/browser-lightweightTheme.css") 1.389 + return sheet; 1.390 + } 1.391 + }); 1.392 + 1.393 + Services.obs.addObserver(this, "lightweight-theme-styling-update", false); 1.394 + Services.obs.addObserver(this, "lightweight-theme-optimized", false); 1.395 + if (document.documentElement.hasAttribute("lwtheme")) 1.396 + this.updateStyleSheet(document.documentElement.style.backgroundImage); 1.397 + }, 1.398 + 1.399 + uninit: function () { 1.400 + Services.obs.removeObserver(this, "lightweight-theme-styling-update"); 1.401 + Services.obs.removeObserver(this, "lightweight-theme-optimized"); 1.402 + }, 1.403 + 1.404 + /** 1.405 + * Append the headerImage to the background-image property of all rulesets in 1.406 + * browser-lightweightTheme.css. 1.407 + * 1.408 + * @param headerImage - a string containing a CSS image for the lightweight theme header. 1.409 + */ 1.410 + updateStyleSheet: function(headerImage) { 1.411 + if (!this.styleSheet) 1.412 + return; 1.413 + this.substituteRules(this.styleSheet.cssRules, headerImage); 1.414 + }, 1.415 + 1.416 + substituteRules: function(ruleList, headerImage, existingStyleRulesModified = 0) { 1.417 + let styleRulesModified = 0; 1.418 + for (let i = 0; i < ruleList.length; i++) { 1.419 + let rule = ruleList[i]; 1.420 + if (rule instanceof Ci.nsIDOMCSSGroupingRule) { 1.421 + // Add the number of modified sub-rules to the modified count 1.422 + styleRulesModified += this.substituteRules(rule.cssRules, headerImage, existingStyleRulesModified + styleRulesModified); 1.423 + } else if (rule instanceof Ci.nsIDOMCSSStyleRule) { 1.424 + if (!rule.style.backgroundImage) 1.425 + continue; 1.426 + let modifiedIndex = existingStyleRulesModified + styleRulesModified; 1.427 + if (!this._modifiedStyles[modifiedIndex]) 1.428 + this._modifiedStyles[modifiedIndex] = { backgroundImage: rule.style.backgroundImage }; 1.429 + 1.430 + rule.style.backgroundImage = this._modifiedStyles[modifiedIndex].backgroundImage + ", " + headerImage; 1.431 + styleRulesModified++; 1.432 + } else { 1.433 + Cu.reportError("Unsupported rule encountered"); 1.434 + } 1.435 + } 1.436 + return styleRulesModified; 1.437 + }, 1.438 + 1.439 + // nsIObserver 1.440 + observe: function (aSubject, aTopic, aData) { 1.441 + if ((aTopic != "lightweight-theme-styling-update" && aTopic != "lightweight-theme-optimized") || 1.442 + !this.styleSheet) 1.443 + return; 1.444 + 1.445 + if (aTopic == "lightweight-theme-optimized" && aSubject != window) 1.446 + return; 1.447 + 1.448 + let themeData = JSON.parse(aData); 1.449 + if (!themeData) 1.450 + return; 1.451 + this.updateStyleSheet("url(" + themeData.headerURL + ")"); 1.452 + }, 1.453 +};