| <!DOCTYPE html> |
| <meta charset="utf-8"> |
| <title>Shadow Host Style Sharing Tests</title> |
| <link rel="help" href="https://drafts.csswg.org/css-shadow/#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> |