tor-browser

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

commit d7679d7acc1bca859a296e21655686c90ec1874b
parent 5b66be067d5c6aa2bfa2adbf50b8fe763e826291
Author: Nicolas Chevobbe <nchevobbe@mozilla.com>
Date:   Fri, 14 Nov 2025 10:29:13 +0000

Bug 1947747 - [devtools] Handle ::view-transition elements in the Rules view. r=devtools-reviewers,ochameau.

We had to handle the label on the inherited section for view transition pseudo element,
and fix StyleRuleActor#currentlySelectedElementComputedStyle so inactiveCSS will
perform as expected on view transition pseudo element nodes.

A test is added to check that the rules are properly displayed when a view transition
pseudo element is selected in the markup view, and that the inherited properties
are visible and that the expected declarations are overridden (acts as a client
test for Bug 1997145)

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

Diffstat:
Mdevtools/client/inspector/rules/test/browser_rules_pseudo-element_02.js | 160+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mdevtools/client/inspector/rules/test/doc_pseudoelement.html | 25+++++++++++++++++++++++++
Mdevtools/server/actors/style-rule.js | 53+++++++++++++++++++++++++++++++++++++----------------
3 files changed, 222 insertions(+), 16 deletions(-)

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 @@ -241,4 +241,164 @@ add_task(async function () { declarations: [{ name: "color", value: "#333" }], }, ]); + + info("Check rules on ::view-transition"); + const htmlNodeFront = await getNodeFront("html", inspector); + + const onMarkupMutation = inspector.once("markupmutation"); + await SpecialPowers.spawn(gBrowser.selectedBrowser, [], async () => { + const document = content.document; + const transition = document.startViewTransition(() => { + document.querySelector(".transition").append("updated"); + }); + await transition.ready; + await transition.updateCallbackDone; + }); + await onMarkupMutation; + + const htmlChildren = await inspector.markup.walker.children(htmlNodeFront); + const viewTransitionNodeFront = htmlChildren.nodes[2]; + + is( + viewTransitionNodeFront.getAttribute("type"), + ":view-transition", + "Got expected ::view-transition node front" + ); + + await selectNode(viewTransitionNodeFront, inspector); + + checkRuleViewContent(view, [ + { + selector: `::view-transition`, + declarations: [{ name: "color", value: `lime` }], + }, + { + header: "Inherited from html", + }, + { + selector: `html:active-view-transition`, + inherited: true, + declarations: [{ name: "color", value: "peachpuff", overridden: true }], + }, + { + selector: `*`, + inherited: true, + declarations: [{ name: "cursor", value: "default" }], + }, + ]); + + const viewTransitionChildren = await inspector.markup.walker.children( + viewTransitionNodeFront + ); + const viewTransitionGroupNodeFront = viewTransitionChildren.nodes[0]; + is( + viewTransitionGroupNodeFront.getAttribute("type"), + ":view-transition-group", + "Got expected ::view-transition-group node front" + ); + + const viewTransitionGroupChildren = await inspector.markup.walker.children( + viewTransitionGroupNodeFront + ); + const viewTransitionImagePairNodeFront = viewTransitionGroupChildren.nodes[0]; + is( + viewTransitionImagePairNodeFront.getAttribute("type"), + ":view-transition-image-pair", + "Got expected ::view-transition-image-pair node front" + ); + + const viewTransitionImagePairChildren = + await inspector.markup.walker.children(viewTransitionImagePairNodeFront); + const [viewTransitionOldNodeFront, viewTransitionNewNodeFront] = + viewTransitionImagePairChildren.nodes; + is( + viewTransitionOldNodeFront.getAttribute("type"), + ":view-transition-old", + "Got expected ::view-transition-old node front" + ); + is( + viewTransitionNewNodeFront.getAttribute("type"), + ":view-transition-new", + "Got expected ::view-transition-new node front" + ); + + info("Check rules on ::view-transition-old"); + await selectNode(viewTransitionOldNodeFront, inspector); + checkRuleViewContent(view, [ + { + selector: `::view-transition-old(root), ::view-transition-new(root)`, + declarations: [ + { name: "animation-duration", value: `1000s` }, + { name: "top", value: `1em` }, + { name: "gap", value: `10px`, inactiveCSS: true }, + ], + }, + { + header: "Inherited from ::view-transition", + }, + { + selector: `::view-transition`, + inherited: true, + declarations: [{ name: "color", value: `lime` }], + }, + { + header: "Inherited from html", + }, + { + selector: `html:active-view-transition`, + inherited: true, + declarations: [{ name: "color", value: "peachpuff", overridden: true }], + }, + { + selector: `*`, + inherited: true, + declarations: [{ name: "cursor", value: "default" }], + }, + ]); + + info("Check rules on ::view-transition-new"); + await selectNode(viewTransitionNewNodeFront, inspector); + checkRuleViewContent(view, [ + { + selector: `::view-transition-new(root)`, + declarations: [ + { name: "animation-duration", value: `3600s` }, + { name: "color", value: `thistle` }, + ], + }, + { + selector: `::view-transition-old(root), ::view-transition-new(root)`, + declarations: [ + { name: "animation-duration", value: `1000s`, overridden: true }, + { + name: "top", + value: `1em`, + // This shouldn't be inactive. See Bug 1998357 + inactiveCSS: true, + }, + { name: "gap", value: `10px`, inactiveCSS: true }, + ], + }, + { + header: "Inherited from ::view-transition", + }, + { + selector: `::view-transition`, + inherited: true, + declarations: [{ name: "color", value: `lime`, overridden: true }], + }, + { + header: "Inherited from html", + }, + { + selector: `html:active-view-transition`, + inherited: true, + declarations: [{ name: "color", value: "peachpuff", overridden: true }], + }, + { + selector: `*`, + inherited: true, + declarations: [{ name: "cursor", value: "default" }], + }, + ]); }); diff --git a/devtools/client/inspector/rules/test/doc_pseudoelement.html b/devtools/client/inspector/rules/test/doc_pseudoelement.html @@ -166,6 +166,29 @@ details[open]::details-content { border: 4px solid darkmagenta; } +html:active-view-transition { + color: peachpuff; +} + +::view-transition { + color: lime; +} + +/* Use very long animation-duration so the view-transition pseudo elements are available + during the whole test */ +::view-transition-old(root), +::view-transition-new(root) { + animation-duration: 1000s; + top: 1em; + gap: 10px; +} + +::view-transition-new(root) { + /* This should override the previous rule declaration when ::view-transition-new(root) is selected */ + animation-duration: 3600s; + color: thistle; +} + </style> </head> <body> @@ -214,6 +237,8 @@ details[open]::details-content { <p>In details</p> </details> + <aside class="transition">Transition</section> + <script> "use strict"; // This is the only way to have the ::backdrop style to be applied diff --git a/devtools/server/actors/style-rule.js b/devtools/server/actors/style-rule.js @@ -29,6 +29,12 @@ loader.lazyRequireGetter( ); loader.lazyRequireGetter( this, + "getNodeDisplayName", + "resource://devtools/server/actors/inspector/utils.js", + true +); +loader.lazyRequireGetter( + this, "SharedCssLogic", "resource://devtools/shared/inspector/css-logic.js" ); @@ -292,21 +298,45 @@ class StyleRuleActor extends Actor { } /** + * Returns true if the pseudo element anonymous node (e.g. ::before, ::marker, …) is selected. + * Returns false if a non pseudo element node is selected and we're looking into its pseudo + * elements rules (i.e. this is for the "Pseudo-elements" section in the Rules view") + */ + get isPseudoElementAnonymousNodeSelected() { + if (!this._pseudoElement) { + return false; + } + + // `this._pseudoElement` is the returned value by getNodeDisplayName, i.e that does + // differ from this.pageStyle.selectedElement.implementedPseudoElement (e.g. for + // view transition element, it will be `::view-transition-group(root)`, while + // implementedPseudoElement will be `::view-transition-group`). + return ( + this._pseudoElement === getNodeDisplayName(this.pageStyle.selectedElement) + ); + } + + /** * StyleRuleActor is spawned once per CSS Rule, but will be refreshed based on the * currently selected DOM Element, which is updated when PageStyleActor.getApplied * is called. */ get currentlySelectedElement() { let { selectedElement } = this.pageStyle; - if (!this._pseudoElement) { + // If we're not handling a pseudo element, or if the pseudo element node + // (e.g. ::before, ::marker, …) is the one selected in the markup view, we can + // directly return selected element. + if (!this._pseudoElement || this.isPseudoElementAnonymousNodeSelected) { return selectedElement; } - // Otherwise, we can be in one of two cases: - // - we are selecting a pseudo element, and that pseudo element is referenced - // by `selectedElement` - // - we are selecting the pseudo element "parent", we need to walk down the tree - // from `selectedElemnt` to find the pseudo element. + // Otherwise we are selecting the pseudo element "parent" (binding), and we need to + // walk down the tree from `selectedElement` to find the pseudo element. + + // FIXME: ::view-transition pseudo elements don't have a _moz_generated_content_ prefixed + // nodename, but have specific type and name attribute. + // At the moment this isn't causing any issues because we don't display the view + // transition rules in the pseudo element section, but this should be fixed in Bug 1998345. const pseudo = this._pseudoElement.replaceAll(":", ""); const nodeName = `_moz_generated_content_${pseudo}`; @@ -334,20 +364,11 @@ class StyleRuleActor extends Actor { const { selectedElement } = this.pageStyle; - // We can be in one of two cases: - // - we are selecting a pseudo element, and that pseudo element is referenced - // by `selectedElement` - // - we are selecting the pseudo element "parent". - // implementPseudoElement returns the pseudo-element string if this element represents - // a pseudo-element, or null otherwise. See https://searchfox.org/mozilla-central/rev/1b90936792b2c71ef931cb1b8d6baff9d825592e/dom/webidl/Element.webidl#102-107 - const isPseudoElementParentSelected = - selectedElement.implementedPseudoElement !== this._pseudoElement; - return selectedElement.ownerGlobal.getComputedStyle( selectedElement, // If we are selecting the pseudo element parent, we need to pass the pseudo element // to getComputedStyle to actually get the computed style of the pseudo element. - isPseudoElementParentSelected ? this._pseudoElement : null + !this.isPseudoElementAnonymousNodeSelected ? this._pseudoElement : null ); }