/* 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}`); } };