Files
sousa-gecko/testing/web-platform/tests/scroll-animations/css/timeline-scope.html
T
Kevin Ellis b89ed2465e Bug 2045348 [wpt PR 60427] - [SDA] Fix naming conflict resolution, a=testonly
Automatic update from web-platform-tests
[SDA] Fix naming conflict resolution

Resolving name conflicts by selecting last in flat tree order was
already implemented behind a flag. The same flag was used for making
names global by default. This patch decouples conflict resolution
from global scope. Though resolutions on the same issue (#12581)
handling the full set of resolutions requires updating more tests,
which in turns entails more risk, and potentially slowing the upstream
merge. Remaining resolutions on this issue will be tackled in follow
up CLs.

https://chromestatus.com/feature/5139380420345856

Bug: 519612140
Change-Id: Id0d53dbb27b8bedeb246aa4091f6ab04840cf2f4
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7899178
Commit-Queue: Kevin Ellis <kevers@chromium.org>
Reviewed-by: Robert Flack <flackr@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1642366}

--

wpt-commits: d80bfc6ef0909bb6f71d41339532e37ffd30cad8
wpt-pr: 60427
2026-07-10 09:23:20 +00:00

324 lines
9.5 KiB
HTML

<!DOCTYPE html>
<title>Behavior of the timeline-scope property</title>
<link rel="help" src="https://github.com/w3c/csswg-drafts/issues/7759">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/web-animations/testcommon.js"></script>
<main id=main></main>
<script>
async function inflate(t, template) {
t.add_cleanup(() => main.replaceChildren());
return runAndWaitForFrameUpdate(() => {
main.append(template.content.cloneNode(true));
});
}
async function scrollTop(e, value) {
e.scrollTop = value;
await waitForNextFrame();
}
</script>
<style>
@keyframes anim {
from { width: 0px; }
to { width: 200px; }
}
.scroller {
overflow-y: hidden;
width: 200px;
height: 200px;
}
.scroller > .content {
margin: 400px 0px;
width: 100px;
height: 100px;
background-color: green;
}
.target {
background-color: coral;
width: 0px;
animation: anim auto linear;
animation-timeline: --t1;
}
.timeline {
scroll-timeline-name: --t1;
}
.scope {
timeline-scope: --t1;
}
</style>
<!-- Basic Behavior -->
<template id=deferred_timeline>
<div class="scope">
<div class=target>Test</div>
<div class="scroller timeline">
<div class=content></div>
</div>
</div>
</template>
<script>
promise_test(async (t) => {
await inflate(t, deferred_timeline);
let scroller = main.querySelector('.scroller');
let target = main.querySelector('.target');
const anim = target.getAnimations()[0];
await anim.ready;
await scrollTop(scroller, 350); // 50%
assert_equals(getComputedStyle(target).width, '100px'); // 0px => 200px, 50%
}, 'Descendant can attach to deferred timeline');
</script>
<template id=deferred_timeline_no_attachments>
<div class="scope">
<div class=target>Test</div>
<div class="scroller">
<div class=content></div>
</div>
</div>
</template>
<script>
promise_test(async (t) => {
await inflate(t, deferred_timeline_no_attachments);
let scroller = main.querySelector('.scroller');
let target = main.querySelector('.target');
await scrollTop(scroller, 350); // 50%
assert_equals(getComputedStyle(target).width, '0px');
}, 'Deferred timeline with no attachments');
</script>
<template id=scroll_timeline_inner_interference>
<div class="scroller timeline">
<div class=content>
<div class=target>Test</div>
<div class="scroller timeline">
<div class=content></div>
</div>
</div>
</div>
</template>
<script>
promise_test(async (t) => {
await inflate(t, scroll_timeline_inner_interference);
let scroller = main.querySelector('.scroller');
let target = main.querySelector('.target');
await scrollTop(scroller, 350); // 50%
assert_equals(getComputedStyle(target).width, '100px'); // 0px => 200px, 50%
}, 'Inner timeline does not interfere with outer timeline');
</script>
<template id=deferred_timeline_two_attachments>
<div class="scope">
<div class=target>Test</div>
<div class="scroller timeline">
<div class=content></div>
</div>
<!-- Extra attachment -->
<div class="timeline"></div>
</div>
</template>
<script>
promise_test(async (t) => {
await inflate(t, deferred_timeline_two_attachments);
let scroller = main.querySelector('.scroller');
let target = main.querySelector('.target');
await scrollTop(scroller, 350); // 50%
assert_equals(getComputedStyle(target).width, '0px');
}, 'Deferred timeline with two attachments');
</script>
<!-- Dynamic Reattachment -->
<template id=deferred_timeline_reattach>
<div class="scope">
<div class=target>Test</div>
<div class="scroller timeline">
<div class=content></div>
</div>
<div class="scroller">
<div class=content></div>
</div>
</div>
</template>
<script>
promise_test(async (t) => {
await inflate(t, deferred_timeline_reattach);
let scrollers = main.querySelectorAll('.scroller');
assert_equals(scrollers.length, 2);
let target = main.querySelector('.target');
await scrollTop(scrollers[0], 350); // 50%
await scrollTop(scrollers[1], 175); // 25%
// Attached to scrollers[0].
assert_equals(getComputedStyle(target).width, '100px'); // 0px => 200px, 50%
// Reattach to scrollers[1].
await runAndWaitForFrameUpdate(() => {
scrollers[0].classList.remove('timeline');
scrollers[1].classList.add('timeline');
});
assert_equals(getComputedStyle(target).width, '50px'); // 0px => 200px, 25%
}, 'Dynamically re-attaching');
</script>
<template id=deferred_timeline_dynamic_detach>
<div class="scope">
<div class=target>Test</div>
<div class="scroller timeline">
<div class=content></div>
</div>
<div class="scroller timeline">
<div class=content></div>
</div>
</div>
</template>
<script>
promise_test(async (t) => {
await inflate(t, deferred_timeline_dynamic_detach);
let scrollers = main.querySelectorAll('.scroller');
assert_equals(scrollers.length, 2);
let target = main.querySelector('.target');
await scrollTop(scrollers[0], 350); // 50%
await scrollTop(scrollers[1], 175); // 25%
// Attached to two timelines initially:
// Resolve conflict by selecting last match in flat tree order.
assert_equals(getComputedStyle(target).width, '50px');
// Detach scrollers[1].
await runAndWaitForFrameUpdate(() => {
scrollers[1].classList.remove('timeline');
});
assert_equals(getComputedStyle(target).width, '100px'); // 0px => 200px, 50%
// Also detach scrollers[0].
scrollers[0].classList.remove('timeline');
await waitForNextFrame();
assert_equals(getComputedStyle(target).width, '0px');
}, 'Dynamically detaching');
</script>
<template id=deferred_timeline_attached_removed>
<div class="scope">
<div class=target>Test</div>
<div class="scroller timeline">
<div class=content></div>
</div>
</div>
</template>
<script>
promise_test(async (t) => {
await inflate(t, deferred_timeline_attached_removed);
let scroller = main.querySelector('.scroller');
let target = main.querySelector('.target');
await scrollTop(scroller, 350); // 50%
assert_equals(getComputedStyle(target).width, '100px'); // 0px => 200px, 50%
let scroller_parent = scroller.parentElement;
scroller.remove();
await waitForNextFrame();
assert_equals(getComputedStyle(target).width, '0px');
scroller_parent.append(scroller);
await scrollTop(scroller, 350); // 50%
assert_equals(getComputedStyle(target).width, '100px'); // 0px => 200px, 50%
}, 'Removing/inserting element with attaching timeline');
</script>
<template id=deferred_timeline_attached_display_none>
<div class="scope">
<div class=target>Test</div>
<div class="scroller timeline">
<div class=content></div>
</div>
</div>
</template>
<script>
promise_test(async (t) => {
await inflate(t, deferred_timeline_attached_display_none);
let scroller = main.querySelector('.scroller');
let target = main.querySelector('.target');
await scrollTop(scroller, 350); // 50%
assert_equals(getComputedStyle(target).width, '100px'); // 0px => 200px, 50%
scroller.style.display = 'none';
await waitForNextFrame();
assert_equals(getComputedStyle(target).width, '0px');
scroller.style.display = 'block';
await scrollTop(scroller, 350); // 50%
assert_equals(getComputedStyle(target).width, '100px'); // 0px => 200px, 50%
}, 'Ancestor attached element becoming display:none/block');
</script>
<template id=deferred_timeline_appearing>
<div class=container>
<div class=target>Test</div>
<div class="scroller timeline">
<div class=content></div>
</div>
</div>
</template>
<script>
promise_test(async (t) => {
await inflate(t, deferred_timeline_appearing);
let container = main.querySelector('.container');
let scroller = main.querySelector('.scroller');
let target = main.querySelector('.target');
await scrollTop(scroller, 350); // 50%
// Not attached to any timeline initially.
assert_equals(getComputedStyle(target).width, '0px');
// Add the deferred timeline.
container.classList.add('scope');
await waitForNextFrame();
assert_equals(getComputedStyle(target).width, '100px'); // 0px => 200px, 50%
// Remove the deferred timeline.
container.classList.remove('scope');
await waitForNextFrame();
assert_equals(getComputedStyle(target).width, '0px');
}, 'A deferred timeline appearing dynamically in the ancestor chain');
</script>
<template id=deferred_timeline_on_self>
<div class="scroller timeline scope">
<div class=content>
<div class=target></div>
</div>
<div class=scroller2></div>
</div>
</template>
<script>
promise_test(async (t) => {
await inflate(t, deferred_timeline_on_self);
let scroller = main.querySelector('.scroller');
let target = main.querySelector('.target');
await scrollTop(scroller, 525); // 75%
assert_equals(getComputedStyle(target).width, '150px'); // 0px => 200px, 75%
// A second scroll-timeline now attaches to the same root.
let scroller2 = main.querySelector('.scroller2');
scroller2.classList.add('timeline');
await waitForNextFrame();
// The deferred timeline produced by timeline-scope is now inactive,
// but it doesn't matter, because we preferred to attach
// to the non-deferred timeline.
assert_equals(getComputedStyle(target).width, '150px'); // 0px => 200px, 75%
}, 'Animations prefer non-deferred timelines');
</script>