tor-browser

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

commit eeb0443b54ecb31c803b37580db3de4fc7bb39b8
parent 8b78ccb4f9114fac45965164bc3b73bcfaaf0c65
Author: Nicolas Chevobbe <nchevobbe@mozilla.com>
Date:   Sun, 23 Nov 2025 22:45:19 +0000

Bug 1998704 - [devtools] Allow to edit pseudo element rules selectors. r=devtools-reviewers,jdescottes.

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

Diffstat:
Mdevtools/client/inspector/rules/test/browser_rules_edit-selector-pseudo-element.js | 56++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mdevtools/client/inspector/rules/views/rule-editor.js | 9+++------
Mdevtools/server/actors/page-style.js | 16++++++++++++++--
3 files changed, 73 insertions(+), 8 deletions(-)

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 @@ -14,6 +14,9 @@ const TEST_URI = ` <h1 class=foo>pseudo</h1>`; add_task(async function test_inline_sheet() { + // Avoid focusing the first declaration after editing the selector + await pushPref("devtools.inspector.rule-view.focusNextOnEnter", false); + await addTab( `data:text/html,<meta charset=utf8>${encodeURIComponent(TEST_URI)}` ); @@ -106,4 +109,57 @@ add_task(async function test_inline_sheet() { "false", "pseudo element rule does match back the h1 node" ); + + info( + "Check that we can edit the selector when the pseudo element node is selected" + ); + const h1NodeFront = await getNodeFront("h1", inspector); + let h1NodeFrontChildren = await inspector.walker.children(h1NodeFront); + const h1AfterNodeFront = h1NodeFrontChildren.nodes.at(-1); + await selectNode(h1AfterNodeFront, inspector); + // sanity check + is( + inspector.selection.nodeFront.displayName, + "::after", + "We selected the ::after pseudo element" + ); + + info(`Modify "h1::after" into ".foo::after"`); + ruleEditor = getRuleViewRuleEditor(view, 0); + editor = await focusEditableField(view, ruleEditor.selectorText); + onRuleViewChanged = view.once("ruleview-changed"); + editor.input.value = ".foo::after"; + EventUtils.synthesizeKey("KEY_Enter"); + info("waiting for <onRuleViewChanged>"); + await onRuleViewChanged; + + // Get the new rule editor reference + ruleEditor = getRuleViewRuleEditor(view, 0); + is(ruleEditor.selectorText.textContent, ".foo::after"); + is( + ruleEditor.element.getAttribute("unmatched"), + "false", + "pseudo element rule still matches" + ); + + info(`Modify ".foo::after" into "h2::after"`); + ruleEditor = getRuleViewRuleEditor(view, 0); + editor = await focusEditableField(view, ruleEditor.selectorText); + onRuleViewChanged = view.once("ruleview-changed"); + const onSelection = inspector.selection.once("new-node-front"); + editor.input.value = "h2::after"; + EventUtils.synthesizeKey("KEY_Enter"); + await onRuleViewChanged; + await onSelection; + is( + inspector.selection.nodeFront, + h1NodeFront, + "The parent node of the pseudo element was selected" + ); + h1NodeFrontChildren = await inspector.walker.children(h1NodeFront); + is( + h1NodeFrontChildren.nodes.find(child => child.displayName === "::after"), + undefined, + "The ::after pseudo element was removed" + ); }); diff --git a/devtools/client/inspector/rules/views/rule-editor.js b/devtools/client/inspector/rules/views/rule-editor.js @@ -141,14 +141,11 @@ RuleEditor.prototype = { }, get isSelectorEditable() { - const trait = + return ( this.isEditable && this.rule.domRule.type !== ELEMENT_STYLE && - this.rule.domRule.type !== CSSRule.KEYFRAME_RULE; - - // Do not allow editing anonymousselectors until we can - // detect mutations on pseudo elements in Bug 1034110. - return trait && !this.rule.elementStyle.element.isNativeAnonymous; + this.rule.domRule.type !== CSSRule.KEYFRAME_RULE + ); }, _create() { diff --git a/devtools/server/actors/page-style.js b/devtools/server/actors/page-style.js @@ -807,7 +807,12 @@ class PageStyleActor extends Actor { } _nodeIsListItem(node) { - const display = CssLogic.getComputedStyle(node).getPropertyValue("display"); + const computed = CssLogic.getComputedStyle(node); + if (!computed) { + return false; + } + + const display = computed.getPropertyValue("display"); // This is written this way to handle `inline list-item` and such. return display.split(" ").includes("list-item"); } @@ -1048,6 +1053,7 @@ class PageStyleActor extends Actor { if (entry.rule.type === ELEMENT_STYLE) { continue; } + entry.matchedSelectorIndexes = []; const domRule = entry.rule.rawRule; const element = entry.inherited @@ -1057,6 +1063,13 @@ class PageStyleActor extends Actor { const pseudos = []; const { bindingElement, pseudo } = CssLogic.getBindingElementAndPseudo(element); + + // if we couldn't find a binding element, we can't call domRule.selectorMatchesElement, + // so bail out + if (!bindingElement) { + continue; + } + if (pseudo) { pseudos.push(pseudo); } else if (entry.rule.pseudoElements.size) { @@ -1070,7 +1083,6 @@ class PageStyleActor extends Actor { } const relevantLinkVisited = CssLogic.hasVisitedState(bindingElement); - entry.matchedSelectorIndexes = []; const len = domRule.selectorCount; for (let i = 0; i < len; i++) { for (const pseudoElementName of pseudos) {