diff options
Diffstat (limited to '')
-rw-r--r-- | devtools/server/actors/watcher.js | 353 |
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"); } }; |