tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

commit 4ae808c8e166d0e148123a092a6a7b0745c994d4
parent 4a56cbd3c8b83555f96487b38f3202dfa77eab6e
Author: Nicolas Chevobbe <nchevobbe@mozilla.com>
Date:   Fri, 21 Nov 2025 06:55:49 +0000

Bug 2000219 - [devtools] Properly detect unmatched selectors in Pseudo elements section. r=devtools-reviewers,bomsy.

Differential Revision: https://phabricator.services.mozilla.com/D273181

Diffstat:
Mdevtools/client/inspector/rules/test/browser_rules_pseudo-element_01.js | 23-----------------------
Mdevtools/client/inspector/rules/test/browser_rules_pseudo-element_02.js | 71+++++++++++++++++++++++++++++++++++++++++++----------------------------
Mdevtools/client/inspector/rules/test/doc_pseudoelement.html | 9+++++++++
Mdevtools/client/inspector/rules/views/rule-editor.js | 13++++---------
Mdevtools/server/actors/page-style.js | 42+++++++++++++++++++++++++++++++-----------
Mdevtools/server/actors/style-rule.js | 16++++++++++++++++
6 files changed, 103 insertions(+), 71 deletions(-)

diff --git a/devtools/client/inspector/rules/test/browser_rules_pseudo-element_01.js b/devtools/client/inspector/rules/test/browser_rules_pseudo-element_01.js @@ -610,29 +610,6 @@ async function assertPseudoElementRulesNumbers( ); } - // If we do have pseudo element rules displayed, ensure we don't mark their selectors - // as matched or unmatched - if ( - rules.elementRules.length && - view._elementStyle.rules.length !== rules.elementRules.length - ) { - const pseudoElementContainer = view.styleWindow.document.getElementById( - "pseudo-elements-container" - ); - const selectors = Array.from( - pseudoElementContainer.querySelectorAll(".ruleview-selector") - ); - ok(selectors.length, "We do have selectors for pseudo element rules"); - ok( - selectors.every( - selectorEl => - !selectorEl.classList.contains("matched") && - !selectorEl.classList.contains("unmatched") - ), - "Pseudo element selectors are not marked as matched nor unmatched" - ); - } - return rules; } diff --git a/devtools/client/inspector/rules/test/browser_rules_pseudo-element_02.js b/devtools/client/inspector/rules/test/browser_rules_pseudo-element_02.js @@ -3,7 +3,7 @@ "use strict"; -// Test that pseudoelements are displayed correctly in the markup view. +// Test that pseudo elements rules are displayed correctly in Rules view. const TEST_URI = URL_ROOT + "doc_pseudoelement.html"; @@ -18,11 +18,7 @@ add_task(async function () { info("Check rules on #topleft::before node"); const beforeElement = children.nodes[0]; - is( - beforeElement.tagName, - "_moz_generated_content_before", - "tag name is correct" - ); + is(beforeElement.displayName, "::before", "display name is correct"); await selectNode(beforeElement, inspector); checkRuleViewContent(view, [ { @@ -65,12 +61,8 @@ add_task(async function () { ]); info("Check rules on #topleft::after node"); - const afterElement = children.nodes[children.nodes.length - 1]; - is( - afterElement.tagName, - "_moz_generated_content_after", - "tag name is correct" - ); + const afterElement = children.nodes.at(-1); + is(afterElement.displayName, "::after", "display name is correct"); await selectNode(afterElement, inspector); checkRuleViewContent(view, [ { @@ -114,9 +106,9 @@ add_task(async function () { const listChildren = await inspector.markup.walker.children(listNode); const listAfterNode = listChildren.nodes.at(-1); is( - listAfterNode.tagName, - "_moz_generated_content_after", - "tag name is correct for #list::after" + listAfterNode.displayName, + "::after", + "display name is correct for #list::after" ); const listAfterChildren = await inspector.markup.walker.children(listAfterNode); @@ -127,9 +119,9 @@ add_task(async function () { ); const listAfterMarkerNode = listAfterChildren.nodes[0]; is( - listAfterMarkerNode.tagName, - "_moz_generated_content_marker", - "tag name is correct for #list::after::marker" + listAfterMarkerNode.displayName, + "::marker", + "display name is correct for #list::after::marker" ); info("Check rules on #list-item::marker node"); await selectNode(listAfterMarkerNode, inspector); @@ -170,11 +162,7 @@ add_task(async function () { info("Check rules on #list-item::marker node"); const markerElement = listItemChildren.nodes[0]; - is( - markerElement.tagName, - "_moz_generated_content_marker", - "tag name is correct" - ); + is(markerElement.displayName, "::marker", "display name is correct"); await selectNode(markerElement, inspector); checkRuleViewContent(view, [ { @@ -204,11 +192,7 @@ add_task(async function () { info("Check rules on #list-item::before node"); const listBeforeElement = listItemChildren.nodes[1]; - is( - listBeforeElement.tagName, - "_moz_generated_content_before", - "tag name is correct" - ); + is(listBeforeElement.displayName, "::before", "display name is correct"); await selectNode(listBeforeElement, inspector); checkRuleViewContent(view, [ { @@ -242,6 +226,37 @@ add_task(async function () { }, ]); + info("Check unmatched selector parts in Pseudo element section"); + await selectNode("#with-unmatched-selector", inspector); + checkRuleViewContent(view, [ + { + header: `Pseudo-elements`, + }, + { + selector: `#with-unmatched-selector::before, ~~unknown::before~~, #with-unmatched-selector::after, ~~anotherunknown~~`, + declarations: [{ name: "content", value: `"unmatched pseudo"` }], + }, + { + header: `This Element`, + }, + { + selector: `element`, + declarations: [], + }, + { + selector: `*`, + declarations: [{ name: "cursor", value: "default" }], + }, + { + header: "Inherited from body", + }, + { + selector: `body`, + inherited: true, + declarations: [{ name: "color", value: "#333" }], + }, + ]); + info("Check rules on ::view-transition"); const htmlNodeFront = await getNodeFront("html", inspector); diff --git a/devtools/client/inspector/rules/test/doc_pseudoelement.html b/devtools/client/inspector/rules/test/doc_pseudoelement.html @@ -189,6 +189,13 @@ html:active-view-transition { color: thistle; } +#with-unmatched-selector::before, +unknown::before, +#with-unmatched-selector::after, +anotherunknown { + content: "unmatched pseudo"; +} + </style> </head> <body> @@ -210,6 +217,8 @@ html:active-view-transition { <p>Bottom Left<br />Position</p> </div> + <div id="with-unmatched-selector">Some unmatched pseudo-element selector part</div> + <ol id="list"> <li id="list-item" class="box">List element</li> </ol> diff --git a/devtools/client/inspector/rules/views/rule-editor.js b/devtools/client/inspector/rules/views/rule-editor.js @@ -934,16 +934,11 @@ RuleEditor.prototype = { }); } - let containerClass = "ruleview-selector "; - - // Only add matched/unmatched class when the rule does have some matched - // selectors. We don't always have some (e.g. rules for pseudo elements) - - if (this.rule.matchedSelectorIndexes.length) { - containerClass += this.rule.matchedSelectorIndexes.includes(selectorIndex) + const containerClass = + "ruleview-selector " + + (this.rule.matchedSelectorIndexes.includes(selectorIndex) ? "matched" - : "unmatched"; - } + : "unmatched"); let selectorContainerTitle; if ( diff --git a/devtools/server/actors/page-style.js b/devtools/server/actors/page-style.js @@ -179,7 +179,11 @@ class PageStyleActor extends Actor { */ _styleRef(item, pseudoElement, userAdded = false) { if (this.refMap.has(item)) { - return this.refMap.get(item); + const styleRuleActor = this.refMap.get(item); + if (pseudoElement) { + styleRuleActor.addPseudo(pseudoElement); + } + return styleRuleActor; } const actor = new StyleRuleActor({ pageStyle: this, @@ -1042,22 +1046,38 @@ class PageStyleActor extends Actor { ? entry.inherited.rawNode : node.rawNode; + const pseudos = []; const { bindingElement, pseudo } = CssLogic.getBindingElementAndPseudo(element); + if (pseudo) { + pseudos.push(pseudo); + } else if (entry.rule.pseudoElements.size) { + // if `node` is not a pseudo element but the rule applies to some pseudo elements, + // we need to pass those to CSSStyleRule#selectorMatchesElement + pseudos.push(...entry.rule.pseudoElements); + } else { + // If the rule doesn't apply to any pseudo, set a null item so we'll still do + // the proper check below + pseudos.push(null); + } + const relevantLinkVisited = CssLogic.hasVisitedState(bindingElement); entry.matchedSelectorIndexes = []; - const len = domRule.selectorCount; for (let i = 0; i < len; i++) { - if ( - domRule.selectorMatchesElement( - i, - bindingElement, - pseudo, - relevantLinkVisited - ) - ) { - entry.matchedSelectorIndexes.push(i); + for (const pseudoElementName of pseudos) { + if ( + domRule.selectorMatchesElement( + i, + bindingElement, + pseudoElementName, + relevantLinkVisited + ) + ) { + entry.matchedSelectorIndexes.push(i); + // if we matched the selector for one pseudo, no need to check the other ones + break; + } } } } diff --git a/devtools/server/actors/style-rule.js b/devtools/server/actors/style-rule.js @@ -94,7 +94,11 @@ class StyleRuleActor extends Actor { this.pageStyle = pageStyle; this.rawStyle = item.style; this._userAdded = userAdded; + this._pseudoElements = new Set(); this._pseudoElement = pseudoElement; + if (pseudoElement) { + this._pseudoElements.add(pseudoElement); + } this._parentSheet = null; // Parsed CSS declarations from this.form().declarations used to check CSS property // names and values before tracking changes. Using cached values instead of accessing @@ -149,6 +153,10 @@ class StyleRuleActor extends Actor { this.rawNode = null; this.rawRule = null; this._declarations = null; + if (this._pseudoElements) { + this._pseudoElements.clear(); + this._pseudoElements = null; + } } // Objects returned by this actor are owned by the PageStyleActor @@ -372,6 +380,14 @@ class StyleRuleActor extends Actor { ); } + get pseudoElements() { + return this._pseudoElements; + } + + addPseudo(pseudoElement) { + this._pseudoElements.add(pseudoElement); + } + getDocument(sheet) { if (!sheet.associatedDocument) { throw new Error(