commit e999124172a9c0bcf9e5c0f30b664b800d63100a
parent 5d0f81407228eaa8181159e5d8bd5cd7b96722f2
Author: Nicolas Chevobbe <nchevobbe@mozilla.com>
Date: Fri, 24 Oct 2025 14:29:54 +0000
Bug 1995943 - Add test for CSSStyleRule::selectorMatchesElement on :host rules. r=layout-reviewers,dshin.
Differential Revision: https://phabricator.services.mozilla.com/D269727
Diffstat:
4 files changed, 158 insertions(+), 2 deletions(-)
diff --git a/layout/inspector/tests/mochitest.toml b/layout/inspector/tests/mochitest.toml
@@ -95,6 +95,8 @@ skip-if = ["os == 'android'"]
["test_selectormatcheselement.html"]
+["test_selectormatcheselement_host.html"]
+
["test_selectormatcheselement_scope.html"]
["test_supports.html"]
diff --git a/layout/inspector/tests/test_selectormatcheselement.html b/layout/inspector/tests/test_selectormatcheselement.html
@@ -4,7 +4,7 @@
https://bugzilla.mozilla.org/show_bug.cgi?id=1037519
-->
<head>
- <title>Test for nsIDOMUtils::selectorMatchesElement</title>
+ <title>Test for CSSStyleRule::selectorMatchesElement</title>
<script src="/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
<style type="text/css">
diff --git a/layout/inspector/tests/test_selectormatcheselement_host.html b/layout/inspector/tests/test_selectormatcheselement_host.html
@@ -0,0 +1,154 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for CSSStyleRule::selectorMatchesElement on :host rules</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+ <div id="host"></div>
+ <script>
+ /***** Add Shadow DOM *****/
+ const topLevelHostEl = document.getElementById("host");
+ const topShadow = topLevelHostEl.attachShadow({
+ mode: "open"
+ });
+ const topLevelShadowSectionEl = document.createElement("section");
+ topLevelShadowSectionEl.id = "top-level-shadow-section";
+ const nestedHostEl = document.createElement("div");
+ nestedHostEl.id = "nested-host";
+
+ topLevelShadowSectionEl.append("top shadow dom section",)
+ topShadow.append(topLevelShadowSectionEl, nestedHostEl);
+
+ const sharedStyleSheet = new CSSStyleSheet();
+ sharedStyleSheet.replaceSync(`
+ section, [test-rule-shared] {
+ color: var(--a);
+ }
+ :host, [test-rule-shared] {
+ --a: red;
+ }`);
+
+ const topLevelStyleSheet = new CSSStyleSheet();
+ topLevelStyleSheet.replaceSync(`
+ :where(section), [test-rule] {
+ background-color: var(--b);
+ }
+ :host, [test-rule] {
+ --b: gold;
+ }`);
+
+ topShadow.adoptedStyleSheets = [sharedStyleSheet, topLevelStyleSheet];
+
+
+ const nestedShadow = nestedHostEl.attachShadow({
+ mode: "open"
+ });
+ const nestedShadowSectionEl = document.createElement("section");
+ nestedShadowSectionEl.textContent = "nested shadow dom section";
+ nestedShadowSectionEl.id = "nested-shadow-dom-section";
+ nestedShadow.append(nestedShadowSectionEl);
+
+ const nestedStyleSheet = new CSSStyleSheet();
+ nestedStyleSheet.replaceSync(`
+ :is(section), [test-rule-nested] {
+ border-color: var(--c);
+ }
+ :host, [test-rule-nested] {
+ --c: lime;
+ }`);
+ nestedShadow.adoptedStyleSheets = [sharedStyleSheet, nestedStyleSheet];
+
+ /***** Test *****/
+ add_task(async () => {
+ info("Checking rules on top level shadow section el");
+ checkRulesAndSelectors(topLevelShadowSectionEl, [{
+ selectorText: `:where(section), [test-rule]`,
+ selectorMatches: [true, false]
+ },{
+ selectorText: `section, [test-rule-shared]`,
+ selectorMatches: [true, false]
+ }]);
+
+ info("Checking rules on top level host");
+ checkRulesAndSelectors(topLevelHostEl, [{
+ selectorText: `:host, [test-rule-shared]`,
+ selectorMatches: [true, false]
+ },{
+ selectorText: `:host, [test-rule]`,
+ selectorMatches: [true, false]
+ }]);
+
+ info("Checking rules on nested shadow section el");
+ checkRulesAndSelectors(nestedShadowSectionEl, [{
+ selectorText: `section, [test-rule-shared]`,
+ selectorMatches: [true, false]
+ },{
+ selectorText: `:is(section), [test-rule-nested]`,
+ selectorMatches: [true, false]
+ }]);
+
+ info("Checking rules on nested host");
+ checkRulesAndSelectors(nestedHostEl, [{
+ selectorText: `:host, [test-rule-shared]`,
+ selectorMatches: [true, false]
+ },{
+ selectorText: `:host, [test-rule-nested]`,
+ selectorMatches: [true, false]
+ }]);
+
+ info("Check that non-shared rule selectors don't match for non-matching elements");
+ const nonSharedTopLevelHostRules = getNonUAMatchingRules(topLevelHostEl)
+ .filter(rule => rule.parentStyleSheet !== sharedStyleSheet);
+ is(nonSharedTopLevelHostRules.length, 1, "top level host only has 1 non shared rule");
+ is(
+ nonSharedTopLevelHostRules[0].selectorMatchesElement(0, nestedHostEl),
+ false,
+ "non-shared top level host rule does not match nested host"
+ );
+
+ const nonSharedNestedHostRules = getNonUAMatchingRules(nestedHostEl)
+ .filter(rule => rule.parentStyleSheet !== sharedStyleSheet);
+ is(nonSharedNestedHostRules.length, 1, "nested host only has 1 non shared rule");
+ is(
+ nonSharedNestedHostRules[0].selectorMatchesElement(0, topLevelHostEl),
+ false,
+ "non-shared nested host rule does not match top level host"
+ );
+ });
+
+ function checkRulesAndSelectors (element, expectedData) {
+ const rules = getNonUAMatchingRules(element);
+ is(rules.length, expectedData.length, `Got expected number of rules for #${element.id}`);
+ for (let i = 0; i < expectedData.length; i++) {
+ const rule = rules[i];
+ const expected = expectedData[i];
+ is(rule.selectorText, expected.selectorText, `Got expected rule at index ${i} for #${element.id}`);
+ for (let j = 0; j < expected.selectorMatches.length; j++) {
+ const selectorMatch = expected.selectorMatches[j];
+ is(
+ rule.selectorMatchesElement(j, element),
+ selectorMatch,
+ `"${rule.selectorText}" selector part at index ${j} ${selectorMatch ? "matches": "does not match"} #${element.id}`
+ );
+ }
+ }
+ }
+
+ function getNonUAMatchingRules(element) {
+ return SpecialPowers.InspectorUtils.getMatchingCSSRules(element)
+ .filter(rule => {
+ let selector = "";
+ // Accessing selectorText does throw for some rules (these aren't the ones
+ // we're interested in, so it's fine)
+ try {
+ selector = rule.selectorText;
+ } catch (e) {}
+ return selector.includes("[test-rule");
+ })
+ }
+
+ </script>
+</body>
+</html>
diff --git a/layout/inspector/tests/test_selectormatcheselement_scope.html b/layout/inspector/tests/test_selectormatcheselement_scope.html
@@ -2,7 +2,7 @@
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=1934914a
-->
-<title>Test for nsIDOMUtils::selectorMatchesElement with @scope</title>
+<title>Test for CSSStyleRule::selectorMatchesElement with @scope</title>
<script src="/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
<script>