Files
sousa-gecko/testing/web-platform/tests/css/css-pseudo/CSSPseudoElement-view-transitions.tentative.html
T
Daniil Sakhapov 41f49b44a1 Bug 2027021 [wpt PR 58798] - Support ::view-transition* in CSSPseudoElement, a=testonly
Automatic update from web-platform-tests
Support ::view-transition* in CSSPseudoElement

This CL extends the CSSPseudoElement interface to support View
Transition pseudo-elements, enabling developers to interact with them
via the `Element.pseudo()` API.

View transition pseudo-elements represent a structured hierarchy in
the DOM (::view-transition -> ::view-transition-group(name) ->
::view-transition-image-pair(name) -> old/new). This CL ensures that
the CSSPseudoElement proxies accurately reflect this spec-defined
nested tree structure while behaving deterministically.

Key implementations:
- Supported Types: Added parsing and proxy support for
::view-transition, ::view-transition-group,
::view-transition-image-pair, ::view-transition-old, and
::view-transition-new.
- Hierarchy Enforcement: Configured the proxies so that sub-pseudo
queries correctly enforce direct-child relationships (e.g.,
`vt.pseudo("::view-transition-group(name)")` succeeds, while
querying a grandchild directly fails). Cross-name queries are also
strictly validated and rejected.
- Identity Consistency: Proxies are correctly recursively cached.
This guarantees that proxies are identity-consistent across multiple
script transitions on the same element, allowing developers to
reliably attach event listeners.
- Scoping: Validated that these proxies correctly isolate and identify
themselves across different originating elements for scoped view
transitions.

Bug: 496049714

Change-Id: I609c44a85b98ec991b719a3ae0a3a516cc4ca1e0
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7687295
Reviewed-by: Mason Freed <masonf@chromium.org>
Commit-Queue: Daniil Sakhapov <sakhapov@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1606111}

--

wpt-commits: 2726cbbd1d9ece1d87dd733f5b50f0a810a81d88
wpt-pr: 58798
2026-04-09 19:29:50 +00:00

167 lines
8.1 KiB
HTML

<!DOCTYPE html>
<html>
<title>CSS Pseudo Test: CSSPseudoElement with view transitions</title>
<link rel="help" href="https://drafts.csswg.org/css-pseudo-4/#CSSPseudoElement-interface">
<link rel="help" href="https://drafts.csswg.org/css-view-transitions-1/#pseudo">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<style>
#target {
width: 100px;
height: 100px;
background: blue;
contain: paint;
view-transition-name: target;
}
#other {
width: 100px;
height: 100px;
background: green;
contain: paint;
view-transition-name: other;
}
</style>
<div id="target"></div>
<div id="other"></div>
<script>
promise_test(async t => {
assert_implements(document.startViewTransition, "Missing document.startViewTransition");
await new Promise(resolve => requestAnimationFrame(() => requestAnimationFrame(resolve)));
let transition = document.startViewTransition(() => {
document.getElementById("target").style.background = "red";
});
t.add_cleanup(() => transition.skipTransition());
await transition.ready;
let html = document.documentElement;
let vt = html.pseudo("::view-transition");
assert_true(vt instanceof CSSPseudoElement, "::view-transition is CSSPseudoElement");
assert_equals(vt.type, "::view-transition");
assert_equals(vt.parent, html);
let group = vt.pseudo("::view-transition-group(target)");
assert_true(group instanceof CSSPseudoElement, "::view-transition-group works via parent proxy");
assert_equals(group.type, "::view-transition-group");
assert_equals(group.parent, vt, "parent of group should be view-transition");
let imagePair = group.pseudo("::view-transition-image-pair(target)");
assert_true(imagePair instanceof CSSPseudoElement, "::view-transition-image-pair works via parent proxy");
assert_equals(imagePair.type, "::view-transition-image-pair");
assert_equals(imagePair.parent, group, "parent of image-pair should be group");
let oldVT = imagePair.pseudo("::view-transition-old(target)");
assert_true(oldVT instanceof CSSPseudoElement, "::view-transition-old works via parent proxy");
assert_equals(oldVT.type, "::view-transition-old");
assert_equals(oldVT.parent, imagePair, "parent of old should be image-pair");
let newVT = imagePair.pseudo("::view-transition-new(target)");
assert_true(newVT instanceof CSSPseudoElement, "::view-transition-new works via parent proxy");
assert_equals(newVT.type, "::view-transition-new");
assert_equals(newVT.parent, imagePair, "parent of new should be image-pair");
}, "CSSPseudoElement can represent view transition pseudo-elements");
promise_test(async t => {
assert_implements(document.startViewTransition, "Missing document.startViewTransition");
await new Promise(resolve => requestAnimationFrame(() => requestAnimationFrame(resolve)));
let transition = document.startViewTransition(() => {
document.getElementById("target").style.background = "green";
});
t.add_cleanup(() => transition.skipTransition());
await transition.ready;
let html = document.documentElement;
let newVT = html.pseudo("::view-transition-new(target)");
assert_true(newVT instanceof CSSPseudoElement, "Direct fetch of deep pseudo-element works");
assert_equals(newVT.type, "::view-transition-new");
let imagePair = newVT.parent;
assert_true(imagePair instanceof CSSPseudoElement, "Parent is instantiated");
assert_equals(imagePair.type, "::view-transition-image-pair");
let group = imagePair.parent;
assert_true(group instanceof CSSPseudoElement, "Grandparent is instantiated");
assert_equals(group.type, "::view-transition-group");
let vt = group.parent;
assert_true(vt instanceof CSSPseudoElement, "Great-grandparent is instantiated");
assert_equals(vt.type, "::view-transition");
assert_equals(vt.parent, html, "Ultimate parent is the originating element");
let invalidGrandchild = vt.pseudo("::view-transition-image-pair(target)");
assert_equals(invalidGrandchild, null, "Cannot fetch a grandchild directly from a higher ancestor pseudo");
let invalidGrandchild2 = group.pseudo("::view-transition-new(target)");
assert_equals(invalidGrandchild2, null, "Cannot fetch a grandchild directly from an intermediate pseudo");
let invalidChild = newVT.pseudo("::view-transition-group(target)");
assert_equals(invalidChild, null, "Cannot fetch an ancestor from a descendant");
}, "CSSPseudoElement hierarchy is correctly built when querying sub-pseudo-elements");
promise_test(async t => {
assert_implements(document.startViewTransition, "Missing document.startViewTransition");
await new Promise(resolve => requestAnimationFrame(() => requestAnimationFrame(resolve)));
let transition = document.startViewTransition();
t.add_cleanup(() => transition.skipTransition());
await transition.ready;
let html = document.documentElement;
const types = [
"::view-transition",
"::view-transition-group(root)",
"::view-transition-image-pair(root)",
"::view-transition-old(root)",
"::view-transition-new(root)"
];
for (const type of types) {
let pseudo = html.pseudo(type);
assert_true(pseudo instanceof CSSPseudoElement, `${type} fetched from html is CSSPseudoElement`);
assert_equals(pseudo.type, type.split('(')[0], `${type} has correct type`);
let pseudo2 = html.pseudo(type);
assert_equals(pseudo, pseudo2, `${type} is cached on html`);
}
let vt = html.pseudo("::view-transition");
let groupDirect = html.pseudo("::view-transition-group(root)");
let groupViaProxy = vt.pseudo("::view-transition-group(root)");
assert_equals(groupDirect, groupViaProxy, "VT pseudo elements are identity consistent regardless of fetch path");
let imagePairDirect = html.pseudo("::view-transition-image-pair(root)");
let imagePairViaProxy = groupDirect.pseudo("::view-transition-image-pair(root)");
assert_equals(imagePairDirect, imagePairViaProxy, "VT pseudo elements are identity consistent regardless of fetch path");
}, "CSSPseudoElement for view transitions is identity-consistent and can be fetched directly from originating element");
promise_test(async t => {
assert_implements(document.startViewTransition, "Missing document.startViewTransition");
await new Promise(resolve => requestAnimationFrame(() => requestAnimationFrame(resolve)));
let transition = document.startViewTransition(() => {
document.getElementById("target").style.background = "yellow";
document.getElementById("other").style.background = "yellow";
});
t.add_cleanup(() => transition.skipTransition());
await transition.ready;
let html = document.documentElement;
let groupTarget = html.pseudo("::view-transition-group(target)");
let groupOther = html.pseudo("::view-transition-group(other)");
let groupRoot = html.pseudo("::view-transition-group(root)");
assert_true(groupTarget instanceof CSSPseudoElement, "Target group exists");
assert_true(groupOther instanceof CSSPseudoElement, "Other group exists");
assert_true(groupRoot instanceof CSSPseudoElement, "Root group exists");
assert_not_equals(groupTarget, groupRoot, "Named transitions must not match the root transition");
assert_not_equals(groupTarget, groupOther, "Different view transition names must return different instances");
// Cross-name query validation:
// ::view-transition-group(target).pseudo("::view-transition-image-pair(other)") should fail
let invalidCrossChild = groupTarget.pseudo("::view-transition-image-pair(other)");
assert_equals(invalidCrossChild, null, "Cannot fetch a child with a mismatched view transition name from an intermediate proxy");
// ::view-transition.pseudo("::view-transition-group(target)") should work (A(null).pseudo(B(target)))
let vt = html.pseudo("::view-transition");
let groupTargetViaVT = vt.pseudo("::view-transition-group(target)");
assert_equals(groupTargetViaVT, groupTarget, "Fetching named group from root VT proxy works");
}, "CSSPseudoElement isolates view transitions with different names and validates them during traversal");
</script>
</html>