tor-browser

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

commit 4147d8ed8b77dd5fb0b35f4ca05b6beeca2f6801
parent e44fab07fd3d713e6495fbc2e49d262801e29704
Author: Nicolas Chevobbe <nchevobbe@mozilla.com>
Date:   Tue,  2 Dec 2025 11:35:00 +0000

Bug 1940930 - [devtools] Fix add new rule for shadow-root elements. r=devtools-reviewers,jdescottes

We only needed to retrieve the shadow root when the element is in the shadow DOM
A test is added to cover adding a rule and a declaration on shadow DOM elements
from the rules view.

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

Diffstat:
Mdevtools/client/inspector/rules/test/browser_part1.toml | 2++
Mdevtools/client/inspector/rules/test/browser_rules_add-rule-and-property.js | 12++++++++++++
Adevtools/client/inspector/rules/test/browser_rules_add-rule-shadow-dom.js | 135+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mdevtools/server/actors/page-style.js | 23++++++++++++++---------
Mdevtools/server/actors/style-sheets.js | 7++++++-
Mdevtools/server/actors/utils/stylesheets-manager.js | 5+++--
6 files changed, 172 insertions(+), 12 deletions(-)

diff --git a/devtools/client/inspector/rules/test/browser_part1.toml b/devtools/client/inspector/rules/test/browser_part1.toml @@ -62,6 +62,8 @@ support-files = [ ["browser_rules_add-rule-pseudo-class.js"] +["browser_rules_add-rule-shadow-dom.js"] + ["browser_rules_add-rule-then-property-edit-selector.js"] ["browser_rules_add-rule-with-menu.js"] diff --git a/devtools/client/inspector/rules/test/browser_rules_add-rule-and-property.js b/devtools/client/inspector/rules/test/browser_rules_add-rule-and-property.js @@ -28,4 +28,16 @@ add_task(async function () { const prop = textProps[textProps.length - 1]; is(prop.name, "font-weight", "The last property name is font-weight"); is(prop.value, "bold", "The last property value is bold"); + + info( + "Add another rule to make sure we reuse the stylesheet we created the first time we added a rule" + ); + await addNewRuleAndDismissEditor(inspector, view, "#testid", 1); + + const styleSheetsCount = await SpecialPowers.spawn( + gBrowser.selectedBrowser, + [], + () => content.document.styleSheets.length + ); + is(styleSheetsCount, 1, "Only one stylesheet was created in the document"); }); diff --git a/devtools/client/inspector/rules/test/browser_rules_add-rule-shadow-dom.js b/devtools/client/inspector/rules/test/browser_rules_add-rule-shadow-dom.js @@ -0,0 +1,135 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Tests adding a rule on elements in shadow DOM + +const TEST_URL = + `data:text/html;charset=utf-8,` + + encodeURIComponent(` + <html> + <body> + <test-component> + <div slot="my-slot">a slot</div> + </test-component> + + <script> + 'use strict'; + customElements.define('test-component', class extends HTMLElement { + constructor() { + super(); + let shadowRoot = this.attachShadow({mode: 'open'}); + shadowRoot.innerHTML = '<h1>hello</h1><slot name="my-slot"></slot>'; + } + }); + </script> + </body> + </html> +`); + +add_task(async function () { + await addTab(TEST_URL); + const { inspector, view } = await openRuleView(); + const { markup } = inspector; + + // <test-component> is a shadow host. + info("Find and expand the test-component shadow DOM host."); + const hostFront = await getNodeFront("test-component", inspector); + + await markup.expandNode(hostFront); + await waitForMultipleChildrenUpdates(inspector); + + info( + "Test that expanding a shadow host shows shadow root and one host child." + ); + const hostContainer = markup.getContainer(hostFront); + + info("Expand the shadow root"); + const childContainers = hostContainer.getChildContainers(); + const shadowRootContainer = childContainers[0]; + await expandContainer(inspector, shadowRootContainer); + + const [h1Container, slotContainer] = shadowRootContainer.getChildContainers(); + + info("Add a rule on the h1 node in the shadow DOM"); + await selectNode(h1Container.node, inspector); + + // Add the rule + await addNewRuleAndDismissEditor(inspector, view, "h1", 1); + // and a property + await addProperty(view, 1, "color", "red"); + + await checkRuleViewContent(view, [ + { + selector: "element", + declarations: [], + }, + { + selector: "h1", + declarations: [{ name: "color", value: "red", dirty: true }], + }, + ]); + let computedColor = await SpecialPowers.spawn( + gBrowser.selectedBrowser, + [], + () => + content.getComputedStyle( + content.document + .querySelector("test-component") + .shadowRoot.querySelector("h1") + ).color + ); + is( + computedColor, + "rgb(255, 0, 0)", + "The declaration was properly assigned to the shadow DOM h1" + ); + + info("Add a rule on the slot node in the shadow DOM"); + await selectNode(slotContainer.node, inspector); + + // Add the rule + await addNewRuleAndDismissEditor(inspector, view, "slot", 1); + // and a property + await addProperty(view, 1, "color", "blue"); + + await checkRuleViewContent(view, [ + { + selector: "element", + declarations: [], + }, + { + selector: "slot", + declarations: [{ name: "color", value: "blue", dirty: true }], + }, + ]); + computedColor = await SpecialPowers.spawn( + gBrowser.selectedBrowser, + [], + () => + content.getComputedStyle( + content.document + .querySelector("test-component") + .shadowRoot.querySelector("slot") + ).color + ); + is( + computedColor, + "rgb(0, 0, 255)", + "The declaration was properly assigned to the shadow DOM h1" + ); + + const shadowRootStyleSheetsCount = await SpecialPowers.spawn( + gBrowser.selectedBrowser, + [], + () => + content.document.querySelector("test-component").shadowRoot.styleSheets + .length + ); + is( + shadowRootStyleSheetsCount, + 1, + "Only one stylesheet was created in the shadow root" + ); +}); diff --git a/devtools/server/actors/page-style.js b/devtools/server/actors/page-style.js @@ -94,8 +94,8 @@ class PageStyleActor extends Actor { // Latest node queried for its applied styles. this.selectedElement = null; - // Maps document elements to style elements, used to add new rules. - this.styleElements = new WeakMap(); + // Maps root node (document|ShadowRoot) to stylesheets, which are used to add new rules. + this.styleSheetsByRootNode = new WeakMap(); this.onFrameUnload = this.onFrameUnload.bind(this); @@ -122,7 +122,7 @@ class PageStyleActor extends Actor { this.refMap = null; this.selectedElement = null; this.cssLogic = null; - this.styleElements = null; + this.styleSheetsByRootNode = null; this._observedRules = []; } @@ -1219,7 +1219,7 @@ class PageStyleActor extends Actor { * On page navigation, tidy up remaining objects. */ onFrameUnload() { - this.styleElements = new WeakMap(); + this.styleSheetsByRootNode = new WeakMap(); } _onStylesheetUpdated({ resourceId, updateKind, updates = {} }) { @@ -1270,14 +1270,19 @@ class PageStyleActor extends Actor { async addNewRule(node, pseudoClasses) { let sheet = null; const doc = node.rawNode.ownerDocument; + const rootNode = node.rawNode.getRootNode(); + if ( - this.styleElements.has(doc) && - this.styleElements.get(doc).ownerNode?.isConnected + this.styleSheetsByRootNode.has(rootNode) && + this.styleSheetsByRootNode.get(rootNode).ownerNode?.isConnected ) { - sheet = this.styleElements.get(doc); + sheet = this.styleSheetsByRootNode.get(rootNode); } else { - sheet = await this.styleSheetsManager.addStyleSheet(doc); - this.styleElements.set(doc, sheet); + sheet = await this.styleSheetsManager.addStyleSheet( + doc, + node.rawNode.containingShadowRoot || doc.documentElement + ); + this.styleSheetsByRootNode.set(rootNode, sheet); } const cssRules = sheet.cssRules; diff --git a/devtools/server/actors/style-sheets.js b/devtools/server/actors/style-sheets.js @@ -83,7 +83,12 @@ class StyleSheetsActor extends Actor { */ async addStyleSheet(text, fileName = null) { const styleSheetsManager = this._getStyleSheetsManager(); - await styleSheetsManager.addStyleSheet(this.document, text, fileName); + await styleSheetsManager.addStyleSheet( + this.document, + this.document.documentElement, + text, + fileName + ); } _getStyleSheetsManager() { diff --git a/devtools/server/actors/utils/stylesheets-manager.js b/devtools/server/actors/utils/stylesheets-manager.js @@ -270,13 +270,14 @@ class StyleSheetsManager extends EventEmitter { * * @param {Document} document * Document that the new style sheet belong to. + * @param {Element} parent + * The element into which we'll append the <style> element * @param {string} text * Content of style sheet. * @param {string} fileName * If the stylesheet adding is from file, `fileName` indicates the path. */ - async addStyleSheet(document, text, fileName) { - const parent = document.documentElement; + async addStyleSheet(document, parent, text, fileName) { const style = document.createElementNS( "http://www.w3.org/1999/xhtml", "style"