diff options
Diffstat (limited to 'toolkit/components/extensions/child/ext-runtime.js')
-rw-r--r-- | toolkit/components/extensions/child/ext-runtime.js | 143 |
1 files changed, 143 insertions, 0 deletions
diff --git a/toolkit/components/extensions/child/ext-runtime.js b/toolkit/components/extensions/child/ext-runtime.js new file mode 100644 index 0000000000..8cf5c445e3 --- /dev/null +++ b/toolkit/components/extensions/child/ext-runtime.js @@ -0,0 +1,143 @@ +/* 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"; + +ChromeUtils.defineESModuleGetters(this, { + WebNavigationFrames: "resource://gre/modules/WebNavigationFrames.sys.mjs", +}); + +/* eslint-disable jsdoc/check-param-names */ +/** + * With optional arguments on both ends, this case is ambiguous: + * runtime.sendMessage("string", {} or nullish) + * + * Sending a message within the extension is more common than sending + * an empty object to another extension, so we prefer that conclusion. + * + * @param {string?} [extensionId] + * @param {any} message + * @param {object?} [options] + * @param {Function} [callback] + * @returns {{extensionId: string?, message: any, callback: Function?}} + */ +/* eslint-enable jsdoc/check-param-names */ +function parseBonkersArgs(...args) { + let Error = ExtensionUtils.ExtensionError; + let callback = typeof args[args.length - 1] === "function" && args.pop(); + + // We don't support any options anymore, so only an empty object is valid. + function validOptions(v) { + return v == null || (typeof v === "object" && !Object.keys(v).length); + } + + if (args.length === 1 || (args.length === 2 && validOptions(args[1]))) { + // Interpret as passing null for extensionId (message within extension). + args.unshift(null); + } + let [extensionId, message, options] = args; + + if (!args.length) { + throw new Error("runtime.sendMessage's message argument is missing"); + } else if (!validOptions(options)) { + throw new Error("runtime.sendMessage's options argument is invalid"); + } else if (args.length === 4 && args[3] && !callback) { + throw new Error("runtime.sendMessage's last argument is not a function"); + } else if (args[3] != null || args.length > 4) { + throw new Error("runtime.sendMessage received too many arguments"); + } else if (extensionId && typeof extensionId !== "string") { + throw new Error("runtime.sendMessage's extensionId argument is invalid"); + } + return { extensionId, message, callback }; +} + +this.runtime = class extends ExtensionAPI { + getAPI(context) { + let { extension } = context; + + return { + runtime: { + onConnect: context.messenger.onConnect.api(), + onMessage: context.messenger.onMessage.api(), + + onConnectExternal: context.messenger.onConnectEx.api(), + onMessageExternal: context.messenger.onMessageEx.api(), + + connect(extensionId, options) { + let name = options?.name ?? ""; + return context.messenger.connect({ name, extensionId }); + }, + + sendMessage(...args) { + let arg = parseBonkersArgs(...args); + return context.messenger.sendRuntimeMessage(arg); + }, + + connectNative(name) { + return context.messenger.connect({ name, native: true }); + }, + + sendNativeMessage(nativeApp, message) { + return context.messenger.sendNativeMessage(nativeApp, message); + }, + + get lastError() { + return context.lastError; + }, + + getManifest() { + return Cu.cloneInto(extension.manifest, context.cloneScope); + }, + + id: extension.id, + + getURL(url) { + return extension.baseURI.resolve(url); + }, + + getFrameId(target) { + let frameId = WebNavigationFrames.getFromWindow(target); + if (frameId >= 0) { + return frameId; + } + // Not a WindowProxy, perhaps an embedder element? + + let type; + try { + type = Cu.getClassName(target, true); + } catch (e) { + // Not a valid object, will throw below. + } + + const embedderTypes = [ + "HTMLIFrameElement", + "HTMLFrameElement", + "HTMLEmbedElement", + "HTMLObjectElement", + ]; + + if (embedderTypes.includes(type)) { + if (!target.browsingContext) { + return -1; + } + return WebNavigationFrames.getFrameId(target.browsingContext); + } + + throw new ExtensionUtils.ExtensionError("Invalid argument"); + }, + }, + }; + } + + getAPIObjectForRequest(context, request) { + if (request.apiObjectType === "Port") { + const port = context.messenger.getPortById(request.apiObjectId); + if (!port) { + throw new Error(`Port API object not found: ${request}`); + } + return port.api; + } + + throw new Error(`Unexpected apiObjectType: ${request}`); + } +}; |