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:
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) {