mirror of
https://repo.dactyloidae.xyz/Dactyloidae/UXP.git
synced 2026-09-10 02:08:38 +09:00
Rename Toolkit's webextensions component directory to better reflect what it is.
This commit is contained in:
parent
6955450f7a
commit
d7d16e7fbb
242 changed files with 4 additions and 2 deletions
|
|
@ -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,
|
||||
};
|
||||
Loading…
Add table
Add a link
Reference in a new issue