summaryrefslogtreecommitdiffstats
path: root/devtools/server/actors
diff options
context:
space:
mode:
Diffstat (limited to 'devtools/server/actors')
-rw-r--r--devtools/server/actors/accessibility/audit/contrast.js16
-rw-r--r--devtools/server/actors/blackboxing.js7
-rw-r--r--devtools/server/actors/breakpoint-list.js7
-rw-r--r--devtools/server/actors/inspector/css-logic.js33
-rw-r--r--devtools/server/actors/inspector/event-collector.js64
-rw-r--r--devtools/server/actors/inspector/node-picker.js74
-rw-r--r--devtools/server/actors/inspector/node.js32
-rw-r--r--devtools/server/actors/inspector/walker.js12
-rw-r--r--devtools/server/actors/page-style.js36
-rw-r--r--devtools/server/actors/perf.js4
-rw-r--r--devtools/server/actors/resources/index.js16
-rw-r--r--devtools/server/actors/resources/jstracer-state.js15
-rw-r--r--devtools/server/actors/resources/network-events.js8
-rw-r--r--devtools/server/actors/resources/sources.js2
-rw-r--r--devtools/server/actors/resources/utils/parent-process-storage.js11
-rw-r--r--devtools/server/actors/style-rule.js60
-rw-r--r--devtools/server/actors/target-configuration.js12
-rw-r--r--devtools/server/actors/targets/base-target-actor.js12
-rw-r--r--devtools/server/actors/targets/session-data-processors/breakpoints.js4
-rw-r--r--devtools/server/actors/targets/session-data-processors/event-breakpoints.js7
-rw-r--r--devtools/server/actors/targets/session-data-processors/index.js7
-rw-r--r--devtools/server/actors/targets/session-data-processors/thread-configuration.js7
-rw-r--r--devtools/server/actors/targets/session-data-processors/xhr-breakpoints.js5
-rw-r--r--devtools/server/actors/targets/target-actor-registry.sys.mjs13
-rw-r--r--devtools/server/actors/targets/webextension.js6
-rw-r--r--devtools/server/actors/targets/window-global.js71
-rw-r--r--devtools/server/actors/targets/worker.js6
-rw-r--r--devtools/server/actors/thread-configuration.js7
-rw-r--r--devtools/server/actors/thread.js17
-rw-r--r--devtools/server/actors/tracer.js41
-rw-r--r--devtools/server/actors/utils/event-breakpoints.js19
-rw-r--r--devtools/server/actors/utils/inactive-property-helper.js2
-rw-r--r--devtools/server/actors/utils/sources-manager.js6
-rw-r--r--devtools/server/actors/utils/style-utils.js66
-rw-r--r--devtools/server/actors/utils/stylesheets-manager.js31
-rw-r--r--devtools/server/actors/watcher.js393
-rw-r--r--devtools/server/actors/watcher/ParentProcessWatcherRegistry.sys.mjs (renamed from devtools/server/actors/watcher/WatcherRegistry.sys.mjs)138
-rw-r--r--devtools/server/actors/watcher/SessionDataHelpers.sys.mjs (renamed from devtools/server/actors/watcher/SessionDataHelpers.jsm)62
-rw-r--r--devtools/server/actors/watcher/browsing-context-helpers.sys.mjs2
-rw-r--r--devtools/server/actors/watcher/moz.build8
-rw-r--r--devtools/server/actors/watcher/target-helpers/content-process-jsprocessactor-startup.js26
-rw-r--r--devtools/server/actors/watcher/target-helpers/frame-helper.js330
-rw-r--r--devtools/server/actors/watcher/target-helpers/moz.build14
-rw-r--r--devtools/server/actors/watcher/target-helpers/process-helper.js115
-rw-r--r--devtools/server/actors/watcher/target-helpers/service-worker-helper.js220
-rw-r--r--devtools/server/actors/watcher/target-helpers/service-worker-jsprocessactor-startup.js26
-rw-r--r--devtools/server/actors/watcher/target-helpers/worker-helper.js137
-rw-r--r--devtools/server/actors/webconsole/commands/manager.js19
-rw-r--r--devtools/server/actors/webconsole/eval-with-debugger.js10
49 files changed, 734 insertions, 1502 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/css-logic.js b/devtools/server/actors/inspector/css-logic.js
index 8ef0978915..0a602a55cc 100644
--- a/devtools/server/actors/inspector/css-logic.js
+++ b/devtools/server/actors/inspector/css-logic.js
@@ -253,19 +253,28 @@ class CssLogic {
if (cssSheet._passId != this._passId) {
cssSheet._passId = this._passId;
- // Find import and keyframes rules.
- for (const aDomRule of cssSheet.getCssRules()) {
- const ruleClassName = ChromeUtils.getClassName(aDomRule);
- if (
- ruleClassName === "CSSImportRule" &&
- aDomRule.styleSheet &&
- this.mediaMatches(aDomRule)
- ) {
- this._cacheSheet(aDomRule.styleSheet);
- } else if (ruleClassName === "CSSKeyframesRule") {
- this._keyframesRules.push(aDomRule);
+ // Find import and keyframes rules. We loop through all the stylesheet recursively,
+ // so we can go through nested rules.
+ const traverseRules = ruleList => {
+ for (const aDomRule of ruleList) {
+ const ruleClassName = ChromeUtils.getClassName(aDomRule);
+ if (
+ ruleClassName === "CSSImportRule" &&
+ aDomRule.styleSheet &&
+ this.mediaMatches(aDomRule)
+ ) {
+ this._cacheSheet(aDomRule.styleSheet);
+ } else if (ruleClassName === "CSSKeyframesRule") {
+ this._keyframesRules.push(aDomRule);
+ }
+
+ if (aDomRule.cssRules) {
+ traverseRules(aDomRule.cssRules);
+ }
}
- }
+ };
+
+ traverseRules(cssSheet.getCssRules());
}
}
diff --git a/devtools/server/actors/inspector/event-collector.js b/devtools/server/actors/inspector/event-collector.js
index 7ae7ac45d7..ff49107b4f 100644
--- a/devtools/server/actors/inspector/event-collector.js
+++ b/devtools/server/actors/inspector/event-collector.js
@@ -256,26 +256,32 @@ class MainEventCollector {
* An array of unfiltered event listeners or an empty array
*/
getDOMListeners(node) {
- let listeners;
+ const listeners = [];
+ const listenersTargets = [];
+
if (
typeof node.nodeName !== "undefined" &&
node.nodeName.toLowerCase() === "html"
) {
- const winListeners =
- Services.els.getListenerInfoFor(node.ownerGlobal) || [];
- const docElementListeners = Services.els.getListenerInfoFor(node) || [];
- const docListeners =
- Services.els.getListenerInfoFor(node.parentNode) || [];
-
- listeners = [...winListeners, ...docElementListeners, ...docListeners];
+ listenersTargets.push(node.ownerGlobal, node, node.parentNode);
} else {
- listeners = Services.els.getListenerInfoFor(node) || [];
+ listenersTargets.push(node);
}
- return listeners.filter(listener => {
- const obj = this.unwrap(listener.listenerObject);
- return !obj || !obj[EXCLUDED_LISTENER];
- });
+ for (const el of listenersTargets) {
+ const elListeners = Services.els.getListenerInfoFor(el);
+ if (!elListeners) {
+ continue;
+ }
+ for (const listener of elListeners) {
+ const obj = this.unwrap(listener.listenerObject);
+ if (!obj || !obj[EXCLUDED_LISTENER]) {
+ listeners.push(listener);
+ }
+ }
+ }
+
+ return listeners;
}
getJQuery(node) {
@@ -460,8 +466,10 @@ class JQueryEventCollector extends MainEventCollector {
}
if (eventsObj) {
- for (const [type, events] of Object.entries(eventsObj)) {
- for (const [, event] of Object.entries(events)) {
+ for (const type in eventsObj) {
+ const events = eventsObj[type];
+ for (const key in events) {
+ const event = events[key];
// Skip events that are part of jQueries internals.
if (node.nodeType == node.DOCUMENT_NODE && event.selector) {
continue;
@@ -517,9 +525,9 @@ class JQueryLiveEventCollector extends MainEventCollector {
return handlers;
}
- const data = jQuery._data || jQuery.data;
+ const jqueryData = jQuery._data || jQuery.data;
- if (data) {
+ if (jqueryData) {
// Live events are added to the document and bubble up to all elements.
// Any element matching the specified selector will trigger the live
// event.
@@ -527,32 +535,34 @@ class JQueryLiveEventCollector extends MainEventCollector {
let events = null;
try {
- events = data(win.document, "events");
+ events = jqueryData(win.document, "events");
} catch (e) {
// We have no access to a JS object. This is probably due to a CORS
// violation. Using try / catch is the only way to avoid this error.
}
- if (events) {
- for (const [, eventHolder] of Object.entries(events)) {
- for (const [idx, event] of Object.entries(eventHolder)) {
+ if (events && node.ownerDocument && node.matches) {
+ for (const eventName in events) {
+ const eventHolder = events[eventName];
+ for (const idx in eventHolder) {
if (typeof idx !== "string" || isNaN(parseInt(idx, 10))) {
continue;
}
- let selector = event.selector;
+ const event = eventHolder[idx];
+ let { selector, data } = event;
- if (!selector && event.data) {
- selector = event.data.selector || event.data || event.selector;
+ if (!selector && data) {
+ selector = data.selector || data;
}
- if (!selector || !node.ownerDocument) {
+ if (!selector) {
continue;
}
let matches;
try {
- matches = node.matches && node.matches(selector);
+ matches = node.matches(selector);
} catch (e) {
// Invalid selector, do nothing.
}
@@ -581,7 +591,7 @@ class JQueryLiveEventCollector extends MainEventCollector {
},
};
- if (!eventInfo.type && event.data?.live) {
+ if (!eventInfo.type && data?.live) {
eventInfo.type = event.data.live;
}
diff --git a/devtools/server/actors/inspector/node-picker.js b/devtools/server/actors/inspector/node-picker.js
index 4e090959c9..21777ced80 100644
--- a/devtools/server/actors/inspector/node-picker.js
+++ b/devtools/server/actors/inspector/node-picker.js
@@ -79,42 +79,52 @@ class NodePicker {
* @returns HTMLElement
*/
_findNodeAtMouseEventPosition(event) {
- const winUtils = this._targetActor.window.windowUtils;
+ const win = this._targetActor.window;
+ const winUtils = win.windowUtils;
const rectSize = 1;
- const elements = winUtils.nodesFromRect(
- // aX
- event.clientX,
- // aY
- event.clientY,
- // aTopSize
- rectSize,
- // aRightSize
- rectSize,
- // aBottomSize
- rectSize,
- // aLeftSize
- rectSize,
- // aIgnoreRootScrollFrame
- true,
- // aFlushLayout
- false,
- // aOnlyVisible
- true,
- // aTransparencyThreshold
- 1
- );
+ const elements = Array.from(
+ winUtils.nodesFromRect(
+ // aX
+ event.clientX,
+ // aY
+ event.clientY,
+ // aTopSize
+ rectSize,
+ // aRightSize
+ rectSize,
+ // aBottomSize
+ rectSize,
+ // aLeftSize
+ rectSize,
+ // aIgnoreRootScrollFrame
+ true,
+ // aFlushLayout
+ false,
+ // aOnlyVisible
+ true,
+ // aTransparencyThreshold
+ 1
+ )
+ ).filter(element => {
+ // Strip out text nodes, we want to highlight Elements only
+ return !win.Text.isInstance(element);
+ });
- // ⚠️ When a highlighter was added to the page (which is the case at this point),
- // the first element is the html node, and might be the last one as well (See Bug 1744941).
- // Until we figure this out, let's pick the second returned item when hit this.
- if (
- elements.length > 1 &&
- ChromeUtils.getClassName(elements[0]) == "HTMLHtmlElement"
- ) {
- return elements[1];
+ if (!elements.length) {
+ return null;
}
- return elements[0];
+ if (elements.length === 1) {
+ return elements[0];
+ }
+
+ // Let's return the first element that we find that is not a parent of another matching
+ // element, so we get the "deepest" element possible.
+ // At this points, we have at least 2 elements and are guaranteed to find an element
+ // which is not the parent of any other ones.
+ return elements.find(
+ element => !elements.some(e => element !== e && element.contains(e))
+ );
}
/**
diff --git a/devtools/server/actors/inspector/node.js b/devtools/server/actors/inspector/node.js
index 294e3e9564..fc4aa8f367 100644
--- a/devtools/server/actors/inspector/node.js
+++ b/devtools/server/actors/inspector/node.js
@@ -185,13 +185,14 @@ class NodeActor extends Actor {
const hostActor = shadowRoot
? this.walker.getNode(this.rawNode.host)
: null;
+ const nodeType = this.rawNode.nodeType;
const form = {
actor: this.actorID,
host: hostActor ? hostActor.actorID : undefined,
baseURI: this.rawNode.baseURI,
parent: parentNode ? parentNode.actorID : undefined,
- nodeType: this.rawNode.nodeType,
+ nodeType,
namespaceURI: this.rawNode.namespaceURI,
nodeName: this.rawNode.nodeName,
nodeValue: this.rawNode.nodeValue,
@@ -227,10 +228,24 @@ class NodeActor extends Actor {
isInHTMLDocument:
this.rawNode.ownerDocument &&
this.rawNode.ownerDocument.contentType === "text/html",
- hasEventListeners: this._hasEventListeners,
traits: {},
};
+ // The event collector can be expensive, so only check for events on nodes that
+ // can display the `event` badge.
+ if (
+ nodeType !== Node.COMMENT_NODE &&
+ nodeType !== Node.TEXT_NODE &&
+ nodeType !== Node.CDATA_SECTION_NODE &&
+ nodeType !== Node.DOCUMENT_NODE &&
+ nodeType !== Node.DOCUMENT_TYPE_NODE &&
+ !form.isMarkerPseudoElement &&
+ !form.isBeforePseudoElement &&
+ !form.isAfterPseudoElement
+ ) {
+ form.hasEventListeners = this.hasEventListeners();
+ }
+
if (this.isDocumentElement()) {
form.isDocumentElement = true;
}
@@ -422,12 +437,15 @@ class NodeActor extends Actor {
* Are there event listeners that are listening on this node? This method
* uses all parsers registered via event-parsers.js.registerEventParser() to
* check if there are any event listeners.
+ *
+ * @returns {Boolean}
*/
- get _hasEventListeners() {
- // We need to pass a debugger instance from this compartment because
- // otherwise we can't make use of it inside the event-collector module.
- const dbg = this.getParent().targetActor.makeDebugger();
- return this._eventCollector.hasEventListeners(this.rawNode, dbg);
+ hasEventListeners(refreshCache = false) {
+ if (this._hasEventListenersCached === undefined || refreshCache) {
+ const result = this._eventCollector.hasEventListeners(this.rawNode);
+ this._hasEventListenersCached = result;
+ }
+ return this._hasEventListenersCached;
}
writeAttrs() {
diff --git a/devtools/server/actors/inspector/walker.js b/devtools/server/actors/inspector/walker.js
index 50df1720b7..b4a0b7410b 100644
--- a/devtools/server/actors/inspector/walker.js
+++ b/devtools/server/actors/inspector/walker.js
@@ -327,7 +327,7 @@ class WalkerActor extends Actor {
const mutation = {
type: "events",
target: actor.actorID,
- hasEventListeners: actor._hasEventListeners,
+ hasEventListeners: actor.hasEventListeners(/* refreshCache */ true),
};
this.queueMutation(mutation);
}
@@ -339,11 +339,7 @@ class WalkerActor extends Actor {
return {
actor: this.actorID,
root: this.rootNode.form(),
- traits: {
- // @backward-compat { version 125 } Indicate to the client that it can use getIdrefNode.
- // This trait can be removed once 125 hits release.
- hasGetIdrefNode: true,
- },
+ traits: {},
};
}
@@ -384,7 +380,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 +428,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..7a425bc3d5 100644
--- a/devtools/server/actors/page-style.js
+++ b/devtools/server/actors/page-style.js
@@ -9,7 +9,6 @@ const {
pageStyleSpec,
} = require("resource://devtools/shared/specs/page-style.js");
-const { getCSSLexer } = require("resource://devtools/shared/css/lexer.js");
const {
LongStringActor,
} = require("resource://devtools/server/actors/string.js");
@@ -704,11 +703,12 @@ class PageStyleActor extends Actor {
case "::first-line":
case "::selection":
case "::highlight":
+ case "::target-text":
return true;
case "::marker":
return this._nodeIsListItem(node);
case "::backdrop":
- return node.matches(":modal");
+ return node.matches(":modal, :popover-open");
case "::cue":
return node.nodeName == "VIDEO";
case "::file-selector-button":
@@ -896,13 +896,15 @@ class PageStyleActor extends Actor {
// Traverse through all the available keyframes rule and add
// the keyframes rule that matches the computed animation name
for (const keyframesRule of this.cssLogic.keyframesRules) {
- if (animationNames.indexOf(keyframesRule.name) > -1) {
- for (const rule of keyframesRule.cssRules) {
- entries.push({
- rule: this._styleRef(rule),
- keyframes: this._styleRef(keyframesRule),
- });
- }
+ if (!animationNames.includes(keyframesRule.name)) {
+ continue;
+ }
+
+ for (const rule of keyframesRule.cssRules) {
+ entries.push({
+ rule: this._styleRef(rule),
+ keyframes: this._styleRef(keyframesRule),
+ });
}
}
}
@@ -1279,22 +1281,28 @@ class PageStyleActor extends Actor {
return;
}
- const lexer = getCSSLexer(selectorText);
+ const lexer = new InspectorCSSParser(selectorText);
let token;
while ((token = lexer.nextToken())) {
if (
- token.tokenType === "symbol" &&
- ((shouldRetrieveClasses && token.text === ".") ||
- (shouldRetrieveIds && token.text === "#"))
+ token.tokenType === "Delim" &&
+ shouldRetrieveClasses &&
+ token.text === "."
) {
token = lexer.nextToken();
if (
- token.tokenType === "ident" &&
+ token.tokenType === "Ident" &&
token.text.toLowerCase().startsWith(search)
) {
result.add(token.text);
}
}
+ if (token.tokenType === "IDHash" && shouldRetrieveIds) {
+ const idWithoutHash = token.value;
+ if (idWithoutHash.startsWith(search)) {
+ result.add(idWithoutHash);
+ }
+ }
}
}
}
diff --git a/devtools/server/actors/perf.js b/devtools/server/actors/perf.js
index 3f561256c9..0424f3048e 100644
--- a/devtools/server/actors/perf.js
+++ b/devtools/server/actors/perf.js
@@ -58,6 +58,7 @@ exports.PerfActor = class PerfActor extends Actor {
"stackwalk",
"cpu",
"responsiveness",
+ "memory",
],
threads: options.threads || ["GeckoMain", "Compositor"],
activeTabID: RecordingUtils.getActiveBrowserID(),
@@ -157,7 +158,7 @@ exports.PerfActor = class PerfActor extends Actor {
// Note! If emitting new events make sure and update the list of bridged
// events in the perf actor.
switch (topic) {
- case "profiler-started":
+ case "profiler-started": {
const param = subject.QueryInterface(Ci.nsIProfilerStartParams);
this.emit(
topic,
@@ -168,6 +169,7 @@ exports.PerfActor = class PerfActor extends Actor {
param.activeTabID
);
break;
+ }
case "profiler-stopped":
this.emit(topic);
break;
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..2be6e981ea 100644
--- a/devtools/server/actors/style-rule.js
+++ b/devtools/server/actors/style-rule.js
@@ -9,7 +9,9 @@ const {
styleRuleSpec,
} = require("resource://devtools/shared/specs/style-rule.js");
-const { getCSSLexer } = require("resource://devtools/shared/css/lexer.js");
+const {
+ InspectorCSSParserWrapper,
+} = require("resource://devtools/shared/css/lexer.js");
const TrackChangeEmitter = require("resource://devtools/server/actors/utils/track-change-emitter.js");
const {
getRuleText,
@@ -502,6 +504,12 @@ class StyleRuleActor extends Actor {
// Flag that will be set to true if the rule has a non-at-rule parent rule
let computeDesugaredSelector = false;
+ // We don't want to compute ancestor rules for keyframe rule, as they can only be
+ // in @keyframes rules.
+ if (this.ruleClassName === "CSSKeyframeRule") {
+ return { ancestorData, computeDesugaredSelector };
+ }
+
// Go through all ancestor so we can build an array of all the media queries and
// layers this rule is in.
for (const ancestorRule of this.ancestorRules) {
@@ -724,7 +732,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 +831,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;
@@ -1303,23 +1324,20 @@ function getSelectorOffsets(initialText, line, column) {
line,
column
);
- const lexer = getCSSLexer(text);
+ const lexer = new InspectorCSSParserWrapper(text);
// Search forward for the opening brace.
let endOffset;
- while (true) {
- const token = lexer.nextToken();
- if (!token) {
- break;
- }
- if (token.tokenType === "symbol" && token.text === "{") {
+ let token;
+ while ((token = lexer.nextToken())) {
+ if (token.tokenType === "CurlyBracketBlock") {
if (endOffset === undefined) {
break;
}
return [textOffset, textOffset + endOffset];
}
// Preserve comments and whitespace just before the "{".
- if (token.tokenType !== "comment" && token.tokenType !== "whitespace") {
+ if (token.tokenType !== "Comment" && token.tokenType !== "WhiteSpace") {
endOffset = token.endOffset;
}
}
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..3de93af513 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;
}
@@ -508,9 +521,15 @@ class WindowGlobalTargetActor extends BaseTargetActor {
* @return {Array}
*/
get windows() {
- return this.docShells.map(docShell => {
- return docShell.domWindow;
- });
+ const windows = [];
+ for (const docShell of this.docShells) {
+ try {
+ windows.push(docShell.domWindow);
+ } catch (e) {
+ // Ignore destroying docshells which may throw when accessing domWindow property.
+ }
+ }
+ return windows;
}
/**
@@ -687,6 +706,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 +755,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 +1349,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 +1415,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 +1726,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 +1768,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..590834305f 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");
@@ -121,6 +119,17 @@ class TracerActor extends Actor {
return;
}
+ // Ignore WindowGlobal target actors for WindowGlobal of iframes running in the same process and thread as their parent document.
+ // isProcessRoot will be true for each WindowGlobal being the top parent within a given process.
+ // It will typically be true for WindowGlobal of iframe running in a distinct origin and process,
+ // but only for the top iframe document. It will also be true for the top level tab document.
+ if (
+ this.targetActor.window &&
+ !this.targetActor.window.windowGlobalChild?.isProcessRoot
+ ) {
+ return;
+ }
+
this.logMethod = options.logMethod || LOG_METHODS.STDOUT;
if (this.logMethod == LOG_METHODS.PROFILER) {
@@ -136,10 +145,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 +179,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 +239,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 +519,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..eddd986e87 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"),
@@ -195,6 +200,10 @@ const AVAILABLE_BREAKPOINTS = [
items: [
generalEvent("keyboard", "beforeinput"),
generalEvent("keyboard", "input"),
+ generalEvent("keyboard", "textInput", () =>
+ // Services.prefs isn't available on worker targets
+ Services.prefs?.getBoolPref("dom.events.textevent.enabled")
+ ),
generalEvent("keyboard", "keydown"),
generalEvent("keyboard", "keyup"),
generalEvent("keyboard", "keypress"),
@@ -483,17 +492,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/inactive-property-helper.js b/devtools/server/actors/utils/inactive-property-helper.js
index 3f6e748167..9f7a685f75 100644
--- a/devtools/server/actors/utils/inactive-property-helper.js
+++ b/devtools/server/actors/utils/inactive-property-helper.js
@@ -88,10 +88,10 @@ const HIGHLIGHT_PSEUDO_ELEMENTS_STYLING_SPEC_URL =
const HIGHLIGHT_PSEUDO_ELEMENTS = [
"::highlight",
"::selection",
+ "::target-text",
// Below are properties not yet implemented in Firefox (Bug 1694053)
"::grammar-error",
"::spelling-error",
- "::target-text",
];
const REGEXP_HIGHLIGHT_PSEUDO_ELEMENTS = new RegExp(
`${HIGHLIGHT_PSEUDO_ELEMENTS.join("|")}`
diff --git a/devtools/server/actors/utils/sources-manager.js b/devtools/server/actors/utils/sources-manager.js
index fda37a3184..981a7c8213 100644
--- a/devtools/server/actors/utils/sources-manager.js
+++ b/devtools/server/actors/utils/sources-manager.js
@@ -101,7 +101,8 @@ class SourcesManager extends EventEmitter {
this._thread.threadLifetimePool.manage(actor);
this._sourceActors.set(source, actor);
- if (this._sourcesByInternalSourceId && source.id) {
+ // source.id can be 0 for WASM sources
+ if (this._sourcesByInternalSourceId && Number.isInteger(source.id)) {
this._sourcesByInternalSourceId.set(source.id, source);
}
@@ -157,7 +158,8 @@ class SourcesManager extends EventEmitter {
if (!this._sourcesByInternalSourceId) {
this._sourcesByInternalSourceId = new Map();
for (const source of this._thread.dbg.findSources()) {
- if (source.id) {
+ // source.id can be 0 for WASM sources
+ if (Number.isInteger(source.id)) {
this._sourcesByInternalSourceId.set(source.id, source);
}
}
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..34d2509420 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,58 @@ 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,
+ })
+ .catch(e => {
+ // Ignore any process that got destroyed while trying to send the request
+ if (!domProcess.canSend) {
+ console.warn(
+ "Content process closed while requesting targets",
+ domProcess.name,
+ domProcess.remoteType
+ );
+ return;
+ }
+ throw e;
+ })
+ );
+ }
+ await Promise.all(promises);
}
/**
@@ -242,7 +291,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 +300,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 +356,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 +504,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 +527,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 +563,46 @@ 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);
-
- // 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
+ ParentProcessWatcherRegistry.watchResources(this, resourceTypes);
+
+ 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",
+ })
+ .catch(e => {
+ // Ignore any process that got destroyed while trying to send the request
+ if (!domProcess.canSend) {
+ console.warn(
+ "Content process closed while requesting resources",
+ domProcess.name,
+ domProcess.remoteType
+ );
+ return;
+ }
+ throw e;
+ })
);
- 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 +622,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 +652,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 +669,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 +691,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 +786,50 @@ 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,
+ 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,
- });
- })
- );
+ })
+ .catch(e => {
+ // Ignore any process that got destroyed while trying to send the request
+ if (!domProcess.canSend) {
+ console.warn(
+ "Content process closed while sending session data",
+ domProcess.name,
+ domProcess.remoteType
+ );
+ return;
+ }
+ throw e;
+ })
+ );
+ }
+ 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 +850,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 +894,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}`
diff --git a/devtools/server/actors/webconsole/eval-with-debugger.js b/devtools/server/actors/webconsole/eval-with-debugger.js
index 34836c354f..b510b0e47b 100644
--- a/devtools/server/actors/webconsole/eval-with-debugger.js
+++ b/devtools/server/actors/webconsole/eval-with-debugger.js
@@ -325,6 +325,7 @@ function getEvalResult(
if (noSideEffectDebugger) {
noSideEffectDebugger.removeAllDebuggees();
noSideEffectDebugger.onNativeCall = undefined;
+ noSideEffectDebugger.shouldAvoidSideEffects = false;
}
}
}
@@ -457,11 +458,15 @@ function makeSideeffectFreeDebugger(targetActorDbg) {
try {
dbg.addDebuggee(global);
} catch (e) {
- // Ignore the following exception which can happen for some globals in the browser toolbox
+ // Ignore exceptions from the following cases:
+ // * A global from the same compartment (happens with parent process)
+ // * A dead wrapper (happens when the reference gets nuked after
+ // findAllGlobals call)
if (
!e.message.includes(
"debugger and debuggee must be in different compartments"
- )
+ ) &&
+ !e.message.includes("can't access dead object")
) {
throw e;
}
@@ -544,6 +549,7 @@ function makeSideeffectFreeDebugger(targetActorDbg) {
// Returning null terminates the current evaluation.
return null;
};
+ dbg.shouldAvoidSideEffects = true;
return dbg;
}