essaging; contentContext.callOnClose(this); } close() { super.close(); this.contentContext = null; this.sandbox = null; } async logActivity(type, name, data) { return this.contentContext.logActivity(type, name, data); } get cloneScope() { return this.sandbox; } #forwardGetterToOwnerContext(name) { Object.defineProperty(this, name, { configurable: true, enumerable: true, get() { return this.contentContext[name]; }, }); } get browserObj() { const browser = {}; // The set of APIs exposed to user scripts is minimal. For simplicity and // minimizing overhead, we do not use Schemas-generated bindings. const wrapF = func => { return (...args) => { try { return func.apply(this, args); } catch (e) { throw this.normalizeError(e); } }; }; if (this.enableMessaging) { browser.runtime = {}; browser.runtime.connect = wrapF(this.runtimeConnect); browser.runtime.sendMessage = wrapF(this.runtimeSendMessage); } const value = Cu.cloneInto(browser, this.sandbox, { cloneFunctions: true }); return redefineGetter(this, "browserObj", value); } runtimeConnect(...args) { args = this.#schemaCheckParameters("runtime", "connect", args); let [extensionId, options] = args; if (extensionId !== null) { throw new ExtensionError("extensionId is not supported"); } let name = options?.name ?? ""; return this.contentContext.messenger.connect({ context: this, userScriptWorldId: this.worldId, name, }); } runtimeSendMessage(...args) { // Simplified version of parseBonkersArgs in child/ext-runtime.js let callback = typeof args[args.length - 1] === "function" && args.pop(); // The extensionId and options parameters are an optional part of the // runtime.sendMessage() interface, but not supported in user scripts: // runtime.sendMessage() will only trigger runtime.onUserScriptMessage and // never runtime.onMessage nor runtime.onMessageExternal. if (!args.length) { throw new ExtensionError( "runtime.sendMessage's message argument is missing" ); } else if (args.length > 1) { throw new ExtensionError( "runtime.sendMessage received too many arguments" ); } let [message] = args; return this.contentContext.messenger.sendRuntimeMessage({ context: this, userScriptWorldId: this.worldId, message, callback, }); } #schemaCheckParameters(namespace, method, args) { let ns = this.contentContext.childManager.schema.getNamespace(namespace); let schemaContext = lazy.Schemas.paramsValidationContexts.get(this); return ns.get(method).checkParameters(args, schemaContext); } } class WorldCollection { /** @type {Map} */ static allByContext = new DefaultMap(context => new WorldCollection(context)); /** @type {Map} */ sandboxes = new DefaultMap(worldId => this.newSandbox(worldId)); /** * Retrieve a Sandbox for the given context and worldId. May throw if the * context has unloaded. * * @param {ContentScriptContextChild} context * Context that wraps the document where the scripts should execute. * The context keeps track of the document & extension lifetime. * @param {string} worldId * The identifier of the userScript world. * @returns {Sandbox} */ static sandboxFor(context, worldId) { return WorldCollection.allByContext.get(context).sandboxes.get(worldId); } constructor(context) { if (context.unloaded) { throw new Error("Cannot create user script world after context unloaded"); } this.context = context; this.configHolder = WorldConfigHolder.allMaps.get(context.extension); context.callOnClose(this); } close() { WorldCollection.allByContext.delete(this.context); for (let sandbox of this.sandboxes.values()) { Cu.nukeSandbox(sandbox); } this.sandboxes.clear(); } newSandbox(worldId) { let contentWindow = this.context.contentWindow; let docPrincipal = contentWindow.document.nodePrincipal; let policy = this.context.extension.policy; if (docPrincipal.isSystemPrincipal) { throw new Error("User scripts are not supported in system principals"); } let sandbox = Cu.Sandbox([docPrincipal], { metadata: { "inner-window-id": this.context.innerWindowID, addonId: policy.id, }, sandboxName: `User script world ${worldId} for ${policy.debugName}`, sandboxPrototype: contentWindow, sandboxContentSecurityPolicy: this.configHolder.getCSPForWorldId(worldId), sameZoneAs: contentWindow, wantXrays: true, isWebExtensionContentScript: true, wantExportHelpers: true, originAttributes: docPrincipal.originAttributes, }); let messaging = this.configHolder.isMessagingEnabledForWorldId(worldId); if (messaging) { let userScriptContext = new UserScriptContext( this.context, sandbox, worldId, messaging ); const getBrowserObj = () => userScriptContext.browserObj; lazy.Schemas.exportLazyGetter(sandbox, "browser", getBrowserObj); lazy.Schemas.exportLazyGetter(sandbox, "chrome", getBrowserObj); } return sandbox; } } export const ExtensionUserScriptsContent = { sandboxFor(context, worldId) { return WorldCollection.sandboxFor(context, worldId); }, updateWorldConfig(extension, reset, update) { let configHolder = WorldConfigHolder.allMaps.get(extension); if (reset) { for (let worldId of reset) { configHolder.resetWorldConfiguration(worldId); } } if (update) { for (let properties of update) { configHolder.configureWorld(properties); } } }, }; PK