summaryrefslogtreecommitdiffstats
path: root/toolkit/components/webextensions/ExtensionParent.jsm
diff options
context:
space:
mode:
authorwolfbeast <mcwerewolf@wolfbeast.com>2019-04-05 20:01:10 +0200
committerwolfbeast <mcwerewolf@wolfbeast.com>2019-04-05 20:01:10 +0200
commitc3b63b831cd2c64700e875b28540212c7c881ac6 (patch)
treeedd98fcbd2004d3b562904f822bf6c3322fc7f52 /toolkit/components/webextensions/ExtensionParent.jsm
parentd432e068a21c815d5d5e7bcbc1cc8c6e77a7d1e0 (diff)
parentcc07da9cb4d6e7a53f8d953427ffc2bca2e0c2df (diff)
downloadUXP-c3b63b831cd2c64700e875b28540212c7c881ac6.tar
UXP-c3b63b831cd2c64700e875b28540212c7c881ac6.tar.gz
UXP-c3b63b831cd2c64700e875b28540212c7c881ac6.tar.lz
UXP-c3b63b831cd2c64700e875b28540212c7c881ac6.tar.xz
UXP-c3b63b831cd2c64700e875b28540212c7c881ac6.zip
Merge branch 'master' into 816
Diffstat (limited to 'toolkit/components/webextensions/ExtensionParent.jsm')
-rw-r--r--toolkit/components/webextensions/ExtensionParent.jsm551
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,
-};