/* 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/. */ /** * This is a default implementation of amIWebInstallListener that should work * for most applications but can be overriden. It notifies the observer service * about blocked installs. For normal installs it pops up an install * confirmation when all the add-ons have been downloaded. */ "use strict"; const Cc = Components.classes; const Ci = Components.interfaces; const Cr = Components.results; const Cu = Components.utils; Cu.import("resource://gre/modules/XPCOMUtils.jsm"); Cu.import("resource://gre/modules/AddonManager.jsm"); Cu.import("resource://gre/modules/Services.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "PromptUtils", "resource://gre/modules/SharedPromptUtils.jsm"); const URI_XPINSTALL_DIALOG = "chrome://mozapps/content/xpinstall/xpinstallConfirm.xul"; // Installation can begin from any of these states const READY_STATES = [ AddonManager.STATE_AVAILABLE, AddonManager.STATE_DOWNLOAD_FAILED, AddonManager.STATE_INSTALL_FAILED, AddonManager.STATE_CANCELLED ]; Cu.import("resource://gre/modules/Log.jsm"); const LOGGER_ID = "addons.weblistener"; // Create a new logger for use by the Addons Web Listener // (Requires AddonManager.jsm) var logger = Log.repository.getLogger(LOGGER_ID); function notifyObservers(aTopic, aBrowser, aUri, aInstalls) { let info = { browser: aBrowser, originatingURI: aUri, installs: aInstalls, QueryInterface: XPCOMUtils.generateQI([Ci.amIWebInstallInfo]) }; Services.obs.notifyObservers(info, aTopic, null); } /** * Creates a new installer to monitor downloads and prompt to install when * ready * * @param aBrowser * The browser that started the installations * @param aUrl * The URL that started the installations * @param aInstalls * An array of AddonInstalls */ function Installer(aBrowser, aUrl, aInstalls) { this.browser = aBrowser; this.url = aUrl; this.downloads = aInstalls; this.installed = []; notifyObservers("addon-install-started", aBrowser, aUrl, aInstalls); aInstalls.forEach(function(aInstall) { aInstall.addListener(this); // Start downloading if it hasn't already begun if (READY_STATES.indexOf(aInstall.state) != -1) aInstall.install(); }, this); this.checkAllDownloaded(); } Installer.prototype = { browser: null, downloads: null, installed: null, isDownloading: true, /** * Checks if all downloads are now complete and if so prompts to install. */ checkAllDownloaded: function() { // Prevent re-entrancy caused by the confirmation dialog cancelling unwanted // installs. if (!this.isDownloading) return; var failed = []; var installs = []; for (let install of this.downloads) { switch (install.state) { case AddonManager.STATE_AVAILABLE: case AddonManager.STATE_DOWNLOADING: // Exit early if any add-ons haven't started downloading yet or are // still downloading return; case AddonManager.STATE_DOWNLOAD_FAILED: failed.push(install); break; case AddonManager.STATE_DOWNLOADED: // App disabled items are not compatible and so fail to install if (install.addon.appDisabled) failed.push(install); else installs.push(install); if (install.linkedInstalls) { install.linkedInstalls.forEach(function(aInstall) { aInstall.addListener(this); // App disabled items are not compatible and so fail to install if (aInstall.addon.appDisabled) failed.push(aInstall); else installs.push(aInstall); }, this); } break; case AddonManager.STATE_CANCELLED: // Just ignore cancelled downloads break; default: logger.warn("Download of " + install.sourceURI.spec + " in unexpected state " + install.state); } } this.isDownloading = false; this.downloads = installs; if (failed.length > 0) { // Stop listening and cancel any installs that are failed because of // compatibility reasons. failed.forEach(function(aInstall) { if (aInstall.state == AddonManager.STATE_DOWNLOADED) { aInstall.removeListener(this); aInstall.cancel(); } }, this); notifyObservers("addon-install-failed", this.browser, this.url, failed); } // If none of the downloads were successful then exit early if (this.downloads.length == 0) return; // Check for a custom installation prompt that may be provided by the // applicaton if ("@mozilla.org/addons/web-install-prompt;1" in Cc) { try { let prompt = Cc["@mozilla.org/addons/web-install-prompt;1"]. getService(Ci.amIWebInstallPrompt); prompt.confirm(this.browser, this.url, this.downloads, this.downloads.length); return; } catch (e) {} } let args = {}; args.url = this.url; args.installs = this.downloads; args.wrappedJSObject = args; try { let parentWindow = null; if (this.browser) { parentWindow = this.browser.ownerDocument.defaultView; PromptUtils.fireDialogEvent(parentWindow, "DOMWillOpenModalDialog", this.browser); } Services.ww.openWindow(parentWindow, URI_XPINSTALL_DIALOG, null, "chrome,modal,centerscreen", args); } catch (e) { logger.warn("Exception showing install confirmation dialog", e); this.downloads.forEach(function(aInstall) { aInstall.removeListener(this); // Cancel the installs, as currently there is no way to make them fail // from here. aInstall.cancel(); }, this); notifyObservers("addon-install-cancelled", this.browser, this.url, this.downloads); } }, /** * Checks if all installs are now complete and if so notifies observers. */ checkAllInstalled: function() { var failed = []; for (let install of this.downloads) { switch(install.state) { case AddonManager.STATE_DOWNLOADED: case AddonManager.STATE_INSTALLING: // Exit early if any add-ons haven't started installing yet or are // still installing return; case AddonManager.STATE_INSTALL_FAILED: failed.push(install); break; } } this.downloads = null; if (failed.length > 0) notifyObservers("addon-install-failed", this.browser, this.url, failed); if (this.installed.length > 0) notifyObservers("addon-install-complete", this.browser, this.url, this.installed); this.installed = null; }, onDownloadCancelled: function(aInstall) { aInstall.removeListener(this); this.checkAllDownloaded(); }, onDownloadFailed: function(aInstall) { aInstall.removeListener(this); this.checkAllDownloaded(); }, onDownloadEnded: function(aInstall) { this.checkAllDownloaded(); return false; }, onInstallCancelled: function(aInstall) { aInstall.removeListener(this); this.checkAllInstalled(); }, onInstallFailed: function(aInstall) { aInstall.removeListener(this); this.checkAllInstalled(); }, onInstallEnded: function(aInstall) { aInstall.removeListener(this); this.installed.push(aInstall); // If installing a theme that is disabled and can be enabled then enable it if (aInstall.addon.type == "theme" && aInstall.addon.userDisabled == true && aInstall.addon.appDisabled == false) { aInstall.addon.userDisabled = false; } this.checkAllInstalled(); } }; function extWebInstallListener() { } extWebInstallListener.prototype = { /** * @see amIWebInstallListener.idl */ onWebInstallDisabled: function(aBrowser, aUri, aInstalls) { let info = { browser: aBrowser, originatingURI: aUri, installs: aInstalls, QueryInterface: XPCOMUtils.generateQI([Ci.amIWebInstallInfo]) }; Services.obs.notifyObservers(info, "addon-install-disabled", null); }, /** * @see amIWebInstallListener.idl */ onWebInstallOriginBlocked: function(aBrowser, aUri, aInstalls) { let info = { browser: aBrowser, originatingURI: aUri, installs: aInstalls, install: function() { }, QueryInterface: XPCOMUtils.generateQI([Ci.amIWebInstallInfo]) }; Services.obs.notifyObservers(info, "addon-install-origin-blocked", null); return false; }, /** * @see amIWebInstallListener.idl */ onWebInstallBlocked: function(aBrowser, aUri, aInstalls) { let info = { browser: aBrowser, originatingURI: aUri, installs: aInstalls, install: function() { new Installer(this.browser, this.originatingURI, this.installs); }, QueryInterface: XPCOMUtils.generateQI([Ci.amIWebInstallInfo]) }; Services.obs.notifyObservers(info, "addon-install-blocked", null); return false; }, /** * @see amIWebInstallListener.idl */ onWebInstallRequested: function(aBrowser, aUri, aInstalls) { new Installer(aBrowser, aUri, aInstalls); // We start the installs ourself return false; }, classDescription: "XPI Install Handler", contractID: "@mozilla.org/addons/web-install-listener;1", classID: Components.ID("{0f38e086-89a3-40a5-8ffc-9b694de1d04a}"), QueryInterface: XPCOMUtils.generateQI([Ci.amIWebInstallListener, Ci.amIWebInstallListener2]) }; this.NSGetFactory = XPCOMUtils.generateNSGetFactory([extWebInstallListener]);