shadow-cascade-order-001.html (16221B)
1 <!DOCTYPE html> 2 <html> 3 <head> 4 <title>Shadow DOM: CSS Style Rule cascading</title> 5 <meta name="assert" content="Cascading order test for style rules from various shadow trees."> 6 <link rel="author" title="Takayoshi Kochi" href="mailto:kochi@google.com"> 7 <link rel="help" href="https://drafts.csswg.org/css-scoping-1/#shadow-cascading"> 8 <script src="/resources/testharness.js"></script> 9 <script src="/resources/testharnessreport.js"></script> 10 </head> 11 <body> 12 <div id="log"></div> 13 <script> 14 15 // Taken from the example in 16 // https://github.com/w3c/webcomponents/blob/gh-pages/proposals/Shadow-DOM-Cascade-Order.md 17 // https://github.com/w3c/webcomponents/issues/316 18 // https://github.com/w3c/webcomponents/blob/gh-pages/proposals/Shadow-DOM-Cascade-Order-in-v1.md 19 // with element renamed and style rule location changed. 20 // 21 // <style>my-item { color: red; }</style> 22 // <my-list> 23 // <:shadow> 24 // <style>::slotted(my-item) { color: blue; }</style> 25 // <slot></slot> 26 // </:shadow> 27 // <my-item style="color: green;"> 28 // <:shadow> 29 // <style>:host { color: yellow; }</style> 30 // <slot></slot> 31 // </:shadow> 32 // ITEM 33 // </my-item> 34 // </my-list> 35 // 36 // There are 4 possible style rules that applies to <my-item> above: 37 // 1. document-wide style 38 // 2. ::slotted style in the shadow in <my-list> 39 // 3. :host style in the shadow in <my-item> 40 // 4. inline style within <my-item> itself. 41 // 42 // It could be possible to nest many more shadow trees in <my-list>, 43 // but to prevent the number of combination explosion, such case is covered 44 // in another test file. 45 // 46 // So testing cases where 2 style rules are competing, 47 // 4C2 = 6 combinations exist, multiplied by 4, which is the possible 48 // combination of applying "!important" for the 2 style rules. 49 50 function createMyList(mode, slottedStyle, hostStyle, inlineStyle) { 51 var myList = document.createElement('my-list'); 52 var root = myList.attachShadow({'mode': mode}); 53 root.innerHTML = '<style>' + slottedStyle + '</style><slot></slot>'; 54 var myItem = document.createElement('my-item'); 55 if (inlineStyle !== '') 56 myItem.setAttribute('style', inlineStyle); 57 myList.appendChild(myItem); 58 var root2 = myItem.attachShadow({'mode': mode}); 59 root2.innerHTML = '<style>' + hostStyle + '</style><slot></slot>'; 60 myItem.appendChild(document.createTextNode('ITEM')); 61 return myList; 62 } 63 64 function testCascadingOrder(mode) { 65 // In all test cases, the rule specified as "color: green" should win. 66 var testCases = [ 67 // [A] Cases between document, ::slotteed, :host, and inline 68 { 69 title: 'A1. document vs ::slotted, document rule should win', 70 documentStyle: 'my-item { color: green; }', 71 slottedStyle: '::slotted(my-item) { color: red; }', 72 hostStyle: '', 73 inlineStyle: '' 74 }, 75 { 76 title: 'A2. document vs :host, document rule should win', 77 documentStyle: 'my-item { color: green; }', 78 slottedStyle: '', 79 hostStyle: ':host { color: red; }', 80 inlineStyle: '' 81 }, 82 { 83 title: 'A3. document vs inline, inline rule should win', 84 documentStyle: 'my-item { color: red; }', 85 slottedStyle: '', 86 hostStyle: '', 87 inlineStyle: 'color: green;' 88 }, 89 { 90 title: 'A4. ::slotted vs :host, earlier in tree-of-trees rule should win', 91 documentStyle: '', 92 slottedStyle: '::slotted(my-item) { color: green; }', 93 hostStyle: ':host { color: red; }', 94 inlineStyle: '' 95 }, 96 { 97 title: 'A5. ::slotted vs inline, inline rule should win', 98 documentStyle: '', 99 slottedStyle: '::slotted(my-item) { color: red; }', 100 hostStyle: '', 101 inlineStyle: 'color: green;' 102 }, 103 { 104 title: 'A6. :host vs inline, inline rule should win', 105 documentStyle: '', 106 slottedStyle: '', 107 hostStyle: ':host { color: red; }', 108 inlineStyle: 'color: green;' 109 }, 110 111 // [B] Stronger rule is still stronger with !important 112 { 113 title: 'B1. document with !important vs ::slotted, document rule should win', 114 documentStyle: 'my-item { color: green !important; }', 115 slottedStyle: '::slotted(my-item) { color: red; }', 116 hostStyle: '', 117 inlineStyle: '' 118 }, 119 { 120 title: 'B2. document with !important vs :host, document rule should win', 121 documentStyle: 'my-item { color: green !important; }', 122 slottedStyle: '', 123 hostStyle: ':host { color: red; }', 124 inlineStyle: '' 125 }, 126 { 127 title: 'B3. document vs inline with !important, inline rule should win', 128 documentStyle: 'my-item { color: red; }', 129 slottedStyle: '', 130 hostStyle: '', 131 inlineStyle: 'color: green !important;' 132 }, 133 { 134 title: 'B4. ::slotted with !important vs :host, earlier in tree-of-trees rule should win', 135 documentStyle: '', 136 slottedStyle: '::slotted(my-item) { color: green !important; }', 137 hostStyle: ':host { color: red; }', 138 inlineStyle: '' 139 }, 140 { 141 title: 'B5. ::slotted vs inline with !important, inline rule should win', 142 documentStyle: '', 143 slottedStyle: '::slotted(my-item) { color: green !important; }', 144 hostStyle: '', 145 inlineStyle: 'color: red;' 146 }, 147 { 148 title: 'B6. :host vs inline with !important, inline rule should win', 149 documentStyle: '', 150 slottedStyle: '', 151 hostStyle: ':host { color: red; }', 152 inlineStyle: 'color: green !important;' 153 }, 154 155 // [C] Weaker rule gets stronger with !important 156 { 157 title: 'C1. document vs ::slotted with !important, ::slotted rule should win', 158 documentStyle: 'my-item { color: red; }', 159 slottedStyle: '::slotted(my-item) { color: green !important; }', 160 hostStyle: '', 161 inlineStyle: '' 162 }, 163 { 164 title: 'C2. document vs :host with !important, :host rule should win', 165 documentStyle: 'my-item { color: red; }', 166 slottedStyle: '', 167 hostStyle: ':host { color: green !important; }', 168 inlineStyle: '' 169 }, 170 { 171 title: 'C3. document with !important vs inline, document rule should win', 172 documentStyle: 'my-item { color: green !important; }', 173 slottedStyle: '', 174 hostStyle: '', 175 inlineStyle: 'color: red;' 176 }, 177 { 178 title: 'C4. ::slotted vs :host with !important, later in tree-of-trees rule should win', 179 documentStyle: '', 180 slottedStyle: '::slotted(my-item) { color: green !important; }', 181 hostStyle: ':host { color: red; }', 182 inlineStyle: '' 183 }, 184 { 185 title: 'C5. ::slotted with !important vs inline, ::slotted rule should win', 186 documentStyle: '', 187 slottedStyle: '::slotted(my-item) { color: green !important; }', 188 hostStyle: '', 189 inlineStyle: 'color: red;' 190 }, 191 { 192 title: 'C6. :host with !important vs inline, :host rule should win', 193 documentStyle: '', 194 slottedStyle: '', 195 hostStyle: ':host { color: green !important; }', 196 inlineStyle: 'color: red;' 197 }, 198 199 // [D] Cases between document, ::slotteed, :host, and inline, both with !important 200 { 201 title: 'D1. document vs ::slotted both with !important, ::slotted rule should win', 202 documentStyle: 'my-item { color: red !important; }', 203 slottedStyle: '::slotted(my-item) { color: green !important; }', 204 hostStyle: '', 205 inlineStyle: '' 206 }, 207 { 208 title: 'D2. document vs :host both with !important, :host rule should win', 209 documentStyle: 'my-item { color: red !important; }', 210 slottedStyle: '', 211 hostStyle: ':host { color: green !important; }', 212 inlineStyle: '' 213 }, 214 { 215 title: 'D3. document vs inline both with !important, inline rule should win', 216 documentStyle: 'my-item { color: red !important; }', 217 slottedStyle: '', 218 hostStyle: '', 219 inlineStyle: 'color: green !important;' 220 }, 221 { 222 title: 'D4. ::slotted vs :host both with !important, later in tree-of-trees rule should win', 223 documentStyle: '', 224 slottedStyle: '::slotted(my-item) { color: red !important; }', 225 hostStyle: ':host { color: green !important; }', 226 inlineStyle: '' 227 }, 228 { 229 title: 'D5. ::slotted vs inline both with !important, ::slotted rule should win', 230 documentStyle: '', 231 slottedStyle: '::slotted(my-item) { color: green !important; }', 232 hostStyle: '', 233 inlineStyle: 'color: red !important;' 234 }, 235 { 236 title: 'D6. :host vs inline both with !important, :host rule should win', 237 documentStyle: '', 238 slottedStyle: '', 239 hostStyle: ':host { color: green !important; }', 240 inlineStyle: 'color: red !important;' 241 }, 242 // [E] Putting all together 243 { 244 title: 'E1. all style applied, inline rule should win', 245 documentStyle: 'my-item { color: red; }', 246 slottedStyle: '::slotted(my-item) { color: blue; }', 247 hostStyle: ':host { color: yellow; }', 248 inlineStyle: 'color: green;' 249 }, 250 { 251 title: 'E2. all styles with !important applied, rule in the last tree-of-trees should win', 252 documentStyle: 'my-item { color: red !important; }', 253 slottedStyle: '::slotted(my-item) { color: blue !important; }', 254 hostStyle: ':host { color: green !important; }', 255 inlineStyle: 'color: yellow !important;' 256 }, 257 ]; 258 259 for (var i = 0; i < testCases.length; ++i) { 260 var testCase = testCases[i]; 261 var documentStyle = document.createElement('style'); 262 documentStyle.appendChild(document.createTextNode(testCase['documentStyle'])); 263 document.head.appendChild(documentStyle); 264 265 var myList = createMyList(mode, 266 testCase['slottedStyle'], testCase['hostStyle'], testCase['inlineStyle']); 267 document.body.appendChild(myList); 268 269 test(function () { 270 var myItem = myList.querySelector('my-item'); 271 assert_equals(window.getComputedStyle(myItem).color, 'rgb(0, 128, 0)', 272 testCase['title']); 273 }, testCase['title'] + ' for ' + mode + ' mode.'); 274 275 myList.parentNode.removeChild(myList); 276 document.head.removeChild(documentStyle) 277 } 278 } 279 280 // Open or Closed should not make any difference in style application. 281 testCascadingOrder('open'); 282 testCascadingOrder('closed'); 283 284 285 // Taken from the example in 286 // https://github.com/w3c/webcomponents/blob/gh-pages/proposals/Shadow-DOM-Cascade-Order.md 287 // https://github.com/w3c/webcomponents/issues/316 288 // https://github.com/w3c/webcomponents/blob/gh-pages/proposals/Shadow-DOM-Cascade-Order-in-v1.md 289 // with element renamed and style rule location changed. 290 // 291 // <style>my-item { color: red; }</style> 292 // <my-list> 293 // <:shadow> 294 // <style>::slotted(my-item) { color: blue; }</style> 295 // <div> 296 // <:shadow> 297 // <slot/> 298 // </:shadow> 299 // <slot/> 300 // </div> 301 // </:shadow> 302 // <my-item style="color: green;"> 303 // <:shadow> 304 // <style>:host { color: yellow; }</style> 305 // <slot/> 306 // </:shadow> 307 // ITEM 308 // </my-item> 309 // </my-list> 310 // 311 // The difference from the example tree above is that <my-list> has 2 levels of 312 // shadow trees, each with ::slotted(my-list) style rules. 313 314 function createMyListWith2LevelShadow(mode, slottedStyle1, slottedStyle2, hostStyle) { 315 var myList = document.createElement('my-list'); 316 var root = myList.attachShadow({'mode': mode}); 317 root.innerHTML = '<style>' + slottedStyle1 + '</style><div><slot></slot></div>'; 318 var div = root.querySelector('div'); 319 var root2 = div.attachShadow({'mode': mode}); 320 root2.innerHTML = '<style>' + slottedStyle2 + '</style><slot></slot>'; 321 var myItem = document.createElement('my-item'); 322 myList.appendChild(myItem); 323 var root3 = myItem.attachShadow({'mode': mode}); 324 root3.innerHTML = '<style>' + hostStyle + '</style><slot></slot>'; 325 myItem.appendChild(document.createTextNode('ITEM')); 326 return myList; 327 } 328 329 function testCascadingOrderWith2LevelShadow(mode) { 330 // In all test cases, the rule specified as "color: green" should win. 331 var testCases = [ 332 { 333 title: 'F1. document vs others, document (the first rule in tree-of-trees order) rule should win', 334 documentStyle: 'my-item { color: green; }', 335 slottedStyle1: '::slotted(my-item) { color: red; }', 336 slottedStyle2: '::slotted(my-item) { color: red; }', 337 hostStyle: ':host { color: red; }', 338 }, 339 { 340 title: 'F2. document with !important vs others, document rule should win', 341 documentStyle: 'my-item { color: green !important; }', 342 slottedStyle1: '::slotted(my-item) { color: red; }', 343 slottedStyle2: '::slotted(my-item) { color: red; }', 344 hostStyle: ':host { color: red; }', 345 }, 346 { 347 title: 'F3. document vs ::slotted with !important, important rule should win', 348 documentStyle: 'my-item { color: red; }', 349 slottedStyle1: '::slotted(my-item) { color: green !important; }', 350 slottedStyle2: '::slotted(my-item) { color: red; }', 351 hostStyle: ':host { color: red; }', 352 }, 353 { 354 title: 'F4. document vs ::slotted with !important, important rule should win', 355 documentStyle: 'my-item { color: red; }', 356 slottedStyle1: '::slotted(my-item) { color: red; }', 357 slottedStyle2: '::slotted(my-item) { color: green !important; }', 358 hostStyle: ':host { color: red; }', 359 }, 360 { 361 title: 'F5. document vs :host with !important, important rule should win', 362 documentStyle: 'my-item { color: red; }', 363 slottedStyle1: '::slotted(my-item) { color: red; }', 364 slottedStyle2: '::slotted(my-item) { color: red; }', 365 hostStyle: ':host { color: green !important; }', 366 }, 367 { 368 title: 'F6. all rules with !important, the last rule in tree-of-trees should win', 369 documentStyle: 'my-item { color: red !important; }', 370 slottedStyle1: '::slotted(my-item) { color: red !important; }', 371 slottedStyle2: '::slotted(my-item) { color: red !important; }', 372 hostStyle: ':host { color: green !important ; }', 373 } 374 ]; 375 376 for (var i = 0; i < testCases.length; ++i) { 377 var testCase = testCases[i]; 378 var documentStyle = document.createElement('style'); 379 documentStyle.appendChild(document.createTextNode(testCase['documentStyle'])); 380 document.head.appendChild(documentStyle); 381 382 var myList = createMyListWith2LevelShadow(mode, 383 testCase['slottedStyle1'], testCase['slottedStyle2'], testCase['hostStyle']); 384 document.body.appendChild(myList); 385 386 test(function () { 387 var myItem = myList.querySelector('my-item'); 388 assert_equals(window.getComputedStyle(myItem).color, 'rgb(0, 128, 0)', 389 testCase['title']); 390 }, testCase['title'] + ' for ' + mode + ' mode.'); 391 392 myList.parentNode.removeChild(myList); 393 document.head.removeChild(documentStyle) 394 } 395 } 396 397 // Open or Closed should not make any difference in style application. 398 testCascadingOrderWith2LevelShadow('open'); 399 testCascadingOrderWith2LevelShadow('closed'); 400 401 </script> 402 </body> 403 </html>