forked from mirrors/gecko-dev
Backed out changeset 481558275c1d (bug 1651518) Backed out changeset 5337435e8bca (bug 1651518) Backed out changeset 8fe0c1a4da7f (bug 1651518) Backed out changeset 840a0d9db260 (bug 1651518) Backed out changeset eaab9de5819d (bug 1651518) Backed out changeset cf71de92d782 (bug 1651518) Backed out changeset d778691464db (bug 1651518) Backed out changeset af28e5f719e8 (bug 1883847)
494 lines
16 KiB
JavaScript
494 lines
16 KiB
JavaScript
/* 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/. */
|
|
|
|
import { EventEmitter } from "resource://gre/modules/EventEmitter.sys.mjs";
|
|
import { ContentProcessWatcherRegistry } from "resource://devtools/server/connectors/js-process-actor/ContentProcessWatcherRegistry.sys.mjs";
|
|
|
|
const lazy = {};
|
|
ChromeUtils.defineESModuleGetters(
|
|
lazy,
|
|
{
|
|
ProcessTargetWatcher:
|
|
"resource://devtools/server/connectors/js-process-actor/target-watchers/process.sys.mjs",
|
|
SessionDataHelpers:
|
|
"resource://devtools/server/actors/watcher/SessionDataHelpers.sys.mjs",
|
|
ServiceWorkerTargetWatcher:
|
|
"resource://devtools/server/connectors/js-process-actor/target-watchers/service_worker.sys.mjs",
|
|
WorkerTargetWatcher:
|
|
"resource://devtools/server/connectors/js-process-actor/target-watchers/worker.sys.mjs",
|
|
WindowGlobalTargetWatcher:
|
|
"resource://devtools/server/connectors/js-process-actor/target-watchers/window-global.sys.mjs",
|
|
},
|
|
{ global: "contextual" }
|
|
);
|
|
|
|
// TargetActorRegistery has to be shared between all devtools instances
|
|
// and so is loaded into the shared global.
|
|
ChromeUtils.defineESModuleGetters(
|
|
lazy,
|
|
{
|
|
TargetActorRegistry:
|
|
"resource://devtools/server/actors/targets/target-actor-registry.sys.mjs",
|
|
},
|
|
{ global: "shared" }
|
|
);
|
|
|
|
export class DevToolsProcessChild extends JSProcessActorChild {
|
|
constructor() {
|
|
super();
|
|
|
|
// The EventEmitter interface is used for DevToolsTransport's packet-received event.
|
|
EventEmitter.decorate(this);
|
|
}
|
|
|
|
#watchers = {
|
|
// Keys are target types, which are defined in this CommonJS Module:
|
|
// https://searchfox.org/mozilla-central/rev/0e9ea50a999420d93df0e4e27094952af48dd3b8/devtools/server/actors/targets/index.js#7-14
|
|
// We avoid loading it as this ESM should be lightweight and avoid spawning DevTools CommonJS Loader until
|
|
// whe know we have to instantiate a Target Actor.
|
|
frame: {
|
|
// Number of active watcher actors currently watching for the given target type
|
|
activeListener: 0,
|
|
|
|
// Instance of a target watcher class whose task is to observe new target instances
|
|
get watcher() {
|
|
return lazy.WindowGlobalTargetWatcher;
|
|
},
|
|
},
|
|
|
|
process: {
|
|
activeListener: 0,
|
|
get watcher() {
|
|
return lazy.ProcessTargetWatcher;
|
|
},
|
|
},
|
|
|
|
worker: {
|
|
activeListener: 0,
|
|
get watcher() {
|
|
return lazy.WorkerTargetWatcher;
|
|
},
|
|
},
|
|
|
|
service_worker: {
|
|
activeListener: 0,
|
|
get watcher() {
|
|
return lazy.ServiceWorkerTargetWatcher;
|
|
},
|
|
},
|
|
};
|
|
|
|
#initialized = false;
|
|
|
|
/**
|
|
* Called when this JSProcess Actor instantiate either when we start observing for first target types,
|
|
* or when the process just started.
|
|
*/
|
|
instantiate() {
|
|
if (this.#initialized) {
|
|
return;
|
|
}
|
|
this.#initialized = true;
|
|
// Create and watch for future target actors for each watcher currently watching some target types
|
|
for (const watcherDataObject of ContentProcessWatcherRegistry.getAllWatchersDataObjects()) {
|
|
this.#watchInitialTargetsForWatcher(watcherDataObject);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Instantiate and watch future target actors based on the already watched targets.
|
|
*
|
|
* @param Object watcherDataObject
|
|
* See ContentProcessWatcherRegistry.
|
|
*/
|
|
#watchInitialTargetsForWatcher(watcherDataObject) {
|
|
const { sessionData, sessionContext } = watcherDataObject;
|
|
|
|
// About WebExtension, see note in addOrSetSessionDataEntry.
|
|
// Their target actor aren't created by this class, but session data is still managed by it
|
|
// and we need to pass the initial session data coming to already instantiated target actor.
|
|
if (sessionContext.type == "webextension") {
|
|
const { watcherActorID } = watcherDataObject;
|
|
const connectionPrefix = watcherActorID.replace(/watcher\d+$/, "");
|
|
const targetActors = lazy.TargetActorRegistry.getTargetActors(
|
|
sessionContext,
|
|
connectionPrefix
|
|
);
|
|
if (targetActors.length) {
|
|
// Pass initialization data to the target actor
|
|
for (const type in sessionData) {
|
|
// `sessionData` will also contain `browserId` as well as entries with empty arrays,
|
|
// which shouldn't be processed.
|
|
const entries = sessionData[type];
|
|
if (!Array.isArray(entries) || !entries.length) {
|
|
continue;
|
|
}
|
|
targetActors[0].addOrSetSessionDataEntry(type, entries, false, "set");
|
|
}
|
|
}
|
|
}
|
|
|
|
// Ignore the call if the watched targets property isn't populated yet.
|
|
// This typically happens when instantiating the JS Process Actor on toolbox opening,
|
|
// where the actor is spawn early and a watchTarget message comes later with the `targets` array set.
|
|
if (!sessionData.targets) {
|
|
return;
|
|
}
|
|
|
|
for (const targetType of sessionData.targets) {
|
|
this.#watchNewTargetTypeForWatcher(watcherDataObject, targetType, true);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Instantiate and watch future target actors based on the already watched targets.
|
|
*
|
|
* @param Object watcherDataObject
|
|
* See ContentProcessWatcherRegistry.
|
|
* @param String targetType
|
|
* New typeof target to start watching.
|
|
* @param Boolean isProcessActorStartup
|
|
* True when we are watching for targets during this JS Process actor instantiation.
|
|
* It shouldn't be the case on toolbox opening, but only when a new process starts.
|
|
* On toolbox opening, the Actor will receive an explicit watchTargets query.
|
|
*/
|
|
#watchNewTargetTypeForWatcher(
|
|
watcherDataObject,
|
|
targetType,
|
|
isProcessActorStartup
|
|
) {
|
|
const { watchingTargetTypes } = watcherDataObject;
|
|
// Ensure creating and watching only once per target type and watcher actor.
|
|
if (watchingTargetTypes.includes(targetType)) {
|
|
return;
|
|
}
|
|
watchingTargetTypes.push(targetType);
|
|
|
|
// Update sessionData as watched target types are a Session Data
|
|
// used later for example by worker target watcher
|
|
lazy.SessionDataHelpers.addOrSetSessionDataEntry(
|
|
watcherDataObject.sessionData,
|
|
"targets",
|
|
[targetType],
|
|
"add"
|
|
);
|
|
|
|
this.#watchers[targetType].activeListener++;
|
|
|
|
// Start listening for platform events when we are observing this type for the first time
|
|
if (this.#watchers[targetType].activeListener === 1) {
|
|
this.#watchers[targetType].watcher.watch();
|
|
}
|
|
|
|
// And instantiate targets for the already existing instances
|
|
this.#watchers[targetType].watcher.createTargetsForWatcher(
|
|
watcherDataObject,
|
|
isProcessActorStartup
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Stop watching for all target types and destroy all existing targets actor
|
|
* related to a given watcher actor.
|
|
*
|
|
* @param {Object} watcherDataObject
|
|
* @param {String} targetType
|
|
* @param {Object} options
|
|
*/
|
|
#unwatchTargetsForWatcher(watcherDataObject, targetType, options) {
|
|
const { watchingTargetTypes } = watcherDataObject;
|
|
const targetTypeIndex = watchingTargetTypes.indexOf(targetType);
|
|
// Ignore targetTypes which were not observed
|
|
if (targetTypeIndex === -1) {
|
|
return;
|
|
}
|
|
// Update to the new list of currently watched target types
|
|
watchingTargetTypes.splice(targetTypeIndex, 1);
|
|
|
|
// Update sessionData as watched target types are a Session Data
|
|
// used later for example by worker target watcher
|
|
lazy.SessionDataHelpers.removeSessionDataEntry(
|
|
watcherDataObject.sessionData,
|
|
"targets",
|
|
[targetType]
|
|
);
|
|
|
|
this.#watchers[targetType].activeListener--;
|
|
|
|
// Stop observing for platform events
|
|
if (this.#watchers[targetType].activeListener === 0) {
|
|
this.#watchers[targetType].watcher.unwatch();
|
|
}
|
|
|
|
// Destroy all targets which are still instantiated for this type
|
|
this.#watchers[targetType].watcher.destroyTargetsForWatcher(
|
|
watcherDataObject,
|
|
options
|
|
);
|
|
|
|
// Unregister the watcher if we stopped watching for all target types
|
|
if (!watchingTargetTypes.length) {
|
|
ContentProcessWatcherRegistry.remove(watcherDataObject);
|
|
}
|
|
|
|
// If we removed the last watcher, clean the internal state of this class.
|
|
if (ContentProcessWatcherRegistry.isEmpty()) {
|
|
this.didDestroy(options);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Cleanup everything around a given watcher actor
|
|
*
|
|
* @param {Object} watcherDataObject
|
|
*/
|
|
#destroyWatcher(watcherDataObject) {
|
|
const { watchingTargetTypes } = watcherDataObject;
|
|
// Clone the array as it will be modified during the loop execution
|
|
for (const targetType of [...watchingTargetTypes]) {
|
|
this.#unwatchTargetsForWatcher(watcherDataObject, targetType);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Used by DevTools Transport to send packets to the content process.
|
|
*
|
|
* @param {JSON} packet
|
|
* @param {String} prefix
|
|
*/
|
|
sendPacket(packet, prefix) {
|
|
this.sendAsyncMessage("DevToolsProcessChild:packet", { packet, prefix });
|
|
}
|
|
|
|
/**
|
|
* JsWindowActor API
|
|
*/
|
|
|
|
async sendQuery(msg, args) {
|
|
try {
|
|
const res = await super.sendQuery(msg, args);
|
|
return res;
|
|
} catch (e) {
|
|
console.error("Failed to sendQuery in DevToolsProcessChild", msg);
|
|
console.error(e.toString());
|
|
throw e;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Called by the JSProcessActor API when the process process sent us a message.
|
|
*/
|
|
receiveMessage(message) {
|
|
switch (message.name) {
|
|
case "DevToolsProcessParent:watchTargets": {
|
|
const { watcherActorID, targetType } = message.data;
|
|
const watcherDataObject =
|
|
ContentProcessWatcherRegistry.getWatcherDataObject(watcherActorID);
|
|
return this.#watchNewTargetTypeForWatcher(
|
|
watcherDataObject,
|
|
targetType
|
|
);
|
|
}
|
|
case "DevToolsProcessParent:unwatchTargets": {
|
|
const { watcherActorID, targetType, options } = message.data;
|
|
const watcherDataObject =
|
|
ContentProcessWatcherRegistry.getWatcherDataObject(watcherActorID);
|
|
return this.#unwatchTargetsForWatcher(
|
|
watcherDataObject,
|
|
targetType,
|
|
options
|
|
);
|
|
}
|
|
case "DevToolsProcessParent:addOrSetSessionDataEntry": {
|
|
const { watcherActorID, type, entries, updateType } = message.data;
|
|
return this.#addOrSetSessionDataEntry(
|
|
watcherActorID,
|
|
type,
|
|
entries,
|
|
updateType
|
|
);
|
|
}
|
|
case "DevToolsProcessParent:removeSessionDataEntry": {
|
|
const { watcherActorID, type, entries } = message.data;
|
|
return this.#removeSessionDataEntry(watcherActorID, type, entries);
|
|
}
|
|
case "DevToolsProcessParent:destroyWatcher": {
|
|
const { watcherActorID } = message.data;
|
|
const watcherDataObject =
|
|
ContentProcessWatcherRegistry.getWatcherDataObject(
|
|
watcherActorID,
|
|
true
|
|
);
|
|
// The watcher may already be destroyed if the client unwatched for all target types.
|
|
if (watcherDataObject) {
|
|
return this.#destroyWatcher(watcherDataObject);
|
|
}
|
|
return null;
|
|
}
|
|
case "DevToolsProcessParent:packet":
|
|
return this.emit("packet-received", message);
|
|
default:
|
|
throw new Error(
|
|
"Unsupported message in DevToolsProcessParent: " + message.name
|
|
);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* The parent process requested that some session data have been added or set.
|
|
*
|
|
* @param {String} watcherActorID
|
|
* The Watcher Actor ID requesting to add new session data
|
|
* @param {String} type
|
|
* The type of data to be added
|
|
* @param {Array<Object>} entries
|
|
* The values to be added to this type of data
|
|
* @param {String} updateType
|
|
* "add" will only add the new entries in the existing data set.
|
|
* "set" will update the data set with the new entries.
|
|
*/
|
|
async #addOrSetSessionDataEntry(watcherActorID, type, entries, updateType) {
|
|
const watcherDataObject =
|
|
ContentProcessWatcherRegistry.getWatcherDataObject(watcherActorID);
|
|
|
|
// Maintain the copy of `sessionData` so that it is up-to-date when
|
|
// a new worker target needs to be instantiated
|
|
const { sessionData } = watcherDataObject;
|
|
lazy.SessionDataHelpers.addOrSetSessionDataEntry(
|
|
sessionData,
|
|
type,
|
|
entries,
|
|
updateType
|
|
);
|
|
|
|
// This type is really specific to Service Workers and doesn't need to be transferred to any target.
|
|
// We only need to instantiate and destroy the target actors based on this new host.
|
|
const { watchingTargetTypes } = watcherDataObject;
|
|
if (type == "browser-element-host") {
|
|
if (watchingTargetTypes.includes("service_worker")) {
|
|
this.#watchers.service_worker.watcher.updateBrowserElementHost(
|
|
watcherDataObject
|
|
);
|
|
}
|
|
return;
|
|
}
|
|
|
|
const promises = [];
|
|
for (const targetActor of watcherDataObject.actors) {
|
|
promises.push(
|
|
targetActor.addOrSetSessionDataEntry(type, entries, false, updateType)
|
|
);
|
|
}
|
|
|
|
// Very special codepath for Web Extensions.
|
|
// Their WebExtension Target Actor is still created manually by WebExtensionDescritpor.getTarget,
|
|
// via a message manager. That, instead of being instantiated via the WatcherActor.watchTargets and this JSProcess actor.
|
|
// The Watcher Actor will still instantiate a JS Actor for the WebExt DOM Content Process
|
|
// and send the addOrSetSessionDataEntry query. But as the target actor isn't managed by the JS Actor,
|
|
// we have to manually retrieve it via the TargetActorRegistry.
|
|
if (sessionData.sessionContext.type == "webextension") {
|
|
const connectionPrefix = watcherActorID.replace(/watcher\d+$/, "");
|
|
const targetActors = lazy.TargetActorRegistry.getTargetActors(
|
|
sessionData.sessionContext,
|
|
connectionPrefix
|
|
);
|
|
// We will have a single match only in the DOM Process where the add-on runs
|
|
if (targetActors.length) {
|
|
promises.push(
|
|
targetActors[0].addOrSetSessionDataEntry(
|
|
type,
|
|
entries,
|
|
false,
|
|
updateType
|
|
)
|
|
);
|
|
}
|
|
}
|
|
await Promise.all(promises);
|
|
|
|
if (watchingTargetTypes.includes("worker")) {
|
|
await this.#watchers.worker.watcher.addOrSetSessionDataEntry(
|
|
watcherDataObject,
|
|
type,
|
|
entries,
|
|
updateType
|
|
);
|
|
}
|
|
if (watchingTargetTypes.includes("service_worker")) {
|
|
await this.#watchers.service_worker.watcher.addOrSetSessionDataEntry(
|
|
watcherDataObject,
|
|
type,
|
|
entries,
|
|
updateType
|
|
);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* The parent process requested that some session data have been removed.
|
|
*
|
|
* @param {String} watcherActorID
|
|
* The Watcher Actor ID requesting to remove session data
|
|
* @param {String}} type
|
|
* The type of data to be removed
|
|
* @param {Array<Object>} entries
|
|
* The values to be removed to this type of data
|
|
*/
|
|
#removeSessionDataEntry(watcherActorID, type, entries) {
|
|
const watcherDataObject =
|
|
ContentProcessWatcherRegistry.getWatcherDataObject(watcherActorID, true);
|
|
|
|
// When we unwatch resources after targets during the devtools shutdown,
|
|
// the watcher will be removed on last target type unwatch.
|
|
if (!watcherDataObject) {
|
|
return;
|
|
}
|
|
|
|
// Maintain the copy of `sessionData` so that it is up-to-date when
|
|
// a new worker target needs to be instantiated
|
|
lazy.SessionDataHelpers.removeSessionDataEntry(
|
|
watcherDataObject.sessionData,
|
|
type,
|
|
entries
|
|
);
|
|
|
|
for (const targetActor of watcherDataObject.actors) {
|
|
targetActor.removeSessionDataEntry(type, entries);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Observer service notification handler.
|
|
*
|
|
* @param {DOMWindow|Document} subject
|
|
* A window for *-document-global-created
|
|
* A document for *-page-{shown|hide}
|
|
* @param {String} topic
|
|
*/
|
|
observe = (subject, topic) => {
|
|
if (topic === "init-devtools-content-process-actor") {
|
|
// This is triggered by the process actor registration and some code in process-helper.js
|
|
// which defines a unique topic to be observed
|
|
this.instantiate();
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Called by JS Process Actor API when the current process is destroyed,
|
|
* but also within this class when the last watcher stopped watching for targets.
|
|
*/
|
|
didDestroy() {
|
|
// Stop watching for all target types
|
|
for (const entry of Object.values(this.#watchers)) {
|
|
if (entry.activeListener > 0) {
|
|
entry.watcher.unwatch();
|
|
entry.activeListener = 0;
|
|
}
|
|
}
|
|
|
|
ContentProcessWatcherRegistry.clear();
|
|
}
|
|
}
|
|
|
|
export class BrowserToolboxDevToolsProcessChild extends DevToolsProcessChild {}
|