From a90a5cba08fdf6c0ceb95101c275108a152a3aed Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Wed, 12 Jun 2024 07:35:37 +0200 Subject: Merging upstream version 127.0. Signed-off-by: Daniel Baumann --- devtools/server/actors/inspector/css-logic.js | 33 +++++---- .../server/actors/inspector/event-collector.js | 64 ++++++++++-------- devtools/server/actors/inspector/node-picker.js | 74 +++++++++++--------- devtools/server/actors/inspector/node.js | 32 +++++++-- devtools/server/actors/inspector/walker.js | 8 +-- devtools/server/actors/page-style.js | 35 ++++++---- devtools/server/actors/perf.js | 4 +- devtools/server/actors/style-rule.js | 23 ++++--- devtools/server/actors/targets/window-global.js | 12 +++- devtools/server/actors/tracer.js | 11 +++ devtools/server/actors/utils/event-breakpoints.js | 4 ++ .../actors/utils/inactive-property-helper.js | 2 +- devtools/server/actors/utils/sources-manager.js | 6 +- devtools/server/actors/watcher.js | 78 +++++++++++++++++----- .../server/actors/webconsole/eval-with-debugger.js | 10 ++- .../highlight-pseudo-elements.mjs | 16 +++++ .../test_inspector-inactive-property-helper.html | 3 + devtools/server/tracer/tracer.sys.mjs | 31 +++++++++ 18 files changed, 312 insertions(+), 134 deletions(-) (limited to 'devtools/server') 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; } -- cgit v1.2.3