diff options
Diffstat (limited to 'toolkit/components/webextensions/ExtensionParent.jsm')
-rw-r--r-- | toolkit/components/webextensions/ExtensionParent.jsm | 551 |
1 files changed, 0 insertions, 551 deletions
diff --git a/toolkit/components/webextensions/ExtensionParent.jsm b/toolkit/components/webextensions/ExtensionParent.jsm deleted file mode 100644 index b88500d1e..000000000 --- a/toolkit/components/webextensions/ExtensionParent.jsm +++ /dev/null @@ -1,551 +0,0 @@ -/* 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 module contains code for managing APIs that need to run in the - * parent process, and handles the parent side of operations that need - * to be proxied from ExtensionChild.jsm. - */ - -const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components; - -/* exported ExtensionParent */ - -this.EXPORTED_SYMBOLS = ["ExtensionParent"]; - -Cu.import("resource://gre/modules/Services.jsm"); -Cu.import("resource://gre/modules/XPCOMUtils.jsm"); - -XPCOMUtils.defineLazyModuleGetter(this, "AddonManager", - "resource://gre/modules/AddonManager.jsm"); -XPCOMUtils.defineLazyModuleGetter(this, "AppConstants", - "resource://gre/modules/AppConstants.jsm"); -XPCOMUtils.defineLazyModuleGetter(this, "MessageChannel", - "resource://gre/modules/MessageChannel.jsm"); -XPCOMUtils.defineLazyModuleGetter(this, "NativeApp", - "resource://gre/modules/NativeMessaging.jsm"); -XPCOMUtils.defineLazyModuleGetter(this, "NetUtil", - "resource://gre/modules/NetUtil.jsm"); -XPCOMUtils.defineLazyModuleGetter(this, "Schemas", - "resource://gre/modules/Schemas.jsm"); - -Cu.import("resource://gre/modules/ExtensionCommon.jsm"); -Cu.import("resource://gre/modules/ExtensionUtils.jsm"); - -var { - BaseContext, - SchemaAPIManager, -} = ExtensionCommon; - -var { - MessageManagerProxy, - SpreadArgs, - defineLazyGetter, - findPathInObject, -} = ExtensionUtils; - -const BASE_SCHEMA = "chrome://extensions/content/schemas/manifest.json"; -const CATEGORY_EXTENSION_SCHEMAS = "webextension-schemas"; -const CATEGORY_EXTENSION_SCRIPTS = "webextension-scripts"; - -let schemaURLs = new Set(); - -if (!AppConstants.RELEASE_OR_BETA) { - schemaURLs.add("chrome://extensions/content/schemas/experiments.json"); -} - -let GlobalManager; -let ParentAPIManager; -let ProxyMessenger; - -// This object loads the ext-*.js scripts that define the extension API. -let apiManager = new class extends SchemaAPIManager { - constructor() { - super("main"); - this.initialized = null; - } - - // Loads all the ext-*.js scripts currently registered. - lazyInit() { - if (this.initialized) { - return this.initialized; - } - - // Load order matters here. The base manifest defines types which are - // extended by other schemas, so needs to be loaded first. - let promise = Schemas.load(BASE_SCHEMA).then(() => { - let promises = []; - for (let [/* name */, url] of XPCOMUtils.enumerateCategoryEntries(CATEGORY_EXTENSION_SCHEMAS)) { - promises.push(Schemas.load(url)); - } - for (let url of schemaURLs) { - promises.push(Schemas.load(url)); - } - return Promise.all(promises); - }); - - for (let [/* name */, value] of XPCOMUtils.enumerateCategoryEntries(CATEGORY_EXTENSION_SCRIPTS)) { - this.loadScript(value); - } - - this.initialized = promise; - return this.initialized; - } - - registerSchemaAPI(namespace, envType, getAPI) { - if (envType == "addon_parent" || envType == "content_parent") { - super.registerSchemaAPI(namespace, envType, getAPI); - } - } -}(); - -// Subscribes to messages related to the extension messaging API and forwards it -// to the relevant message manager. The "sender" field for the `onMessage` and -// `onConnect` events are updated if needed. -ProxyMessenger = { - _initialized: false, - init() { - if (this._initialized) { - return; - } - this._initialized = true; - - // TODO(robwu): When addons move to a separate process, we should use the - // parent process manager(s) of the addon process(es) instead of the - // in-process one. - let pipmm = Services.ppmm.getChildAt(0); - // Listen on the global frame message manager because content scripts send - // and receive extension messages via their frame. - // Listen on the parent process message manager because `runtime.connect` - // and `runtime.sendMessage` requests must be delivered to all frames in an - // addon process (by the API contract). - // And legacy addons are not associated with a frame, so that is another - // reason for having a parent process manager here. - let messageManagers = [Services.mm, pipmm]; - - MessageChannel.addListener(messageManagers, "Extension:Connect", this); - MessageChannel.addListener(messageManagers, "Extension:Message", this); - MessageChannel.addListener(messageManagers, "Extension:Port:Disconnect", this); - MessageChannel.addListener(messageManagers, "Extension:Port:PostMessage", this); - }, - - receiveMessage({target, messageName, channelId, sender, recipient, data, responseType}) { - if (recipient.toNativeApp) { - let {childId, toNativeApp} = recipient; - if (messageName == "Extension:Message") { - let context = ParentAPIManager.getContextById(childId); - return new NativeApp(context, toNativeApp).sendMessage(data); - } - if (messageName == "Extension:Connect") { - let context = ParentAPIManager.getContextById(childId); - NativeApp.onConnectNative(context, target.messageManager, data.portId, sender, toNativeApp); - return true; - } - // "Extension:Port:Disconnect" and "Extension:Port:PostMessage" for - // native messages are handled by NativeApp. - return; - } - let extension = GlobalManager.extensionMap.get(sender.extensionId); - let receiverMM = this._getMessageManagerForRecipient(recipient); - if (!extension || !receiverMM) { - return Promise.reject({ - result: MessageChannel.RESULT_NO_HANDLER, - message: "No matching message handler for the given recipient.", - }); - } - - if ((messageName == "Extension:Message" || - messageName == "Extension:Connect") && - apiManager.global.tabGetSender) { - // From ext-tabs.js, undefined on Android. - apiManager.global.tabGetSender(extension, target, sender); - } - return MessageChannel.sendMessage(receiverMM, messageName, data, { - sender, - recipient, - responseType, - }); - }, - - /** - * @param {object} recipient An object that was passed to - * `MessageChannel.sendMessage`. - * @returns {object|null} The message manager matching the recipient if found. - */ - _getMessageManagerForRecipient(recipient) { - let {extensionId, tabId} = recipient; - // tabs.sendMessage / tabs.connect - if (tabId) { - // `tabId` being set implies that the tabs API is supported, so we don't - // need to check whether `TabManager` exists. - let tab = apiManager.global.TabManager.getTab(tabId, null, null); - return tab && tab.linkedBrowser.messageManager; - } - - // runtime.sendMessage / runtime.connect - if (extensionId) { - // TODO(robwu): map the extensionId to the addon parent process's message - // manager when they run in a separate process. - return Services.ppmm.getChildAt(0); - } - - return null; - }, -}; - -// Responsible for loading extension APIs into the right globals. -GlobalManager = { - // Map[extension ID -> Extension]. Determines which extension is - // responsible for content under a particular extension ID. - extensionMap: new Map(), - initialized: false, - - init(extension) { - if (this.extensionMap.size == 0) { - ProxyMessenger.init(); - apiManager.on("extension-browser-inserted", this._onExtensionBrowser); - this.initialized = true; - } - - this.extensionMap.set(extension.id, extension); - }, - - uninit(extension) { - this.extensionMap.delete(extension.id); - - if (this.extensionMap.size == 0 && this.initialized) { - apiManager.off("extension-browser-inserted", this._onExtensionBrowser); - this.initialized = false; - } - }, - - _onExtensionBrowser(type, browser) { - browser.messageManager.loadFrameScript(`data:, - Components.utils.import("resource://gre/modules/ExtensionContent.jsm"); - ExtensionContent.init(this); - addEventListener("unload", function() { - ExtensionContent.uninit(this); - }); - `, false); - }, - - getExtension(extensionId) { - return this.extensionMap.get(extensionId); - }, - - injectInObject(context, isChromeCompat, dest) { - apiManager.generateAPIs(context, dest); - SchemaAPIManager.generateAPIs(context, context.extension.apis, dest); - }, -}; - -/** - * The proxied parent side of a context in ExtensionChild.jsm, for the - * parent side of a proxied API. - */ -class ProxyContextParent extends BaseContext { - constructor(envType, extension, params, xulBrowser, principal) { - super(envType, extension); - - this.uri = NetUtil.newURI(params.url); - - this.incognito = params.incognito; - - // This message manager is used by ParentAPIManager to send messages and to - // close the ProxyContext if the underlying message manager closes. This - // message manager object may change when `xulBrowser` swaps docshells, e.g. - // when a tab is moved to a different window. - this.messageManagerProxy = new MessageManagerProxy(xulBrowser); - - Object.defineProperty(this, "principal", { - value: principal, enumerable: true, configurable: true, - }); - - this.listenerProxies = new Map(); - - apiManager.emit("proxy-context-load", this); - } - - get cloneScope() { - return this.sandbox; - } - - get xulBrowser() { - return this.messageManagerProxy.eventTarget; - } - - get parentMessageManager() { - return this.messageManagerProxy.messageManager; - } - - shutdown() { - this.unload(); - } - - unload() { - if (this.unloaded) { - return; - } - this.messageManagerProxy.dispose(); - super.unload(); - apiManager.emit("proxy-context-unload", this); - } -} - -defineLazyGetter(ProxyContextParent.prototype, "apiObj", function() { - let obj = {}; - GlobalManager.injectInObject(this, false, obj); - return obj; -}); - -defineLazyGetter(ProxyContextParent.prototype, "sandbox", function() { - return Cu.Sandbox(this.principal); -}); - -/** - * The parent side of proxied API context for extension content script - * running in ExtensionContent.jsm. - */ -class ContentScriptContextParent extends ProxyContextParent { -} - -/** - * The parent side of proxied API context for extension page, such as a - * background script, a tab page, or a popup, running in - * ExtensionChild.jsm. - */ -class ExtensionPageContextParent extends ProxyContextParent { - constructor(envType, extension, params, xulBrowser) { - super(envType, extension, params, xulBrowser, extension.principal); - - this.viewType = params.viewType; - } - - // The window that contains this context. This may change due to moving tabs. - get xulWindow() { - return this.xulBrowser.ownerGlobal; - } - - get windowId() { - if (!apiManager.global.WindowManager || this.viewType == "background") { - return; - } - // viewType popup or tab: - return apiManager.global.WindowManager.getId(this.xulWindow); - } - - get tabId() { - if (!apiManager.global.TabManager) { - return; // Not yet supported on Android. - } - let {gBrowser} = this.xulBrowser.ownerGlobal; - let tab = gBrowser && gBrowser.getTabForBrowser(this.xulBrowser); - return tab && apiManager.global.TabManager.getId(tab); - } - - onBrowserChange(browser) { - super.onBrowserChange(browser); - this.xulBrowser = browser; - } - - shutdown() { - apiManager.emit("page-shutdown", this); - super.shutdown(); - } -} - -ParentAPIManager = { - proxyContexts: new Map(), - - init() { - Services.obs.addObserver(this, "message-manager-close", false); - - Services.mm.addMessageListener("API:CreateProxyContext", this); - Services.mm.addMessageListener("API:CloseProxyContext", this, true); - Services.mm.addMessageListener("API:Call", this); - Services.mm.addMessageListener("API:AddListener", this); - Services.mm.addMessageListener("API:RemoveListener", this); - }, - - observe(subject, topic, data) { - if (topic === "message-manager-close") { - let mm = subject; - for (let [childId, context] of this.proxyContexts) { - if (context.parentMessageManager === mm) { - this.closeProxyContext(childId); - } - } - } - }, - - shutdownExtension(extensionId) { - for (let [childId, context] of this.proxyContexts) { - if (context.extension.id == extensionId) { - context.shutdown(); - this.proxyContexts.delete(childId); - } - } - }, - - receiveMessage({name, data, target}) { - switch (name) { - case "API:CreateProxyContext": - this.createProxyContext(data, target); - break; - - case "API:CloseProxyContext": - this.closeProxyContext(data.childId); - break; - - case "API:Call": - this.call(data, target); - break; - - case "API:AddListener": - this.addListener(data, target); - break; - - case "API:RemoveListener": - this.removeListener(data); - break; - } - }, - - createProxyContext(data, target) { - let {envType, extensionId, childId, principal} = data; - if (this.proxyContexts.has(childId)) { - throw new Error("A WebExtension context with the given ID already exists!"); - } - - let extension = GlobalManager.getExtension(extensionId); - if (!extension) { - throw new Error(`No WebExtension found with ID ${extensionId}`); - } - - let context; - if (envType == "addon_parent") { - // Privileged addon contexts can only be loaded in documents whose main - // frame is also the same addon. - if (principal.URI.prePath !== extension.baseURI.prePath || - !target.contentPrincipal.subsumes(principal)) { - throw new Error(`Refused to create privileged WebExtension context for ${principal.URI.spec}`); - } - context = new ExtensionPageContextParent(envType, extension, data, target); - } else if (envType == "content_parent") { - context = new ContentScriptContextParent(envType, extension, data, target, principal); - } else { - throw new Error(`Invalid WebExtension context envType: ${envType}`); - } - this.proxyContexts.set(childId, context); - }, - - closeProxyContext(childId) { - let context = this.proxyContexts.get(childId); - if (context) { - context.unload(); - this.proxyContexts.delete(childId); - } - }, - - call(data, target) { - let context = this.getContextById(data.childId); - if (context.parentMessageManager !== target.messageManager) { - throw new Error("Got message on unexpected message manager"); - } - - let reply = result => { - if (!context.parentMessageManager) { - Cu.reportError("Cannot send function call result: other side closed connection"); - return; - } - - context.parentMessageManager.sendAsyncMessage( - "API:CallResult", - Object.assign({ - childId: data.childId, - callId: data.callId, - }, result)); - }; - - try { - let args = Cu.cloneInto(data.args, context.sandbox); - let result = findPathInObject(context.apiObj, data.path)(...args); - - if (data.callId) { - result = result || Promise.resolve(); - - result.then(result => { - result = result instanceof SpreadArgs ? [...result] : [result]; - - reply({result}); - }, error => { - error = context.normalizeError(error); - reply({error: {message: error.message}}); - }); - } - } catch (e) { - if (data.callId) { - let error = context.normalizeError(e); - reply({error: {message: error.message}}); - } else { - Cu.reportError(e); - } - } - }, - - addListener(data, target) { - let context = this.getContextById(data.childId); - if (context.parentMessageManager !== target.messageManager) { - throw new Error("Got message on unexpected message manager"); - } - - let {childId} = data; - - function listener(...listenerArgs) { - return context.sendMessage( - context.parentMessageManager, - "API:RunListener", - { - childId, - listenerId: data.listenerId, - path: data.path, - args: listenerArgs, - }, - { - recipient: {childId}, - }); - } - - context.listenerProxies.set(data.listenerId, listener); - - let args = Cu.cloneInto(data.args, context.sandbox); - findPathInObject(context.apiObj, data.path).addListener(listener, ...args); - }, - - removeListener(data) { - let context = this.getContextById(data.childId); - let listener = context.listenerProxies.get(data.listenerId); - findPathInObject(context.apiObj, data.path).removeListener(listener); - }, - - getContextById(childId) { - let context = this.proxyContexts.get(childId); - if (!context) { - let error = new Error("WebExtension context not found!"); - Cu.reportError(error); - throw error; - } - return context; - }, -}; - -ParentAPIManager.init(); - - -const ExtensionParent = { - GlobalManager, - ParentAPIManager, - apiManager, -}; |