tor-browser

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

commit 8cd9454cc16378a9903445bdc8d66b6f9962bb31
parent 405436996272546c93bd78764a4ec90082b06c62
Author: Nicolas Chevobbe <nchevobbe@mozilla.com>
Date:   Fri, 21 Nov 2025 14:59:51 +0000

Bug 2000876 - [devtools] Fix unmatched rule after editing selector in pseudo element section. r=devtools-reviewers,jdescottes.

`PageStyleActor#findEntryMatchingRule` wasn't looking into the selected node,
but directly into its parent, and was passing an erroneous `inherited` parameter
to `_getAllElementRules`, which was preventing to find the actual entry matching
the edited rule.

We take this as an opportunity to change the signature of the method so it better
aligns with what you'd expect from a `find` method (return the entry or null if
not found).

With the proper entry retrieved, the appropriate pseudo element will be passed
to `selectorMatchesElement`, which in the end will properly mark the edited
rule as matching in the client.

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

Diffstat:
Mdevtools/client/inspector/rules/test/browser_part1.toml | 2++
Adevtools/client/inspector/rules/test/browser_rules_edit-selector-pseudo-element.js | 109+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mdevtools/server/actors/page-style.js | 42+++++++++++++++++++++++++-----------------
Mdevtools/server/actors/style-rule.js | 4++--
4 files changed, 138 insertions(+), 19 deletions(-)

diff --git a/devtools/client/inspector/rules/test/browser_part1.toml b/devtools/client/inspector/rules/test/browser_part1.toml @@ -256,6 +256,8 @@ fail-if = ["a11y_checks"] # Bug 1849028 clicked element may not be focusable and ["browser_rules_edit-selector-nested-rules.js"] +["browser_rules_edit-selector-pseudo-element.js"] + ["browser_rules_edit-selector_01.js"] ["browser_rules_edit-selector_02.js"] diff --git a/devtools/client/inspector/rules/test/browser_rules_edit-selector-pseudo-element.js b/devtools/client/inspector/rules/test/browser_rules_edit-selector-pseudo-element.js @@ -0,0 +1,109 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Testing editing pseudo element selector in the rule view. +const TEST_URI = ` + <style> + h1::before { + content: "-"; + color: lime; + } + </style> + <h1 class=foo>pseudo</h1>`; + +add_task(async function test_inline_sheet() { + await addTab( + `data:text/html,<meta charset=utf8>${encodeURIComponent(TEST_URI)}` + ); + const { inspector, view } = await openRuleView(); + + info("Check that we can edit the selectors in the pseudo elements section"); + await selectNode("h1", inspector); + + info("Expand pseudo elements section"); + const pseudoElementToggle = view.styleDocument.querySelector( + `[aria-controls="pseudo-elements-container"]` + ); + // sanity check + is( + pseudoElementToggle.ariaExpanded, + "false", + "pseudo element section is collapsed at first" + ); + pseudoElementToggle.click(); + is( + pseudoElementToggle.ariaExpanded, + "true", + "pseudo element section is now expanded" + ); + + info(`Modify "h1::before" into ".foo::before"`); + let ruleEditor = getRuleViewRuleEditor(view, 1, 0); + let editor = await focusEditableField(view, ruleEditor.selectorText); + let onRuleViewChanged = view.once("ruleview-changed"); + editor.input.value = ".foo::before"; + EventUtils.synthesizeKey("KEY_Enter"); + await onRuleViewChanged; + + // Get the new rule editor reference + ruleEditor = getRuleViewRuleEditor(view, 1, 0); + is(ruleEditor.selectorText.textContent, ".foo::before"); + is( + ruleEditor.element.getAttribute("unmatched"), + "false", + "pseudo element rule still matches" + ); + + info(`Modify ".foo::before" into ".foo::after"`); + ruleEditor = getRuleViewRuleEditor(view, 1, 0); + editor = await focusEditableField(view, ruleEditor.selectorText); + onRuleViewChanged = view.once("ruleview-changed"); + editor.input.value = ".foo::after"; + EventUtils.synthesizeKey("KEY_Enter"); + await onRuleViewChanged; + + // Get the new rule editor reference + ruleEditor = getRuleViewRuleEditor(view, 1, 0); + is(ruleEditor.selectorText.textContent, ".foo::after"); + is( + ruleEditor.element.getAttribute("unmatched"), + "false", + "pseudo element rule still matches" + ); + + info(`Modify ".foo::after" into unmatching "h2::after"`); + ruleEditor = getRuleViewRuleEditor(view, 1, 0); + editor = await focusEditableField(view, ruleEditor.selectorText); + onRuleViewChanged = view.once("ruleview-changed"); + editor.input.value = "h2::after"; + EventUtils.synthesizeKey("KEY_Enter"); + await onRuleViewChanged; + + // Get the new rule editor reference + ruleEditor = getRuleViewRuleEditor(view, 1, 0); + is(ruleEditor.selectorText.textContent, "h2::after"); + is( + ruleEditor.element.getAttribute("unmatched"), + "true", + "pseudo element rule does not match h1 anymore" + ); + + info(`Modify "h2::after" back into matching "h1::after"`); + ruleEditor = getRuleViewRuleEditor(view, 1, 0); + editor = await focusEditableField(view, ruleEditor.selectorText); + onRuleViewChanged = view.once("ruleview-changed"); + editor.input.value = "h1::after"; + EventUtils.synthesizeKey("KEY_Enter"); + await onRuleViewChanged; + + // Get the new rule editor reference + ruleEditor = getRuleViewRuleEditor(view, 1, 0); + is(ruleEditor.selectorText.textContent, "h1::after"); + is( + ruleEditor.element.getAttribute("unmatched"), + "false", + "pseudo element rule does match back the h1 node" + ); +}); diff --git a/devtools/server/actors/page-style.js b/devtools/server/actors/page-style.js @@ -979,28 +979,36 @@ class PageStyleActor extends Actor { } /** - * Given a node and a CSS rule, walk up the DOM looking for a - * matching element rule. Return an array of all found entries, in - * the form generated by _getAllElementRules. Note that this will - * always return an array of either zero or one element. + * Given a node and a CSS rule, walk up the DOM looking for a matching element rule. * - * @param {NodeActor} node the node - * @param {CSSStyleRule} filterRule the rule to filter for - * @return {Array} array of zero or one elements; if one, the element - * is the entry as returned by _getAllElementRules. + * @param {NodeActor} nodeActor the node + * @param {CSSStyleRule} matchingRule the rule to find the entry for + * @return {Object|null} An entry as returned by _getAllElementRules, or null if no entry + * matching the passed rule was find */ - findEntryMatchingRule(node, filterRule) { + findEntryMatchingRule(nodeActor, matchingRule) { const options = { matchedSelectors: true, inherited: true }; - let entries = []; - let parent = this.walker.parentNode(node); - while (parent && parent.rawNode.nodeType != Node.DOCUMENT_NODE) { - entries = entries.concat( - this._getAllElementRules(parent, parent, options) - ); - parent = this.walker.parentNode(parent); + let currentNodeActor = nodeActor; + while ( + currentNodeActor && + currentNodeActor.rawNode.nodeType != Node.DOCUMENT_NODE + ) { + for (const entry of this._getAllElementRules( + currentNodeActor, + // inherited + nodeActor !== currentNodeActor ? currentNodeActor : null, + options + )) { + if (entry.rule.rawRule === matchingRule) { + return entry; + } + } + + currentNodeActor = this.walker.parentNode(currentNodeActor); } - return entries.filter(entry => entry.rule.rawRule === filterRule); + // If we reached the document node without finding the rule, return null + return null; } /** diff --git a/devtools/server/actors/style-rule.js b/devtools/server/actors/style-rule.js @@ -1375,8 +1375,8 @@ class StyleRuleActor extends Actor { if (newCssRule) { const ruleEntry = this.pageStyle.findEntryMatchingRule(node, newCssRule); - if (ruleEntry.length === 1) { - entries = this.pageStyle.getAppliedProps(node, ruleEntry, { + if (ruleEntry) { + entries = this.pageStyle.getAppliedProps(node, [ruleEntry], { matchedSelectors: true, }); } else {