summaryrefslogtreecommitdiffstats
path: root/devtools/server
diff options
context:
space:
mode:
Diffstat (limited to 'devtools/server')
-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.js8
-rw-r--r--devtools/server/actors/page-style.js35
-rw-r--r--devtools/server/actors/perf.js4
-rw-r--r--devtools/server/actors/style-rule.js23
-rw-r--r--devtools/server/actors/targets/window-global.js12
-rw-r--r--devtools/server/actors/tracer.js11
-rw-r--r--devtools/server/actors/utils/event-breakpoints.js4
-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/watcher.js78
-rw-r--r--devtools/server/actors/webconsole/eval-with-debugger.js10
-rw-r--r--devtools/server/tests/chrome/inactive-property-helper/highlight-pseudo-elements.mjs16
-rw-r--r--devtools/server/tests/chrome/test_inspector-inactive-property-helper.html3
-rw-r--r--devtools/server/tracer/tracer.sys.mjs31
18 files changed, 312 insertions, 134 deletions
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 fbf417565c..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: {},
};
}
diff --git a/devtools/server/actors/page-style.js b/devtools/server/actors/page-style.js
index b37816c85f..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");
@@ -709,7 +708,7 @@ class PageStyleActor extends Actor {
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":
@@ -897,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),
+ });
}
}
}
@@ -1280,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/style-rule.js b/devtools/server/actors/style-rule.js
index 9ddd6a380c..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) {
@@ -1316,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/targets/window-global.js b/devtools/server/actors/targets/window-global.js
index f8f5e5f3c6..3de93af513 100644
--- a/devtools/server/actors/targets/window-global.js
+++ b/devtools/server/actors/targets/window-global.js
@@ -521,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;
}
/**
diff --git a/devtools/server/actors/tracer.js b/devtools/server/actors/tracer.js
index d98749ceb1..590834305f 100644
--- a/devtools/server/actors/tracer.js
+++ b/devtools/server/actors/tracer.js
@@ -119,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) {
diff --git a/devtools/server/actors/utils/event-breakpoints.js b/devtools/server/actors/utils/event-breakpoints.js
index 8fbefec804..eddd986e87 100644
--- a/devtools/server/actors/utils/event-breakpoints.js
+++ b/devtools/server/actors/utils/event-breakpoints.js
@@ -200,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"),
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/watcher.js b/devtools/server/actors/watcher.js
index 10de102229..34d2509420 100644
--- a/devtools/server/actors/watcher.js
+++ b/devtools/server/actors/watcher.js
@@ -258,10 +258,24 @@ exports.WatcherActor = class WatcherActor extends Actor {
continue;
}
promises.push(
- domProcess.getActor(this._jsActorName).watchTargets({
- watcherActorID: this.actorID,
- targetType,
- })
+ 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);
@@ -561,13 +575,27 @@ exports.WatcherActor = class WatcherActor extends Actor {
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",
- })
+ 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;
+ })
);
}
await Promise.all(promises);
@@ -769,13 +797,27 @@ exports.WatcherActor = class WatcherActor extends Actor {
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,
- })
+ 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);
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;
}
diff --git a/devtools/server/tests/chrome/inactive-property-helper/highlight-pseudo-elements.mjs b/devtools/server/tests/chrome/inactive-property-helper/highlight-pseudo-elements.mjs
index bcb5b8763c..05fbefeec5 100644
--- a/devtools/server/tests/chrome/inactive-property-helper/highlight-pseudo-elements.mjs
+++ b/devtools/server/tests/chrome/inactive-property-helper/highlight-pseudo-elements.mjs
@@ -152,4 +152,20 @@ export default [
rules: ["span::selection { -webkit-text-stroke: 4px navy; }"],
isActive: true,
},
+ {
+ info: "display is inactive on ::target-text",
+ property: "display",
+ tagName: "span",
+ rules: ["span::target-text { display: grid; }"],
+ isActive: false,
+ expectedMsgId: "inactive-css-highlight-pseudo-elements-not-supported",
+ },
+ {
+ // accept background shorthand, even if it might hold inactive values
+ info: "background is active on ::target-text",
+ property: "background",
+ tagName: "span",
+ rules: ["span::target-text { background: red; }"],
+ isActive: true,
+ },
];
diff --git a/devtools/server/tests/chrome/test_inspector-inactive-property-helper.html b/devtools/server/tests/chrome/test_inspector-inactive-property-helper.html
index c3d9d26aee..7844d49e7b 100644
--- a/devtools/server/tests/chrome/test_inspector-inactive-property-helper.html
+++ b/devtools/server/tests/chrome/test_inspector-inactive-property-helper.html
@@ -17,17 +17,20 @@ SimpleTest.waitForExplicitFinish();
const CUSTOM_HIGHLIGHT_API = "dom.customHighlightAPI.enabled";
const TEXT_WRAP_BALANCE = "layout.css.text-wrap-balance.enabled";
const ALIGN_CONTENT_BLOCKS = "layout.css.align-content.blocks.enabled";
+ const TEXT_FRAGMENTS = "dom.text_fragments.enabled";
Services.prefs.setBoolPref(INACTIVE_CSS_PREF, true);
Services.prefs.setBoolPref(CUSTOM_HIGHLIGHT_API, true);
Services.prefs.setBoolPref(TEXT_WRAP_BALANCE, true);
Services.prefs.setBoolPref(ALIGN_CONTENT_BLOCKS, true);
+ Services.prefs.setBoolPref(TEXT_FRAGMENTS, true);
SimpleTest.registerCleanupFunction(() => {
Services.prefs.clearUserPref(INACTIVE_CSS_PREF);
Services.prefs.clearUserPref(CUSTOM_HIGHLIGHT_API);
Services.prefs.clearUserPref(TEXT_WRAP_BALANCE);
Services.prefs.clearUserPref(ALIGN_CONTENT_BLOCKS);
+ Services.prefs.clearUserPref(TEXT_FRAGMENTS);
});
const FOLDER = "./inactive-property-helper";
diff --git a/devtools/server/tracer/tracer.sys.mjs b/devtools/server/tracer/tracer.sys.mjs
index 6fe1334f2b..120a937ce9 100644
--- a/devtools/server/tracer/tracer.sys.mjs
+++ b/devtools/server/tracer/tracer.sys.mjs
@@ -312,6 +312,37 @@ class JavaScriptTracer {
this.debuggerNotificationObserver.addListener(this.eventListener);
this.debuggerNotificationObserver.connect(this.tracedGlobal);
+ // When we are tracing a document, also ensure connecting to all its children iframe globals.
+ // If we don't, Debugger API would fire onEnterFrame for their JavaScript code,
+ // but DOM Events wouldn't be notified by DebuggerNotificationObserver.
+ if (!isWorker && this.tracedGlobal instanceof Ci.nsIDOMWindow) {
+ const { browserId } = this.tracedGlobal.browsingContext;
+ // Keep track of any future global
+ this.dbg.onNewGlobalObject = g => {
+ try {
+ const win = g.unsafeDereference();
+ // only process globals relating to documents, and which are within the debugged tab
+ if (
+ win instanceof Ci.nsIDOMWindow &&
+ win.browsingContext.browserId == browserId
+ ) {
+ this.dbg.addDebuggee(g);
+ this.debuggerNotificationObserver.connect(win);
+ }
+ } catch (e) {}
+ };
+ // Register all, already existing children
+ for (const browsingContext of this.tracedGlobal.browsingContext.getAllBrowsingContextsInSubtree()) {
+ try {
+ // Only consider children which run in the same process, and exposes their window object
+ if (browsingContext.window) {
+ this.dbg.addDebuggee(browsingContext.window);
+ this.debuggerNotificationObserver.connect(browsingContext.window);
+ }
+ } catch (e) {}
+ }
+ }
+
this.currentDOMEvent = null;
}