Source code
Revision control
Copy as Markdown
Other Tools
Test Info:
- This WPT test may be referenced by the following Test IDs:
- /css/css-cascade/revert-rule-shadow.html - WPT Dashboard Interop Dashboard
<!DOCTYPE html>
<title>The revert-rule keyword: interaction with shadow DOM scopes</title>
<link rel="author" title="Tim Nguyen" href="https://github.com/nt1m">
<link rel="help" href="https://drafts.csswg.org/css-cascade-5/#revert-rule-keyword">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<!-- Test 1: revert-rule in document rule falls back to :host rule -->
<div id="host1"></div>
<script>
host1.attachShadow({mode: 'open'}).innerHTML = `
<style>
:host { color: green; }
</style>
`;
</script>
<style>
#host1 {
color: red;
color: revert-rule;
}
</style>
<script>
test(() => {
assert_true(CSS.supports('color:revert-rule'));
assert_equals(getComputedStyle(host1).color, 'rgb(0, 128, 0)');
}, 'revert-rule in document rule falls back to :host rule');
</script>
<!-- Test 2: revert-rule in document rule falls back to ::slotted rule -->
<div id="host2"><div id="slotted2"></div></div>
<script>
host2.attachShadow({mode: 'open'}).innerHTML = `
<style>
::slotted(*) { color: green; }
</style>
<slot></slot>
`;
</script>
<style>
#slotted2 {
color: red;
color: revert-rule;
}
</style>
<script>
test(() => {
assert_equals(getComputedStyle(slotted2).color, 'rgb(0, 128, 0)');
}, 'revert-rule in document rule falls back to ::slotted rule');
</script>
<!-- Test 3: revert-rule in :host rule has no effect when a document rule wins -->
<div id="host3"></div>
<script>
host3.attachShadow({mode: 'open'}).innerHTML = `
<style>
:host { color: revert-rule; }
</style>
`;
</script>
<style>
#host3 { color: green; }
</style>
<script>
test(() => {
assert_equals(getComputedStyle(host3).color, 'rgb(0, 128, 0)');
}, 'revert-rule in :host rule has no effect when a document rule already wins');
</script>
<!-- Test 4: revert-rule in document rule with no fallback falls through to unset -->
<div id="host4"></div>
<script>
host4.attachShadow({mode: 'open'}).innerHTML = `<slot></slot>`;
</script>
<style>
#host4 {
color: red;
color: revert-rule;
}
</style>
<script>
test(() => {
// No :host rule exists; color is inherited from the document body (rgb(0,0,0)).
assert_equals(getComputedStyle(host4).color, 'rgb(0, 0, 0)');
}, 'revert-rule with no shadow fallback falls through to unset (inherited)');
</script>
<!-- Test 5: revert-rule in document rule, multiple competing :host rules -->
<div id="host5"></div>
<script>
host5.attachShadow({mode: 'open'}).innerHTML = `
<style>
:host { color: green; }
:host { color: red; }
</style>
`;
</script>
<style>
#host5 {
color: blue;
color: revert-rule;
}
</style>
<script>
test(() => {
assert_equals(getComputedStyle(host5).color, 'rgb(255, 0, 0)');
}, 'revert-rule in document rule falls back to the winning :host rule');
</script>
<!-- Test 6: revert-rule in document rule, :host rule also reverts -->
<div id="host6"></div>
<script>
host6.attachShadow({mode: 'open'}).innerHTML = `
<style>
:host { color: green; }
:host { color: revert-rule; }
</style>
`;
</script>
<style>
#host6 {
color: blue;
color: revert-rule;
}
</style>
<script>
test(() => {
// The document revert-rule falls back to the :host block. Within the :host
// block, the second :host rule says revert-rule, which falls back to the
// first :host rule's green.
assert_equals(getComputedStyle(host6).color, 'rgb(0, 128, 0)');
}, 'revert-rule chain across document rule and :host rule');
</script>
<!-- Test 7: revert-rule in ::part() rule falls back to earlier ::part() rule -->
<div id="host7"></div>
<script>
{
const shadow = host7.attachShadow({mode: 'open'});
shadow.innerHTML = `<span id="p7" part="mypart">text</span>`;
}
</script>
<style>
#host7::part(mypart) { color: green; }
#host7::part(mypart) { color: revert-rule; }
</style>
<script>
test(() => {
const part = host7.shadowRoot.getElementById('p7');
assert_equals(getComputedStyle(part).color, 'rgb(0, 128, 0)');
}, 'revert-rule in ::part() rule falls back to earlier ::part() rule');
</script>
<!-- Test 8: revert-rule in ::part() rule falls back to shadow-internal rule -->
<div id="host8"></div>
<script>
{
const shadow = host8.attachShadow({mode: 'open'});
shadow.innerHTML = `
<style>span { color: green; }</style>
<span id="p8" part="mypart">text</span>
`;
}
</script>
<style>
#host8::part(mypart) { color: revert-rule; }
</style>
<script>
test(() => {
// The ::part() rule reverts, falling back to the shadow-internal span rule (green).
const part = host8.shadowRoot.getElementById('p8');
assert_equals(getComputedStyle(part).color, 'rgb(0, 128, 0)');
}, 'revert-rule in ::part() rule falls back to shadow-internal rule');
</script>
<!-- Test 9: revert-rule in shadow-internal rule, ::part() rule wins -->
<div id="host9"></div>
<script>
{
const shadow = host9.attachShadow({mode: 'open'});
shadow.innerHTML = `
<style>
span { color: blue; }
span { color: revert-rule; }
</style>
<span id="p9" part="mypart">text</span>
`;
}
</script>
<style>
#host9::part(mypart) { color: green; }
</style>
<script>
test(() => {
// The ::part() rule in the document wins. revert-rule inside the shadow
// does not affect the winning ::part() declaration.
const part = host9.shadowRoot.getElementById('p9');
assert_equals(getComputedStyle(part).color, 'rgb(0, 128, 0)');
}, 'revert-rule in shadow-internal rule has no effect when ::part() rule wins');
</script>
<!-- Test 10: revert-rule chain across two ::part() rules -->
<div id="host10"></div>
<script>
{
const shadow = host10.attachShadow({mode: 'open'});
shadow.innerHTML = `<span id="p10" part="mypart">text</span>`;
}
</script>
<style>
#host10::part(mypart) { color: red; }
#host10::part(mypart) { color: green; }
#host10::part(mypart) { color: revert-rule; }
</style>
<script>
test(() => {
// Third rule reverts to what would exist without it → second rule (green).
const part = host10.shadowRoot.getElementById('p10');
assert_equals(getComputedStyle(part).color, 'rgb(0, 128, 0)');
}, 'revert-rule in third ::part() rule falls back to second ::part() rule');
</script>
<!-- Test 11: revert-rule in ::part() and :host on same host element -->
<div id="host11"></div>
<script>
{
const shadow = host11.attachShadow({mode: 'open'});
shadow.innerHTML = `
<style>:host { color: blue; }</style>
<span id="p11" part="mypart">text</span>
`;
}
</script>
<style>
#host11 { color: red; color: revert-rule; }
#host11::part(mypart) { color: green; }
</style>
<script>
test(() => {
// Host color reverts to :host rule (blue).
assert_equals(getComputedStyle(host11).color, 'rgb(0, 0, 255)');
// Part color is set by ::part() rule (green), independent of host revert.
const part = host11.shadowRoot.getElementById('p11');
assert_equals(getComputedStyle(part).color, 'rgb(0, 128, 0)');
}, 'revert-rule on host and ::part() rule on part are independent');
</script>
<!-- Test 12: revert-rule in ::part() rule falls back across selector specificity -->
<div id="host12" class="host12cls"></div>
<script>
{
const shadow = host12.attachShadow({mode: 'open'});
shadow.innerHTML = `<span id="p12" part="mypart">text</span>`;
}
</script>
<style>
#host12::part(mypart) { color: green; }
#host12.host12cls::part(mypart) { color: revert-rule; }
</style>
<script>
test(() => {
// Higher specificity rule reverts, so falls back to the lower-specificity green.
const part = host12.shadowRoot.getElementById('p12');
assert_equals(getComputedStyle(part).color, 'rgb(0, 128, 0)');
}, 'revert-rule in higher-specificity ::part() rule falls back to lower-specificity ::part() rule');
</script>