/* 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 component serves as integration between the platform and AddonManager. * It is responsible for initializing and shutting down the AddonManager as well * as passing new installs from webpages to the AddonManager. */ import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs"; const lazy = {}; XPCOMUtils.defineLazyPreferenceGetter( lazy, "separatePrivilegedMozillaWebContentProcess", "browser.tabs.remote.separatePrivilegedMozillaWebContentProcess", false ); XPCOMUtils.defineLazyPreferenceGetter( lazy, "extensionsWebAPITesting", "extensions.webapi.testing", false ); // The old XPInstall error codes const EXECUTION_ERROR = -203; const CANT_READ_ARCHIVE = -207; const USER_CANCELLED = -210; const DOWNLOAD_ERROR = -228; const UNSUPPORTED_TYPE = -244; const SUCCESS = 0; const MSG_INSTALL_ENABLED = "WebInstallerIsInstallEnabled"; const MSG_INSTALL_ADDON = "WebInstallerInstallAddonFromWebpage"; const MSG_INSTALL_CALLBACK = "WebInstallerInstallCallback"; const MSG_PROMISE_REQUEST = "WebAPIPromiseRequest"; const MSG_PROMISE_RESULT = "WebAPIPromiseResult"; const MSG_INSTALL_EVENT = "WebAPIInstallEvent"; const MSG_INSTALL_CLEANUP = "WebAPICleanup"; const MSG_ADDON_EVENT_REQ = "WebAPIAddonEventRequest"; const MSG_ADDON_EVENT = "WebAPIAddonEvent"; var AddonManager, AddonManagerPrivate; export function amManager() { ({ AddonManager, AddonManagerPrivate } = ChromeUtils.importESModule( "resource://gre/modules/AddonManager.sys.mjs" )); Services.mm.addMessageListener(MSG_INSTALL_ENABLED, this); Services.mm.addMessageListener(MSG_PROMISE_REQUEST, this); Services.mm.addMessageListener(MSG_INSTALL_CLEANUP, this); Services.mm.addMessageListener(MSG_ADDON_EVENT_REQ, this); Services.ppmm.addMessageListener(MSG_INSTALL_ADDON, this); Services.obs.addObserver(this, "message-manager-close"); Services.obs.addObserver(this, "message-manager-disconnect"); AddonManager.webAPI.setEventHandler(this.sendEvent); // Needed so receiveMessage can be called directly by JS callers this.wrappedJSObject = this; } amManager.prototype = { observe(aSubject, aTopic) { switch (aTopic) { case "addons-startup": AddonManagerPrivate.startup(); break; case "message-manager-close": case "message-manager-disconnect": this.childClosed(aSubject); break; } }, installAddonFromWebpage(aPayload, aBrowser, aCallback) { let retval = true; const { mimetype, triggeringPrincipal, hash, icon, name, uri } = aPayload; // NOTE: consider removing this call to isInstallAllowed from here, later it is going to be called // again from inside AddonManager.installAddonFromWebpage as part of the block/allow logic. // // The sole purpose of the call here seems to be "clearing the optional InstallTrigger callback", // which seems to be actually wrong if we are still proceeding to call getInstallForURL and the same // logic used to block the install flow using the exact same method call later on. if (!AddonManager.isInstallAllowed(mimetype, triggeringPrincipal)) { aCallback = null; retval = false; } let telemetryInfo = { source: AddonManager.getInstallSourceFromHost(aPayload.sourceHost), sourceURL: aPayload.sourceURL, }; if ("method" in aPayload) { telemetryInfo.method = aPayload.method; } AddonManager.getInstallForURL(uri, { hash, name, icon, browser: aBrowser, triggeringPrincipal, telemetryInfo, sendCookies: true, }).then(aInstall => { function callCallback(status) { try { aCallback?.onInstallEnded(uri, status); } catch (e) { Cu.reportError(e); } } if (!aInstall) { callCallback(UNSUPPORTED_TYPE); return; } if (aCallback) { aInstall.addListener({ onDownloadCancelled() { callCallback(USER_CANCELLED); }, onDownloadFailed(aInstall) { if (aInstall.error == AddonManager.ERROR_CORRUPT_FILE) { callCallback(CANT_READ_ARCHIVE); } else { callCallback(DOWNLOAD_ERROR); } }, onInstallFailed() { callCallback(EXECUTION_ERROR); }, onInstallEnded() { callCallback(SUCCESS); }, }); } AddonManager.installAddonFromWebpage( mimetype, aBrowser, triggeringPrincipal, aInstall, { hasCrossOriginAncestor: aPayload.hasCrossOriginAncestor, } ); }); return retval; }, notify() { AddonManagerPrivate.backgroundUpdateTimerHandler(); }, // Maps message manager instances for content processes to the associated // AddonListener instances. addonListeners: new Map(), _addAddonListener(target) { if (!this.addonListeners.has(target)) { let handler = (event, id) => { target.sendAsyncMessage(MSG_ADDON_EVENT, { event, id }); }; let listener = { onEnabling: addon => handler("onEnabling", addon.id), onEnabled: addon => handler("onEnabled", addon.id), onDisabling: addon => handler("onDisabling", addon.id), onDisabled: addon => handler("onDisabled", addon.id), onInstalling: addon => handler("onInstalling", addon.id), onInstalled: addon => handler("onInstalled", addon.id), onUninstalling: addon => handler("onUninstalling", addon.id), onUninstalled: addon => handler("onUninstalled", addon.id), onOperationCancelled: addon => handler("onOperationCancelled", addon.id), }; this.addonListeners.set(target, listener); AddonManager.addAddonListener(listener); } }, _removeAddonListener(target) { if (this.addonListeners.has(target)) { AddonManager.removeAddonListener(this.addonListeners.get(target)); this.addonListeners.delete(target); } }, /** * messageManager callback function. * * Listens to requests from child processes for InstallTrigger * activity, and sends back callbacks. */ receiveMessage(aMessage) { let payload = aMessage.data; switch (aMessage.name) { case MSG_INSTALL_ENABLED: return AddonManager.isInstallEnabled(payload.mimetype); case MSG_INSTALL_ADDON: { let browser = payload.browsingContext.top.embedderElement; let callback = null; if (payload.callbackID != -1) { let mm = browser.messageManager; callback = { onInstallEnded(url, status) { mm.sendAsyncMessage(MSG_INSTALL_CALLBACK, { callbackID: payload.callbackID, url, status, }); }, }; } return this.installAddonFromWebpage(payload, browser, callback); } case MSG_PROMISE_REQUEST: { if ( !lazy.extensionsWebAPITesting && lazy.separatePrivilegedMozillaWebContentProcess && aMessage.target && aMessage.target.remoteType != null && aMessage.target.remoteType !== "privilegedmozilla" ) { return undefined; } let mm = aMessage.target.messageManager; let resolve = value => { mm.sendAsyncMessage(MSG_PROMISE_RESULT, { callbackID: payload.callbackID, resolve: value, }); }; let reject = value => { mm.sendAsyncMessage(MSG_PROMISE_RESULT, { callbackID: payload.callbackID, reject: value, }); }; let API = AddonManager.webAPI; if (payload.type in API) { API[payload.type](aMessage.target, ...payload.args).then( resolve, reject ); } else { reject("Unknown Add-on API request."); } break; } case MSG_INSTALL_CLEANUP: { if ( !lazy.extensionsWebAPITesting && lazy.separatePrivilegedMozillaWebContentProcess && aMessage.target && aMessage.target.remoteType != null && aMessage.target.remoteType !== "privilegedmozilla" ) { return undefined; } AddonManager.webAPI.clearInstalls(payload.ids); break; } case MSG_ADDON_EVENT_REQ: { if ( !lazy.extensionsWebAPITesting && lazy.separatePrivilegedMozillaWebContentProcess && aMessage.target && aMessage.target.remoteType != null && aMessage.target.remoteType !== "privilegedmozilla" ) { return undefined; } let target = aMessage.target.messageManager; if (payload.enabled) { this._addAddonListener(target); } else { this._removeAddonListener(target); } } } return undefined; }, childClosed(target) { AddonManager.webAPI.clearInstallsFrom(target); this._removeAddonListener(target); }, sendEvent(mm, data) { mm.sendAsyncMessage(MSG_INSTALL_EVENT, data); }, classID: Components.ID("{4399533d-08d1-458c-a87a-235f74451cfa}"), QueryInterface: ChromeUtils.generateQI([ "amIAddonManager", "nsITimerCallback", "nsIObserver", ]), }; const BLOCKLIST_SYS_MJS = "resource://gre/modules/Blocklist.sys.mjs"; ChromeUtils.defineESModuleGetters(lazy, { Blocklist: BLOCKLIST_SYS_MJS }); export function BlocklistService() { this.wrappedJSObject = this; } BlocklistService.prototype = { STATE_NOT_BLOCKED: Ci.nsIBlocklistService.STATE_NOT_BLOCKED, STATE_SOFTBLOCKED: Ci.nsIBlocklistService.STATE_SOFTBLOCKED, STATE_BLOCKED: Ci.nsIBlocklistService.STATE_BLOCKED, get isLoaded() { return Cu.isESModuleLoaded(BLOCKLIST_SYS_MJS) && lazy.Blocklist.isLoaded; }, observe(...args) { return lazy.Blocklist.observe(...args); }, notify() { lazy.Blocklist.notify(); }, classID: Components.ID("{66354bc9-7ed1-4692-ae1d-8da97d6b205e}"), QueryInterface: ChromeUtils.generateQI([ "nsIObserver", "nsIBlocklistService", "nsITimerCallback", ]), };