summaryrefslogtreecommitdiffstats
path: root/devtools/server/actors/watcher.js
diff options
context:
space:
mode:
Diffstat (limited to 'devtools/server/actors/watcher.js')
-rw-r--r--devtools/server/actors/watcher.js353
1 files changed, 178 insertions, 175 deletions
diff --git a/devtools/server/actors/watcher.js b/devtools/server/actors/watcher.js
index 935d33faa8..10de102229 100644
--- a/devtools/server/actors/watcher.js
+++ b/devtools/server/actors/watcher.js
@@ -11,13 +11,12 @@ const { TargetActorRegistry } = ChromeUtils.importESModule(
"resource://devtools/server/actors/targets/target-actor-registry.sys.mjs",
{ global: "shared" }
);
-const { WatcherRegistry } = ChromeUtils.importESModule(
- "resource://devtools/server/actors/watcher/WatcherRegistry.sys.mjs",
- // WatcherRegistry needs to be a true singleton and loads ActorManagerParent
+const { ParentProcessWatcherRegistry } = ChromeUtils.importESModule(
+ "resource://devtools/server/actors/watcher/ParentProcessWatcherRegistry.sys.mjs",
+ // ParentProcessWatcherRegistry needs to be a true singleton and loads ActorManagerParent
// which also has to be a true singleton.
{ global: "shared" }
);
-const Targets = require("resource://devtools/server/actors/targets/index.js");
const { getAllBrowsingContextsForContext } = ChromeUtils.importESModule(
"resource://devtools/server/actors/watcher/browsing-context-helpers.sys.mjs",
{ global: "contextual" }
@@ -26,28 +25,6 @@ const {
SESSION_TYPES,
} = require("resource://devtools/server/actors/watcher/session-context.js");
-const TARGET_HELPERS = {};
-loader.lazyRequireGetter(
- TARGET_HELPERS,
- Targets.TYPES.FRAME,
- "resource://devtools/server/actors/watcher/target-helpers/frame-helper.js"
-);
-loader.lazyRequireGetter(
- TARGET_HELPERS,
- Targets.TYPES.PROCESS,
- "resource://devtools/server/actors/watcher/target-helpers/process-helper.js"
-);
-loader.lazyRequireGetter(
- TARGET_HELPERS,
- Targets.TYPES.SERVICE_WORKER,
- "devtools/server/actors/watcher/target-helpers/service-worker-helper"
-);
-loader.lazyRequireGetter(
- TARGET_HELPERS,
- Targets.TYPES.WORKER,
- "resource://devtools/server/actors/watcher/target-helpers/worker-helper.js"
-);
-
loader.lazyRequireGetter(
this,
"NetworkParentActor",
@@ -137,6 +114,14 @@ exports.WatcherActor = class WatcherActor extends Actor {
// but there are certain cases when a new target is available before the
// old target is destroyed.
this._currentWindowGlobalTargets = new Map();
+
+ // The Browser Toolbox requires to load modules in a distinct compartment in order
+ // to be able to debug system compartments modules (most of Firefox internal codebase).
+ // This is a requirement coming from SpiderMonkey Debugger API and relates to the thread actor.
+ this._jsActorName =
+ sessionContext.type == SESSION_TYPES.ALL
+ ? "BrowserToolboxDevToolsProcess"
+ : "DevToolsProcess";
}
get sessionContext() {
@@ -176,16 +161,33 @@ exports.WatcherActor = class WatcherActor extends Actor {
}
destroy() {
- // Force unwatching for all types, even if we weren't watching.
- // This is fine as unwatchTarget is NOOP if we weren't already watching for this target type.
- for (const targetType of Object.values(Targets.TYPES)) {
- this.unwatchTargets(targetType);
+ // Only try to notify content processes if the watcher was in the registry.
+ // Otherwise it means that it wasn't connected to any process and the JS Process Actor
+ // wouldn't be registered.
+ if (ParentProcessWatcherRegistry.getWatcher(this.actorID)) {
+ // Emit one IPC message on destroy to all the processes
+ const domProcesses = ChromeUtils.getAllDOMProcesses();
+ for (const domProcess of domProcesses) {
+ domProcess.getActor(this._jsActorName).destroyWatcher({
+ watcherActorID: this.actorID,
+ });
+ }
}
- this.unwatchResources(Object.values(Resources.TYPES));
- WatcherRegistry.unregisterWatcher(this);
+ // Ensure destroying all Resource Watcher instantiated in the parent process
+ Resources.unwatchResources(
+ this,
+ Resources.getParentProcessResourceTypes(Object.values(Resources.TYPES))
+ );
+
+ ParentProcessWatcherRegistry.unregisterWatcher(this.actorID);
- // Destroy the actor at the end so that its actorID keeps being defined.
+ // In case the watcher actor is leaked, prevent leaking the browser window
+ this._browserElement = null;
+
+ // Destroy the actor in order to ensure destroying all its children actors.
+ // As this actor is a pool with children actors, when the transport/connection closes
+ // we expect all actors and its children to be destroyed.
super.destroy();
}
@@ -196,7 +198,7 @@ exports.WatcherActor = class WatcherActor extends Actor {
* Returns the list of currently watched resource types.
*/
get sessionData() {
- return WatcherRegistry.getSessionData(this);
+ return ParentProcessWatcherRegistry.getSessionData(this);
}
form() {
@@ -225,11 +227,44 @@ exports.WatcherActor = class WatcherActor extends Actor {
* Type of context to observe. See Targets.TYPES object.
*/
async watchTargets(targetType) {
- WatcherRegistry.watchTargets(this, targetType);
+ ParentProcessWatcherRegistry.watchTargets(this, targetType);
+
+ // When debugging a tab, ensure processing the top level target first
+ // (for now, other session context types are instantiating the top level target
+ // from the descriptor's getTarget method instead of the Watcher)
+ let topLevelTargetProcess;
+ if (this.sessionContext.type == SESSION_TYPES.BROWSER_ELEMENT) {
+ topLevelTargetProcess =
+ this.browserElement.browsingContext.currentWindowGlobal?.domProcess;
+ if (topLevelTargetProcess) {
+ await topLevelTargetProcess.getActor(this._jsActorName).watchTargets({
+ watcherActorID: this.actorID,
+ targetType,
+ });
+ // Stop execution if we were destroyed in the meantime
+ if (this.isDestroyed()) {
+ return;
+ }
+ }
+ }
- const targetHelperModule = TARGET_HELPERS[targetType];
- // Await the registration in order to ensure receiving the already existing targets
- await targetHelperModule.createTargets(this);
+ // We have to reach out all the content processes as the page may navigate
+ // to any other content process when navigating to another origin.
+ // It may even run in the parent process when loading about:robots.
+ const domProcesses = ChromeUtils.getAllDOMProcesses();
+ const promises = [];
+ for (const domProcess of domProcesses) {
+ if (domProcess == topLevelTargetProcess) {
+ continue;
+ }
+ promises.push(
+ domProcess.getActor(this._jsActorName).watchTargets({
+ watcherActorID: this.actorID,
+ targetType,
+ })
+ );
+ }
+ await Promise.all(promises);
}
/**
@@ -242,7 +277,7 @@ exports.WatcherActor = class WatcherActor extends Actor {
* true when this is called as the result of a change to the devtools.browsertoolbox.scope pref
*/
unwatchTargets(targetType, options = {}) {
- const isWatchingTargets = WatcherRegistry.unwatchTargets(
+ const isWatchingTargets = ParentProcessWatcherRegistry.unwatchTargets(
this,
targetType,
options
@@ -251,14 +286,20 @@ exports.WatcherActor = class WatcherActor extends Actor {
return;
}
- const targetHelperModule = TARGET_HELPERS[targetType];
- targetHelperModule.destroyTargets(this, options);
+ const domProcesses = ChromeUtils.getAllDOMProcesses();
+ for (const domProcess of domProcesses) {
+ domProcess.getActor(this._jsActorName).unwatchTargets({
+ watcherActorID: this.actorID,
+ targetType,
+ options,
+ });
+ }
// Unregister the JS Actors if there is no more DevTools code observing any target/resource,
// unless we're switching mode (having both condition at the same time should only
// happen in tests).
if (!options.isModeSwitching) {
- WatcherRegistry.maybeUnregisterJSActors();
+ ParentProcessWatcherRegistry.maybeUnregisterJSActors();
}
}
@@ -301,7 +342,12 @@ exports.WatcherActor = class WatcherActor extends Actor {
this._flushIframeTargets(actor.innerWindowId);
if (this.sessionContext.type == SESSION_TYPES.BROWSER_ELEMENT) {
- this.updateDomainSessionDataForServiceWorkers(actor.url);
+ // Ignore any pending exception as this request may be pending
+ // while the toolbox closes. And we don't want to delay target emission
+ // on this as this is a implementation detail.
+ this.updateDomainSessionDataForServiceWorkers(actor.url).catch(
+ () => {}
+ );
}
} else if (this._currentWindowGlobalTargets.has(actor.topInnerWindowId)) {
// Emit the event immediately if the top-level target is already available
@@ -444,18 +490,18 @@ exports.WatcherActor = class WatcherActor extends Actor {
}
/**
- * Try to retrieve a parent process TargetActor which is ignored by the
- * TARGET_HELPERS. Examples:
- * - top level target for the browser toolbox
- * - xpcshell target for xpcshell debugging
+ * Try to retrieve Target Actors instantiated in the parent process which aren't
+ * instantiated via the Watcher actor (and its dependencies):
+ * - top level target for the browser toolboxes
+ * - xpcshell targets for xpcshell debugging
*
* See comment in `watchResources`.
*
- * @return {TargetActor|null} Matching target actor if any, null otherwise.
+ * @return {Set<TargetActor>} Matching target actors.
*/
- getTargetActorInParentProcess() {
- if (TargetActorRegistry.xpcShellTargetActor) {
- return TargetActorRegistry.xpcShellTargetActor;
+ getTargetActorsInParentProcess() {
+ if (TargetActorRegistry.xpcShellTargetActors.size) {
+ return TargetActorRegistry.xpcShellTargetActors;
}
// Note: For browser-element debugging, the WindowGlobalTargetActor returned here is created
@@ -467,12 +513,18 @@ exports.WatcherActor = class WatcherActor extends Actor {
switch (this.sessionContext.type) {
case "all":
- return actors.find(actor => actor.typeName === "parentProcessTarget");
+ const parentProcessTargetActor = actors.find(
+ actor => actor.typeName === "parentProcessTarget"
+ );
+ if (parentProcessTargetActor) {
+ return new Set([parentProcessTargetActor]);
+ }
+ return new Set();
case "browser-element":
case "webextension":
// All target actors for browser-element and webextension sessions
// should be created using the JS Window actors.
- return null;
+ return new Set();
default:
throw new Error(
"Unsupported session context type: " + this.sessionContext.type
@@ -497,41 +549,32 @@ exports.WatcherActor = class WatcherActor extends Actor {
);
// Bail out early if all resources were watched from parent process.
- // In this scenario, we do not need to update these resource types in the WatcherRegistry
+ // In this scenario, we do not need to update these resource types in the ParentProcessWatcherRegistry
// as targets do not care about them.
if (!Resources.hasResourceTypesForTargets(resourceTypes)) {
return;
}
- WatcherRegistry.watchResources(this, resourceTypes);
+ ParentProcessWatcherRegistry.watchResources(this, resourceTypes);
- // Fetch resources from all existing targets
- for (const targetType in TARGET_HELPERS) {
- // We process frame targets even if we aren't watching them,
- // because frame target helper codepath handles the top level target, if it runs in the *content* process.
- // It will do another check to `isWatchingTargets(FRAME)` internally.
- // Note that the workaround at the end of this method, using TargetActorRegistry
- // is specific to top level target running in the *parent* process.
- if (
- !WatcherRegistry.isWatchingTargets(this, targetType) &&
- targetType != Targets.TYPES.FRAME
- ) {
- continue;
- }
- const targetResourceTypes = Resources.getResourceTypesForTargetType(
- resourceTypes,
- targetType
+ const promises = [];
+ const domProcesses = ChromeUtils.getAllDOMProcesses();
+ for (const domProcess of domProcesses) {
+ promises.push(
+ domProcess.getActor(this._jsActorName).addOrSetSessionDataEntry({
+ watcherActorID: this.actorID,
+ sessionContext: this.sessionContext,
+ type: "resources",
+ entries: resourceTypes,
+ updateType: "add",
+ })
);
- if (!targetResourceTypes.length) {
- continue;
- }
- const targetHelperModule = TARGET_HELPERS[targetType];
- await targetHelperModule.addOrSetSessionDataEntry({
- watcher: this,
- type: "resources",
- entries: targetResourceTypes,
- updateType: "add",
- });
+ }
+ await Promise.all(promises);
+
+ // Stop execution if we were destroyed in the meantime
+ if (this.isDestroyed()) {
+ return;
}
/*
@@ -551,8 +594,8 @@ exports.WatcherActor = class WatcherActor extends Actor {
* We will eventually get rid of this code once all targets are properly supported by
* the Watcher Actor and we have target helpers for all of them.
*/
- const targetActor = this.getTargetActorInParentProcess();
- if (targetActor) {
+ const targetActors = this.getTargetActorsInParentProcess();
+ for (const targetActor of targetActors) {
const targetActorResourceTypes = Resources.getResourceTypesForTargetType(
resourceTypes,
targetActor.targetType
@@ -581,13 +624,13 @@ exports.WatcherActor = class WatcherActor extends Actor {
);
// Bail out early if all resources were all watched from parent process.
- // In this scenario, we do not need to update these resource types in the WatcherRegistry
+ // In this scenario, we do not need to update these resource types in the ParentProcessWatcherRegistry
// as targets do not care about them.
if (!Resources.hasResourceTypesForTargets(resourceTypes)) {
return;
}
- const isWatchingResources = WatcherRegistry.unwatchResources(
+ const isWatchingResources = ParentProcessWatcherRegistry.unwatchResources(
this,
resourceTypes
);
@@ -598,34 +641,20 @@ exports.WatcherActor = class WatcherActor extends Actor {
// Prevent trying to unwatch when the related BrowsingContext has already
// been destroyed
if (!this.isContextDestroyed()) {
- for (const targetType in TARGET_HELPERS) {
- // Frame target helper handles the top level target, if it runs in the content process
- // so we should always process it. It does a second check to isWatchingTargets.
- if (
- !WatcherRegistry.isWatchingTargets(this, targetType) &&
- targetType != Targets.TYPES.FRAME
- ) {
- continue;
- }
- const targetResourceTypes = Resources.getResourceTypesForTargetType(
- resourceTypes,
- targetType
- );
- if (!targetResourceTypes.length) {
- continue;
- }
- const targetHelperModule = TARGET_HELPERS[targetType];
- targetHelperModule.removeSessionDataEntry({
- watcher: this,
+ const domProcesses = ChromeUtils.getAllDOMProcesses();
+ for (const domProcess of domProcesses) {
+ domProcess.getActor(this._jsActorName).removeSessionDataEntry({
+ watcherActorID: this.actorID,
+ sessionContext: this.sessionContext,
type: "resources",
- entries: targetResourceTypes,
+ entries: resourceTypes,
});
}
}
// See comment in watchResources.
- const targetActor = this.getTargetActorInParentProcess();
- if (targetActor) {
+ const targetActors = this.getTargetActorsInParentProcess();
+ for (const targetActor of targetActors) {
const targetActorResourceTypes = Resources.getResourceTypesForTargetType(
resourceTypes,
targetActor.targetType
@@ -634,7 +663,7 @@ exports.WatcherActor = class WatcherActor extends Actor {
}
// Unregister the JS Window Actor if there is no more DevTools code observing any target/resource
- WatcherRegistry.maybeUnregisterJSActors();
+ ParentProcessWatcherRegistry.maybeUnregisterJSActors();
}
clearResources(resourceTypes) {
@@ -729,34 +758,36 @@ exports.WatcherActor = class WatcherActor extends Actor {
* "set" will update the data set with the new entries.
*/
async addOrSetDataEntry(type, entries, updateType) {
- WatcherRegistry.addOrSetSessionDataEntry(this, type, entries, updateType);
-
- await Promise.all(
- Object.values(Targets.TYPES)
- .filter(
- targetType =>
- // We process frame targets even if we aren't watching them,
- // because frame target helper codepath handles the top level target, if it runs in the *content* process.
- // It will do another check to `isWatchingTargets(FRAME)` internally.
- // Note that the workaround at the end of this method, using TargetActorRegistry
- // is specific to top level target running in the *parent* process.
- WatcherRegistry.isWatchingTargets(this, targetType) ||
- targetType === Targets.TYPES.FRAME
- )
- .map(async targetType => {
- const targetHelperModule = TARGET_HELPERS[targetType];
- await targetHelperModule.addOrSetSessionDataEntry({
- watcher: this,
- type,
- entries,
- updateType,
- });
- })
+ ParentProcessWatcherRegistry.addOrSetSessionDataEntry(
+ this,
+ type,
+ entries,
+ updateType
);
+ const promises = [];
+ const domProcesses = ChromeUtils.getAllDOMProcesses();
+ for (const domProcess of domProcesses) {
+ promises.push(
+ domProcess.getActor(this._jsActorName).addOrSetSessionDataEntry({
+ watcherActorID: this.actorID,
+ sessionContext: this.sessionContext,
+ type,
+ entries,
+ updateType,
+ })
+ );
+ }
+ await Promise.all(promises);
+
+ // Stop execution if we were destroyed in the meantime
+ if (this.isDestroyed()) {
+ return;
+ }
+
// See comment in watchResources
- const targetActor = this.getTargetActorInParentProcess();
- if (targetActor) {
+ const targetActors = this.getTargetActorsInParentProcess();
+ for (const targetActor of targetActors) {
await targetActor.addOrSetSessionDataEntry(
type,
entries,
@@ -777,27 +808,21 @@ exports.WatcherActor = class WatcherActor extends Actor {
* List of values to remove from this data type.
*/
removeDataEntry(type, entries) {
- WatcherRegistry.removeSessionDataEntry(this, type, entries);
-
- Object.values(Targets.TYPES)
- .filter(
- targetType =>
- // See comment in addOrSetDataEntry
- WatcherRegistry.isWatchingTargets(this, targetType) ||
- targetType === Targets.TYPES.FRAME
- )
- .forEach(targetType => {
- const targetHelperModule = TARGET_HELPERS[targetType];
- targetHelperModule.removeSessionDataEntry({
- watcher: this,
- type,
- entries,
- });
+ ParentProcessWatcherRegistry.removeSessionDataEntry(this, type, entries);
+
+ const domProcesses = ChromeUtils.getAllDOMProcesses();
+ for (const domProcess of domProcesses) {
+ domProcess.getActor(this._jsActorName).removeSessionDataEntry({
+ watcherActorID: this.actorID,
+ sessionContext: this.sessionContext,
+ type,
+ entries,
});
+ }
// See comment in addOrSetDataEntry
- const targetActor = this.getTargetActorInParentProcess();
- if (targetActor) {
+ const targetActors = this.getTargetActorsInParentProcess();
+ for (const targetActor of targetActors) {
targetActor.removeSessionDataEntry(type, entries);
}
}
@@ -827,35 +852,13 @@ exports.WatcherActor = class WatcherActor extends Actor {
host = new URL(newTargetUrl).host;
} catch (e) {}
- WatcherRegistry.addOrSetSessionDataEntry(
+ ParentProcessWatcherRegistry.addOrSetSessionDataEntry(
this,
"browser-element-host",
[host],
"set"
);
- // This SessionData attribute is only used when debugging service workers.
- // Avoid instantiating the JS Process Actors if we aren't watching for SW,
- // or if we aren't watching for them just yet.
- // But still update the WatcherRegistry, so that when we start watching
- // and instantiate the target, the host will be set to the right value.
- //
- // Note that it is very important to avoid calling Service worker target helper's
- // addOrSetSessionDataEntry. Otherwise, when we aren't watching for SW at all,
- // we won't call destroyTargets on watcher actor destruction,
- // and as a consequence never unregister the js process actor.
- if (
- !WatcherRegistry.isWatchingTargets(this, Targets.TYPES.SERVICE_WORKER)
- ) {
- return;
- }
-
- const targetHelperModule = TARGET_HELPERS[Targets.TYPES.SERVICE_WORKER];
- await targetHelperModule.addOrSetSessionDataEntry({
- watcher: this,
- type: "browser-element-host",
- entries: [host],
- updateType: "set",
- });
+ return this.addOrSetDataEntry("browser-element-host", [host], "set");
}
};