# -*- Mode: javascript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 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/. const gXPInstallObserver = { _findChildShell: function (aDocShell, aSoughtShell) { if (aDocShell == aSoughtShell) return aDocShell; var node = aDocShell.QueryInterface(Components.interfaces.nsIDocShellTreeNode); 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: function (aDocShell) { for (let browser of gBrowser.browsers) { if (this._findChildShell(browser.docShell, aDocShell)) return browser; } return null; }, observe: function (aSubject, aTopic, aData) { var brandBundle = document.getElementById("bundle_brand"); var installInfo = aSubject.QueryInterface(Components.interfaces.amIWebInstallInfo); var win = installInfo.originatingWindow; var shell = win.QueryInterface(Components.interfaces.nsIInterfaceRequestor) .getInterface(Components.interfaces.nsIWebNavigation) .QueryInterface(Components.interfaces.nsIDocShell); var browser = this._getBrowser(shell); if (!browser) return; const anchorID = "addons-notification-icon"; var messageString, action; var brandShortName = brandBundle.getString("brandShortName"); var notificationID = aTopic; // Make notifications persist a minimum of 30 seconds var options = { timeout: Date.now() + 30000 }; switch (aTopic) { case "addon-install-disabled": notificationID = "xpinstall-disabled" 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); } }; } PopupNotifications.show(browser, notificationID, messageString, anchorID, action, null, options); break; case "addon-install-blocked": messageString = gNavigatorBundle.getFormattedString("xpinstallPromptWarning", [brandShortName, installInfo.originatingURI.host]); 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: function() { secHistogram.add(Ci.nsISecurityUITelemetry.WARNING_ADDON_ASKING_PREVENTED_CLICK_THROUGH); installInfo.install(); } }; secHistogram.add(Ci.nsISecurityUITelemetry.WARNING_ADDON_ASKING_PREVENTED); PopupNotifications.show(browser, notificationID, messageString, anchorID, action, null, options); break; case "addon-install-started": 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("addonDownloading"); messageString = PluralForm.get(installInfo.installs.length, messageString); options.installs = installInfo.installs; options.contentWindow = browser.contentWindow; options.sourceURI = browser.currentURI; options.eventCallback = function(aEvent) { if (aEvent != "removed") return; options.contentWindow = null; options.sourceURI = null; }; PopupNotifications.show(browser, notificationID, messageString, anchorID, null, null, options); break; case "addon-install-failed": // TODO This isn't terribly ideal for the multiple failure case for (let install of installInfo.installs) { let host = (installInfo.originatingURI instanceof Ci.nsIStandardURL) && installInfo.originatingURI.host; if (!host) host = (install.sourceURI instanceof Ci.nsIStandardURL) && install.sourceURI.host; let error = (host || install.error == 0) ? "addonError" : "addonLocalError"; if (install.error != 0) error += install.error; else if (install.addon.blocklistState == Ci.nsIBlocklistService.STATE_BLOCKED) error += "Blocklisted"; else error += "Incompatible"; messageString = gNavigatorBundle.getString(error); messageString = messageString.replace("#1", install.name); if (host) messageString = messageString.replace("#2", host); messageString = messageString.replace("#3", brandShortName); messageString = messageString.replace("#4", Services.appinfo.version); PopupNotifications.show(browser, notificationID, messageString, anchorID, action, null, options); } break; case "addon-install-complete": var needsRestart = installInfo.installs.some(function(i) { return i.addon.pendingOperations != AddonManager.PENDING_NONE; }); if (needsRestart) { messageString = gNavigatorBundle.getString("addonsInstalledNeedsRestart"); action = { label: gNavigatorBundle.getString("addonInstallRestartButton"), accessKey: gNavigatorBundle.getString("addonInstallRestartButton.accesskey"), callback: function() { Application.restart(); } }; } 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 notificaion on dismissal, since it's possible to cancel the // install through the addons manager UI, making the "restart" prompt // irrelevant. options.removeOnDismissal = true; PopupNotifications.show(browser, notificationID, messageString, anchorID, action, null, options); break; } } }; /* * When addons are installed/uninstalled, check and see if the number of items * on the add-on bar changed: * - If an add-on was installed, incrementing the count, show the bar. * - If an add-on was uninstalled, and no more items are left, hide the bar. */ let AddonsMgrListener = { get addonBar() document.getElementById("addon-bar"), get statusBar() document.getElementById("status-bar"), getAddonBarItemCount: function() { // Take into account the contents of the status bar shim for the count. var itemCount = this.statusBar.childNodes.length; var defaultOrNoninteractive = this.addonBar.getAttribute("defaultset") .split(",") .concat(["separator", "spacer", "spring"]); for (let item of this.addonBar.currentSet.split(",")) { if (defaultOrNoninteractive.indexOf(item) == -1) itemCount++; } return itemCount; }, onInstalling: function(aAddon) { this.lastAddonBarCount = this.getAddonBarItemCount(); }, onInstalled: function(aAddon) { if (this.getAddonBarItemCount() > this.lastAddonBarCount) setToolbarVisibility(this.addonBar, true); }, onUninstalling: function(aAddon) { this.lastAddonBarCount = this.getAddonBarItemCount(); }, onUninstalled: function(aAddon) { if (this.getAddonBarItemCount() == 0) setToolbarVisibility(this.addonBar, false); }, onEnabling: function(aAddon) this.onInstalling(), onEnabled: function(aAddon) this.onInstalled(), onDisabling: function(aAddon) this.onUninstalling(), onDisabled: function(aAddon) this.onUninstalled(), }; var LightWeightThemeWebInstaller = { handleEvent: function (event) { switch (event.type) { case "InstallBrowserTheme": case "PreviewBrowserTheme": case "ResetBrowserThemePreview": // ignore requests from background tabs if (event.target.ownerDocument.defaultView.top != content) return; } switch (event.type) { case "InstallBrowserTheme": this._installRequest(event); break; case "PreviewBrowserTheme": this._preview(event); break; case "ResetBrowserThemePreview": this._resetPreview(event); break; case "pagehide": case "TabSelect": this._resetPreview(); break; } }, get _manager () { var temp = {}; Cu.import("resource://gre/modules/LightweightThemeManager.jsm", temp); delete this._manager; return this._manager = temp.LightweightThemeManager; }, _installRequest: function (event) { var node = event.target; var data = this._getThemeFromNode(node); if (!data) return; if (this._isAllowed(node)) { this._install(data); return; } var allowButtonText = gNavigatorBundle.getString("lwthemeInstallRequest.allowButton"); var allowButtonAccesskey = gNavigatorBundle.getString("lwthemeInstallRequest.allowButton.accesskey"); var message = gNavigatorBundle.getFormattedString("lwthemeInstallRequest.message", [node.ownerDocument.location.host]); var buttons = [{ label: allowButtonText, accessKey: allowButtonAccesskey, callback: function () { LightWeightThemeWebInstaller._install(data); } }]; this._removePreviousNotifications(); var notificationBox = gBrowser.getNotificationBox(); var notificationBar = notificationBox.appendNotification(message, "lwtheme-install-request", "", notificationBox.PRIORITY_INFO_MEDIUM, buttons); notificationBar.persistence = 1; }, _install: function (newLWTheme) { var previousLWTheme = this._manager.currentTheme; var listener = { onEnabling: function(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: function () { Application.restart(); } }; let options = { timeout: Date.now() + 30000 }; PopupNotifications.show(gBrowser.selectedBrowser, "addon-theme-change", messageString, "addons-notification-icon", action, null, options); }, onEnabled: function(aAddon) { LightWeightThemeWebInstaller._postInstallNotification(newLWTheme, previousLWTheme); } }; AddonManager.addAddonListener(listener); this._manager.currentTheme = newLWTheme; AddonManager.removeAddonListener(listener); }, _postInstallNotification: function (newTheme, previousTheme) { function text(id) { return gNavigatorBundle.getString("lwthemePostInstallNotification." + id); } var buttons = [{ label: text("undoButton"), accessKey: text("undoButton.accesskey"), callback: function () { LightWeightThemeWebInstaller._manager.forgetUsedTheme(newTheme.id); LightWeightThemeWebInstaller._manager.currentTheme = previousTheme; } }, { label: text("manageButton"), accessKey: text("manageButton.accesskey"), callback: function () { BrowserOpenAddonsMgr("addons://list/theme"); } }]; this._removePreviousNotifications(); var notificationBox = gBrowser.getNotificationBox(); var notificationBar = notificationBox.appendNotification(text("message"), "lwtheme-install-notification", "", notificationBox.PRIORITY_INFO_MEDIUM, buttons); notificationBar.persistence = 1; notificationBar.timeout = Date.now() + 20000; // 20 seconds }, _removePreviousNotifications: function () { var box = gBrowser.getNotificationBox(); ["lwtheme-install-request", "lwtheme-install-notification"].forEach(function (value) { var notification = box.getNotificationWithValue(value); if (notification) box.removeNotification(notification); }); }, _previewWindow: null, _preview: function (event) { if (!this._isAllowed(event.target)) return; var data = this._getThemeFromNode(event.target); if (!data) return; this._resetPreview(); this._previewWindow = event.target.ownerDocument.defaultView; this._previewWindow.addEventListener("pagehide", this, true); gBrowser.tabContainer.addEventListener("TabSelect", this, false); this._manager.previewTheme(data); }, _resetPreview: function (event) { if (!this._previewWindow || event && !this._isAllowed(event.target)) return; this._previewWindow.removeEventListener("pagehide", this, true); this._previewWindow = null; gBrowser.tabContainer.removeEventListener("TabSelect", this, false); this._manager.resetPreview(); }, _isAllowed: function (node) { var pm = Services.perms; var uri = node.ownerDocument.documentURIObject; return pm.testPermission(uri, "install") == pm.ALLOW_ACTION; }, _getThemeFromNode: function (node) { return this._manager.parseTheme(node.getAttribute("data-browsertheme"), node.baseURI); } }