1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139
|
<!DOCTYPE html>
<meta charset=utf-8>
<title>Correctness of worklet animation state when timeline becomes newly
active or inactive.</title>
<link rel="help" href="https://drafts.css-houdini.org/css-animationworklet/">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/web-animations/testcommon.js"></script>
<script src="common.js"></script>
<style>
.scroller {
overflow: auto;
height: 100px;
width: 100px;
}
.contents {
height: 1000px;
width: 100%;
}
</style>
<body>
<div id="log"></div>
<script>
'use strict';
function createScroller(test) {
var scroller = createDiv(test);
scroller.innerHTML = "<div class='contents'></div>";
scroller.classList.add('scroller');
return scroller;
}
function createScrollLinkedWorkletAnimation(test) {
const timeline = new ScrollTimeline({
scrollSource: createScroller(test),
});
const DURATION = 1000; // ms
const KEYFRAMES = { transform: ['translateY(100px)', 'translateY(200px)'] };
return new WorkletAnimation('passthrough', new KeyframeEffect(createDiv(test),
KEYFRAMES, DURATION), timeline);
}
setup(setupAndRegisterTests, {explicit_done: true});
function setupAndRegisterTests() {
registerPassthroughAnimator().then(() => {
promise_test(async t => {
const animation = createScrollLinkedWorkletAnimation(t);
const scroller = animation.timeline.scrollSource;
const target = animation.effect.target;
// There is no direct way to control when local times of composited
// animations are synced to the main thread. This test uses another
// composited worklet animation with an always active timeline as an
// indicator of when the sync is ready. The sync is done when animation
// effect's output has changed as a result of advancing the timeline.
const animationRef = createScrollLinkedWorkletAnimation(t);
const scrollerRef = animationRef.timeline.scrollSource;
const targetRef = animationRef.effect.target;
const maxScroll = scroller.scrollHeight - scroller.clientHeight;
scroller.scrollTop = 0.2 * maxScroll;
// Make the timeline inactive.
scroller.style.display = "none"
// Force relayout.
scroller.scrollTop;
animation.play();
animationRef.play();
assert_equals(animation.currentTime, null,
'Initial current time must be unresolved in idle state.');
assert_equals(animation.startTime, null,
'Initial start time must be unresolved in idle state.');
waitForAnimationFrameWithCondition(_=> {
return animation.playState == "running"
});
assert_equals(animation.currentTime, null,
'Initial current time must be unresolved in playing state.');
assert_equals(animation.startTime, null,
'Initial start time must be unresolved in playing state.');
scrollerRef.scrollTop = 0.2 * maxScroll;
// Wait until local times are synced back to the main thread.
await waitForAnimationFrameWithCondition(_ => {
return animationRef.effect.getComputedTiming().localTime == 200;
});
assert_equals(animation.effect.getComputedTiming().localTime, null,
'The underlying effect local time must be undefined while the ' +
'timeline is inactive.');
// Make the timeline active.
scroller.style.display = "";
// Wait for new animation frame which allows the timeline to compute new
// current time.
await waitForNextFrame();
assert_times_equal(animation.currentTime, 200,
'Current time must be initialized.');
assert_times_equal(animation.startTime, 0,
'Start time must be initialized.');
scrollerRef.scrollTop = 0.4 * maxScroll;
// Wait until local times are synced back to the main thread.
await waitForAnimationFrameWithCondition(_ => {
return animationRef.effect.getComputedTiming().localTime == 400;
});
assert_times_equal(animation.effect.getComputedTiming().localTime, 200,
'When the timeline becomes newly active, the underlying effect\'s ' +
'timing should be properly updated.');
// Make the timeline inactive again.
scroller.style.display = "none"
await waitForNextFrame();
assert_times_equal(animation.currentTime, 200,
'Current time must be the previous current time.');
assert_equals(animation.startTime, null,
'Initial start time must be unresolved.');
scrollerRef.scrollTop = 0.6 * maxScroll;
// Wait until local times are synced back to the main thread.
await waitForAnimationFrameWithCondition(_ => {
return animationRef.effect.getComputedTiming().localTime == 600;
});
assert_times_equal(animation.effect.getComputedTiming().localTime, 200,
'When the timeline becomes newly inactive, the underlying effect\'s ' +
'timing should stay unchanged.');
}, 'When timeline time becomes inactive previous current time must be ' +
'the current time and start time unresolved');
done();
});
}
</script>
</body>
|