816 lines
25 KiB
JavaScript
816 lines
25 KiB
JavaScript
/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
|
|
/* vim: set sts=2 sw=2 et tw=80: */
|
|
/* 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/. */
|
|
|
|
/**
|
|
* This file handles extension background service worker logic that runs in the
|
|
* child process.
|
|
*/
|
|
|
|
import {
|
|
ChildAPIManager,
|
|
ChildLocalAPIImplementation,
|
|
ExtensionActivityLogChild,
|
|
MessageEvent,
|
|
Messenger,
|
|
Port,
|
|
ProxyAPIImplementation,
|
|
SimpleEventAPI,
|
|
} from "resource://gre/modules/ExtensionChild.sys.mjs";
|
|
|
|
import { ExtensionCommon } from "resource://gre/modules/ExtensionCommon.sys.mjs";
|
|
import {
|
|
ExtensionPageChild,
|
|
getContextChildManagerGetter,
|
|
} from "resource://gre/modules/ExtensionPageChild.sys.mjs";
|
|
import {
|
|
ExtensionUtils,
|
|
WorkerExtensionError,
|
|
} from "resource://gre/modules/ExtensionUtils.sys.mjs";
|
|
|
|
const { BaseContext, redefineGetter } = ExtensionCommon;
|
|
|
|
const { DefaultMap, getUniqueId } = ExtensionUtils;
|
|
|
|
/**
|
|
* SimpleEventAPI subclass specialized for the worker port events
|
|
* used by WorkerMessenger.
|
|
*/
|
|
class WorkerRuntimePortEvent extends SimpleEventAPI {
|
|
api() {
|
|
return {
|
|
...super.api(),
|
|
createListenerForAPIRequest: (...args) =>
|
|
this.createListenerForAPIRequest(...args),
|
|
};
|
|
}
|
|
|
|
createListenerForAPIRequest(request) {
|
|
const { eventListener } = request;
|
|
return function (port, ...args) {
|
|
return eventListener.callListener(args, {
|
|
apiObjectType: Ci.mozIExtensionListenerCallOptions.RUNTIME_PORT,
|
|
apiObjectDescriptor: { portId: port.portId, name: port.name },
|
|
});
|
|
};
|
|
}
|
|
}
|
|
|
|
/**
|
|
* SimpleEventAPI subclass specialized for the worker runtime messaging events
|
|
* used by WorkerMessenger.
|
|
*/
|
|
class WorkerMessageEvent extends MessageEvent {
|
|
api() {
|
|
return {
|
|
...super.api(),
|
|
createListenerForAPIRequest: (...args) =>
|
|
this.createListenerForAPIRequest(...args),
|
|
};
|
|
}
|
|
|
|
createListenerForAPIRequest(request) {
|
|
const { eventListener } = request;
|
|
return function (message, sender) {
|
|
return eventListener.callListener([message, sender], {
|
|
eventListenerType:
|
|
Ci.mozIExtensionListenerCallOptions.CALLBACK_SEND_RESPONSE,
|
|
});
|
|
};
|
|
}
|
|
}
|
|
|
|
/**
|
|
* MessageEvent subclass specialized for the worker's port API events
|
|
* used by WorkerPort.
|
|
*/
|
|
class WorkerPortEvent extends SimpleEventAPI {
|
|
api() {
|
|
return {
|
|
...super.api(),
|
|
createListenerForAPIRequest: (...args) =>
|
|
this.createListenerForAPIRequest(...args),
|
|
};
|
|
}
|
|
|
|
createListenerForAPIRequest(request) {
|
|
const { eventListener } = request;
|
|
switch (this.name) {
|
|
case "Port.onDisconnect":
|
|
return function (port) {
|
|
eventListener.callListener([], {
|
|
apiObjectType: Ci.mozIExtensionListenerCallOptions.RUNTIME_PORT,
|
|
apiObjectDescriptor: {
|
|
portId: port.portId,
|
|
name: port.name,
|
|
},
|
|
});
|
|
};
|
|
case "Port.onMessage":
|
|
return function (message, port) {
|
|
eventListener.callListener([message], {
|
|
apiObjectType: Ci.mozIExtensionListenerCallOptions.RUNTIME_PORT,
|
|
apiObjectDescriptor: {
|
|
portId: port.portId,
|
|
name: port.name,
|
|
},
|
|
});
|
|
};
|
|
}
|
|
return undefined;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Port subclass specialized for the workers and used by WorkerMessager.
|
|
*/
|
|
class WorkerPort extends Port {
|
|
constructor(context, portId, name, native, sender) {
|
|
const { viewType, contextId } = context;
|
|
if (viewType !== "background_worker") {
|
|
throw new Error(
|
|
`Unexpected viewType "${viewType}" on context ${contextId}`
|
|
);
|
|
}
|
|
|
|
super(context, portId, name, native, sender);
|
|
this.portId = portId;
|
|
}
|
|
|
|
initEventManagers() {
|
|
const { context } = this;
|
|
this.onMessage = new WorkerPortEvent(context, "Port.onMessage");
|
|
this.onDisconnect = new WorkerPortEvent(context, "Port.onDisconnect");
|
|
}
|
|
|
|
getAPI() {
|
|
const api = super.getAPI();
|
|
// Add the portId to the API object, needed by the WorkerMessenger
|
|
// to retrieve the port given the apiObjectId part of the
|
|
// mozIExtensionAPIRequest sent from the ExtensionPort webidl.
|
|
api.portId = this.portId;
|
|
return api;
|
|
}
|
|
|
|
get api() {
|
|
// No need to clone this for the worker, it's on a separate JSRuntime.
|
|
return redefineGetter(this, "api", this.getAPI());
|
|
}
|
|
}
|
|
|
|
/**
|
|
* A Messenger subclass specialized for the background service worker.
|
|
*/
|
|
class WorkerMessenger extends Messenger {
|
|
constructor(context) {
|
|
const { viewType, contextId } = context;
|
|
if (viewType !== "background_worker") {
|
|
throw new Error(
|
|
`Unexpected viewType "${viewType}" on context ${contextId}`
|
|
);
|
|
}
|
|
|
|
super(context);
|
|
|
|
// Used by WebIDL API requests to get a port instance given the apiObjectId
|
|
// received in the API request coming from the ExtensionPort instance
|
|
// returned in the thread where the request was originating from.
|
|
this.portsById = new Map();
|
|
this.context.callOnClose(this);
|
|
}
|
|
|
|
initEventManagers() {
|
|
const { context } = this;
|
|
this.onConnect = new WorkerRuntimePortEvent(context, "runtime.onConnect");
|
|
this.onConnectEx = new WorkerRuntimePortEvent(
|
|
context,
|
|
"runtime.onConnectExternal"
|
|
);
|
|
this.onMessage = new WorkerMessageEvent(this.context, "runtime.onMessage");
|
|
this.onMessageEx = new WorkerMessageEvent(
|
|
context,
|
|
"runtime.onMessageExternal"
|
|
);
|
|
}
|
|
|
|
close() {
|
|
this.portsById.clear();
|
|
}
|
|
|
|
getPortById(portId) {
|
|
return this.portsById.get(portId);
|
|
}
|
|
|
|
/**
|
|
* @typedef {object} ExtensionPortDescriptor
|
|
* https://phabricator.services.mozilla.com/D196385?id=801874#inline-1093734
|
|
*
|
|
* @returns {ExtensionPortDescriptor}
|
|
*/
|
|
connect({ name, native = false, ...args }) {
|
|
let portId = getUniqueId();
|
|
let port = new WorkerPort(this.context, portId, name, !!native);
|
|
this.conduit
|
|
.queryPortConnect({ portId, name, native, ...args })
|
|
.catch(error => port.recvPortDisconnect({ error }));
|
|
this.portsById.set(`${portId}`, port);
|
|
// Extension worker calls this method through the WebIDL bindings,
|
|
// and the Port instance returned by the runtime.connect/connectNative
|
|
// methods will be an instance of ExtensionPort webidl interface based
|
|
// on the ExtensionPortDescriptor dictionary returned by this method.
|
|
return { portId, name };
|
|
}
|
|
|
|
recvPortConnect({ extensionId, portId, name, sender }) {
|
|
let event = sender.id === extensionId ? this.onConnect : this.onConnectEx;
|
|
if (this.context.active && event.fires.size) {
|
|
let port = new WorkerPort(this.context, portId, name, false, sender);
|
|
this.portsById.set(`${port.portId}`, port);
|
|
return event.emit(port).length;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* APIImplementation subclass specialized for handling mozIExtensionAPIRequests
|
|
* originated from webidl bindings.
|
|
*
|
|
* Provides a createListenerForAPIRequest method which is used by
|
|
* WebIDLChildAPIManager to retrieve an API event specific wrapper
|
|
* for the mozIExtensionEventListener for the API events that needs
|
|
* special handling (e.g. runtime.onConnect).
|
|
*
|
|
* createListenerForAPIRequest delegates to the API event the creation
|
|
* of the special event listener wrappers, the EventManager api objects
|
|
* for the events that needs special wrapper are expected to provide
|
|
* a method with the same name.
|
|
*/
|
|
class ChildLocalWebIDLAPIImplementation extends ChildLocalAPIImplementation {
|
|
constructor(pathObj, namespace, name, childApiManager) {
|
|
super(pathObj, namespace, name, childApiManager);
|
|
this.childApiManager = childApiManager;
|
|
}
|
|
|
|
createListenerForAPIRequest(request) {
|
|
return this.pathObj[this.name].createListenerForAPIRequest?.(request);
|
|
}
|
|
|
|
setProperty() {
|
|
// mozIExtensionAPIRequest doesn't support this requestType at the moment,
|
|
// setting a pref would just replace the previous value on the wrapper
|
|
// object living in the owner thread.
|
|
// To be implemented if we have an actual use case where that is needed.
|
|
throw new Error("Unexpected call to setProperty");
|
|
}
|
|
|
|
hasListener() {
|
|
// hasListener is implemented in C++ by ExtensionEventManager, and so
|
|
// a call to this method is unexpected.
|
|
throw new Error("Unexpected call to hasListener");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* APIImplementation subclass specialized for handling API requests related
|
|
* to an API Object type.
|
|
*
|
|
* Retrieving the apiObject instance is delegated internally to the
|
|
* ExtensionAPI subclass that implements the request apiNamespace,
|
|
* through an optional getAPIObjectForRequest method expected to be
|
|
* available on the ExtensionAPI class.
|
|
*/
|
|
class ChildWebIDLObjectTypeImplementation extends ChildLocalWebIDLAPIImplementation {
|
|
constructor(request, childApiManager) {
|
|
const { apiNamespace, apiName, apiObjectType, apiObjectId } = request;
|
|
const api = childApiManager.getExtensionAPIInstance(apiNamespace);
|
|
const pathObj = api.getAPIObjectForRequest?.(
|
|
childApiManager.context,
|
|
request
|
|
);
|
|
if (!pathObj) {
|
|
throw new Error(`apiObject instance not found for ${request}`);
|
|
}
|
|
super(pathObj, apiNamespace, apiName, childApiManager);
|
|
this.fullname = `${apiNamespace}.${apiObjectType}(${apiObjectId}).${apiName}`;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* A ChildAPIManager subclass specialized for handling mozIExtensionAPIRequest
|
|
* originated from the WebIDL bindings.
|
|
*
|
|
* Currently used only for the extension contexts related to the background
|
|
* service worker.
|
|
*/
|
|
class WebIDLChildAPIManager extends ChildAPIManager {
|
|
constructor(...args) {
|
|
super(...args);
|
|
// Map<apiPathToEventString, WeakMap<nsIExtensionEventListener, Function>>
|
|
//
|
|
// apiPathToEventString is a string that represents the full API path
|
|
// related to the event name (e.g. "runtime.onConnect", or "runtime.Port.onMessage")
|
|
this.eventListenerWrappers = new DefaultMap(() => new WeakMap());
|
|
}
|
|
|
|
getImplementation(namespace, name) {
|
|
this.apiCan.findAPIPath(`${namespace}.${name}`);
|
|
let obj = this.apiCan.findAPIPath(namespace);
|
|
|
|
if (obj && name in obj) {
|
|
return new ChildLocalWebIDLAPIImplementation(obj, namespace, name, this);
|
|
}
|
|
|
|
return this.getFallbackImplementation(namespace, name);
|
|
}
|
|
|
|
getImplementationForRequest(request) {
|
|
const { apiNamespace, apiName, apiObjectType } = request;
|
|
if (apiObjectType) {
|
|
return new ChildWebIDLObjectTypeImplementation(request, this);
|
|
}
|
|
return this.getImplementation(apiNamespace, apiName);
|
|
}
|
|
|
|
/**
|
|
* Handles an ExtensionAPIRequest originated by the Extension APIs WebIDL bindings.
|
|
*
|
|
* @param {mozIExtensionAPIRequest} request
|
|
* The object that represents the API request received
|
|
* (including arguments, an event listener wrapper etc)
|
|
*
|
|
* @returns {Partial<mozIExtensionAPIRequestResult>}
|
|
* Result for the API request, either a value to be returned
|
|
* (which has to be a value that can be structure cloned
|
|
* if the request was originated from the worker thread) or
|
|
* an error to raise to the extension code.
|
|
*/
|
|
handleWebIDLAPIRequest(request) {
|
|
try {
|
|
const impl = this.getImplementationForRequest(request);
|
|
let result;
|
|
this.context.withAPIRequest(request, () => {
|
|
if (impl instanceof ProxyAPIImplementation) {
|
|
result = this.handleForProxyAPIImplementation(request, impl);
|
|
} else {
|
|
result = this.callAPIImplementation(request, impl);
|
|
}
|
|
});
|
|
|
|
return {
|
|
type: Ci.mozIExtensionAPIRequestResult.RETURN_VALUE,
|
|
value: result,
|
|
};
|
|
} catch (error) {
|
|
return this.handleExtensionError(error);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Convert an error raised while handling an API request,
|
|
* into the expected mozIExtensionAPIRequestResult.
|
|
*
|
|
* @param {Error | WorkerExtensionError} error
|
|
* @returns {Partial<mozIExtensionAPIRequestResult>}
|
|
*/
|
|
handleExtensionError(error) {
|
|
// Propagate an extension error to the caller on the worker thread.
|
|
if (error instanceof this.context.Error) {
|
|
return {
|
|
type: Ci.mozIExtensionAPIRequestResult.EXTENSION_ERROR,
|
|
value: error,
|
|
};
|
|
}
|
|
|
|
// Otherwise just log it and throw a generic error.
|
|
Cu.reportError(error);
|
|
return {
|
|
type: Ci.mozIExtensionAPIRequestResult.EXTENSION_ERROR,
|
|
value: new this.context.Error("An unexpected error occurred"),
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Handle the given mozIExtensionAPIRequest using the given
|
|
* APIImplementation instance.
|
|
*
|
|
* @param {mozIExtensionAPIRequest} request
|
|
* @param {ChildLocalWebIDLAPIImplementation | ProxyAPIImplementation} impl
|
|
* @returns {any}
|
|
* @throws {Error | WorkerExtensionError}
|
|
*/
|
|
callAPIImplementation(request, impl) {
|
|
const { requestType, normalizedArgs } = request;
|
|
|
|
switch (requestType) {
|
|
// TODO (Bug 1728328): follow up to take callAsyncFunction requireUserInput
|
|
// parameter into account (until then callAsyncFunction, callFunction
|
|
// and callFunctionNoReturn calls do not differ yet).
|
|
case "callAsyncFunction":
|
|
case "callFunction":
|
|
case "callFunctionNoReturn":
|
|
case "getProperty":
|
|
return impl[requestType](normalizedArgs);
|
|
case "addListener": {
|
|
const listener = this.getOrCreateListenerWrapper(request, impl);
|
|
impl.addListener(listener, normalizedArgs);
|
|
|
|
return undefined;
|
|
}
|
|
case "removeListener": {
|
|
const listener = this.getListenerWrapper(request);
|
|
if (listener) {
|
|
// Remove the previously added listener and forget the cleanup
|
|
// observer previously passed to context.callOnClose.
|
|
listener._callOnClose.close();
|
|
this.context.forgetOnClose(listener._callOnClose);
|
|
this.forgetListenerWrapper(request);
|
|
}
|
|
return undefined;
|
|
}
|
|
default:
|
|
throw new Error(
|
|
`Unexpected requestType ${requestType} while handling "${request}"`
|
|
);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Handle the given mozIExtensionAPIRequest using the given
|
|
* ProxyAPIImplementation instance.
|
|
*
|
|
* @param {mozIExtensionAPIRequest} request
|
|
* @param {ProxyAPIImplementation} impl
|
|
* @returns {any}
|
|
* @throws {Error | WorkerExtensionError}
|
|
*/
|
|
handleForProxyAPIImplementation(request, impl) {
|
|
const { requestType } = request;
|
|
switch (requestType) {
|
|
case "callAsyncFunction":
|
|
case "callFunctionNoReturn":
|
|
case "addListener":
|
|
case "removeListener":
|
|
return this.callAPIImplementation(request, impl);
|
|
default:
|
|
// Any other request types (e.g. getProperty or callFunction) are
|
|
// unexpected and so we raise a more detailed error to be logged
|
|
// on the browser console (while the extension will receive the
|
|
// generic "An unexpected error occurred" one).
|
|
throw new Error(
|
|
`Unexpected requestType ${requestType} while handling "${request}"`
|
|
);
|
|
}
|
|
}
|
|
|
|
getAPIPathForWebIDLRequest(request) {
|
|
const { apiNamespace, apiName, apiObjectType } = request;
|
|
if (apiObjectType) {
|
|
return `${apiNamespace}.${apiObjectType}.${apiName}`;
|
|
}
|
|
|
|
return `${apiNamespace}.${apiName}`;
|
|
}
|
|
|
|
/**
|
|
* Return an ExtensionAPI class instance given its namespace.
|
|
*
|
|
* @param {string} namespace
|
|
* @returns {import("ExtensionCommon.sys.mjs").ExtensionAPI}
|
|
*/
|
|
getExtensionAPIInstance(namespace) {
|
|
return this.apiCan.apis.get(namespace);
|
|
}
|
|
|
|
getOrCreateListenerWrapper(request, impl) {
|
|
let listener = this.getListenerWrapper(request);
|
|
if (listener) {
|
|
return listener;
|
|
}
|
|
|
|
// Look for special wrappers that are needed for some API events
|
|
// (e.g. runtime.onMessage/onConnect/...).
|
|
if (impl instanceof ChildLocalWebIDLAPIImplementation) {
|
|
listener = impl.createListenerForAPIRequest(request);
|
|
}
|
|
|
|
const { eventListener } = request;
|
|
listener =
|
|
listener ??
|
|
function (...args) {
|
|
// Default wrapper just forwards all the arguments to the
|
|
// extension callback (all arguments has to be structure cloneable
|
|
// if the extension callback is on the worker thread).
|
|
eventListener.callListener(args);
|
|
};
|
|
listener._callOnClose = {
|
|
close: () => {
|
|
this.eventListenerWrappers.delete(eventListener);
|
|
// Failing to send the request to remove the listener in the parent
|
|
// process shouldn't prevent the extension or context shutdown,
|
|
// otherwise we would leak a WebExtensionPolicy instance.
|
|
try {
|
|
impl.removeListener(listener);
|
|
} catch (err) {
|
|
// Removing a listener when the extension context is being closed can
|
|
// fail if the API is proxied to the parent process and the conduit
|
|
// has been already closed, and so we ignore the error if we are not
|
|
// processing a call proxied to the parent process.
|
|
if (impl instanceof ChildLocalWebIDLAPIImplementation) {
|
|
Cu.reportError(err);
|
|
}
|
|
}
|
|
},
|
|
};
|
|
this.storeListenerWrapper(request, listener);
|
|
this.context.callOnClose(listener._callOnClose);
|
|
return listener;
|
|
}
|
|
|
|
getListenerWrapper(request) {
|
|
const { eventListener } = request;
|
|
if (!(eventListener instanceof Ci.mozIExtensionEventListener)) {
|
|
throw new Error(`Unexpected eventListener type for request: ${request}`);
|
|
}
|
|
const apiPath = this.getAPIPathForWebIDLRequest(request);
|
|
if (!this.eventListenerWrappers.has(apiPath)) {
|
|
return undefined;
|
|
}
|
|
return this.eventListenerWrappers.get(apiPath).get(eventListener);
|
|
}
|
|
|
|
storeListenerWrapper(request, listener) {
|
|
const { eventListener } = request;
|
|
if (!(eventListener instanceof Ci.mozIExtensionEventListener)) {
|
|
throw new Error(`Missing eventListener for request: ${request}`);
|
|
}
|
|
const apiPath = this.getAPIPathForWebIDLRequest(request);
|
|
this.eventListenerWrappers.get(apiPath).set(eventListener, listener);
|
|
}
|
|
|
|
forgetListenerWrapper(request) {
|
|
const { eventListener } = request;
|
|
if (!(eventListener instanceof Ci.mozIExtensionEventListener)) {
|
|
throw new Error(`Missing eventListener for request: ${request}`);
|
|
}
|
|
const apiPath = this.getAPIPathForWebIDLRequest(request);
|
|
if (this.eventListenerWrappers.has(apiPath)) {
|
|
this.eventListenerWrappers.get(apiPath).delete(eventListener);
|
|
}
|
|
}
|
|
}
|
|
|
|
class WorkerContextChild extends BaseContext {
|
|
/**
|
|
* This WorkerContextChild represents an addon execution environment
|
|
* that is running on the worker thread in an extension child process.
|
|
*
|
|
* @param {ExtensionChild} extension This context's owner.
|
|
* @param {object} params
|
|
* @param {mozIExtensionServiceWorkerInfo} params.serviceWorkerInfo
|
|
*/
|
|
constructor(extension, { serviceWorkerInfo }) {
|
|
if (
|
|
!serviceWorkerInfo?.scriptURL ||
|
|
!serviceWorkerInfo?.clientInfoId ||
|
|
!serviceWorkerInfo?.principal
|
|
) {
|
|
throw new Error("Missing or invalid serviceWorkerInfo");
|
|
}
|
|
|
|
super("addon_child", extension);
|
|
this.viewType = "background_worker";
|
|
this.uri = Services.io.newURI(serviceWorkerInfo.scriptURL);
|
|
this.workerClientInfoId = serviceWorkerInfo.clientInfoId;
|
|
this.workerDescriptorId = serviceWorkerInfo.descriptorId;
|
|
this.workerPrincipal = serviceWorkerInfo.principal;
|
|
this.incognito = serviceWorkerInfo.principal.privateBrowsingId > 0;
|
|
|
|
// A mozIExtensionAPIRequest being processed (set by the withAPIRequest
|
|
// method while executing a given callable, can be optionally used by
|
|
// the API implementation methods to access the mozIExtensionAPIRequest
|
|
// being processed and customize their result if necessary to handle
|
|
// requests originated by the webidl bindings).
|
|
this.webidlAPIRequest = null;
|
|
|
|
// This context uses a plain object as a cloneScope (anyway the values
|
|
// moved across thread are going to be automatically serialized/deserialized
|
|
// as structure clone data, we may remove this if we are changing the
|
|
// internals to not use the context.cloneScope).
|
|
this.workerCloneScope = {
|
|
Promise,
|
|
// The instances of this Error constructor will be recognized by the
|
|
// ExtensionAPIRequestHandler as errors that should be propagated to
|
|
// the worker thread and received by extension code that originated
|
|
// the API request.
|
|
Error: WorkerExtensionError,
|
|
};
|
|
}
|
|
|
|
getCreateProxyContextData() {
|
|
const { workerDescriptorId } = this;
|
|
return { workerDescriptorId };
|
|
}
|
|
|
|
/** @type {ConduitGen} */
|
|
openConduit(subject, address) {
|
|
let proc = ChromeUtils.domProcessChild;
|
|
let conduit = proc.getActor("ProcessConduits").openConduit(subject, {
|
|
id: subject.id || getUniqueId(),
|
|
extensionId: this.extension.id,
|
|
envType: this.envType,
|
|
workerScriptURL: this.uri.spec,
|
|
workerDescriptorId: this.workerDescriptorId,
|
|
...address,
|
|
});
|
|
this.callOnClose(conduit);
|
|
conduit.setCloseCallback(() => {
|
|
this.forgetOnClose(conduit);
|
|
});
|
|
return conduit;
|
|
}
|
|
|
|
notifyWorkerLoaded() {
|
|
this.childManager.conduit.sendContextLoaded({
|
|
childId: this.childManager.id,
|
|
extensionId: this.extension.id,
|
|
workerDescriptorId: this.workerDescriptorId,
|
|
});
|
|
}
|
|
|
|
withAPIRequest(request, callable) {
|
|
this.webidlAPIRequest = request;
|
|
try {
|
|
return callable();
|
|
} finally {
|
|
this.webidlAPIRequest = null;
|
|
}
|
|
}
|
|
|
|
getAPIRequest() {
|
|
return this.webidlAPIRequest;
|
|
}
|
|
|
|
/**
|
|
* Captures the most recent stack frame from the WebIDL API request being
|
|
* processed.
|
|
*
|
|
* @returns {nsIStackFrame}
|
|
*/
|
|
getCaller() {
|
|
return this.webidlAPIRequest?.callerSavedFrame;
|
|
}
|
|
|
|
logActivity(type, name, data) {
|
|
ExtensionActivityLogChild.log(this, type, name, data);
|
|
}
|
|
|
|
get cloneScope() {
|
|
return this.workerCloneScope;
|
|
}
|
|
|
|
get principal() {
|
|
return this.workerPrincipal;
|
|
}
|
|
|
|
get tabId() {
|
|
return -1;
|
|
}
|
|
|
|
get useWebIDLBindings() {
|
|
return true;
|
|
}
|
|
|
|
shutdown() {
|
|
this.unload();
|
|
}
|
|
|
|
unload() {
|
|
if (this.unloaded) {
|
|
return;
|
|
}
|
|
|
|
super.unload();
|
|
}
|
|
|
|
get childManager() {
|
|
const childManager = getContextChildManagerGetter(
|
|
{ envType: "addon_parent" },
|
|
WebIDLChildAPIManager
|
|
).call(this);
|
|
return redefineGetter(this, "childManager", childManager);
|
|
}
|
|
|
|
get messenger() {
|
|
return redefineGetter(this, "messenger", new WorkerMessenger(this));
|
|
}
|
|
}
|
|
|
|
export var ExtensionWorkerChild = {
|
|
/** @type {Map<number, WorkerContextChild>} */
|
|
extensionWorkerContexts: new Map(),
|
|
|
|
apiManager: ExtensionPageChild.apiManager,
|
|
|
|
/**
|
|
* Create an extension worker context (on a mozExtensionAPIRequest with
|
|
* requestType "initWorkerContext").
|
|
*
|
|
* @param {ExtensionChild} extension
|
|
* The extension for which the context should be created.
|
|
* @param {mozIExtensionServiceWorkerInfo} serviceWorkerInfo
|
|
*/
|
|
initExtensionWorkerContext(extension, serviceWorkerInfo) {
|
|
if (!WebExtensionPolicy.isExtensionProcess) {
|
|
throw new Error(
|
|
"Cannot create an extension worker context in current process"
|
|
);
|
|
}
|
|
|
|
const swId = serviceWorkerInfo.descriptorId;
|
|
let context = this.extensionWorkerContexts.get(swId);
|
|
if (context) {
|
|
if (context.extension !== extension) {
|
|
throw new Error(
|
|
"A different extension context already exists for this service worker"
|
|
);
|
|
}
|
|
throw new Error(
|
|
"An extension context was already initialized for this service worker"
|
|
);
|
|
}
|
|
|
|
context = new WorkerContextChild(extension, { serviceWorkerInfo });
|
|
this.extensionWorkerContexts.set(swId, context);
|
|
},
|
|
|
|
/**
|
|
* Get an existing extension worker context for the given extension and
|
|
* service worker.
|
|
*
|
|
* @param {ExtensionChild} extension
|
|
* The extension for which the context should be created.
|
|
* @param {mozIExtensionServiceWorkerInfo} serviceWorkerInfo
|
|
*
|
|
* @returns {WorkerContextChild}
|
|
*/
|
|
getExtensionWorkerContext(extension, serviceWorkerInfo) {
|
|
if (!serviceWorkerInfo) {
|
|
return null;
|
|
}
|
|
|
|
const context = this.extensionWorkerContexts.get(
|
|
serviceWorkerInfo.descriptorId
|
|
);
|
|
|
|
if (context?.extension === extension) {
|
|
return context;
|
|
}
|
|
|
|
return null;
|
|
},
|
|
|
|
/**
|
|
* Notify the main process when an extension worker script has been loaded.
|
|
*
|
|
* @param {number} descriptorId The service worker descriptor ID of the destroyed context.
|
|
* @param {WebExtensionPolicy} policy
|
|
*/
|
|
notifyExtensionWorkerContextLoaded(descriptorId, policy) {
|
|
let context = this.extensionWorkerContexts.get(descriptorId);
|
|
if (context) {
|
|
if (context.extension.id !== policy.id) {
|
|
Cu.reportError(
|
|
new Error(
|
|
`ServiceWorker ${descriptorId} does not belong to the expected extension: ${policy.id}`
|
|
)
|
|
);
|
|
return;
|
|
}
|
|
context.notifyWorkerLoaded();
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Close the WorkerContextChild belonging to the given service worker, if any.
|
|
*
|
|
* @param {number} descriptorId The service worker descriptor ID of the destroyed context.
|
|
*/
|
|
destroyExtensionWorkerContext(descriptorId) {
|
|
let context = this.extensionWorkerContexts.get(descriptorId);
|
|
if (context) {
|
|
context.unload();
|
|
this.extensionWorkerContexts.delete(descriptorId);
|
|
}
|
|
},
|
|
|
|
shutdownExtension(extensionId) {
|
|
for (let [workerClientInfoId, context] of this.extensionWorkerContexts) {
|
|
if (context.extension.id == extensionId) {
|
|
context.shutdown();
|
|
this.extensionWorkerContexts.delete(workerClientInfoId);
|
|
}
|
|
}
|
|
},
|
|
};
|