commit 780dd7e9c236b7eccaefbced542e91bbca5fc26f
parent 6cb49e422cad3e0bba008296df7d5929b25e8b43
Author: Denis Palmeiro <dpalmeiro@mozilla.com>
Date: Tue, 30 Dec 2025 15:43:11 +0000
Bug 2006172: Add a few tests to make sure style sharing does not happen in certain cases such as :host rule mismatch. r=emilio
Differential Revision: https://phabricator.services.mozilla.com/D277358
Diffstat:
1 file changed, 227 insertions(+), 0 deletions(-)
diff --git a/testing/web-platform/tests/css/css-scoping/shadow-host-style-sharing.html b/testing/web-platform/tests/css/css-scoping/shadow-host-style-sharing.html
@@ -0,0 +1,227 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Shadow Host Style Sharing Tests</title>
+<link rel="help" href="https://drafts.csswg.org/css-scoping/#host-selector">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<div id="container"></div>
+
+<script>
+const container = document.getElementById('container');
+
+function cleanup() {
+ container.innerHTML = '';
+}
+
+// Test 1: Different :host rules in different stylesheets
+test(() => {
+ cleanup();
+
+ const redSheet = new CSSStyleSheet();
+ redSheet.replaceSync(':host { color: rgb(255, 0, 0); }');
+
+ const blueSheet = new CSSStyleSheet();
+ blueSheet.replaceSync(':host { color: rgb(0, 0, 255); }');
+
+ let useRed = true;
+
+ class DynamicHost extends HTMLElement {
+ constructor() {
+ super();
+ const shadow = this.attachShadow({ mode: 'open' });
+ shadow.innerHTML = '<div>content</div>';
+ shadow.adoptedStyleSheets = [useRed ? redSheet : blueSheet];
+ }
+ }
+ customElements.define('dynamic-host-1', DynamicHost);
+
+ const parent = document.createElement('div');
+
+ // Create siblings: first 10 with red sheet
+ useRed = true;
+ const redHosts = [];
+ for (let i = 0; i < 10; i++) {
+ const host = document.createElement('dynamic-host-1');
+ redHosts.push(host);
+ parent.appendChild(host);
+ }
+
+ // Then 1 with blue sheet as a sibling
+ useRed = false;
+ const blueHost = document.createElement('dynamic-host-1');
+ parent.appendChild(blueHost);
+
+ // Add to document and force style computation
+ container.appendChild(parent);
+ container.offsetHeight;
+
+ assert_equals(getComputedStyle(blueHost).color, 'rgb(0, 0, 255)', 'Must be blue, not red');
+
+ for (const host of redHosts) {
+ assert_equals(getComputedStyle(host).color, 'rgb(255, 0, 0)', 'Must be red');
+ }
+}, 'Different CascadeData must block sharing despite being same element type');
+
+// Test 2: Same :host rules, different inherited styles
+test(() => {
+ cleanup();
+
+ const sharedSheet = new CSSStyleSheet();
+ sharedSheet.replaceSync(':host { font-size: 20px; }');
+
+ class SharedHost extends HTMLElement {
+ constructor() {
+ super();
+ const shadow = this.attachShadow({ mode: 'open' });
+ shadow.adoptedStyleSheets = [sharedSheet];
+ shadow.innerHTML = '<span>content</span>';
+ }
+ }
+ customElements.define('shared-host-2', SharedHost);
+
+ const parent1 = document.createElement('div');
+ parent1.style.color = 'rgb(255, 0, 0)';
+ const host1 = document.createElement('shared-host-2');
+ parent1.appendChild(host1);
+ container.appendChild(parent1);
+
+ const parent2 = document.createElement('div');
+ parent2.style.color = 'rgb(0, 255, 0)';
+ const host2 = document.createElement('shared-host-2');
+ parent2.appendChild(host2);
+ container.appendChild(parent2);
+
+ assert_equals(getComputedStyle(host1).color, 'rgb(255, 0, 0)');
+ assert_equals(getComputedStyle(host2).color, 'rgb(0, 255, 0)');
+}, 'Same :host rules but different inherited styles');
+
+// Test 3: :host with class selector
+test(() => {
+ cleanup();
+
+ const classSheet = new CSSStyleSheet();
+ classSheet.replaceSync(`
+ :host(.active) { background-color: rgb(255, 0, 0); }
+ :host(.inactive) { background-color: rgb(0, 0, 255); }
+ `);
+
+ class ClassHost extends HTMLElement {
+ constructor() {
+ super();
+ const shadow = this.attachShadow({ mode: 'open' });
+ shadow.adoptedStyleSheets = [classSheet];
+ shadow.innerHTML = '<span>content</span>';
+ }
+ }
+ customElements.define('class-host-3', ClassHost);
+
+ const active = document.createElement('class-host-3');
+ active.className = 'active';
+ const inactive = document.createElement('class-host-3');
+ inactive.className = 'inactive';
+
+ container.appendChild(active);
+ container.appendChild(inactive);
+
+ assert_equals(getComputedStyle(active).backgroundColor, 'rgb(255, 0, 0)');
+ assert_equals(getComputedStyle(inactive).backgroundColor, 'rgb(0, 0, 255)');
+}, ':host with class selector should not share across different classes');
+
+// Test 4: :host with attribute selector
+test(() => {
+ cleanup();
+
+ const attrSheet = new CSSStyleSheet();
+ attrSheet.replaceSync(`
+ :host([data-theme="dark"]) { color: rgb(0, 0, 0); }
+ :host([data-theme="light"]) { color: rgb(255, 255, 255); }
+ `);
+
+ class AttrHost extends HTMLElement {
+ constructor() {
+ super();
+ const shadow = this.attachShadow({ mode: 'open' });
+ shadow.adoptedStyleSheets = [attrSheet];
+ shadow.innerHTML = '<span>content</span>';
+ }
+ }
+ customElements.define('attr-host-4', AttrHost);
+
+ const dark = document.createElement('attr-host-4');
+ dark.setAttribute('data-theme', 'dark');
+ const light = document.createElement('attr-host-4');
+ light.setAttribute('data-theme', 'light');
+
+ container.appendChild(dark);
+ container.appendChild(light);
+
+ assert_equals(getComputedStyle(dark).color, 'rgb(0, 0, 0)');
+ assert_equals(getComputedStyle(light).color, 'rgb(255, 255, 255)');
+}, ':host with attribute selector should not share across different attributes');
+
+// Test 5: Same stylesheet, different inline styles
+test(() => {
+ cleanup();
+
+ const sharedSheet = new CSSStyleSheet();
+ sharedSheet.replaceSync(':host { display: block; }');
+
+ class InlineHost extends HTMLElement {
+ constructor() {
+ super();
+ const shadow = this.attachShadow({ mode: 'open' });
+ shadow.adoptedStyleSheets = [sharedSheet];
+ shadow.innerHTML = '<span>content</span>';
+ }
+ }
+ customElements.define('inline-host-5', InlineHost);
+
+ const host1 = document.createElement('inline-host-5');
+ host1.style.color = 'rgb(255, 0, 0)';
+
+ const host2 = document.createElement('inline-host-5');
+ host2.style.color = 'rgb(0, 255, 0)';
+
+ container.appendChild(host1);
+ container.appendChild(host2);
+
+ assert_equals(getComputedStyle(host1).color, 'rgb(255, 0, 0)');
+ assert_equals(getComputedStyle(host2).color, 'rgb(0, 255, 0)');
+}, 'Same CascadeData but different inline styles should not share');
+
+// Test 6: Same :host rules but different shadow stylesheets
+test(() => {
+ cleanup();
+
+ const sheetA = new CSSStyleSheet();
+ sheetA.replaceSync(':host { color: rgb(255, 0, 0); }');
+
+ const sheetB = new CSSStyleSheet();
+ sheetB.replaceSync(':host { color: rgb(255, 0, 0); } span { font-weight: bold; }');
+
+ let whichSheet = 'a';
+
+ class SubtleHost extends HTMLElement {
+ constructor() {
+ super();
+ const shadow = this.attachShadow({ mode: 'open' });
+ shadow.adoptedStyleSheets = [whichSheet === 'a' ? sheetA : sheetB];
+ shadow.innerHTML = '<span>content</span>';
+ }
+ }
+ customElements.define('subtle-host-6', SubtleHost);
+
+ whichSheet = 'a';
+ const hostA = document.createElement('subtle-host-6');
+
+ whichSheet = 'b';
+ const hostB = document.createElement('subtle-host-6');
+
+ container.appendChild(hostA);
+ container.appendChild(hostB);
+
+ assert_equals(getComputedStyle(hostA).color, 'rgb(255, 0, 0)');
+ assert_equals(getComputedStyle(hostB).color, 'rgb(255, 0, 0)');
+}, 'Same :host rules but different CascadeData should prevent sharing');
+</script>