servo/tests/wpt/web-platform-tests/scroll-animations/css/animation-timeline-named-scroll-progress-timeline.tentative.html

679 lines
22 KiB
HTML

<!DOCTYPE html>
<title>The animation-timeline: scroll-timeline-name</title>
<meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1">
<link rel="help" src="https://drafts.csswg.org/scroll-animations-1/rewrite#scroll-timelines-named">
<link rel="help" src="https://github.com/w3c/csswg-drafts/issues/6674">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/web-animations/testcommon.js"></script>
<script src="support/testcommon.js"></script>
<style>
@keyframes anim {
from { translate: 50px; }
to { translate: 150px; }
}
#target {
width: 100px;
height: 100px;
}
.square {
width: 100px;
height: 100px;
}
.square-container {
width: 300px;
height: 300px;
}
.scroller {
overflow: scroll;
}
.content {
inline-size: 100%;
block-size: 100%;
padding-inline-end: 100px;
padding-block-end: 100px;
}
</style>
<body>
<div id="log"></div>
<script>
"use strict";
setup(assert_implements_animation_timeline);
function createScroller(t, scrollerSizeClass) {
let scroller = document.createElement('div');
let className = scrollerSizeClass || 'square';
scroller.className = `scroller ${className}`;
let content = document.createElement('div');
content.className = 'content';
scroller.appendChild(content);
t.add_cleanup(function() {
content.remove();
scroller.remove();
});
return scroller;
}
function createTarget(t) {
let target = document.createElement('div');
target.id = 'target';
t.add_cleanup(function() {
target.remove();
});
return target;
}
function createScrollerAndTarget(t, scrollerSizeClass) {
return [createScroller(t, scrollerSizeClass), createTarget(t)];
}
// -------------------------
// Test scroll-timeline-name
// -------------------------
promise_test(async t => {
let target = document.createElement('div');
target.id = 'target';
target.className = 'scroller';
let content = document.createElement('div');
content.className = 'content';
// <div id='target' class='scroller'>
// <div id='content'></div>
// </div>
document.body.appendChild(target);
target.appendChild(content);
target.style.scrollTimelineName = 'timeline';
target.style.animation = "anim 10s linear";
target.style.animationTimeline = 'timeline';
target.scrollTop = 50; // 50%, in [0, 100].
await waitForNextFrame();
assert_equals(getComputedStyle(target).translate, '100px');
content.remove();
target.remove();
}, 'scroll-timeline-name is referenceable in animation-timeline on the ' +
'declaring element itself');
promise_test(async t => {
let [parent, target] = createScrollerAndTarget(t, 'square-container');
// <div id='parent' class='scroller'>
// <div id='target'></div>
// <div id='content'></div>
// </div>
document.body.appendChild(parent);
parent.insertBefore(target, parent.firstElementChild);
parent.style.scrollTimelineName = 'timeline';
target.style.animation = "anim 10s linear";
target.style.animationTimeline = 'timeline';
parent.scrollTop = 100; // 50%, in [0, 200].
await waitForNextFrame();
assert_equals(getComputedStyle(target).translate, '100px');
}, "scroll-timeline-name is referenceable in animation-timeline on that " +
"element's descendants");
promise_test(async t => {
let [sibling, target] = createScrollerAndTarget(t);
// <div id='sibling' class='scroller'> ... </div>
// <div id='target'></div>
document.body.appendChild(sibling);
document.body.appendChild(target);
sibling.style.scrollTimelineName = 'timeline';
target.style.animation = "anim 10s linear";
target.style.animationTimeline = 'timeline';
sibling.scrollTop = 50; // 50%, in [0, 100].
await waitForNextFrame();
assert_equals(getComputedStyle(target).translate, '100px');
}, "scroll-timeline-name is referenceable in animation-timeline on that " +
"element's following siblings");
promise_test(async t => {
let [sibling, target] = createScrollerAndTarget(t);
let parent = document.createElement('div');
// <div id='sibling' class='scroller'> ... </div>
// <div id='parent'>
// <div id='target'></div>
// </div>
document.body.appendChild(sibling);
document.body.appendChild(parent);
parent.appendChild(target);
sibling.style.scrollTimelineName = 'timeline';
target.style.animation = "anim 10s linear";
target.style.animationTimeline = 'timeline';
sibling.scrollTop = 50; // 50%, in [0, 100].
await waitForNextFrame();
assert_equals(getComputedStyle(target).translate, '100px');
parent.remove();
}, "scroll-timeline-name is referenceable in animation-timeline on that " +
"element's following siblings' descendants");
// FIXME: We may use global scope for scroll-timeline-name.
// See https://github.com/w3c/csswg-drafts/issues/7047
promise_test(async t => {
let [sibling, target] = createScrollerAndTarget(t);
// <div id='target'></div>
// <div id='sibling' class='scroller'> ... </div>
document.body.appendChild(target);
document.body.appendChild(sibling);
sibling.style.scrollTimelineName = 'timeline';
target.style.animation = "anim 10s linear";
target.style.animationTimeline = 'timeline';
sibling.scrollTop = 50; // 50%, in [0, 100].
await waitForNextFrame();
assert_equals(getComputedStyle(target).translate, '50px',
'Animation with unknown timeline name holds current time at zero');
}, "scroll-timeline-name is not referenceable in animation-timeline on that " +
"element's previous siblings");
promise_test(async t => {
let [sibling, target] = createScrollerAndTarget(t);
let parent = document.createElement('div');
parent.className = 'scroller square-container';
let content = document.createElement('div');
content.className = 'content';
// <div id='parent' class='scroller'>
// <div id='sibling' class='scroller'> ... </div>
// <div id='target'></div>
// <div id='content'></div>
// </div>
document.body.appendChild(parent);
parent.appendChild(sibling);
parent.appendChild(target);
parent.appendChild(content);
parent.style.scrollTimelineName = 'timeline';
parent.style.scrollTimelineAxis = 'inline';
sibling.style.scrollTimelineName = 'timeline';
target.style.animation = "anim 10s linear";
target.style.animationTimeline = 'timeline';
parent.scrollTop = 50; // 25%, in [0, 200].
sibling.scrollTop = 50; // 50%, in [0, 100].
await waitForNextFrame();
assert_equals(getComputedStyle(target).translate, '100px');
content.remove();
parent.remove();
}, 'scroll-timeline-name is matched based on tree order, which considers ' +
'siblings closer than parents');
promise_test(async t => {
let sibling = document.createElement('div');
sibling.className = 'square';
sibling.style.overflowX = 'clip'; // This makes overflow-y be clip as well.
let target = document.createElement('div');
target.id = 'target';
// <div id='sibling' style='overflow-x: clip'></div>
// <div id='target'></div>
document.body.appendChild(sibling);
document.body.appendChild(target);
sibling.style.scrollTimelineName = 'timeline';
target.style.animation = "anim 10s linear";
target.style.animationTimeline = 'timeline';
sibling.scrollTop = 50; // 50%, in [0, 100].
await waitForNextFrame();
assert_equals(getComputedStyle(target).translate, 'none',
'Animation with an unresolved current time');
target.remove();
sibling.remove();
}, 'scroll-timeline-name on an element which is not a scroll-container');
promise_test(async t => {
let [sibling, target] = createScrollerAndTarget(t);
let main = document.createElement('div');
main.id = 'name';
// <div id='main'>
// <div id='sibling' class='scroller'> ... </div>
// <div id='target'></div>
// </div>
document.body.appendChild(main);
main.appendChild(sibling);
main.appendChild(target);
target.style.animation = 'anim 10s linear';
target.style.animationTimeline = 'timeline';
sibling.scrollTop = 50; // 50%, in [50, 150].
await waitForNextFrame();
// Unknown animation-timeline, current time held at zero.
assert_equals(getComputedStyle(target).translate, '50px');
// Ensure that #main (an ancestor of the scroller) needs style recalc.
main.style.background = 'lightgray';
sibling.style.scrollTimelineName = 'timeline';
await waitForCSSScrollTimelineStyle();
assert_equals(getComputedStyle(target).translate, '100px');
main.remove();
}, 'scroll-timeline-name affects subsequent siblings when changed');
promise_test(async t => {
let target = createTarget(t);
// <div id='target'></div>
document.body.appendChild(target);
target.style.animation = 'anim 10s linear';
target.style.animationTimeline = 'timeline';
// Unknown animation-timeline, current time held at zero.
assert_equals(getComputedStyle(target).translate, '50px');
let scroller = createScroller(t);
// <div class='scroller'> ... </div>
// <div id='target'></div>
document.body.insertBefore(scroller, target);
scroller.style.scrollTimelineName = 'timeline';
await waitForNextFrame();
assert_equals(getComputedStyle(target).translate, '50px');
// Ensure that time is not just held at zero.
scroller.scrollTop = 50; // 50%, in [50, 150].
await waitForNextFrame();
assert_equals(getComputedStyle(target).translate, '100px');
}, 'scroll-timeline-name on inserted element affects subsequent siblings');
promise_test(async t => {
let [scroller, target] = createScrollerAndTarget(t);
// <div class='scroller'> ... </div>
// <div id='target'></div>
document.body.appendChild(scroller);
document.body.appendChild(target);
scroller.scrollTop = 50; // 50%, in [50, 150].
await waitForNextFrame();
scroller.style.scrollTimelineName = 'timeline';
target.style.animation = 'anim 10s linear';
target.style.animationTimeline = 'timeline';
await waitForCSSScrollTimelineStyle();
assert_equals(getComputedStyle(target).translate, '100px');
// This effectively removes the CSS-created ScrollTimeline on this element,
// thus invoking "setting the timeline of an animation" [1] with a null-
// timeline on affected elements. This in turn causes the current time to
// become unresolved [2], ultimately resulting in no effect value.
//
// [1] https://drafts.csswg.org/web-animations-1/#setting-the-timeline
// [2] https://drafts.csswg.org/web-animations-1/#the-current-time-of-an-animation
scroller.remove();
await waitForNextFrame();
assert_equals(getComputedStyle(target).translate, 'none');
}, 'scroll-timeline-name on removed element affects subsequent siblings');
promise_test(async t => {
let [scroller, target] = createScrollerAndTarget(t);
// <div class='scroller' style='display:none'> ... </div>
// <div id='target'></div>
scroller.style.display = 'none';
document.body.appendChild(scroller);
document.body.appendChild(target);
scroller.style.scrollTimelineName = 'timeline';
target.style.animation = 'anim 10s linear';
target.style.animationTimeline = 'timeline';
// Unknown animation-timeline, current time held at zero.
assert_equals(getComputedStyle(target).translate, '50px');
scroller.style.display = 'block';
scroller.scrollTop = 50; // 50%, in [50, 150].
await waitForNextFrame();
assert_equals(getComputedStyle(target).translate, '100px');
}, 'scroll-timeline-name on element leaving display:none affects subsequent siblings');
promise_test(async t => {
let [scroller, target] = createScrollerAndTarget(t);
// <div class='scroller'> ... </div>
// <div id='target'></div>
document.body.appendChild(scroller);
document.body.appendChild(target);
scroller.scrollTop = 50; // 50%, in [50, 150].
await waitForNextFrame();
scroller.style.scrollTimelineName = 'timeline';
target.style.animation = 'anim 10s linear';
target.style.animationTimeline = 'timeline';
await waitForCSSScrollTimelineStyle();
assert_equals(getComputedStyle(target).translate, '100px');
// See comment in the test "scroll-timeline-name on removed element ..." for
// an explantation of this result. (Setting display:none is similar to
// removing the element).
scroller.style.display = 'none';
await waitForNextFrame();
assert_equals(getComputedStyle(target).translate, 'none');
}, 'scroll-timeline-name on element becoming display:none affects subsequent siblings');
promise_test(async t => {
let [scroller, target] = createScrollerAndTarget(t);
// <div id='scroller' class='scroller'> ... </div>
// <div id='target'></div>
document.body.appendChild(scroller);
document.body.appendChild(target);
scroller.style.scrollTimelineName = 'timeline';
scroller.style.display = 'none';
target.style.animation = "anim 10s linear";
target.style.animationTimeline = 'timeline';
await waitForNextFrame();
const anim = target.getAnimations()[0];
assert_true(!!anim, 'Failed to create animation');
assert_equals(anim.timeline, null);
// Hold time of animation is zero.
assert_equals(getComputedStyle(target).translate, '50px');
scroller.style.display = 'block';
scroller.scrollTop = 50;
await waitForNextFrame();
assert_true(!!anim.timeline, 'Failed to create timeline');
assert_equals(getComputedStyle(target).translate, '100px');
}, 'scroll-timeline-name on element not resolved until element becomes visible');
promise_test(async t => {
let [scroller, target] = createScrollerAndTarget(t);
// <div id='scroller' class='scroller'> ... </div>
// <div id='target'></div>
document.body.appendChild(scroller);
document.body.appendChild(target);
scroller.style.scrollTimelineName = 'timeline-A';
scroller.scrollTop = 50;
target.style.animation = "anim 10s linear";
target.style.animationTimeline = 'timeline-B';
await waitForNextFrame();
const anim = target.getAnimations()[0];
assert_true(!!anim, 'Failed to create animation');
assert_equals(anim.timeline, null);
// Hold time of animation is zero.
assert_equals(getComputedStyle(target).translate, '50px');
scroller.style.scrollTimelineName = 'timeline-B';
await waitForNextFrame();
assert_true(!!anim.timeline, 'Failed to create timeline');
assert_equals(getComputedStyle(target).translate, '100px');
}, 'Change in scroll-timeline-name to match animation timeline updates animation.');
promise_test(async t => {
let [scroller, target] = createScrollerAndTarget(t);
// <div id='scroller' class='scroller'> ... </div>
// <div id='target'></div>
document.body.appendChild(scroller);
document.body.appendChild(target);
scroller.style.scrollTimelineName = 'timeline-A';
scroller.scrollTop = 50;
target.style.animation = "anim 10s linear";
target.style.animationTimeline = 'timeline-A';
await waitForNextFrame();
const anim = target.getAnimations()[0];
assert_true(!!anim, 'Failed to create animation');
assert_true(!!anim.timeline, 'Failed to create timeline');
assert_equals(getComputedStyle(target).translate, '100px');
scroller.style.scrollTimelineName = 'timeline-B';
await waitForNextFrame();
assert_equals(anim.timeline, null, 'Failed to remove timeline');
assert_equals(getComputedStyle(target).translate, 'none');
}, 'Change in scroll-timeline-name to no longer match animation timeline updates animation.');
promise_test(async t => {
let target = createTarget(t);
let scroller1 = createScroller(t);
let scroller2 = createScroller(t);
target.style.animation = 'anim 10s linear';
target.style.animationTimeline = 'timeline';
scroller1.style.scrollTimelineName = 'timeline';
scroller2.style.scrollTimelineName = 'timeline';
scroller1.id = 'A';
scroller2.id = 'B';
// <div class='scroller' id='A'> ... </div> (scroller1)
// <div class='scroller' id="B"> ... </div> (scroller2)
// <div id='target'></div>
document.body.appendChild(scroller1);
document.body.appendChild(scroller2);
document.body.append(target);
scroller1.scrollTop = 10; // 10%, in [50, 150].
scroller2.scrollTop = 50; // 50%, in [50, 150].
await waitForNextFrame();
// The named timeline lookup should select scroller2.
let anim = target.getAnimations()[0];
assert_true(!!anim, 'Failed to fetch animation');
assert_equals(anim.timeline.source.id, 'B');
assert_equals(getComputedStyle(target).translate, '100px');
scroller2.remove();
// Now it should select scroller1.
anim = target.getAnimations()[0];
assert_true(!!anim, 'Failed to fetch animation after update');
assert_true(!!anim.timeline, 'Animation no longer has a timeline');
assert_equals(anim.timeline.source.id, 'A', 'Timeline not updated');
assert_equals(getComputedStyle(target).translate, '60px');
}, 'Timeline lookup finds next candidate when element is removed');
promise_test(async t => {
let target = createTarget(t);
let scroller1 = createScroller(t);
target.style.animation = 'anim 10s linear';
target.style.animationTimeline = 'timeline';
scroller1.style.scrollTimelineName = 'timeline';
scroller1.id = 'A';
// <div class='scroller' id='A'> ... </div> (scroller1)
// <div id='target'></div>
document.body.appendChild(scroller1);
document.body.append(target);
scroller1.scrollTop = 10; // 10%, in [50, 150].
await waitForNextFrame();
const anim = target.getAnimations()[0];
assert_true(!!anim.timeline, 'Failed to retrieve animation');
assert_equals(anim.timeline.source.id, 'A');
assert_equals(getComputedStyle(target).translate, '60px');
await waitForNextFrame();
let scroller2 = createScroller(t);
scroller2.style.scrollTimelineName = 'timeline';
scroller2.id = 'B';
// <div class='scroller' id="A"> ... </div> (scroller1)
// <div class='scroller' id="B"> ... </div> (scroller2)
// <div id='target'></div>
document.body.insertBefore(scroller2, target);
scroller2.scrollTop = 50; // 50%, in [50, 150].
await waitForNextFrame();
// The timeline should be updated to scroller2.
assert_true(!!anim.timeline, 'Animation no longer has a timeline');
assert_equals(anim.timeline.source.id, 'B', 'Timeline not updated');
assert_equals(getComputedStyle(target).translate, '100px');
}, 'Timeline lookup updates candidate when closer match available.');
promise_test(async t => {
let target = createTarget(t);
// <div id='target'></div>
document.body.append(target);
target.style.animation = "anim 10s linear";
target.style.animationTimeline = 'timeline';
await waitForNextFrame();
// Timeline initially cannot be resolved, resulting in a null
// timeline. The animation's hold time is zero.
let anim = document.getAnimations()[0];
assert_equals(getComputedStyle(target).translate, '50px');
await waitForNextFrame();
let scroller = createScroller(t);
scroller.style.scrollTimelineName = 'timeline';
// <div class='scroller'> ... </div> (scroller1)
// <div id='target'></div>
document.body.insertBefore(scroller, target);
scroller.scrollTop = 50; // 50%, in [50, 150].
await waitForNextFrame();
// The timeline should be updated to scroller.
assert_equals(getComputedStyle(target).translate, '100px');
}, 'Timeline lookup updates candidate when match becomes available.');
// -------------------------
// Test scroll-timeline-axis
// -------------------------
promise_test(async t => {
let [scroller, target] = createScrollerAndTarget(t);
scroller.style.writingMode = 'vertical-lr';
document.body.appendChild(scroller);
document.body.appendChild(target);
scroller.style.scrollTimeline = 'timeline block';
target.style.animation = "anim 10s linear";
target.style.animationTimeline = 'timeline';
scroller.scrollLeft = 50;
await waitForNextFrame();
assert_equals(getComputedStyle(target).translate, '100px');
}, 'scroll-timeline-axis is block');
promise_test(async t => {
let [scroller, target] = createScrollerAndTarget(t);
scroller.style.writingMode = 'vertical-lr';
document.body.appendChild(scroller);
document.body.appendChild(target);
scroller.style.scrollTimeline = 'timeline inline';
target.style.animation = "anim 10s linear";
target.style.animationTimeline = 'timeline';
scroller.scrollTop = 50;
await waitForNextFrame();
assert_equals(getComputedStyle(target).translate, '100px');
}, 'scroll-timeline-axis is inline');
promise_test(async t => {
let [scroller, target] = createScrollerAndTarget(t);
scroller.style.writingMode = 'vertical-lr';
document.body.appendChild(scroller);
document.body.appendChild(target);
scroller.style.scrollTimeline = 'timeline horizontal';
target.style.animation = "anim 10s linear";
target.style.animationTimeline = 'timeline';
scroller.scrollLeft = 50;
await waitForNextFrame();
assert_equals(getComputedStyle(target).translate, '100px');
}, 'scroll-timeline-axis is horizontal');
promise_test(async t => {
let [scroller, target] = createScrollerAndTarget(t);
scroller.style.writingMode = 'vertical-lr';
document.body.appendChild(scroller);
document.body.appendChild(target);
scroller.style.scrollTimeline = 'timeline vertical';
target.style.animation = "anim 10s linear";
target.style.animationTimeline = 'timeline';
scroller.scrollTop = 50;
await waitForNextFrame();
assert_equals(getComputedStyle(target).translate, '100px');
}, 'scroll-timeline-axis is vertical');
promise_test(async t => {
let [scroller, target] = createScrollerAndTarget(t);
document.body.appendChild(scroller);
document.body.appendChild(target);
scroller.style.scrollTimeline = 'timeline block';
target.style.animation = "anim 10s linear";
target.style.animationTimeline = 'timeline';
scroller.scrollTop = 50;
scroller.scrollLeft = 25;
await waitForNextFrame();
assert_equals(getComputedStyle(target).translate, '100px');
scroller.style.scrollTimelineAxis = 'inline';
await waitForNextFrame();
assert_equals(getComputedStyle(target).translate, '75px');
}, 'scroll-timeline-axis is mutated');
</script>
</body>