/* 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/. */ "use strict"; /** * This script contains the minimum, skeleton content process code that we need * in order to lazily load other extension modules when they are first * necessary. Anything which is not likely to be needed immediately, or shortly * after startup, in *every* browser process live outside of this file. */ var EXPORTED_SYMBOLS = ["ExtensionProcessScript"]; const {MessageChannel} = ChromeUtils.import("resource://gre/modules/MessageChannel.jsm"); const {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm"); const {XPCOMUtils} = ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm"); XPCOMUtils.defineLazyModuleGetters(this, { ExtensionChild: "resource://gre/modules/ExtensionChild.jsm", ExtensionCommon: "resource://gre/modules/ExtensionCommon.jsm", ExtensionContent: "resource://gre/modules/ExtensionContent.jsm", ExtensionPageChild: "resource://gre/modules/ExtensionPageChild.jsm", }); const {ExtensionUtils} = ChromeUtils.import("resource://gre/modules/ExtensionUtils.jsm"); XPCOMUtils.defineLazyGetter(this, "console", () => ExtensionCommon.getConsole()); const { DefaultWeakMap, getInnerWindowID, } = ExtensionUtils; const {sharedData} = Services.cpmm; function getData(extension, key = "") { return sharedData.get(`extension/${extension.id}/${key}`); } // We need to avoid touching Services.appinfo here in order to prevent // the wrong version from being cached during xpcshell test startup. // eslint-disable-next-line mozilla/use-services const appinfo = Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULRuntime); const isContentProcess = appinfo.processType == appinfo.PROCESS_TYPE_CONTENT; var extensions = new DefaultWeakMap(policy => { return new ExtensionChild.BrowserExtensionContent(policy); }); var ExtensionManager; class ExtensionGlobal { constructor(global) { this.global = global; this.global.addMessageListener("Extension:SetFrameData", this); this.frameData = null; MessageChannel.addListener(global, "Extension:Capture", this); MessageChannel.addListener(global, "Extension:DetectLanguage", this); MessageChannel.addListener(global, "Extension:Execute", this); MessageChannel.addListener(global, "WebNavigation:GetFrame", this); MessageChannel.addListener(global, "WebNavigation:GetAllFrames", this); } get messageFilterStrict() { return { innerWindowID: getInnerWindowID(this.global.content), }; } getFrameData(force = false) { if (!this.frameData && force) { this.frameData = this.global.sendSyncMessage("Extension:GetTabAndWindowId")[0]; } return this.frameData; } receiveMessage({target, messageName, recipient, data, name}) { switch (name) { case "Extension:SetFrameData": if (this.frameData) { Object.assign(this.frameData, data); } else { this.frameData = data; } if (data.viewType && WebExtensionPolicy.isExtensionProcess) { ExtensionPageChild.expectViewLoad(this.global, data.viewType); } return; } // SetFrameData does not have a recipient extension, or it would be // an extension process. Anything following this point must have // a recipient extension, so check access to the window. let policy = WebExtensionPolicy.getByID(recipient.extensionId); if (!policy.canAccessWindow(this.global.content)) { throw new Error("Extension cannot access window"); } return ExtensionContent.receiveMessage(this.global, messageName, target, data, recipient); } } ExtensionManager = { // WeakMap> registeredContentScripts: new DefaultWeakMap((policy) => new Map()), globals: new WeakMap(), init() { MessageChannel.setupMessageManagers([Services.cpmm]); Services.cpmm.addMessageListener("Extension:Startup", this); Services.cpmm.addMessageListener("Extension:Shutdown", this); Services.cpmm.addMessageListener("Extension:FlushJarCache", this); Services.cpmm.addMessageListener("Extension:RegisterContentScript", this); Services.cpmm.addMessageListener("Extension:UnregisterContentScripts", this); // eslint-disable-next-line mozilla/balanced-listeners Services.obs.addObserver( global => this.globals.set(global, new ExtensionGlobal(global)), "tab-content-frameloader-created"); for (let id of sharedData.get("extensions/activeIDs") || []) { this.initExtension(getData({id})); } }, initExtensionPolicy(extension) { let policy = WebExtensionPolicy.getByID(extension.id); if (!policy) { let localizeCallback; if (extension.localize) { // We have a real Extension object. localizeCallback = extension.localize.bind(extension); } else { // We have serialized extension data; localizeCallback = str => extensions.get(policy).localize(str); } let {backgroundScripts} = extension; if (!backgroundScripts && WebExtensionPolicy.isExtensionProcess) { ({backgroundScripts} = getData(extension, "extendedData") || {}); } policy = new WebExtensionPolicy({ id: extension.id, mozExtensionHostname: extension.uuid, name: extension.name, baseURL: extension.resourceURL, permissions: extension.permissions, allowedOrigins: extension.whiteListedHosts, webAccessibleResources: extension.webAccessibleResources, contentSecurityPolicy: extension.contentSecurityPolicy, localizeCallback, backgroundScripts, contentScripts: extension.contentScripts, }); policy.debugName = `${JSON.stringify(policy.name)} (ID: ${policy.id}, ${policy.getURL()})`; // Register any existent dynamically registered content script for the extension // when a content process is started for the first time (which also cover // a content process that crashed and it has been recreated). const registeredContentScripts = this.registeredContentScripts.get(policy); for (let [scriptId, options] of getData(extension, "contentScripts") || []) { const script = new WebExtensionContentScript(policy, options); // If the script is a userScript, add the additional userScriptOptions // property to the WebExtensionContentScript instance. if ("userScriptOptions" in options) { script.userScriptOptions = options.userScriptOptions; } policy.registerContentScript(script); registeredContentScripts.set(scriptId, script); } policy.active = true; policy.instanceId = extension.instanceId; policy.optionalPermissions = extension.optionalPermissions; } return policy; }, initExtension(data) { if (typeof data === "string") { data = getData({id: data}); } let policy = this.initExtensionPolicy(data); policy.injectContentScripts(); }, receiveMessage({name, data}) { try { switch (name) { case "Extension:Startup": this.initExtension(data); break; case "Extension:Shutdown": { let policy = WebExtensionPolicy.getByID(data.id); if (policy) { if (extensions.has(policy)) { extensions.get(policy).shutdown(); } if (isContentProcess) { policy.active = false; } } break; } case "Extension:FlushJarCache": ExtensionUtils.flushJarCache(data.path); break; case "Extension:RegisterContentScript": { let policy = WebExtensionPolicy.getByID(data.id); if (policy) { const registeredContentScripts = this.registeredContentScripts.get(policy); const type = "userScriptOptions" in data.options ? "userScript" : "contentScript"; if (registeredContentScripts.has(data.scriptId)) { Cu.reportError(new Error( `Registering ${type} ${data.scriptId} on ${data.id} more than once`)); } else { const script = new WebExtensionContentScript(policy, data.options); // If the script is a userScript, add the additional userScriptOptions // property to the WebExtensionContentScript instance. if (type === "userScript") { script.userScriptOptions = data.options.userScriptOptions; } policy.registerContentScript(script); registeredContentScripts.set(data.scriptId, script); } } break; } case "Extension:UnregisterContentScripts": { let policy = WebExtensionPolicy.getByID(data.id); if (policy) { const registeredContentScripts = this.registeredContentScripts.get(policy); for (const scriptId of data.scriptIds) { const script = registeredContentScripts.get(scriptId); if (script) { policy.unregisterContentScript(script); registeredContentScripts.delete(scriptId); } } } break; } } } catch (e) { Cu.reportError(e); } Services.cpmm.sendAsyncMessage(`${name}Complete`); }, }; var ExtensionProcessScript = { extensions, getFrameData(global, force) { let extGlobal = ExtensionManager.globals.get(global); return extGlobal && extGlobal.getFrameData(force); }, initExtension(extension) { return ExtensionManager.initExtensionPolicy(extension); }, initExtensionDocument(policy, doc, privileged) { let extension = extensions.get(policy); if (privileged) { ExtensionPageChild.initExtensionContext(extension, doc.defaultView); } else { ExtensionContent.initExtensionContext(extension, doc.defaultView); } }, getExtensionChild(id) { let policy = WebExtensionPolicy.getByID(id); if (policy) { return extensions.get(policy); } }, preloadContentScript(contentScript) { ExtensionContent.contentScripts.get(contentScript).preload(); }, loadContentScript(contentScript, window) { return ExtensionContent.contentScripts.get(contentScript).injectInto(window); }, }; ExtensionManager.init();