tor-browser

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

commit c9eff182af092e92aeccbd00b925af6cbde8a99f
parent 46bd7de55f384c396cd9a681ab8ca8f811c81e55
Author: Nicolas Chevobbe <nchevobbe@mozilla.com>
Date:   Fri, 14 Nov 2025 07:28:39 +0000

Bug 1152343 - [devtools] Fix computed panel matching selectors for pseudo element nodes. r=devtools-reviewers,jdescottes.

When the currently selected element is a pseudo element (::after, ::before, …),
the call to selectorMatchesElement would always return false as it doesn't handle
anonymous element.
To fix this, we retrieve the eventual bindingElement and pseudo of the selected
element, that we pass together to selectorMatchesElement.

Some test cases are added to make sure this works properly now.
We also move `checkMatchedSelectorForProperty` to head.js so we can easily
reuse it in different tests.

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

Diffstat:
Mdevtools/client/inspector/boxmodel/test/browser_boxmodel_pseudo-element.js | 4++--
Mdevtools/client/inspector/computed/test/browser_computed_matched-selectors-order.js | 52----------------------------------------------------
Mdevtools/client/inspector/computed/test/browser_computed_pseudo-element.js | 93+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------
Mdevtools/client/inspector/computed/test/doc_pseudoelement.html | 9++++++++-
Mdevtools/client/inspector/computed/test/head.js | 76++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------
Mdevtools/server/actors/inspector/css-logic.js | 4+++-
6 files changed, 165 insertions(+), 73 deletions(-)

diff --git a/devtools/client/inspector/boxmodel/test/browser_boxmodel_pseudo-element.js b/devtools/client/inspector/boxmodel/test/browser_boxmodel_pseudo-element.js @@ -52,7 +52,7 @@ const res1 = [ }, { selector: ".boxmodel-margin.boxmodel-left > span", - value: "4", // (100 - (10 * 2) - (20 * 2) - 32) / 2 + value: "auto", }, { selector: ".boxmodel-margin.boxmodel-bottom > span", @@ -60,7 +60,7 @@ const res1 = [ }, { selector: ".boxmodel-margin.boxmodel-right > span", - value: "4", // (100 - (10 * 2) - (20 * 2) - 32) / 2 + value: "auto", }, { selector: ".boxmodel-padding.boxmodel-top > span", diff --git a/devtools/client/inspector/computed/test/browser_computed_matched-selectors-order.js b/devtools/client/inspector/computed/test/browser_computed_matched-selectors-order.js @@ -900,55 +900,3 @@ async function checkBackgroundColorMatchedSelectors( content.document.getElementById(`style-${id}`)?.remove(); }); } - -async function checkMatchedSelectorForProperty( - view, - { property, expectedComputedValue, expectedMatchedSelectors } -) { - const propertyView = getPropertyView(view, property); - ok(propertyView, `found PropertyView for "${property}"`); - const { valueNode } = propertyView; - is( - valueNode.textContent, - expectedComputedValue, - `Expected displayed computed value for "${property}"` - ); - - is(propertyView.hasMatchedSelectors, true, "hasMatchedSelectors is true"); - - info("Expanding the matched selectors"); - propertyView.matchedExpanded = true; - await propertyView.refreshMatchedSelectors(); - - const selectorsEl = - propertyView.matchedSelectorsContainer.querySelectorAll(".rule-text"); - is( - selectorsEl.length, - expectedMatchedSelectors.length, - "Expected number of selectors are displayed" - ); - - selectorsEl.forEach((selectorEl, index) => { - is( - selectorEl.querySelector(".fix-get-selection").innerText, - expectedMatchedSelectors[index].selector, - `Selector #${index} is the expected one` - ); - is( - selectorEl.querySelector(".computed-other-property-value").innerText, - expectedMatchedSelectors[index].value, - `Selector #${index} ("${expectedMatchedSelectors[index].selector}") has the expected "${property}"` - ); - const classToMatch = index === 0 ? "bestmatch" : "matched"; - const expectedMatch = expectedMatchedSelectors[index].match ?? true; - is( - selectorEl.classList.contains(classToMatch), - expectedMatch, - `Selector #${index} ("${expectedMatchedSelectors[index].selector}") element does ${expectedMatch ? "" : "not "}have a matching class` - ); - }); -} - -function getPropertyView(computedView, name) { - return computedView.propertyViews.find(view => view.name === name); -} diff --git a/devtools/client/inspector/computed/test/browser_computed_pseudo-element.js b/devtools/client/inspector/computed/test/browser_computed_pseudo-element.js @@ -24,15 +24,90 @@ async function testTopLeft(inspector, view) { const beforeElement = children.nodes[0]; await selectNode(beforeElement, inspector); - let top = getComputedViewPropertyValue(view, "top"); - is(top, "0px", "The computed view shows the correct top"); - let left = getComputedViewPropertyValue(view, "left"); - is(left, "0px", "The computed view shows the correct left"); - const afterElement = children.nodes[children.nodes.length - 1]; + info("check `top` property on #topleft::before"); + await checkMatchedSelectorForProperty(view, { + property: "top", + expectedComputedValue: "0px", + expectedMatchedSelectors: [ + { + selector: ".topleft::before", + value: "0px", + }, + { + selector: ":where(.topleft)::before", + value: "10px", + }, + ], + }); + + info("check `left` property on #topleft::before"); + await checkMatchedSelectorForProperty(view, { + property: "left", + expectedComputedValue: "0px", + expectedMatchedSelectors: [ + { + selector: ".topleft::before", + value: "0px", + }, + { + selector: ":where(.topleft)::before", + value: "20px", + }, + ], + }); + + info("check `color` property on #topleft::before"); + await checkMatchedSelectorForProperty(view, { + property: "color", + expectedComputedValue: "rgb(0, 255, 0)", + expectedMatchedSelectors: [ + { + selector: ":where(.topleft)::before", + value: "lime", + }, + { + selector: ".topleft", + value: "blue", + match: false, + }, + { + selector: "body", + value: "rgb(51, 51, 51)", + match: false, + }, + { + selector: ":root", + value: "canvastext", + match: false, + }, + ], + }); + + const afterElement = children.nodes.at(-1); await selectNode(afterElement, inspector); - top = getComputedViewPropertyValue(view, "top"); - is(top, "96px", "The computed view shows the correct top"); - left = getComputedViewPropertyValue(view, "left"); - is(left, "96px", "The computed view shows the correct left"); + + info("check `top` property on #topleft::after"); + await checkMatchedSelectorForProperty(view, { + property: "top", + expectedComputedValue: "96px", + expectedMatchedSelectors: [ + { + selector: ".box::after", + value: "50%", + }, + ], + }); + + info("check `left` property on #topleft::after"); + await checkMatchedSelectorForProperty(view, { + property: "left", + expectedComputedValue: "96px", + expectedMatchedSelectors: [ + { + selector: ".box::after", + value: "50%", + }, + ], + }); } diff --git a/devtools/client/inspector/computed/test/doc_pseudoelement.html b/devtools/client/inspector/computed/test/doc_pseudoelement.html @@ -73,11 +73,18 @@ p:first-letter { margin-left: -16px; } +.topleft { + color: blue; +} .topleft:before { top:0; left:0; } - +:where(.topleft)::before { + top:10px; + left:20px; + color: lime; +} .topleft:first-line { color: orange; } diff --git a/devtools/client/inspector/computed/test/head.js b/devtools/client/inspector/computed/test/head.js @@ -67,14 +67,7 @@ function getComputedViewProperty(view, name) { * @return {PropertyView} */ function getComputedViewPropertyView(view, name) { - let propView; - for (const propertyView of view.propertyViews) { - if (propertyView.propertyInfo.name === name) { - propView = propertyView; - break; - } - } - return propView; + return view.propertyViews.find(propertyView => propertyView.name === name); } /** @@ -277,3 +270,70 @@ function failClipboardCheck(expectedPattern) { info("Actual: " + escape(actual)); info("Expected: " + escape(expectedPattern)); } + +/** + * Check that the given property has the expected value and the expected matched selectors + * + * @param {CssComputedView} view + * The instance of the computed view panel + * @param {Object} options + * @param {string} options.property + * The property name to check + * @param {string} options.expectedComputedValue + * The expected value displayed for the property + * @param {Object[]} options.expectedMatchedSelectors + * An array of objects describing the expected matched selectors + * @param {string} options.expectedMatchedSelectors[].selector + * The selector that should be displayed at this index + * @param {string} options.expectedMatchedSelectors[].value + * The value that should be displayed at this index + * @param {boolean} options.expectedMatchedSelectors[].match + * Whether the selector should match the currently selected element. Defaults to true. + */ +async function checkMatchedSelectorForProperty( + view, + { property, expectedComputedValue, expectedMatchedSelectors } +) { + const propertyView = getComputedViewPropertyView(view, property); + ok(propertyView, `found PropertyView for "${property}"`); + const { valueNode } = propertyView; + is( + valueNode.textContent, + expectedComputedValue, + `Expected displayed computed value for "${property}"` + ); + + is(propertyView.hasMatchedSelectors, true, "hasMatchedSelectors is true"); + + info("Expanding the matched selectors"); + propertyView.matchedExpanded = true; + await propertyView.refreshMatchedSelectors(); + + const selectorsEl = + propertyView.matchedSelectorsContainer.querySelectorAll(".rule-text"); + is( + selectorsEl.length, + expectedMatchedSelectors.length, + "Expected number of selectors are displayed" + ); + + selectorsEl.forEach((selectorEl, index) => { + is( + selectorEl.querySelector(".fix-get-selection").innerText, + expectedMatchedSelectors[index].selector, + `Selector #${index} is the expected one` + ); + is( + selectorEl.querySelector(".computed-other-property-value").innerText, + expectedMatchedSelectors[index].value, + `Selector #${index} ("${expectedMatchedSelectors[index].selector}") has the expected "${property}"` + ); + const classToMatch = index === 0 ? "bestmatch" : "matched"; + const expectedMatch = expectedMatchedSelectors[index].match ?? true; + is( + selectorEl.classList.contains(classToMatch), + expectedMatch, + `Selector #${index} ("${expectedMatchedSelectors[index].selector}") element does ${expectedMatch ? "" : "not "}have a matching class` + ); + }); +} diff --git a/devtools/server/actors/inspector/css-logic.js b/devtools/server/actors/inspector/css-logic.js @@ -486,7 +486,9 @@ class CssLogic { selectorMatchesElement(domRule, idx) { let element = this.viewedElement; do { - if (domRule.selectorMatchesElement(idx, element)) { + const { bindingElement, pseudo } = + CssLogic.getBindingElementAndPseudo(element); + if (domRule.selectorMatchesElement(idx, bindingElement, pseudo)) { return true; }