shadow-host-style-sharing.html (7075B)
1 <!DOCTYPE html> 2 <meta charset="utf-8"> 3 <title>Shadow Host Style Sharing Tests</title> 4 <link rel="help" href="https://drafts.csswg.org/css-scoping/#host-selector"> 5 <script src="/resources/testharness.js"></script> 6 <script src="/resources/testharnessreport.js"></script> 7 8 <div id="container"></div> 9 10 <script> 11 const container = document.getElementById('container'); 12 13 function cleanup() { 14 container.innerHTML = ''; 15 } 16 17 // Test 1: Different :host rules in different stylesheets 18 test(() => { 19 cleanup(); 20 21 const redSheet = new CSSStyleSheet(); 22 redSheet.replaceSync(':host { color: rgb(255, 0, 0); }'); 23 24 const blueSheet = new CSSStyleSheet(); 25 blueSheet.replaceSync(':host { color: rgb(0, 0, 255); }'); 26 27 let useRed = true; 28 29 class DynamicHost extends HTMLElement { 30 constructor() { 31 super(); 32 const shadow = this.attachShadow({ mode: 'open' }); 33 shadow.innerHTML = '<div>content</div>'; 34 shadow.adoptedStyleSheets = [useRed ? redSheet : blueSheet]; 35 } 36 } 37 customElements.define('dynamic-host-1', DynamicHost); 38 39 const parent = document.createElement('div'); 40 41 // Create siblings: first 10 with red sheet 42 useRed = true; 43 const redHosts = []; 44 for (let i = 0; i < 10; i++) { 45 const host = document.createElement('dynamic-host-1'); 46 redHosts.push(host); 47 parent.appendChild(host); 48 } 49 50 // Then 1 with blue sheet as a sibling 51 useRed = false; 52 const blueHost = document.createElement('dynamic-host-1'); 53 parent.appendChild(blueHost); 54 55 // Add to document and force style computation 56 container.appendChild(parent); 57 container.offsetHeight; 58 59 assert_equals(getComputedStyle(blueHost).color, 'rgb(0, 0, 255)', 'Must be blue, not red'); 60 61 for (const host of redHosts) { 62 assert_equals(getComputedStyle(host).color, 'rgb(255, 0, 0)', 'Must be red'); 63 } 64 }, 'Different CascadeData must block sharing despite being same element type'); 65 66 // Test 2: Same :host rules, different inherited styles 67 test(() => { 68 cleanup(); 69 70 const sharedSheet = new CSSStyleSheet(); 71 sharedSheet.replaceSync(':host { font-size: 20px; }'); 72 73 class SharedHost extends HTMLElement { 74 constructor() { 75 super(); 76 const shadow = this.attachShadow({ mode: 'open' }); 77 shadow.adoptedStyleSheets = [sharedSheet]; 78 shadow.innerHTML = '<span>content</span>'; 79 } 80 } 81 customElements.define('shared-host-2', SharedHost); 82 83 const parent1 = document.createElement('div'); 84 parent1.style.color = 'rgb(255, 0, 0)'; 85 const host1 = document.createElement('shared-host-2'); 86 parent1.appendChild(host1); 87 container.appendChild(parent1); 88 89 const parent2 = document.createElement('div'); 90 parent2.style.color = 'rgb(0, 255, 0)'; 91 const host2 = document.createElement('shared-host-2'); 92 parent2.appendChild(host2); 93 container.appendChild(parent2); 94 95 assert_equals(getComputedStyle(host1).color, 'rgb(255, 0, 0)'); 96 assert_equals(getComputedStyle(host2).color, 'rgb(0, 255, 0)'); 97 }, 'Same :host rules but different inherited styles'); 98 99 // Test 3: :host with class selector 100 test(() => { 101 cleanup(); 102 103 const classSheet = new CSSStyleSheet(); 104 classSheet.replaceSync(` 105 :host(.active) { background-color: rgb(255, 0, 0); } 106 :host(.inactive) { background-color: rgb(0, 0, 255); } 107 `); 108 109 class ClassHost extends HTMLElement { 110 constructor() { 111 super(); 112 const shadow = this.attachShadow({ mode: 'open' }); 113 shadow.adoptedStyleSheets = [classSheet]; 114 shadow.innerHTML = '<span>content</span>'; 115 } 116 } 117 customElements.define('class-host-3', ClassHost); 118 119 const active = document.createElement('class-host-3'); 120 active.className = 'active'; 121 const inactive = document.createElement('class-host-3'); 122 inactive.className = 'inactive'; 123 124 container.appendChild(active); 125 container.appendChild(inactive); 126 127 assert_equals(getComputedStyle(active).backgroundColor, 'rgb(255, 0, 0)'); 128 assert_equals(getComputedStyle(inactive).backgroundColor, 'rgb(0, 0, 255)'); 129 }, ':host with class selector should not share across different classes'); 130 131 // Test 4: :host with attribute selector 132 test(() => { 133 cleanup(); 134 135 const attrSheet = new CSSStyleSheet(); 136 attrSheet.replaceSync(` 137 :host([data-theme="dark"]) { color: rgb(0, 0, 0); } 138 :host([data-theme="light"]) { color: rgb(255, 255, 255); } 139 `); 140 141 class AttrHost extends HTMLElement { 142 constructor() { 143 super(); 144 const shadow = this.attachShadow({ mode: 'open' }); 145 shadow.adoptedStyleSheets = [attrSheet]; 146 shadow.innerHTML = '<span>content</span>'; 147 } 148 } 149 customElements.define('attr-host-4', AttrHost); 150 151 const dark = document.createElement('attr-host-4'); 152 dark.setAttribute('data-theme', 'dark'); 153 const light = document.createElement('attr-host-4'); 154 light.setAttribute('data-theme', 'light'); 155 156 container.appendChild(dark); 157 container.appendChild(light); 158 159 assert_equals(getComputedStyle(dark).color, 'rgb(0, 0, 0)'); 160 assert_equals(getComputedStyle(light).color, 'rgb(255, 255, 255)'); 161 }, ':host with attribute selector should not share across different attributes'); 162 163 // Test 5: Same stylesheet, different inline styles 164 test(() => { 165 cleanup(); 166 167 const sharedSheet = new CSSStyleSheet(); 168 sharedSheet.replaceSync(':host { display: block; }'); 169 170 class InlineHost extends HTMLElement { 171 constructor() { 172 super(); 173 const shadow = this.attachShadow({ mode: 'open' }); 174 shadow.adoptedStyleSheets = [sharedSheet]; 175 shadow.innerHTML = '<span>content</span>'; 176 } 177 } 178 customElements.define('inline-host-5', InlineHost); 179 180 const host1 = document.createElement('inline-host-5'); 181 host1.style.color = 'rgb(255, 0, 0)'; 182 183 const host2 = document.createElement('inline-host-5'); 184 host2.style.color = 'rgb(0, 255, 0)'; 185 186 container.appendChild(host1); 187 container.appendChild(host2); 188 189 assert_equals(getComputedStyle(host1).color, 'rgb(255, 0, 0)'); 190 assert_equals(getComputedStyle(host2).color, 'rgb(0, 255, 0)'); 191 }, 'Same CascadeData but different inline styles should not share'); 192 193 // Test 6: Same :host rules but different shadow stylesheets 194 test(() => { 195 cleanup(); 196 197 const sheetA = new CSSStyleSheet(); 198 sheetA.replaceSync(':host { color: rgb(255, 0, 0); }'); 199 200 const sheetB = new CSSStyleSheet(); 201 sheetB.replaceSync(':host { color: rgb(255, 0, 0); } span { font-weight: bold; }'); 202 203 let whichSheet = 'a'; 204 205 class SubtleHost extends HTMLElement { 206 constructor() { 207 super(); 208 const shadow = this.attachShadow({ mode: 'open' }); 209 shadow.adoptedStyleSheets = [whichSheet === 'a' ? sheetA : sheetB]; 210 shadow.innerHTML = '<span>content</span>'; 211 } 212 } 213 customElements.define('subtle-host-6', SubtleHost); 214 215 whichSheet = 'a'; 216 const hostA = document.createElement('subtle-host-6'); 217 218 whichSheet = 'b'; 219 const hostB = document.createElement('subtle-host-6'); 220 221 container.appendChild(hostA); 222 container.appendChild(hostB); 223 224 assert_equals(getComputedStyle(hostA).color, 'rgb(255, 0, 0)'); 225 assert_equals(getComputedStyle(hostB).color, 'rgb(255, 0, 0)'); 226 }, 'Same :host rules but different CascadeData should prevent sharing'); 227 </script>