| <!DOCTYPE html> | |
| <title>@scope - specificity</title> | |
| <link rel="help" href="https://drafts.csswg.org/css-cascade-6/#scope-atrule"> | |
| <script src="/resources/testharness.js"></script> | |
| <script src="/resources/testharnessreport.js"></script> | |
| <style id=style> | |
| </style> | |
| <main id=main> | |
| <style id=styleImplicit></style> | |
| <div id=a class=a> | |
| <div id=b class=b> | |
| </div> | |
| </div> | |
| </main> | |
| <script> | |
| // Format a scoped style rule using the selector at scoped_selector's last element, | |
| // with each preceding array item representing an enclosing @scope rule. | |
| // | |
| // Example: | |
| // | |
| // scoped_selector=['@scope (foo)', '@scope (bar)', 'div'] | |
| // declarations='z-index:42' | |
| // => '@scope (foo) { @scope (bar) { div { z-index:42 } } }' | |
| function format_scoped_rule(scoped_selector, declarations) { | |
| if (scoped_selector.length < 2) { | |
| throw "Fail"; | |
| } | |
| let scope_prelude = scoped_selector[0]; | |
| let remainder = scoped_selector.slice(1); | |
| let content = remainder.length == 1 | |
| ? `${remainder[0]} { ${declarations} }` | |
| : format_scoped_rule(remainder, declarations); | |
| return `${scope_prelude} { ${content} }`; | |
| } | |
| // Verify that the specificity of 'scoped_selector' is the same | |
| // as the specificity of 'ref_selector'. Both selectors must select | |
| // an element within #main. | |
| function test_scope_specificity(scoped_selector, ref_selector, style) { | |
| if (style === undefined) { | |
| style = document.getElementById("style"); | |
| } | |
| test(t => { | |
| t.add_cleanup(() => { style.textContent = ''; }); | |
| let element = main.querySelector(ref_selector); | |
| assert_not_equals(element, null); | |
| let scoped_rule = format_scoped_rule(scoped_selector, 'z-index:1'); | |
| let ref_rule = `:is(${ref_selector}) { z-index:2 }`; | |
| style.textContent = `${scoped_rule}`; | |
| assert_equals(getComputedStyle(element).zIndex, '1', 'scoped rule'); | |
| style.textContent = `${ref_rule}`; | |
| assert_equals(getComputedStyle(element).zIndex, '2', 'unscoped rule'); | |
| // The scoped rule should win due to proximity. | |
| style.textContent = `${scoped_rule} ${ref_rule}`; | |
| assert_equals(getComputedStyle(element).zIndex, '1', 'scoped + unscoped'); | |
| // The scoped rule should win due to proximity (reverse). | |
| style.textContent = `${ref_rule} ${scoped_rule}`; | |
| assert_equals(getComputedStyle(element).zIndex, '1', 'unscoped + scoped'); | |
| // Add one (1) to the specificty of the unscoped rule. This should | |
| // cause the unscoped rule to win instead. | |
| style.textContent = `div${ref_rule} ${scoped_rule}`; | |
| assert_equals(getComputedStyle(element).zIndex, '2', 'unscoped + scoped'); | |
| }, format_scoped_rule(scoped_selector, '') + ' and ' + ref_selector); | |
| } | |
| // Selectors within @scope implicitly have `:scope <descendant-combinator>` | |
| // added, but no specificity associated with it is added. | |
| // See https://github.com/w3c/csswg-drafts/issues/10196 | |
| test_scope_specificity(['@scope (#main)', '.b'], '.b'); | |
| test_scope_specificity(['@scope (#main) to (.b)', '.a'], '.a'); | |
| test_scope_specificity(['@scope (#main, .foo, .bar)', '#a'], '#a'); | |
| test_scope_specificity(['@scope (#main)', 'div.b'], 'div.b'); | |
| test_scope_specificity(['@scope (#main)', ':scope .b'], '.a .b'); | |
| // Inherit the specificity of the scope-start selector. | |
| test_scope_specificity(['@scope (#main)', '& .b'], '#main .b'); | |
| test_scope_specificity(['@scope (#main)', 'div .b'], 'div .b'); | |
| test_scope_specificity(['@scope (#main)', '@scope (.a)', '.b'], '.b'); | |
| // Explicit `:scope` adds specficity. | |
| test_scope_specificity(['@scope (#main)', ':scope .b'], ':scope .b'); | |
| // Using & in scoped style with implicit scope root matches the same elements | |
| // as `:scope`, but does not add any specificity. | |
| // https://github.com/w3c/csswg-drafts/issues/10196 | |
| test_scope_specificity(['@scope', '& .b'], ':where(:scope) .b', styleImplicit); | |
| // Using relative selector syntax does not add specificity | |
| test_scope_specificity(['@scope (#main)', '> .a'], ':where(#main) > .a'); | |
| </script> |