mirror of
https://github.com/servo/servo.git
synced 2025-10-12 14:30:25 +01:00
417 lines
13 KiB
HTML
417 lines
13 KiB
HTML
<!DOCTYPE html>
|
|
<title>Scroll Timeline Attachment</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>
|
|
function inflate(t, template) {
|
|
t.add_cleanup(() => main.replaceChildren());
|
|
main.append(template.content.cloneNode(true));
|
|
main.offsetTop;
|
|
}
|
|
|
|
async function scrollTop(e, value) {
|
|
e.scrollTop = value;
|
|
await waitForNextFrame();
|
|
}
|
|
</script>
|
|
<style>
|
|
@keyframes anim {
|
|
from { width: 0px; --applied:true; }
|
|
to { width: 200px; --applied:true; }
|
|
}
|
|
|
|
.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;
|
|
}
|
|
.local {
|
|
scroll-timeline-attachment: local;
|
|
}
|
|
.defer {
|
|
scroll-timeline-attachment: defer;
|
|
}
|
|
.ancestor {
|
|
scroll-timeline-attachment: ancestor;
|
|
}
|
|
|
|
</style>
|
|
|
|
|
|
<!-- Basic Behavior -->
|
|
|
|
<template id=scroll_timeline_defer>
|
|
<div class="timeline defer">
|
|
<div class=target>Test</div>
|
|
<div class="scroller timeline ancestor">
|
|
<div class=content></div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
<script>
|
|
promise_test(async (t) => {
|
|
inflate(t, scroll_timeline_defer);
|
|
let scroller = main.querySelector('.scroller');
|
|
let target = main.querySelector('.target');
|
|
await scrollTop(scroller, 350); // 50%
|
|
assert_equals(getComputedStyle(target).width, '100px'); // 0px => 200px, 50%
|
|
}, 'Descendant can attach to deferred timeline');
|
|
</script>
|
|
|
|
<template id=scroll_timeline_defer_no_attach>
|
|
<div class="timeline defer">
|
|
<div class=target>Test</div>
|
|
<div class="scroller timeline">
|
|
<div class=content></div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
<script>
|
|
promise_test(async (t) => {
|
|
inflate(t, scroll_timeline_defer_no_attach);
|
|
let scroller = main.querySelector('.scroller');
|
|
let target = main.querySelector('.target');
|
|
await scrollTop(scroller, 350); // 50%
|
|
assert_equals(getComputedStyle(target).width, '0px');
|
|
assert_equals(getComputedStyle(target).getPropertyValue('--applied'), '');
|
|
}, 'Deferred timeline with no attachments');
|
|
</script>
|
|
|
|
<template id=scroll_timeline_defer_no_attach_to_prev_sibling>
|
|
<div class="timeline defer">
|
|
<div class="scroller timeline">
|
|
<div class=content></div>
|
|
</div>
|
|
<div class=target>Test</div>
|
|
</div>
|
|
</template>
|
|
<script>
|
|
promise_test(async (t) => {
|
|
inflate(t, scroll_timeline_defer_no_attach_to_prev_sibling);
|
|
let scroller = main.querySelector('.scroller');
|
|
let target = main.querySelector('.target');
|
|
await scrollTop(scroller, 350); // 50%
|
|
assert_equals(getComputedStyle(target).width, '0px');
|
|
assert_equals(getComputedStyle(target).getPropertyValue('--applied'), '');
|
|
}, 'Deferred timeline with no attachments to previous sibling');
|
|
</script>
|
|
|
|
<template id=scroll_timeline_local_ancestor>
|
|
<div class="scroller timeline local">
|
|
<div class=content>
|
|
<div class=target>Test</div>
|
|
<div class="scroller timeline ancestor">
|
|
<div class=content></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
<script>
|
|
promise_test(async (t) => {
|
|
inflate(t, scroll_timeline_local_ancestor);
|
|
let scroller = main.querySelector('.scroller');
|
|
let target = main.querySelector('.target');
|
|
await scrollTop(scroller, 350); // 50%
|
|
assert_equals(getComputedStyle(target).width, '100px'); // 0px => 200px, 50%
|
|
}, 'Timeline with ancestor attachment does not attach to local');
|
|
</script>
|
|
|
|
<template id=scroll_timeline_defer_two_attachments>
|
|
<div class="timeline defer">
|
|
<div class=target>Test</div>
|
|
<div class="scroller timeline ancestor">
|
|
<div class=content></div>
|
|
</div>
|
|
<!-- Extra attachment -->
|
|
<div class="timeline ancestor"></div>
|
|
</div>
|
|
</template>
|
|
<script>
|
|
promise_test(async (t) => {
|
|
inflate(t, scroll_timeline_defer_two_attachments);
|
|
let scroller = main.querySelector('.scroller');
|
|
let target = main.querySelector('.target');
|
|
await scrollTop(scroller, 350); // 50%
|
|
assert_equals(getComputedStyle(target).width, '0px');
|
|
assert_equals(getComputedStyle(target).getPropertyValue('--applied'), '');
|
|
}, 'Deferred timeline with two attachments');
|
|
</script>
|
|
|
|
<!-- Effective Axis of ScrollTimeline -->
|
|
|
|
<template id=scroll_timeline_defer_axis>
|
|
<div class="timeline defer" style="scroll-timeline-axis:inline">
|
|
<div class=target>Test</div>
|
|
<div class="scroller timeline ancestor" style="scroll-timeline-axis:y">
|
|
<div class=content></div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
<script>
|
|
promise_test(async (t) => {
|
|
inflate(t, scroll_timeline_defer_axis);
|
|
let target = main.querySelector('.target');
|
|
assert_equals(target.getAnimations().length, 1);
|
|
let anim = target.getAnimations()[0];
|
|
assert_not_equals(anim.timeline, null);
|
|
assert_equals(anim.timeline.axis, 'y');
|
|
}, 'Axis of deferred timeline is taken from attached timeline');
|
|
</script>
|
|
|
|
|
|
<template id=scroll_timeline_defer_axis_multiple>
|
|
<div class="timeline defer" style="scroll-timeline-axis:inline">
|
|
<div class=target>Test</div>
|
|
<div class="scroller timeline ancestor" style="scroll-timeline-axis:y">
|
|
<div class=content></div>
|
|
</div>
|
|
<!-- Extra attachment -->
|
|
<div class="timeline ancestor"></div>
|
|
</div>
|
|
</template>
|
|
<script>
|
|
promise_test(async (t) => {
|
|
inflate(t, scroll_timeline_defer_axis_multiple);
|
|
let target = main.querySelector('.target');
|
|
assert_equals(target.getAnimations().length, 1);
|
|
let anim = target.getAnimations()[0];
|
|
assert_not_equals(anim.timeline, null);
|
|
assert_equals(anim.timeline.axis, 'block');
|
|
}, 'Axis of deferred timeline with multiple attachments');
|
|
</script>
|
|
|
|
|
|
<!-- Dynamic Reattachment -->
|
|
|
|
|
|
<template id=scroll_timeline_reattach>
|
|
<div class="timeline defer">
|
|
<div class=target>Test</div>
|
|
<div class="scroller timeline ancestor">
|
|
<div class=content></div>
|
|
</div>
|
|
<div class="scroller timeline">
|
|
<div class=content></div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
<script>
|
|
promise_test(async (t) => {
|
|
inflate(t, scroll_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].
|
|
scrollers[0].classList.remove('ancestor');
|
|
scrollers[1].classList.add('ancestor');
|
|
|
|
await waitForNextFrame();
|
|
assert_equals(getComputedStyle(target).width, '50px'); // 0px => 200px, 25%
|
|
}, 'Dynamically re-attaching');
|
|
</script>
|
|
|
|
|
|
<template id=scroll_timeline_dynamic_attach_second>
|
|
<div class="timeline defer">
|
|
<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) => {
|
|
inflate(t, scroll_timeline_dynamic_attach_second);
|
|
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 no timelines initially:
|
|
assert_equals(getComputedStyle(target).width, '0px');
|
|
assert_equals(getComputedStyle(target).getPropertyValue('--applied'), '');
|
|
|
|
// Attach to scrollers[0].
|
|
scrollers[0].classList.add('ancestor');
|
|
await waitForNextFrame();
|
|
assert_equals(getComputedStyle(target).width, '100px'); // 0px => 200px, 50%
|
|
|
|
// Also attach scrollers[1].
|
|
scrollers[1].classList.add('ancestor');
|
|
|
|
await waitForNextFrame();
|
|
assert_equals(getComputedStyle(target).width, '0px');
|
|
assert_equals(getComputedStyle(target).getPropertyValue('--applied'), '');
|
|
}, 'Dynamically attaching');
|
|
</script>
|
|
|
|
|
|
<template id=scroll_timeline_dynamic_detach_second>
|
|
<div class="timeline defer">
|
|
<div class=target>Test</div>
|
|
<div class="scroller timeline ancestor">
|
|
<div class=content></div>
|
|
</div>
|
|
<div class="scroller timeline ancestor">
|
|
<div class=content></div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
<script>
|
|
promise_test(async (t) => {
|
|
inflate(t, scroll_timeline_dynamic_detach_second);
|
|
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:
|
|
assert_equals(getComputedStyle(target).width, '0px');
|
|
assert_equals(getComputedStyle(target).getPropertyValue('--applied'), '');
|
|
|
|
// Detach scrollers[1].
|
|
scrollers[1].classList.remove('ancestor');
|
|
|
|
await waitForNextFrame();
|
|
assert_equals(getComputedStyle(target).width, '100px'); // 0px => 200px, 50%
|
|
|
|
// Also detach scrollers[0].
|
|
scrollers[0].classList.remove('ancestor');
|
|
|
|
await waitForNextFrame();
|
|
assert_equals(getComputedStyle(target).width, '0px');
|
|
assert_equals(getComputedStyle(target).getPropertyValue('--applied'), '');
|
|
}, 'Dynamically detaching');
|
|
</script>
|
|
|
|
<template id=scroll_timeline_ancestor_attached_removed>
|
|
<div class="timeline defer">
|
|
<div class=target>Test</div>
|
|
<div class="scroller timeline ancestor">
|
|
<div class=content></div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
<script>
|
|
promise_test(async (t) => {
|
|
inflate(t, scroll_timeline_ancestor_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');
|
|
assert_equals(getComputedStyle(target).getPropertyValue('--applied'), '');
|
|
|
|
scroller_parent.append(scroller);
|
|
await scrollTop(scroller, 350); // 50%
|
|
assert_equals(getComputedStyle(target).width, '100px'); // 0px => 200px, 50%
|
|
}, 'Removing/inserting ancestor attached element');
|
|
</script>
|
|
|
|
<template id=scroll_timeline_ancestor_attached_display_none>
|
|
<div class="timeline defer">
|
|
<div class=target>Test</div>
|
|
<div class="scroller timeline ancestor">
|
|
<div class=content></div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
<script>
|
|
promise_test(async (t) => {
|
|
inflate(t, scroll_timeline_ancestor_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');
|
|
assert_equals(getComputedStyle(target).getPropertyValue('--applied'), '');
|
|
|
|
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=scroll_timeline_dynamic_defer>
|
|
<style>
|
|
.inner-scroller {
|
|
overflow-y: hidden;
|
|
width: 50px;
|
|
height: 50px;
|
|
}
|
|
.inner-scroller > .content {
|
|
margin: 100px 0px;
|
|
width: 20px;
|
|
height: 20px;
|
|
background-color: red;
|
|
}
|
|
</style>
|
|
<div class="scroller timeline">
|
|
<div class="target content">
|
|
<div class="inner-scroller timeline ancestor">
|
|
<div class=content></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
<script>
|
|
promise_test(async (t) => {
|
|
inflate(t, scroll_timeline_dynamic_defer);
|
|
let target = main.querySelector('.target');
|
|
let outer_scroller = main.querySelector('.scroller');
|
|
let inner_scroller = main.querySelector('.inner-scroller');
|
|
|
|
await scrollTop(outer_scroller, 350); // 50%
|
|
await scrollTop(inner_scroller, 17); // 10%
|
|
|
|
// Attached to outer_scroller (local).
|
|
assert_equals(getComputedStyle(target).width, '100px'); // 0px => 200px, 50%
|
|
|
|
// Effectively attached to inner_scroller.
|
|
outer_scroller.classList.add('defer');
|
|
await waitForNextFrame();
|
|
assert_equals(getComputedStyle(target).width, '20px'); // 0px => 200px, 10%
|
|
|
|
// Attached to outer_scroller again.
|
|
outer_scroller.classList.remove('defer');
|
|
await waitForNextFrame();
|
|
assert_equals(getComputedStyle(target).width, '100px'); // 0px => 200px, 50%
|
|
}, 'Dynamically becoming a deferred timeline');
|
|
</script>
|