diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-15 03:34:50 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-15 03:34:50 +0000 |
commit | def92d1b8e9d373e2f6f27c366d578d97d8960c6 (patch) | |
tree | 2ef34b9ad8bb9a9220e05d60352558b15f513894 /devtools/server/actors | |
parent | Adding debian version 125.0.3-1. (diff) | |
download | firefox-def92d1b8e9d373e2f6f27c366d578d97d8960c6.tar.xz firefox-def92d1b8e9d373e2f6f27c366d578d97d8960c6.zip |
Merging upstream version 126.0.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'devtools/server/actors')
41 files changed, 491 insertions, 1387 deletions
diff --git a/devtools/server/actors/accessibility/audit/contrast.js b/devtools/server/actors/accessibility/audit/contrast.js index 68e7b497f8..95510be4fc 100644 --- a/devtools/server/actors/accessibility/audit/contrast.js +++ b/devtools/server/actors/accessibility/audit/contrast.js @@ -42,15 +42,17 @@ loader.lazyRequireGetter( ); loader.lazyRequireGetter( this, - "DevToolsWorker", - "resource://devtools/shared/worker/worker.js", - true -); -loader.lazyRequireGetter( - this, "InspectorActorUtils", "resource://devtools/server/actors/inspector/utils.js" ); +const lazy = {}; +ChromeUtils.defineESModuleGetters( + lazy, + { + DevToolsWorker: "resource://devtools/shared/worker/worker.sys.mjs", + }, + { global: "contextual" } +); const WORKER_URL = "resource://devtools/server/actors/accessibility/worker.js"; const HIGHLIGHTED_PSEUDO_CLASS = ":-moz-devtools-highlighted"; @@ -58,7 +60,7 @@ const { LARGE_TEXT: { BOLD_LARGE_TEXT_MIN_PIXELS, LARGE_TEXT_MIN_PIXELS }, } = require("resource://devtools/shared/accessibility.js"); -loader.lazyGetter(this, "worker", () => new DevToolsWorker(WORKER_URL)); +loader.lazyGetter(this, "worker", () => new lazy.DevToolsWorker(WORKER_URL)); /** * Get canvas rendering context for the current target window bound by the bounds of the diff --git a/devtools/server/actors/blackboxing.js b/devtools/server/actors/blackboxing.js index 49dfc8180d..8163327b46 100644 --- a/devtools/server/actors/blackboxing.js +++ b/devtools/server/actors/blackboxing.js @@ -9,9 +9,10 @@ const { blackboxingSpec, } = require("resource://devtools/shared/specs/blackboxing.js"); -const { - SessionDataHelpers, -} = require("resource://devtools/server/actors/watcher/SessionDataHelpers.jsm"); +const { SessionDataHelpers } = ChromeUtils.importESModule( + "resource://devtools/server/actors/watcher/SessionDataHelpers.sys.mjs", + { global: "contextual" } +); const { SUPPORTED_DATA } = SessionDataHelpers; const { BLACKBOXING } = SUPPORTED_DATA; diff --git a/devtools/server/actors/breakpoint-list.js b/devtools/server/actors/breakpoint-list.js index 1f9d6c0bf9..a28ffc3f7a 100644 --- a/devtools/server/actors/breakpoint-list.js +++ b/devtools/server/actors/breakpoint-list.js @@ -9,9 +9,10 @@ const { breakpointListSpec, } = require("resource://devtools/shared/specs/breakpoint-list.js"); -const { - SessionDataHelpers, -} = require("resource://devtools/server/actors/watcher/SessionDataHelpers.jsm"); +const { SessionDataHelpers } = ChromeUtils.importESModule( + "resource://devtools/server/actors/watcher/SessionDataHelpers.sys.mjs", + { global: "contextual" } +); const { SUPPORTED_DATA } = SessionDataHelpers; const { BREAKPOINTS, XHR_BREAKPOINTS, EVENT_BREAKPOINTS } = SUPPORTED_DATA; diff --git a/devtools/server/actors/inspector/walker.js b/devtools/server/actors/inspector/walker.js index 50df1720b7..fbf417565c 100644 --- a/devtools/server/actors/inspector/walker.js +++ b/devtools/server/actors/inspector/walker.js @@ -384,7 +384,6 @@ class WalkerActor extends Actor { this.layoutHelpers = null; this._orphaned = null; this._retainedOrphans = null; - this._nodeActorsMap = null; this.targetActor.off("will-navigate", this.onFrameUnload); this.targetActor.off("window-ready", this.onFrameLoad); @@ -433,6 +432,9 @@ class WalkerActor extends Actor { this._onEventListenerChange ); + // Only nullify some key attributes after having removed all the listeners + // as they may still be used in the related listeners. + this._nodeActorsMap = null; this.onMutations = null; this.layoutActor = null; diff --git a/devtools/server/actors/page-style.js b/devtools/server/actors/page-style.js index 1783b58a8f..b37816c85f 100644 --- a/devtools/server/actors/page-style.js +++ b/devtools/server/actors/page-style.js @@ -704,6 +704,7 @@ class PageStyleActor extends Actor { case "::first-line": case "::selection": case "::highlight": + case "::target-text": return true; case "::marker": return this._nodeIsListItem(node); diff --git a/devtools/server/actors/resources/index.js b/devtools/server/actors/resources/index.js index e2857502ad..cfc941a161 100644 --- a/devtools/server/actors/resources/index.js +++ b/devtools/server/actors/resources/index.js @@ -390,6 +390,14 @@ exports.hasResourceTypesForTargets = hasResourceTypesForTargets; * List of all type of resource to stop listening to. */ function unwatchResources(rootOrWatcherOrTargetActor, resourceTypes) { + // If we are given a target actor, filter out the resource types supported by the target. + // When using sharedData to pass types between processes, we are passing them for all target types. + const { targetType } = rootOrWatcherOrTargetActor; + // Only target actors usecase will have a target type. + // For Root and Watcher we process the `resourceTypes` list unfiltered. + if (targetType) { + resourceTypes = getResourceTypesForTargetType(resourceTypes, targetType); + } for (const resourceType of resourceTypes) { // Pull all info about this resource type from `Resources` global object const { watchers } = getResourceTypeEntry( @@ -415,6 +423,14 @@ exports.unwatchResources = unwatchResources; * List of all type of resource to clear. */ function clearResources(rootOrWatcherOrTargetActor, resourceTypes) { + // If we are given a target actor, filter out the resource types supported by the target. + // When using sharedData to pass types between processes, we are passing them for all target types. + const { targetType } = rootOrWatcherOrTargetActor; + // Only target actors usecase will have a target type. + // For Root and Watcher we process the `resourceTypes` list unfiltered. + if (targetType) { + resourceTypes = getResourceTypesForTargetType(resourceTypes, targetType); + } for (const resourceType of resourceTypes) { const { watchers } = getResourceTypeEntry( rootOrWatcherOrTargetActor, diff --git a/devtools/server/actors/resources/jstracer-state.js b/devtools/server/actors/resources/jstracer-state.js index 74491a6ced..1bb4723b55 100644 --- a/devtools/server/actors/resources/jstracer-state.js +++ b/devtools/server/actors/resources/jstracer-state.js @@ -8,13 +8,10 @@ const { TYPES: { JSTRACER_STATE }, } = require("resource://devtools/server/actors/resources/index.js"); -// Bug 1827382, as this module can be used from the worker thread, -// the following JSM may be loaded by the worker loader until -// we have proper support for ESM from workers. -const { - addTracingListener, - removeTracingListener, -} = require("resource://devtools/server/tracer/tracer.jsm"); +const { JSTracer } = ChromeUtils.importESModule( + "resource://devtools/server/tracer/tracer.sys.mjs", + { global: "contextual" } +); const { LOG_METHODS } = require("resource://devtools/server/actors/tracer.js"); const Targets = require("resource://devtools/server/actors/targets/index.js"); @@ -42,7 +39,7 @@ class TracingStateWatcher { this.tracingListener = { onTracingToggled: this.onTracingToggled.bind(this), }; - addTracingListener(this.tracingListener); + JSTracer.addTracingListener(this.tracingListener); } /** @@ -52,7 +49,7 @@ class TracingStateWatcher { if (!this.tracingListener) { return; } - removeTracingListener(this.tracingListener); + JSTracer.removeTracingListener(this.tracingListener); } /** diff --git a/devtools/server/actors/resources/network-events.js b/devtools/server/actors/resources/network-events.js index 909c16e052..9401d835ff 100644 --- a/devtools/server/actors/resources/network-events.js +++ b/devtools/server/actors/resources/network-events.js @@ -9,9 +9,9 @@ const { isWindowGlobalPartOfContext } = ChromeUtils.importESModule( "resource://devtools/server/actors/watcher/browsing-context-helpers.sys.mjs", { global: "contextual" } ); -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" } ); @@ -253,7 +253,7 @@ class NetworkEventWatcher { // (i.e. the process where this Watcher runs) const isParentProcessOnlyBrowserToolbox = this.watcherActor.sessionContext.type == "all" && - !WatcherRegistry.isWatchingTargets( + !ParentProcessWatcherRegistry.isWatchingTargets( this.watcherActor, Targets.TYPES.FRAME ); diff --git a/devtools/server/actors/resources/sources.js b/devtools/server/actors/resources/sources.js index c4f0601106..63a5987e0e 100644 --- a/devtools/server/actors/resources/sources.js +++ b/devtools/server/actors/resources/sources.js @@ -44,6 +44,8 @@ class SourceWatcher { this.sourcesManager = targetActor.sourcesManager; this.onAvailable = onAvailable; + threadActor.attach({}); + // Disable `ThreadActor.newSource` RDP event in order to avoid unnecessary traffic threadActor.disableNewSourceEvents(); diff --git a/devtools/server/actors/resources/utils/parent-process-storage.js b/devtools/server/actors/resources/utils/parent-process-storage.js index 760e6e4d38..1d3a3dd341 100644 --- a/devtools/server/actors/resources/utils/parent-process-storage.js +++ b/devtools/server/actors/resources/utils/parent-process-storage.js @@ -79,11 +79,12 @@ class ParentProcessStorage { watcherActor.sessionContext; await this._spawnActor(addonBrowsingContextID, addonInnerWindowId); } else if (watcherActor.sessionContext.type == "all") { - const parentProcessTargetActor = - this.watcherActor.getTargetActorInParentProcess(); - const { browsingContextID, innerWindowId } = - parentProcessTargetActor.form(); - await this._spawnActor(browsingContextID, innerWindowId); + // Note that there should be only one such target in the browser toolbox. + // The Parent Process Target Actor. + for (const targetActor of this.watcherActor.getTargetActorsInParentProcess()) { + const { browsingContextID, innerWindowId } = targetActor.form(); + await this._spawnActor(browsingContextID, innerWindowId); + } } else { throw new Error( "Unsupported session context type=" + watcherActor.sessionContext.type diff --git a/devtools/server/actors/style-rule.js b/devtools/server/actors/style-rule.js index e9f39fa3d0..9ddd6a380c 100644 --- a/devtools/server/actors/style-rule.js +++ b/devtools/server/actors/style-rule.js @@ -724,7 +724,7 @@ class StyleRuleActor extends Actor { const cssText = await this.pageStyle.styleSheetsManager.getText( resourceId ); - const { text } = getRuleText(cssText, this.line, this.column); + const text = getRuleText(cssText, this.line, this.column); // Cache the result on the rule actor to avoid parsing again next time this._failedToGetRuleText = false; this.authoredText = text; @@ -823,19 +823,32 @@ class StyleRuleActor extends Actor { this.pageStyle.styleSheetsManager.getStyleSheetResourceId( this._parentSheet ); - let cssText = await this.pageStyle.styleSheetsManager.getText(resourceId); - const { offset, text } = getRuleText(cssText, this.line, this.column); - cssText = - cssText.substring(0, offset) + - newText + - cssText.substring(offset + text.length); - - await this.pageStyle.styleSheetsManager.setStyleSheetText( - resourceId, - cssText, - { kind: UPDATE_PRESERVING_RULES } + const sheetText = await this.pageStyle.styleSheetsManager.getText( + resourceId + ); + const cssText = InspectorUtils.replaceBlockRuleBodyTextInStylesheet( + sheetText, + this.line, + this.column, + newText ); + + if (typeof cssText !== "string") { + throw new Error( + "Error in InspectorUtils.replaceBlockRuleBodyTextInStylesheet" + ); + } + + // setStyleSheetText will parse the stylesheet which can be costly, so only do it + // if the text has actually changed. + if (sheetText !== newText) { + await this.pageStyle.styleSheetsManager.setStyleSheetText( + resourceId, + cssText, + { kind: UPDATE_PRESERVING_RULES } + ); + } } this.authoredText = newText; diff --git a/devtools/server/actors/target-configuration.js b/devtools/server/actors/target-configuration.js index b6db235143..e739c1cc3d 100644 --- a/devtools/server/actors/target-configuration.js +++ b/devtools/server/actors/target-configuration.js @@ -9,9 +9,10 @@ const { targetConfigurationSpec, } = require("resource://devtools/shared/specs/target-configuration.js"); -const { - SessionDataHelpers, -} = require("resource://devtools/server/actors/watcher/SessionDataHelpers.jsm"); +const { SessionDataHelpers } = ChromeUtils.importESModule( + "resource://devtools/server/actors/watcher/SessionDataHelpers.sys.mjs", + { global: "contextual" } +); const { isBrowsingContextPartOfContext } = ChromeUtils.importESModule( "resource://devtools/server/actors/watcher/browsing-context-helpers.sys.mjs", { global: "contextual" } @@ -486,7 +487,10 @@ class TargetConfigurationActor extends Actor { "bf-cache-navigation-pageshow", this._onBfCacheNavigation ); - this._restoreParentProcessConfiguration(); + // Avoid trying to restore if the related context is already being destroyed + if (this._browsingContext && !this._browsingContext.isDiscarded) { + this._restoreParentProcessConfiguration(); + } super.destroy(); } } diff --git a/devtools/server/actors/targets/base-target-actor.js b/devtools/server/actors/targets/base-target-actor.js index f3fc2a89e7..646874c4f1 100644 --- a/devtools/server/actors/targets/base-target-actor.js +++ b/devtools/server/actors/targets/base-target-actor.js @@ -203,6 +203,18 @@ class BaseTargetActor extends Actor { ) { return; } + // In the browser toolbox, when debugging the parent process, we should only toggle the tracer in the Parent Process Target Actor. + // We have to ignore any frame target which may run in the parent process. + // For example DevTools documents or a tab running in the parent process. + // (PROCESS_TYPE_DEFAULT refers to the parent process) + if ( + this.sessionContext.type == "all" && + this.targetType === Targets.TYPES.FRAME && + this.typeName != "parentProcessTarget" && + Services.appinfo.processType == Services.appinfo.PROCESS_TYPE_DEFAULT + ) { + return; + } const tracerActor = this.getTargetScopedActor("tracer"); tracerActor.startTracing(options.tracerOptions); } else if (this.hasTargetScopedActor("tracer")) { diff --git a/devtools/server/actors/targets/session-data-processors/breakpoints.js b/devtools/server/actors/targets/session-data-processors/breakpoints.js index 67c270654d..8ecd80ad64 100644 --- a/devtools/server/actors/targets/session-data-processors/breakpoints.js +++ b/devtools/server/actors/targets/session-data-processors/breakpoints.js @@ -32,11 +32,9 @@ module.exports = { threadActor.removeAllBreakpoints(); } const isTargetCreation = threadActor.state == THREAD_STATES.DETACHED; - if (isTargetCreation && !targetActor.targetType.endsWith("worker")) { + if (isTargetCreation) { // If addOrSetSessionDataEntry is called during target creation, attach the // thread actor automatically and pass the initial breakpoints. - // However, do not attach the thread actor for Workers. They use a codepath - // which releases the worker on `attach`. For them, the client will call `attach`. (bug 1691986) await threadActor.attach({ breakpoints: entries }); } else { // If addOrSetSessionDataEntry is called for an existing target, set the new diff --git a/devtools/server/actors/targets/session-data-processors/event-breakpoints.js b/devtools/server/actors/targets/session-data-processors/event-breakpoints.js index 4eb9e4f3a8..1b2dbd847e 100644 --- a/devtools/server/actors/targets/session-data-processors/event-breakpoints.js +++ b/devtools/server/actors/targets/session-data-processors/event-breakpoints.js @@ -16,11 +16,8 @@ module.exports = { updateType ) { const { threadActor } = targetActor; - // Same as comments for XHR breakpoints. See lines 117-118 - if ( - threadActor.state == THREAD_STATES.DETACHED && - !targetActor.targetType.endsWith("worker") - ) { + // The thread actor has to be initialized in order to have functional breakpoints + if (threadActor.state == THREAD_STATES.DETACHED) { threadActor.attach(); } if (updateType == "set") { diff --git a/devtools/server/actors/targets/session-data-processors/index.js b/devtools/server/actors/targets/session-data-processors/index.js index 19b7d69302..72bc769dd1 100644 --- a/devtools/server/actors/targets/session-data-processors/index.js +++ b/devtools/server/actors/targets/session-data-processors/index.js @@ -4,9 +4,10 @@ "use strict"; -const { - SessionDataHelpers, -} = require("resource://devtools/server/actors/watcher/SessionDataHelpers.jsm"); +const { SessionDataHelpers } = ChromeUtils.importESModule( + "resource://devtools/server/actors/watcher/SessionDataHelpers.sys.mjs", + { global: "contextual" } +); const { SUPPORTED_DATA } = SessionDataHelpers; const SessionDataProcessors = {}; diff --git a/devtools/server/actors/targets/session-data-processors/thread-configuration.js b/devtools/server/actors/targets/session-data-processors/thread-configuration.js index ad5c0fe024..381a62f640 100644 --- a/devtools/server/actors/targets/session-data-processors/thread-configuration.js +++ b/devtools/server/actors/targets/session-data-processors/thread-configuration.js @@ -28,12 +28,9 @@ module.exports = { threadOptions[key] = value; } - if ( - !targetActor.targetType.endsWith("worker") && - targetActor.threadActor.state == THREAD_STATES.DETACHED - ) { + if (targetActor.threadActor.state == THREAD_STATES.DETACHED) { await targetActor.threadActor.attach(threadOptions); - } else { + } else if (!targetActor.threadActor.isDestroyed()) { // Regarding `updateType`, `entries` is always a partial set of configurations. // We will acknowledge the passed attribute, but if we had set some other attributes // before this call, they will stay as-is. diff --git a/devtools/server/actors/targets/session-data-processors/xhr-breakpoints.js b/devtools/server/actors/targets/session-data-processors/xhr-breakpoints.js index 3bbcf54aaf..81ecb72fb2 100644 --- a/devtools/server/actors/targets/session-data-processors/xhr-breakpoints.js +++ b/devtools/server/actors/targets/session-data-processors/xhr-breakpoints.js @@ -22,10 +22,7 @@ module.exports = { // The thread actor has to be initialized in order to correctly // retrieve the stack trace when hitting an XHR - if ( - threadActor.state == THREAD_STATES.DETACHED && - !targetActor.targetType.endsWith("worker") - ) { + if (threadActor.state == THREAD_STATES.DETACHED) { await threadActor.attach(); } diff --git a/devtools/server/actors/targets/target-actor-registry.sys.mjs b/devtools/server/actors/targets/target-actor-registry.sys.mjs index 25c1ac1234..bc7adffec2 100644 --- a/devtools/server/actors/targets/target-actor-registry.sys.mjs +++ b/devtools/server/actors/targets/target-actor-registry.sys.mjs @@ -9,7 +9,8 @@ // are still using message manager in order to avoid being destroyed on navigation. // And because of this, these actors aren't using JS Window Actor. const windowGlobalTargetActors = new Set(); -let xpcShellTargetActor = null; + +const xpcShellTargetActors = new Set(); export var TargetActorRegistry = { registerTargetActor(targetActor) { @@ -21,15 +22,15 @@ export var TargetActorRegistry = { }, registerXpcShellTargetActor(targetActor) { - xpcShellTargetActor = targetActor; + xpcShellTargetActors.add(targetActor); }, - unregisterXpcShellTargetActor() { - xpcShellTargetActor = null; + unregisterXpcShellTargetActor(targetActor) { + xpcShellTargetActors.delete(targetActor); }, - get xpcShellTargetActor() { - return xpcShellTargetActor; + get xpcShellTargetActors() { + return xpcShellTargetActors; }, /** diff --git a/devtools/server/actors/targets/webextension.js b/devtools/server/actors/targets/webextension.js index c717b53011..47127dc65c 100644 --- a/devtools/server/actors/targets/webextension.js +++ b/devtools/server/actors/targets/webextension.js @@ -162,6 +162,12 @@ class WebExtensionTargetActor extends ParentProcessTargetActor { // URL shown in the window tittle when the addon debugger is opened). const extensionWindow = this._searchForExtensionWindow(); this.setDocShell(extensionWindow.docShell); + + // `setDocShell` will force the instantiation of the thread actor. + // We now have to initialize it in order to listen for new global + // which allows to properly detect addon reload via _shouldAddNewGlobalAsDebuggee + // which may call _onNewExtensionWindow. + this.threadActor.attach({}); } // Override the ParentProcessTargetActor's override in order to only iterate diff --git a/devtools/server/actors/targets/window-global.js b/devtools/server/actors/targets/window-global.js index 6719f0518d..f8f5e5f3c6 100644 --- a/devtools/server/actors/targets/window-global.js +++ b/devtools/server/actors/targets/window-global.js @@ -381,6 +381,15 @@ class WindowGlobalTargetActor extends BaseTargetActor { // (This is also probably meant to disappear once EFT is the only supported codepath) this._docShellsObserved = false; DevToolsUtils.executeSoon(() => this._watchDocshells()); + + // The `watchedByDevTools` enables gecko behavior tied to this flag, such as: + // - reporting the contents of HTML loaded in the docshells, + // - or capturing stacks for the network monitor. + // + // This flag can only be set on top level BrowsingContexts. + if (!this.browsingContext.parent) { + this.browsingContext.watchedByDevTools = true; + } } get docShell() { @@ -480,6 +489,10 @@ class WindowGlobalTargetActor extends BaseTargetActor { return this.browsingContext?.id; } + get innerWindowId() { + return this.window?.windowGlobalChild.innerWindowId; + } + get browserId() { return this.browsingContext?.browserId; } @@ -687,6 +700,11 @@ class WindowGlobalTargetActor extends BaseTargetActor { response.outerWindowID = this.outerWindowID; } + // If the actor is already being destroyed, avoid re-registering the target scoped actors + if (this.destroying) { + return response; + } + const actors = this._createExtraActors(); Object.assign(response, actors); @@ -731,6 +749,17 @@ class WindowGlobalTargetActor extends BaseTargetActor { this._touchSimulator = null; } + // The watchedByDevTools flag is only set on top level BrowsingContext + // (as it then cascades to all its children), + // and when destroying the target, we should tell the platform we no longer + // observe this BrowsingContext and set this attribute to false. + if ( + this.browsingContext?.watchedByDevTools && + !this.browsingContext.parent + ) { + this.browsingContext.watchedByDevTools = false; + } + // Check for `docShell` availability, as it can be already gone during // Firefox shutdown. if (this.docShell) { @@ -1314,10 +1343,6 @@ class WindowGlobalTargetActor extends BaseTargetActor { if (typeof options.touchEventsOverride !== "undefined") { const enableTouchSimulator = options.touchEventsOverride === "enabled"; - this.docShell.metaViewportOverride = enableTouchSimulator - ? Ci.nsIDocShell.META_VIEWPORT_OVERRIDE_ENABLED - : Ci.nsIDocShell.META_VIEWPORT_OVERRIDE_NONE; - // We want to reload the document if it's an "existing" top level target on which // the touch simulator will be toggled and the user has turned the // "reload on touch simulation" setting on. @@ -1384,7 +1409,14 @@ class WindowGlobalTargetActor extends BaseTargetActor { */ _restoreTargetConfiguration() { if (this._restoreFocus && this.browsingContext?.isActive) { - this.window.focus(); + try { + this.window.focus(); + } catch (e) { + // When closing devtools while navigating, focus() may throw NS_ERROR_XPC_SECURITY_MANAGER_VETO + if (e.result != Cr.NS_ERROR_XPC_SECURITY_MANAGER_VETO) { + throw e; + } + } } } @@ -1688,17 +1720,6 @@ class DebuggerProgressListener { this._knownWindowIDs.set(getWindowID(win), win); } - // The `watchedByDevTools` enables gecko behavior tied to this flag, such as: - // - reporting the contents of HTML loaded in the docshells, - // - or capturing stacks for the network monitor. - // - // This flag is also set in frame-helper but in the case of the browser toolbox, we - // don't have the watcher enabled by default yet, and as a result we need to set it - // here for the parent process window global. - // This should be removed as part of Bug 1709529. - if (this._targetActor.typeName === "parentProcessTarget") { - docShell.browsingContext.watchedByDevTools = true; - } // Immediately enable CSS error reports on new top level docshells, if this was already enabled. // This is specific to MBT and WebExtension targets (so the isRootActor check). if ( @@ -1741,12 +1762,6 @@ class DebuggerProgressListener { for (const win of windows) { this._knownWindowIDs.delete(getWindowID(win)); } - - // We only reset it for parent process target actor as the flag should be set in parent - // process, and thus is set elsewhere for other type of BrowsingContextActor. - if (this._targetActor.typeName === "parentProcessTarget") { - docShell.browsingContext.watchedByDevTools = false; - } } _getWindowsInDocShell(docShell) { diff --git a/devtools/server/actors/targets/worker.js b/devtools/server/actors/targets/worker.js index 20b60cfa24..7604b5be6e 100644 --- a/devtools/server/actors/targets/worker.js +++ b/devtools/server/actors/targets/worker.js @@ -126,12 +126,6 @@ class WorkerTargetActor extends BaseTargetActor { return this._sourcesManager; } - // This is called from the ThreadActor#onAttach method - onThreadAttached() { - // This isn't an RDP event and is only listened to from startup/worker.js. - this.emit("worker-thread-attached"); - } - destroy() { super.destroy(); diff --git a/devtools/server/actors/thread-configuration.js b/devtools/server/actors/thread-configuration.js index f0c697bb51..d3b7e229bf 100644 --- a/devtools/server/actors/thread-configuration.js +++ b/devtools/server/actors/thread-configuration.js @@ -9,9 +9,10 @@ const { threadConfigurationSpec, } = require("resource://devtools/shared/specs/thread-configuration.js"); -const { - SessionDataHelpers, -} = require("resource://devtools/server/actors/watcher/SessionDataHelpers.jsm"); +const { SessionDataHelpers } = ChromeUtils.importESModule( + "resource://devtools/server/actors/watcher/SessionDataHelpers.sys.mjs", + { global: "contextual" } +); const { SUPPORTED_DATA: { THREAD_CONFIGURATION }, } = SessionDataHelpers; diff --git a/devtools/server/actors/thread.js b/devtools/server/actors/thread.js index 07dcc27a6a..0042e76a2a 100644 --- a/devtools/server/actors/thread.js +++ b/devtools/server/actors/thread.js @@ -418,12 +418,6 @@ class ThreadActor extends Actor { this.alreadyAttached = true; this.dbg.enable(); - // Notify the target actor that we've finished attaching. If this is a worker - // thread which was paused until attaching, this will allow content to - // begin executing. - if (this.targetActor.onThreadAttached) { - this.targetActor.onThreadAttached(); - } if (Services.obs) { // Set a wrappedJSObject property so |this| can be sent via the observer service // for the xpcshell harness. @@ -535,6 +529,13 @@ class ThreadActor extends Actor { } async setBreakpoint(location, options) { + // Automatically initialize the thread actor if it wasn't yet done. + // Note that ideally, it should rather be done via reconfigure/thread configuration. + if (this._state === STATES.DETACHED) { + this.attach({}); + this.addAllSources(); + } + let actor = this.breakpointActorMap.get(location); // Avoid resetting the exact same breakpoint twice if (actor && JSON.stringify(actor.options) == JSON.stringify(options)) { @@ -597,7 +598,9 @@ class ThreadActor extends Actor { } getAvailableEventBreakpoints() { - return getAvailableEventBreakpoints(this.targetActor.window); + return getAvailableEventBreakpoints( + this.targetActor.window || this.targetActor.workerGlobal + ); } getActiveEventBreakpoints() { return Array.from(this._activeEventBreakpoints); diff --git a/devtools/server/actors/tracer.js b/devtools/server/actors/tracer.js index bf759cee5f..d98749ceb1 100644 --- a/devtools/server/actors/tracer.js +++ b/devtools/server/actors/tracer.js @@ -4,16 +4,14 @@ "use strict"; -// Bug 1827382, as this module can be used from the worker thread, -// the following JSM may be loaded by the worker loader until -// we have proper support for ESM from workers. -const { - startTracing, - stopTracing, - addTracingListener, - removeTracingListener, - NEXT_INTERACTION_MESSAGE, -} = require("resource://devtools/server/tracer/tracer.jsm"); +const lazy = {}; +ChromeUtils.defineESModuleGetters( + lazy, + { + JSTracer: "resource://devtools/server/tracer/tracer.sys.mjs", + }, + { global: "contextual" } +); const { Actor } = require("resource://devtools/shared/protocol.js"); const { tracerSpec } = require("resource://devtools/shared/specs/tracer.js"); @@ -136,10 +134,10 @@ class TracerActor extends Actor { onTracingPending: this.onTracingPending.bind(this), onTracingDOMMutation: this.onTracingDOMMutation.bind(this), }; - addTracingListener(this.tracingListener); + lazy.JSTracer.addTracingListener(this.tracingListener); this.traceValues = !!options.traceValues; try { - startTracing({ + lazy.JSTracer.startTracing({ global: this.targetActor.window || this.targetActor.workerGlobal, prefix: options.prefix || "", // Enable receiving the `currentDOMEvent` being passed to `onTracingFrame` @@ -170,10 +168,10 @@ class TracerActor extends Actor { return; } // Remove before stopping to prevent receiving the stop notification - removeTracingListener(this.tracingListener); + lazy.JSTracer.removeTracingListener(this.tracingListener); this.tracingListener = null; - stopTracing(); + lazy.JSTracer.stopTracing(); this.logMethod = null; } @@ -230,7 +228,7 @@ class TracerActor extends Actor { if (consoleMessageWatcher) { consoleMessageWatcher.emitMessages([ { - arguments: [NEXT_INTERACTION_MESSAGE], + arguments: [lazy.JSTracer.NEXT_INTERACTION_MESSAGE], styles: [], level: "jstracer", chromeContext: false, @@ -510,7 +508,7 @@ class TracerActor extends Actor { * A string to be displayed as a prefix of any logged frame. * @param {String} options.why * A string to explain why the function stopped. - * See tracer.jsm's FRAME_EXIT_REASONS. + * See tracer.sys.mjs's FRAME_EXIT_REASONS. * @param {Debugger.Object|primitive} options.rv * The returned value. It can be the returned value, or the thrown exception. * It is either a primitive object, otherwise it is a Debugger.Object for any other JS Object type. diff --git a/devtools/server/actors/utils/event-breakpoints.js b/devtools/server/actors/utils/event-breakpoints.js index a7752b8201..8fbefec804 100644 --- a/devtools/server/actors/utils/event-breakpoints.js +++ b/devtools/server/actors/utils/event-breakpoints.js @@ -131,7 +131,8 @@ const AVAILABLE_BREAKPOINTS = [ items: [ // The condition should be removed when "dom.element.popover.enabled" is removed generalEvent("control", "beforetoggle", () => - Services.prefs.getBoolPref("dom.element.popover.enabled") + // Services.prefs isn't available on worker targets + Services.prefs?.getBoolPref("dom.element.popover.enabled") ), generalEvent("control", "blur"), generalEvent("control", "change"), @@ -139,7 +140,11 @@ const AVAILABLE_BREAKPOINTS = [ generalEvent("control", "focusin"), generalEvent("control", "focusout"), // The condition should be removed when "dom.element.invokers.enabled" is removed - generalEvent("control", "invoke", win => "InvokeEvent" in win), + generalEvent( + "control", + "invoke", + global => global && "InvokeEvent" in global + ), generalEvent("control", "reset"), generalEvent("control", "resize"), generalEvent("control", "scroll"), @@ -483,17 +488,17 @@ exports.getAvailableEventBreakpoints = getAvailableEventBreakpoints; /** * Get all available event breakpoints * - * @param {Window} window + * @param {Window|WorkerGlobalScope} global * @returns {Array<Object>} An array containing object with 2 properties, an id and a name, * representing the event. */ -function getAvailableEventBreakpoints(window) { +function getAvailableEventBreakpoints(global) { const available = []; for (const { name, items } of AVAILABLE_BREAKPOINTS) { available.push({ name, events: items - .filter(item => !item.condition || item.condition(window)) + .filter(item => !item.condition || item.condition(global)) .map(item => ({ id: item.id, name: item.name, diff --git a/devtools/server/actors/utils/style-utils.js b/devtools/server/actors/utils/style-utils.js index 5f2e912002..1d52448fb6 100644 --- a/devtools/server/actors/utils/style-utils.js +++ b/devtools/server/actors/utils/style-utils.js @@ -4,8 +4,6 @@ "use strict"; -const { getCSSLexer } = require("resource://devtools/shared/css/lexer.js"); - const XHTML_NS = "http://www.w3.org/1999/xhtml"; const FONT_PREVIEW_TEXT = "Abc"; const FONT_PREVIEW_FONT_SIZE = 40; @@ -120,66 +118,12 @@ function getRuleText(initialText, line, column) { throw new Error("Location information is missing"); } - const { offset: textOffset, text } = getTextAtLineColumn( - initialText, - line, - column - ); - const lexer = getCSSLexer(text); - - // Search forward for the opening brace. - while (true) { - const token = lexer.nextToken(); - if (!token) { - throw new Error("couldn't find start of the rule"); - } - if (token.tokenType === "symbol" && token.text === "{") { - break; - } - } - - // Now collect text until we see the matching close brace. - let braceDepth = 1; - let startOffset, endOffset; - while (true) { - const token = lexer.nextToken(); - if (!token) { - break; - } - if (startOffset === undefined) { - startOffset = token.startOffset; - } - if (token.tokenType === "symbol") { - if (token.text === "{") { - ++braceDepth; - } else if (token.text === "}") { - --braceDepth; - if (braceDepth == 0) { - break; - } - } - } - endOffset = token.endOffset; - } - - // If the rule was of the form "selector {" with no closing brace - // and no properties, just return an empty string. - if (startOffset === undefined) { - return { offset: 0, text: "" }; - } - // If the input didn't have any tokens between the braces (e.g., - // "div {}"), then the endOffset won't have been set yet; so account - // for that here. - if (endOffset === undefined) { - endOffset = startOffset; + const { text } = getTextAtLineColumn(initialText, line, column); + const res = InspectorUtils.getRuleBodyText(text); + if (res === null || typeof res === "undefined") { + throw new Error("Couldn't find rule"); } - - // Note that this approach will preserve comments, despite the fact - // that cssTokenizer skips them. - return { - offset: textOffset + startOffset, - text: text.substring(startOffset, endOffset), - }; + return res; } exports.getRuleText = getRuleText; diff --git a/devtools/server/actors/utils/stylesheets-manager.js b/devtools/server/actors/utils/stylesheets-manager.js index a9c0705e8d..1c065afd4e 100644 --- a/devtools/server/actors/utils/stylesheets-manager.js +++ b/devtools/server/actors/utils/stylesheets-manager.js @@ -446,10 +446,12 @@ class StyleSheetsManager extends EventEmitter { InspectorUtils.parseStyleSheet(styleSheet, text); modifiedStyleSheets.set(styleSheet, text); - const { atRules, ruleCount } = - this.getStyleSheetRuleCountAndAtRules(styleSheet); - + // getStyleSheetRuleCountAndAtRules can be costly, so only call it when needed, + // i.e. when the whole stylesheet is modified, not when a rule body is. + let atRules, ruleCount; if (kind !== UPDATE_PRESERVING_RULES) { + ({ atRules, ruleCount } = + this.getStyleSheetRuleCountAndAtRules(styleSheet)); this.#notifyPropertyChanged(resourceId, "ruleCount", ruleCount); } @@ -465,13 +467,15 @@ class StyleSheetsManager extends EventEmitter { }); } - this.#onStyleSheetUpdated({ - resourceId, - updateKind: "at-rules-changed", - updates: { - resourceUpdates: { atRules }, - }, - }); + if (kind !== UPDATE_PRESERVING_RULES) { + this.#onStyleSheetUpdated({ + resourceId, + updateKind: "at-rules-changed", + updates: { + resourceUpdates: { atRules }, + }, + }); + } } /** @@ -705,6 +709,13 @@ class StyleSheetsManager extends EventEmitter { line: InspectorUtils.getRelativeRuleLine(rule), column: InspectorUtils.getRuleColumn(rule), }); + } else if (className === "CSSPropertyRule") { + atRules.push({ + type: "property", + propertyName: rule.name, + line: InspectorUtils.getRelativeRuleLine(rule), + column: InspectorUtils.getRuleColumn(rule), + }); } } return { 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"); } }; diff --git a/devtools/server/actors/watcher/WatcherRegistry.sys.mjs b/devtools/server/actors/watcher/ParentProcessWatcherRegistry.sys.mjs index ac8bc7f0c8..e9b3a9d50d 100644 --- a/devtools/server/actors/watcher/WatcherRegistry.sys.mjs +++ b/devtools/server/actors/watcher/ParentProcessWatcherRegistry.sys.mjs @@ -24,10 +24,9 @@ * while from the content process, we will read `sharedData` directly. */ -import { ActorManagerParent } from "resource://gre/modules/ActorManagerParent.sys.mjs"; - -const { SessionDataHelpers } = ChromeUtils.import( - "resource://devtools/server/actors/watcher/SessionDataHelpers.jsm" +const { SessionDataHelpers } = ChromeUtils.importESModule( + "resource://devtools/server/actors/watcher/SessionDataHelpers.sys.mjs", + { global: "contextual" } ); const { SUPPORTED_DATA } = SessionDataHelpers; @@ -68,7 +67,7 @@ function persistMapToSharedData() { Services.ppmm.sharedData.flush(); } -export const WatcherRegistry = { +export const ParentProcessWatcherRegistry = { /** * Tells if a given watcher currently watches for a given target type. * @@ -178,13 +177,16 @@ export const WatcherRegistry = { updateType ); + // Flush sharedData before registering the JS Actors as it is used + // during their instantiation. + persistMapToSharedData(); + // Register the JS Window Actor the first time we start watching for something (e.g. resource, target, …). - registerJSWindowActor(); - if (sessionData?.targets?.includes("process")) { + if (watcher.sessionContext.type == "all") { + registerBrowserToolboxJSProcessActor(); + } else { registerJSProcessActor(); } - - persistMapToSharedData(); }, /** @@ -245,9 +247,9 @@ export const WatcherRegistry = { * if we remove all entries. But we aren't removing all breakpoints. * So here, we force clearing any reference to the watcher actor when it destroys. */ - unregisterWatcher(watcher) { - sessionDataByWatcherActor.delete(watcher.actorID); - watcherActors.delete(watcher.actorID); + unregisterWatcher(watcherActorID) { + sessionDataByWatcherActor.delete(watcherActorID); + watcherActors.delete(watcherActorID); this.maybeUnregisterJSActors(); }, @@ -256,7 +258,7 @@ export const WatcherRegistry = { */ maybeUnregisterJSActors() { if (sessionDataByWatcherActor.size == 0) { - unregisterJSWindowActor(); + unregisterBrowserToolboxJSProcessActor(); unregisterJSProcessActor(); } }, @@ -334,74 +336,9 @@ export const WatcherRegistry = { }, }; -// Boolean flag to know if the DevToolsFrame JS Window Actor is currently registered -let isJSWindowActorRegistered = false; - -/** - * Register the JSWindowActor pair "DevToolsFrame". - * - * We should call this method before we try to use this JS Window Actor from the parent process - * (via `WindowGlobal.getActor("DevToolsFrame")` or `WindowGlobal.getActor("DevToolsWorker")`). - * Also, registering it will automatically force spawing the content process JSWindow Actor - * anytime a new document is opened (via DOMWindowCreated event). - */ - -const JSWindowActorsConfig = { - DevToolsFrame: { - parent: { - esModuleURI: - "resource://devtools/server/connectors/js-window-actor/DevToolsFrameParent.sys.mjs", - }, - child: { - esModuleURI: - "resource://devtools/server/connectors/js-window-actor/DevToolsFrameChild.sys.mjs", - events: { - DOMWindowCreated: {}, - DOMDocElementInserted: {}, - pageshow: {}, - pagehide: {}, - }, - }, - allFrames: true, - }, - DevToolsWorker: { - parent: { - esModuleURI: - "resource://devtools/server/connectors/js-window-actor/DevToolsWorkerParent.sys.mjs", - }, - child: { - esModuleURI: - "resource://devtools/server/connectors/js-window-actor/DevToolsWorkerChild.sys.mjs", - events: { - DOMWindowCreated: {}, - }, - }, - allFrames: true, - }, -}; - -function registerJSWindowActor() { - if (isJSWindowActorRegistered) { - return; - } - isJSWindowActorRegistered = true; - ActorManagerParent.addJSWindowActors(JSWindowActorsConfig); -} - -function unregisterJSWindowActor() { - if (!isJSWindowActorRegistered) { - return; - } - isJSWindowActorRegistered = false; - - for (const JSWindowActorName of Object.keys(JSWindowActorsConfig)) { - // ActorManagerParent doesn't expose a "removeActors" method, but it would be equivalent to that: - ChromeUtils.unregisterWindowActor(JSWindowActorName); - } -} - // Boolean flag to know if the DevToolsProcess JS Process Actor is currently registered let isJSProcessActorRegistered = false; +let isBrowserToolboxJSProcessActorRegistered = false; const JSProcessActorConfig = { parent: { @@ -419,7 +356,11 @@ const JSProcessActorConfig = { // The parent process is handled very differently from content processes // This uses the ParentProcessTarget which inherits from BrowsingContextTarget // and is, for now, manually created by the descriptor as the top level target. - includeParent: false, + includeParent: true, +}; + +const BrowserToolboxJSProcessActorConfig = { + ...JSProcessActorConfig, // This JS Process Actor is used to bootstrap DevTools code debugging the privileged code // in content processes. The privileged code runs in the "shared JSM global" (See mozJSModuleLoader). @@ -432,7 +373,7 @@ const JSProcessActorConfig = { }; const PROCESS_SCRIPT_URL = - "resource://devtools/server/actors/watcher/target-helpers/content-process-jsprocessactor-startup.js"; + "resource://devtools/server/connectors/js-process-actor/content-process-jsprocessactor-startup.js"; function registerJSProcessActor() { if (isJSProcessActorRegistered) { @@ -447,6 +388,22 @@ function registerJSProcessActor() { Services.ppmm.loadProcessScript(PROCESS_SCRIPT_URL, true); } +function registerBrowserToolboxJSProcessActor() { + if (isBrowserToolboxJSProcessActorRegistered) { + return; + } + isBrowserToolboxJSProcessActorRegistered = true; + ChromeUtils.registerProcessActor( + "BrowserToolboxDevToolsProcess", + BrowserToolboxJSProcessActorConfig + ); + + // There is no good observer service notification we can listen to to instantiate the JSProcess Actor + // as soon as the process start. + // So manually spawn our JSProcessActor from a process script emitting a custom observer service notification... + Services.ppmm.loadProcessScript(PROCESS_SCRIPT_URL, true); +} + function unregisterJSProcessActor() { if (!isJSProcessActorRegistered) { return; @@ -457,5 +414,24 @@ function unregisterJSProcessActor() { } catch (e) { // If any pending query was still ongoing, this would throw } + if (isBrowserToolboxJSProcessActorRegistered) { + return; + } + Services.ppmm.removeDelayedProcessScript(PROCESS_SCRIPT_URL); +} + +function unregisterBrowserToolboxJSProcessActor() { + if (!isBrowserToolboxJSProcessActorRegistered) { + return; + } + isBrowserToolboxJSProcessActorRegistered = false; + try { + ChromeUtils.unregisterProcessActor("BrowserToolboxDevToolsProcess"); + } catch (e) { + // If any pending query was still ongoing, this would throw + } + if (isJSProcessActorRegistered) { + return; + } Services.ppmm.removeDelayedProcessScript(PROCESS_SCRIPT_URL); } diff --git a/devtools/server/actors/watcher/SessionDataHelpers.jsm b/devtools/server/actors/watcher/SessionDataHelpers.sys.mjs index c70df1744f..def31b77a8 100644 --- a/devtools/server/actors/watcher/SessionDataHelpers.jsm +++ b/devtools/server/actors/watcher/SessionDataHelpers.sys.mjs @@ -2,49 +2,30 @@ * 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/. */ -"use strict"; - /** - * Helper module alongside WatcherRegistry, which focus on updating the "sessionData" object. + * Helper module alongside ParentProcessWatcherRegistry, which focus on updating the "sessionData" object. * This object is shared across processes and threads and have to be maintained in all these runtimes. */ -var EXPORTED_SYMBOLS = ["SessionDataHelpers"]; - const lazy = {}; +ChromeUtils.defineESModuleGetters( + lazy, + { + validateBreakpointLocation: + "resource://devtools/shared/validate-breakpoint.sys.mjs", + }, + { global: "contextual" } +); -if (typeof module == "object") { - // Allow this JSM to also be loaded as a CommonJS module - // Because this module is used from the worker thread, - // (via target-actor-mixin), and workers can't load JSMs via ChromeUtils.import. - loader.lazyRequireGetter( - lazy, - "validateBreakpointLocation", - "resource://devtools/shared/validate-breakpoint.jsm", - true +ChromeUtils.defineLazyGetter(lazy, "validateEventBreakpoint", () => { + const { loader } = ChromeUtils.importESModule( + "resource://devtools/shared/loader/Loader.sys.mjs", + { global: "contextual" } ); - - loader.lazyRequireGetter( - lazy, - "validateEventBreakpoint", - "resource://devtools/server/actors/utils/event-breakpoints.js", - true - ); -} else { - ChromeUtils.defineLazyGetter(lazy, "validateBreakpointLocation", () => { - return ChromeUtils.import( - "resource://devtools/shared/validate-breakpoint.jsm" - ).validateBreakpointLocation; - }); - ChromeUtils.defineLazyGetter(lazy, "validateEventBreakpoint", () => { - const { loader } = ChromeUtils.importESModule( - "resource://devtools/shared/loader/Loader.sys.mjs" - ); - return loader.require( - "resource://devtools/server/actors/utils/event-breakpoints.js" - ).validateEventBreakpoint; - }); -} + return loader.require( + "resource://devtools/server/actors/utils/event-breakpoints.js" + ).validateEventBreakpoint; +}); // List of all arrays stored in `sessionData`, which are replicated across processes and threads const SUPPORTED_DATA = { @@ -151,7 +132,7 @@ function idFunction(v) { return v; } -const SessionDataHelpers = { +export const SessionDataHelpers = { SUPPORTED_DATA, /** @@ -235,10 +216,3 @@ const SessionDataHelpers = { return true; }, }; - -// Allow this JSM to also be loaded as a CommonJS module -// Because this module is used from the worker thread, -// (via target-actor-mixin), and workers can't load JSMs. -if (typeof module == "object") { - module.exports.SessionDataHelpers = SessionDataHelpers; -} diff --git a/devtools/server/actors/watcher/browsing-context-helpers.sys.mjs b/devtools/server/actors/watcher/browsing-context-helpers.sys.mjs index d52cbc5708..cd34c75760 100644 --- a/devtools/server/actors/watcher/browsing-context-helpers.sys.mjs +++ b/devtools/server/actors/watcher/browsing-context-helpers.sys.mjs @@ -382,7 +382,7 @@ export function getAllBrowsingContextsForContext( sessionContext.browserId ); // topBrowsingContext can be null if getCurrentTopByBrowserId is called for a tab that is unloaded. - if (topBrowsingContext) { + if (topBrowsingContext?.embedderElement) { // Unfortunately, getCurrentTopByBrowserId is subject to race conditions and may refer to a BrowsingContext // that already navigated away. // Query the current "live" BrowsingContext by going through the embedder element (i.e. the <browser>/<iframe> element) diff --git a/devtools/server/actors/watcher/moz.build b/devtools/server/actors/watcher/moz.build index 46a9d89718..47d08e8780 100644 --- a/devtools/server/actors/watcher/moz.build +++ b/devtools/server/actors/watcher/moz.build @@ -4,13 +4,9 @@ # 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/. -DIRS += [ - "target-helpers", -] - DevToolsModules( "browsing-context-helpers.sys.mjs", + "ParentProcessWatcherRegistry.sys.mjs", "session-context.js", - "SessionDataHelpers.jsm", - "WatcherRegistry.sys.mjs", + "SessionDataHelpers.sys.mjs", ) diff --git a/devtools/server/actors/watcher/target-helpers/content-process-jsprocessactor-startup.js b/devtools/server/actors/watcher/target-helpers/content-process-jsprocessactor-startup.js deleted file mode 100644 index 1765bcc66c..0000000000 --- a/devtools/server/actors/watcher/target-helpers/content-process-jsprocessactor-startup.js +++ /dev/null @@ -1,26 +0,0 @@ -/* 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/. */ - -"use strict"; - -const { setTimeout } = ChromeUtils.importESModule( - "resource://gre/modules/Timer.sys.mjs" -); - -/* - We can't spawn the JSProcessActor right away and have to spin the event loop. - Otherwise it isn't registered yet and isn't listening to observer service. - Could it be the reason why JSProcessActor aren't spawn via process actor option's child.observers notifications ?? -*/ -setTimeout(function () { - /* - This notification is registered in DevToolsServiceWorker JS process actor's options's `observers` attribute - and will force the JS Process actor to be instantiated in all processes. - */ - Services.obs.notifyObservers(null, "init-devtools-content-process-actor"); - /* - Instead of using observer service, we could also manually call some method of the actor: - ChromeUtils.domProcessChild.getActor("DevToolsProcess").observe(null, "foo"); - */ -}, 0); diff --git a/devtools/server/actors/watcher/target-helpers/frame-helper.js b/devtools/server/actors/watcher/target-helpers/frame-helper.js deleted file mode 100644 index 18d4d8f92e..0000000000 --- a/devtools/server/actors/watcher/target-helpers/frame-helper.js +++ /dev/null @@ -1,330 +0,0 @@ -/* 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/. */ - -"use strict"; - -const { WatcherRegistry } = ChromeUtils.importESModule( - "resource://devtools/server/actors/watcher/WatcherRegistry.sys.mjs", - // WatcherRegistry needs to be a true singleton and loads ActorManagerParent - // which also has to be a true singleton. - { global: "shared" } -); -const { WindowGlobalLogger } = ChromeUtils.importESModule( - "resource://devtools/server/connectors/js-window-actor/WindowGlobalLogger.sys.mjs", - { global: "contextual" } -); -const Targets = require("resource://devtools/server/actors/targets/index.js"); - -const browsingContextAttachedObserverByWatcher = new Map(); - -/** - * Force creating targets for all existing BrowsingContext, that, for a given Watcher Actor. - * - * @param WatcherActor watcher - * The Watcher Actor requesting to watch for new targets. - */ -async function createTargets(watcher) { - // Go over all existing BrowsingContext in order to: - // - Force the instantiation of a DevToolsFrameChild - // - Have the DevToolsFrameChild to spawn the WindowGlobalTargetActor - - // If we have a browserElement, set the watchedByDevTools flag on its related browsing context - // TODO: We should also set the flag for the "parent process" browsing context when we're - // in the browser toolbox. This is blocked by Bug 1675763, and should be handled as part - // of Bug 1709529. - if (watcher.sessionContext.type == "browser-element") { - // The `watchedByDevTools` enables gecko behavior tied to this flag, such as: - // - reporting the contents of HTML loaded in the docshells - // - capturing stacks for the network monitor. - watcher.browserElement.browsingContext.watchedByDevTools = true; - } - - if (!browsingContextAttachedObserverByWatcher.has(watcher)) { - // We store the browserId here as watcher.browserElement.browserId can momentary be - // set to 0 when there's a navigation to a new browsing context. - const browserId = watcher.sessionContext.browserId; - const onBrowsingContextAttached = browsingContext => { - // We want to set watchedByDevTools on new top-level browsing contexts: - // - in the case of the BrowserToolbox/BrowserConsole, that would be the browsing - // contexts of all the tabs we want to handle. - // - for the regular toolbox, browsing context that are being created when navigating - // to a page that forces a new browsing context. - // Then BrowsingContext will propagate to all the tree of children BrowsingContext's. - if ( - !browsingContext.parent && - (watcher.sessionContext.type != "browser-element" || - browserId === browsingContext.browserId) - ) { - browsingContext.watchedByDevTools = true; - } - }; - Services.obs.addObserver( - onBrowsingContextAttached, - "browsing-context-attached" - ); - // We store the observer so we can retrieve it elsewhere (e.g. for removal in destroyTargets). - browsingContextAttachedObserverByWatcher.set( - watcher, - onBrowsingContextAttached - ); - } - - if ( - watcher.sessionContext.isServerTargetSwitchingEnabled && - watcher.sessionContext.type == "browser-element" - ) { - // If server side target switching is enabled, process the top level browsing context first, - // so that we guarantee it is notified to the client first. - // If it is disabled, the top level target will be created from the client instead. - await createTargetForBrowsingContext({ - watcher, - browsingContext: watcher.browserElement.browsingContext, - retryOnAbortError: true, - }); - } - - const browsingContexts = watcher.getAllBrowsingContexts().filter( - // Filter out the top browsing context we just processed. - browsingContext => - browsingContext != watcher.browserElement?.browsingContext - ); - // Await for the all the queries in order to resolve only *after* we received all - // already available targets. - // i.e. each call to `createTargetForBrowsingContext` should end up emitting - // a target-available-form event via the WatcherActor. - await Promise.allSettled( - browsingContexts.map(browsingContext => - createTargetForBrowsingContext({ watcher, browsingContext }) - ) - ); -} - -/** - * (internal helper method) Force creating the target actor for a given BrowsingContext. - * - * @param WatcherActor watcher - * The Watcher Actor requesting to watch for new targets. - * @param BrowsingContext browsingContext - * The context for which a target should be created. - * @param Boolean retryOnAbortError - * Set to true to retry creating existing targets when receiving an AbortError. - * An AbortError is sent when the JSWindowActor pair was destroyed before the query - * was complete, which can happen if the document navigates while the query is pending. - */ -async function createTargetForBrowsingContext({ - watcher, - browsingContext, - retryOnAbortError = false, -}) { - logWindowGlobal(browsingContext.currentWindowGlobal, "Existing WindowGlobal"); - - // We need to set the watchedByDevTools flag on all top-level browsing context. In the - // case of a content toolbox, this is done in the tab descriptor, but when we're in the - // browser toolbox, such descriptor is not created. - // Then BrowsingContext will propagate to all the tree of children BbrowsingContext's. - if (!browsingContext.parent) { - browsingContext.watchedByDevTools = true; - } - - try { - await browsingContext.currentWindowGlobal - .getActor("DevToolsFrame") - .instantiateTarget({ - watcherActorID: watcher.actorID, - connectionPrefix: watcher.conn.prefix, - sessionContext: watcher.sessionContext, - sessionData: watcher.sessionData, - }); - } catch (e) { - console.warn( - "Failed to create DevTools Frame target for browsingContext", - browsingContext.id, - ": ", - e, - retryOnAbortError ? "retrying" : "" - ); - if (retryOnAbortError && e.name === "AbortError") { - await createTargetForBrowsingContext({ - watcher, - browsingContext, - retryOnAbortError, - }); - } else { - throw e; - } - } -} - -/** - * Force destroying all BrowsingContext targets which were related to a given watcher. - * - * @param WatcherActor watcher - * The Watcher Actor requesting to stop watching for new targets. - * @param {object} options - * @param {boolean} options.isModeSwitching - * true when this is called as the result of a change to the devtools.browsertoolbox.scope pref - */ -function destroyTargets(watcher, options) { - // Go over all existing BrowsingContext in order to destroy all targets - const browsingContexts = watcher.getAllBrowsingContexts(); - - for (const browsingContext of browsingContexts) { - logWindowGlobal( - browsingContext.currentWindowGlobal, - "Existing WindowGlobal" - ); - - if (!browsingContext.parent) { - browsingContext.watchedByDevTools = false; - } - - browsingContext.currentWindowGlobal - .getActor("DevToolsFrame") - .destroyTarget({ - watcherActorID: watcher.actorID, - sessionContext: watcher.sessionContext, - options, - }); - } - - if (watcher.sessionContext.type == "browser-element") { - watcher.browserElement.browsingContext.watchedByDevTools = false; - } - - if (browsingContextAttachedObserverByWatcher.has(watcher)) { - Services.obs.removeObserver( - browsingContextAttachedObserverByWatcher.get(watcher), - "browsing-context-attached" - ); - browsingContextAttachedObserverByWatcher.delete(watcher); - } -} - -/** - * Go over all existing BrowsingContext in order to communicate about new data entries - * - * @param WatcherActor watcher - * The Watcher Actor requesting to stop watching for new targets. - * @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 function addOrSetSessionDataEntry({ - watcher, - type, - entries, - updateType, -}) { - const browsingContexts = getWatchingBrowsingContexts(watcher); - const promises = []; - for (const browsingContext of browsingContexts) { - logWindowGlobal( - browsingContext.currentWindowGlobal, - "Existing WindowGlobal" - ); - - const promise = browsingContext.currentWindowGlobal - .getActor("DevToolsFrame") - .addOrSetSessionDataEntry({ - watcherActorID: watcher.actorID, - sessionContext: watcher.sessionContext, - type, - entries, - updateType, - }); - promises.push(promise); - } - // Await for the queries in order to try to resolve only *after* the remote code processed the new data - return Promise.all(promises); -} - -/** - * Notify all existing frame targets that some data entries have been removed - * - * See addOrSetSessionDataEntry for argument documentation. - */ -function removeSessionDataEntry({ watcher, type, entries }) { - const browsingContexts = getWatchingBrowsingContexts(watcher); - for (const browsingContext of browsingContexts) { - logWindowGlobal( - browsingContext.currentWindowGlobal, - "Existing WindowGlobal" - ); - - browsingContext.currentWindowGlobal - .getActor("DevToolsFrame") - .removeSessionDataEntry({ - watcherActorID: watcher.actorID, - sessionContext: watcher.sessionContext, - type, - entries, - }); - } -} - -module.exports = { - createTargets, - destroyTargets, - addOrSetSessionDataEntry, - removeSessionDataEntry, -}; - -/** - * Return the list of BrowsingContexts which should be targeted in order to communicate - * updated session data. - * - * @param WatcherActor watcher - * The watcher actor will be used to know which target we debug - * and what BrowsingContext should be considered. - */ -function getWatchingBrowsingContexts(watcher) { - // If we are watching for additional frame targets, it means that the multiprocess or fission mode is enabled, - // either for a content toolbox or a BrowserToolbox via scope set to everything. - const watchingAdditionalTargets = WatcherRegistry.isWatchingTargets( - watcher, - Targets.TYPES.FRAME - ); - if (watchingAdditionalTargets) { - return watcher.getAllBrowsingContexts(); - } - // By default, when we are no longer watching for frame targets, we should no longer try to - // communicate with any browsing-context. But. - // - // For "browser-element" debugging, all targets are provided by watching by watching for frame targets. - // So, when we are no longer watching for frame, we don't expect to have any frame target to talk to. - // => we should no longer reach any browsing context. - // - // For "all" (=browser toolbox), there is only the special ParentProcessTargetActor we might want to return here. - // But this is actually handled by the WatcherActor which uses `WatcherActor.getTargetActorInParentProcess` to convey session data. - // => we should no longer reach any browsing context. - // - // For "webextension" debugging, there is the special WebExtensionTargetActor, which doesn't run in the parent process, - // so that we can't rely on the same code as the browser toolbox. - // => we should always reach out this particular browsing context. - if (watcher.sessionContext.type == "webextension") { - const browsingContext = BrowsingContext.get( - watcher.sessionContext.addonBrowsingContextID - ); - // The add-on browsing context may be destroying, in which case we shouldn't try to communicate with it - if (browsingContext.currentWindowGlobal) { - return [browsingContext]; - } - } - return []; -} - -// Set to true to log info about about WindowGlobal's being watched. -const DEBUG = false; - -function logWindowGlobal(windowGlobal, message) { - if (!DEBUG) { - return; - } - - WindowGlobalLogger.logWindowGlobal(windowGlobal, message); -} diff --git a/devtools/server/actors/watcher/target-helpers/moz.build b/devtools/server/actors/watcher/target-helpers/moz.build deleted file mode 100644 index 3b00f0ef47..0000000000 --- a/devtools/server/actors/watcher/target-helpers/moz.build +++ /dev/null @@ -1,14 +0,0 @@ -# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- -# vim: set filetype=python: -# 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/. - -DevToolsModules( - "content-process-jsprocessactor-startup.js", - "frame-helper.js", - "process-helper.js", - "service-worker-helper.js", - "service-worker-jsprocessactor-startup.js", - "worker-helper.js", -) diff --git a/devtools/server/actors/watcher/target-helpers/process-helper.js b/devtools/server/actors/watcher/target-helpers/process-helper.js deleted file mode 100644 index e36f0a204c..0000000000 --- a/devtools/server/actors/watcher/target-helpers/process-helper.js +++ /dev/null @@ -1,115 +0,0 @@ -/* 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/. */ - -"use strict"; - -/** - * Return the list of all DOM Processes except the one for the parent process - * - * @return Array<nsIDOMProcessParent> - */ -function getAllContentProcesses() { - return ChromeUtils.getAllDOMProcesses().filter( - process => process.childID !== 0 - ); -} - -/** - * Instantiate all Content Process targets in all the DOM Processes. - * - * @param {WatcherActor} watcher - */ -async function createTargets(watcher) { - const promises = []; - for (const domProcess of getAllContentProcesses()) { - const processActor = domProcess.getActor("DevToolsProcess"); - promises.push( - processActor.instantiateTarget({ - watcherActorID: watcher.actorID, - connectionPrefix: watcher.conn.prefix, - sessionContext: watcher.sessionContext, - sessionData: watcher.sessionData, - }) - ); - } - await Promise.all(promises); -} - -/** - * @param {WatcherActor} watcher - * @param {object} options - * @param {boolean} options.isModeSwitching - * true when this is called as the result of a change to the devtools.browsertoolbox.scope pref - */ -function destroyTargets(watcher, options) { - for (const domProcess of getAllContentProcesses()) { - const processActor = domProcess.getActor("DevToolsProcess"); - processActor.destroyTarget({ - watcherActorID: watcher.actorID, - isModeSwitching: options.isModeSwitching, - }); - } -} - -/** - * Go over all existing content processes in order to communicate about new data entries - * - * @param {Object} options - * @param {WatcherActor} options.watcher - * The Watcher Actor providing new data entries - * @param {string} options.type - * The type of data to be added - * @param {Array<Object>} options.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 function addOrSetSessionDataEntry({ - watcher, - type, - entries, - updateType, -}) { - const promises = []; - for (const domProcess of getAllContentProcesses()) { - const processActor = domProcess.getActor("DevToolsProcess"); - promises.push( - processActor.addOrSetSessionDataEntry({ - watcherActorID: watcher.actorID, - type, - entries, - updateType, - }) - ); - } - await Promise.all(promises); -} - -/** - * Notify all existing content processes that some data entries have been removed - * - * See addOrSetSessionDataEntry for argument documentation. - */ -async function removeSessionDataEntry({ watcher, type, entries }) { - const promises = []; - for (const domProcess of getAllContentProcesses()) { - const processActor = domProcess.getActor("DevToolsProcess"); - promises.push( - processActor.removeSessionDataEntry({ - watcherActorID: watcher.actorID, - type, - entries, - }) - ); - } - await Promise.all(promises); -} - -module.exports = { - createTargets, - destroyTargets, - addOrSetSessionDataEntry, - removeSessionDataEntry, -}; diff --git a/devtools/server/actors/watcher/target-helpers/service-worker-helper.js b/devtools/server/actors/watcher/target-helpers/service-worker-helper.js deleted file mode 100644 index 53fceead17..0000000000 --- a/devtools/server/actors/watcher/target-helpers/service-worker-helper.js +++ /dev/null @@ -1,220 +0,0 @@ -/* 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/. */ - -"use strict"; - -const { waitForTick } = require("resource://devtools/shared/DevToolsUtils.js"); - -const PROCESS_SCRIPT_URL = - "resource://devtools/server/actors/watcher/target-helpers/service-worker-jsprocessactor-startup.js"; - -const PROCESS_ACTOR_NAME = "DevToolsServiceWorker"; -const PROCESS_ACTOR_OPTIONS = { - // Ignore the parent process. - includeParent: false, - - parent: { - esModuleURI: - "resource://devtools/server/connectors/process-actor/DevToolsServiceWorkerParent.sys.mjs", - }, - - child: { - esModuleURI: - "resource://devtools/server/connectors/process-actor/DevToolsServiceWorkerChild.sys.mjs", - - observers: [ - // Tried various notification to ensure starting the actor - // from webServiceWorker processes... but none of them worked. - /* - "chrome-event-target-created", - "webnavigation-create", - "chrome-webnavigation-create", - "webnavigation-destroy", - "chrome-webnavigation-destroy", - "browsing-context-did-set-embedder", - "browsing-context-discarded", - "ipc:content-initializing", - "ipc:content-created", - */ - - // Fallback on firing a very custom notification from a "process script" (loadProcessScript) - "init-devtools-service-worker-actor", - ], - }, -}; - -// List of all active watchers -const gWatchers = new Set(); - -/** - * Register the DevToolsServiceWorker JS Process Actor, - * if we are registering the first watcher actor. - * - * @param {Watcher Actor} watcher - */ -function maybeRegisterProcessActor(watcher) { - const sizeBefore = gWatchers.size; - gWatchers.add(watcher); - - if (sizeBefore == 0 && gWatchers.size == 1) { - ChromeUtils.registerProcessActor(PROCESS_ACTOR_NAME, PROCESS_ACTOR_OPTIONS); - - // For some reason JSProcessActor doesn't work out of the box for `webServiceWorker` content processes. - // So manually spawn our JSProcessActor from a process script emitting an observer service notification... - // The Process script are correctly executed on all process types during their early startup. - Services.ppmm.loadProcessScript(PROCESS_SCRIPT_URL, true); - } -} - -/** - * Unregister the DevToolsServiceWorker JS Process Actor, - * if we are unregistering the last watcher actor. - * - * @param {Watcher Actor} watcher - */ -function maybeUnregisterProcessActor(watcher) { - const sizeBefore = gWatchers.size; - gWatchers.delete(watcher); - - if (sizeBefore == 1 && gWatchers.size == 0) { - ChromeUtils.unregisterProcessActor( - PROCESS_ACTOR_NAME, - PROCESS_ACTOR_OPTIONS - ); - - Services.ppmm.removeDelayedProcessScript(PROCESS_SCRIPT_URL); - } -} - -/** - * Return the list of all DOM Processes except the one for the parent process - * - * @return Array<nsIDOMProcessParent> - */ -function getAllContentProcesses() { - return ChromeUtils.getAllDOMProcesses().filter( - process => process.childID !== 0 - ); -} - -/** - * Force creating targets for all existing service workers for a given Watcher Actor. - * - * @param WatcherActor watcher - * The Watcher Actor requesting to watch for new targets. - */ -async function createTargets(watcher) { - maybeRegisterProcessActor(watcher); - // Go over all existing content process in order to: - // - Force the instantiation of a DevToolsServiceWorkerChild - // - Have the DevToolsServiceWorkerChild to spawn the WorkerTargetActors - - const promises = []; - for (const process of getAllContentProcesses()) { - const promise = process - .getActor(PROCESS_ACTOR_NAME) - .instantiateServiceWorkerTargets({ - watcherActorID: watcher.actorID, - connectionPrefix: watcher.conn.prefix, - sessionContext: watcher.sessionContext, - sessionData: watcher.sessionData, - }); - promises.push(promise); - } - - // Await for the different queries in order to try to resolve only *after* we received - // the already available worker targets. - return Promise.all(promises); -} - -/** - * Force destroying all worker targets which were related to a given watcher. - * - * @param WatcherActor watcher - * The Watcher Actor requesting to stop watching for new targets. - */ -async function destroyTargets(watcher) { - // Go over all existing content processes in order to destroy all targets - for (const process of getAllContentProcesses()) { - let processActor; - try { - processActor = process.getActor(PROCESS_ACTOR_NAME); - } catch (e) { - // Ignore any exception during destroy as we may be closing firefox/devtools/tab - // and that can easily lead to many exceptions. - continue; - } - - processActor.destroyServiceWorkerTargets({ - watcherActorID: watcher.actorID, - sessionContext: watcher.sessionContext, - }); - } - - // browser_dbg-breakpoints-columns.js crashes if we unregister the Process Actor - // in the same event loop as we call destroyServiceWorkerTargets. - await waitForTick(); - - maybeUnregisterProcessActor(watcher); -} - -/** - * Go over all existing JSProcessActor in order to communicate about new data entries - * - * @param WatcherActor watcher - * The Watcher Actor requesting to update data entries. - * @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 function addOrSetSessionDataEntry({ - watcher, - type, - entries, - updateType, -}) { - maybeRegisterProcessActor(watcher); - const promises = []; - for (const process of getAllContentProcesses()) { - const promise = process - .getActor(PROCESS_ACTOR_NAME) - .addOrSetSessionDataEntry({ - watcherActorID: watcher.actorID, - sessionContext: watcher.sessionContext, - type, - entries, - updateType, - }); - promises.push(promise); - } - // Await for the queries in order to try to resolve only *after* the remote code processed the new data - return Promise.all(promises); -} - -/** - * Notify all existing frame targets that some data entries have been removed - * - * See addOrSetSessionDataEntry for argument documentation. - */ -function removeSessionDataEntry({ watcher, type, entries }) { - for (const process of getAllContentProcesses()) { - process.getActor(PROCESS_ACTOR_NAME).removeSessionDataEntry({ - watcherActorID: watcher.actorID, - sessionContext: watcher.sessionContext, - type, - entries, - }); - } -} - -module.exports = { - createTargets, - destroyTargets, - addOrSetSessionDataEntry, - removeSessionDataEntry, -}; diff --git a/devtools/server/actors/watcher/target-helpers/service-worker-jsprocessactor-startup.js b/devtools/server/actors/watcher/target-helpers/service-worker-jsprocessactor-startup.js deleted file mode 100644 index 03f042ad68..0000000000 --- a/devtools/server/actors/watcher/target-helpers/service-worker-jsprocessactor-startup.js +++ /dev/null @@ -1,26 +0,0 @@ -/* 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/. */ - -"use strict"; - -const { setTimeout } = ChromeUtils.importESModule( - "resource://gre/modules/Timer.sys.mjs" -); - -/* - We can't spawn the JSProcessActor right away and have to spin the event loop. - Otherwise it isn't registered yet and isn't listening to observer service. - Could it be the reason why JSProcessActor aren't spawn via process actor option's child.observers notifications ?? -*/ -setTimeout(function () { - /* - This notification is registered in DevToolsServiceWorker JS process actor's options's `observers` attribute - and will force the JS Process actor to be instantiated in all processes. - */ - Services.obs.notifyObservers(null, "init-devtools-service-worker-actor"); - /* - Instead of using observer service, we could also manually call some method of the actor: - ChromeUtils.domProcessChild.getActor("DevToolsServiceWorker").observe(null, "foo"); - */ -}, 0); diff --git a/devtools/server/actors/watcher/target-helpers/worker-helper.js b/devtools/server/actors/watcher/target-helpers/worker-helper.js deleted file mode 100644 index 671d1dc706..0000000000 --- a/devtools/server/actors/watcher/target-helpers/worker-helper.js +++ /dev/null @@ -1,137 +0,0 @@ -/* 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/. */ - -"use strict"; - -const DEVTOOLS_WORKER_JS_WINDOW_ACTOR_NAME = "DevToolsWorker"; - -/** - * Force creating targets for all existing workers for a given Watcher Actor. - * - * @param WatcherActor watcher - * The Watcher Actor requesting to watch for new targets. - */ -async function createTargets(watcher) { - // Go over all existing BrowsingContext in order to: - // - Force the instantiation of a DevToolsWorkerChild - // - Have the DevToolsWorkerChild to spawn the WorkerTargetActors - const browsingContexts = watcher.getAllBrowsingContexts({ - acceptSameProcessIframes: true, - forceAcceptTopLevelTarget: true, - }); - const promises = []; - for (const browsingContext of browsingContexts) { - const promise = browsingContext.currentWindowGlobal - .getActor(DEVTOOLS_WORKER_JS_WINDOW_ACTOR_NAME) - .instantiateWorkerTargets({ - watcherActorID: watcher.actorID, - connectionPrefix: watcher.conn.prefix, - sessionContext: watcher.sessionContext, - sessionData: watcher.sessionData, - }); - promises.push(promise); - } - - // Await for the different queries in order to try to resolve only *after* we received - // the already available worker targets. - return Promise.all(promises); -} - -/** - * Force destroying all worker targets which were related to a given watcher. - * - * @param WatcherActor watcher - * The Watcher Actor requesting to stop watching for new targets. - */ -async function destroyTargets(watcher) { - // Go over all existing BrowsingContext in order to destroy all targets - const browsingContexts = watcher.getAllBrowsingContexts({ - acceptSameProcessIframes: true, - forceAcceptTopLevelTarget: true, - }); - for (const browsingContext of browsingContexts) { - let windowActor; - try { - windowActor = browsingContext.currentWindowGlobal.getActor( - DEVTOOLS_WORKER_JS_WINDOW_ACTOR_NAME - ); - } catch (e) { - continue; - } - - windowActor.destroyWorkerTargets({ - watcherActorID: watcher.actorID, - sessionContext: watcher.sessionContext, - }); - } -} - -/** - * Go over all existing BrowsingContext in order to communicate about new data entries - * - * @param WatcherActor watcher - * The Watcher Actor requesting to stop watching for new targets. - * @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 function addOrSetSessionDataEntry({ - watcher, - type, - entries, - updateType, -}) { - const browsingContexts = watcher.getAllBrowsingContexts({ - acceptSameProcessIframes: true, - forceAcceptTopLevelTarget: true, - }); - const promises = []; - for (const browsingContext of browsingContexts) { - const promise = browsingContext.currentWindowGlobal - .getActor(DEVTOOLS_WORKER_JS_WINDOW_ACTOR_NAME) - .addOrSetSessionDataEntry({ - watcherActorID: watcher.actorID, - sessionContext: watcher.sessionContext, - type, - entries, - updateType, - }); - promises.push(promise); - } - // Await for the queries in order to try to resolve only *after* the remote code processed the new data - return Promise.all(promises); -} - -/** - * Notify all existing frame targets that some data entries have been removed - * - * See addOrSetSessionDataEntry for argument documentation. - */ -function removeSessionDataEntry({ watcher, type, entries }) { - const browsingContexts = watcher.getAllBrowsingContexts({ - acceptSameProcessIframes: true, - forceAcceptTopLevelTarget: true, - }); - for (const browsingContext of browsingContexts) { - browsingContext.currentWindowGlobal - .getActor(DEVTOOLS_WORKER_JS_WINDOW_ACTOR_NAME) - .removeSessionDataEntry({ - watcherActorID: watcher.actorID, - sessionContext: watcher.sessionContext, - type, - entries, - }); - } -} - -module.exports = { - createTargets, - destroyTargets, - addOrSetSessionDataEntry, - removeSessionDataEntry, -}; diff --git a/devtools/server/actors/webconsole/commands/manager.js b/devtools/server/actors/webconsole/commands/manager.js index 025e197e3b..e96e0a617f 100644 --- a/devtools/server/actors/webconsole/commands/manager.js +++ b/devtools/server/actors/webconsole/commands/manager.js @@ -11,13 +11,6 @@ loader.lazyRequireGetter( true ); -loader.lazyRequireGetter( - this, - ["DOM_MUTATIONS"], - "resource://devtools/server/tracer/tracer.jsm", - true -); - loader.lazyGetter(this, "l10n", () => { return new Localization( [ @@ -27,6 +20,16 @@ loader.lazyGetter(this, "l10n", () => { true ); }); + +const lazy = {}; +ChromeUtils.defineESModuleGetters( + lazy, + { + JSTracer: "resource://devtools/server/tracer/tracer.sys.mjs", + }, + { global: "contextual" } +); + const USAGE_STRING_MAPPING = { block: "webconsole-commands-usage-block", trace: "webconsole-commands-usage-trace3", @@ -888,7 +891,7 @@ WebConsoleCommandsManager.register({ } else if (typeof args["dom-mutations"] == "string") { // Otherwise consider the value as coma seperated list and remove any white space. traceDOMMutations = args["dom-mutations"].split(",").map(e => e.trim()); - const acceptedValues = Object.values(DOM_MUTATIONS); + const acceptedValues = Object.values(lazy.JSTracer.DOM_MUTATIONS); if (!traceDOMMutations.every(e => acceptedValues.includes(e))) { throw new Error( `:trace --dom-mutations only accept a list of strings whose values can be: ${acceptedValues}` |