scope-specificity.html (3903B)
1 <!DOCTYPE html> 2 <title>@scope - specificity</title> 3 <link rel="help" href="https://drafts.csswg.org/css-cascade-6/#scope-atrule"> 4 <script src="/resources/testharness.js"></script> 5 <script src="/resources/testharnessreport.js"></script> 6 <style id=style> 7 </style> 8 <main id=main> 9 <style id=styleImplicit></style> 10 <div id=a class=a> 11 <div id=b class=b> 12 </div> 13 </div> 14 </main> 15 <script> 16 17 // Format a scoped style rule using the selector at scoped_selector's last element, 18 // with each preceding array item representing an enclosing @scope rule. 19 // 20 // Example: 21 // 22 // scoped_selector=['@scope (foo)', '@scope (bar)', 'div'] 23 // declarations='z-index:42' 24 // => '@scope (foo) { @scope (bar) { div { z-index:42 } } }' 25 function format_scoped_rule(scoped_selector, declarations) { 26 if (scoped_selector.length < 2) { 27 throw "Fail"; 28 } 29 let scope_prelude = scoped_selector[0]; 30 let remainder = scoped_selector.slice(1); 31 let content = remainder.length == 1 32 ? `${remainder[0]} { ${declarations} }` 33 : format_scoped_rule(remainder, declarations); 34 return `${scope_prelude} { ${content} }`; 35 } 36 37 // Verify that the specificity of 'scoped_selector' is the same 38 // as the specificity of 'ref_selector'. Both selectors must select 39 // an element within #main. 40 function test_scope_specificity(scoped_selector, ref_selector, style) { 41 if (style === undefined) { 42 style = document.getElementById("style"); 43 } 44 test(t => { 45 t.add_cleanup(() => { style.textContent = ''; }); 46 47 let element = main.querySelector(ref_selector); 48 assert_not_equals(element, null); 49 50 let scoped_rule = format_scoped_rule(scoped_selector, 'z-index:1'); 51 let ref_rule = `:is(${ref_selector}) { z-index:2 }`; 52 53 style.textContent = `${scoped_rule}`; 54 assert_equals(getComputedStyle(element).zIndex, '1', 'scoped rule'); 55 56 style.textContent = `${ref_rule}`; 57 assert_equals(getComputedStyle(element).zIndex, '2', 'unscoped rule'); 58 59 // The scoped rule should win due to proximity. 60 style.textContent = `${scoped_rule} ${ref_rule}`; 61 assert_equals(getComputedStyle(element).zIndex, '1', 'scoped + unscoped'); 62 63 // The scoped rule should win due to proximity (reverse). 64 style.textContent = `${ref_rule} ${scoped_rule}`; 65 assert_equals(getComputedStyle(element).zIndex, '1', 'unscoped + scoped'); 66 67 // Add one (1) to the specificty of the unscoped rule. This should 68 // cause the unscoped rule to win instead. 69 style.textContent = `div${ref_rule} ${scoped_rule}`; 70 assert_equals(getComputedStyle(element).zIndex, '2', 'unscoped + scoped'); 71 }, format_scoped_rule(scoped_selector, '') + ' and ' + ref_selector); 72 } 73 74 // Selectors within @scope implicitly have `:scope <descendant-combinator>` 75 // added, but no specificity associated with it is added. 76 // See https://github.com/w3c/csswg-drafts/issues/10196 77 test_scope_specificity(['@scope (#main)', '.b'], '.b'); 78 test_scope_specificity(['@scope (#main) to (.b)', '.a'], '.a'); 79 test_scope_specificity(['@scope (#main, .foo, .bar)', '#a'], '#a'); 80 test_scope_specificity(['@scope (#main)', 'div.b'], 'div.b'); 81 test_scope_specificity(['@scope (#main)', ':scope .b'], '.a .b'); 82 // & behaves like :where(:scope) - No specificity is added. 83 // See https://github.com/w3c/csswg-drafts/issues/9740 84 test_scope_specificity(['@scope (#main)', '& .b'], ':where(#main) .b'); 85 test_scope_specificity(['@scope (#main)', 'div .b'], 'div .b'); 86 test_scope_specificity(['@scope (#main)', '@scope (.a)', '.b'], '.b'); 87 // Explicit `:scope` adds specficity. 88 test_scope_specificity(['@scope (#main)', ':scope .b'], ':scope .b'); 89 // & behaves like :where(:scope), even for implicit scope roots. 90 test_scope_specificity(['@scope', '& .b'], ':where(:scope) .b', styleImplicit); 91 // Using relative selector syntax does not add specificity 92 test_scope_specificity(['@scope (#main)', '> .a'], ':where(#main) > .a'); 93 </script>