summaryrefslogtreecommitdiffstats
path: root/testing/web-platform/tests/animation-worklet/playback-rate.https.html
diff options
context:
space:
mode:
Diffstat (limited to 'testing/web-platform/tests/animation-worklet/playback-rate.https.html')
-rw-r--r--testing/web-platform/tests/animation-worklet/playback-rate.https.html345
1 files changed, 345 insertions, 0 deletions
diff --git a/testing/web-platform/tests/animation-worklet/playback-rate.https.html b/testing/web-platform/tests/animation-worklet/playback-rate.https.html
new file mode 100644
index 0000000000..2e2fb9a099
--- /dev/null
+++ b/testing/web-platform/tests/animation-worklet/playback-rate.https.html
@@ -0,0 +1,345 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>The playback rate of a worklet animation</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>
+'use strict';
+// Presence of playback rate adds FP operations to calculating start_time
+// and current_time of animations. That's why it's needed to increase FP error
+// for comparing times in these tests.
+window.assert_times_equal = (actual, expected, description) => {
+ assert_approx_equals(actual, expected, 0.002, description);
+};
+</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 createWorkletAnimation(test) {
+ const DURATION = 10000; // ms
+ const KEYFRAMES = { transform: ['translateY(100px)', 'translateY(200px)'] };
+ return new WorkletAnimation('passthrough', new KeyframeEffect(createDiv(test),
+ KEYFRAMES, DURATION), document.timeline);
+}
+
+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 = 10000; // 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 = createWorkletAnimation(t);
+
+ animation.playbackRate = 0.5;
+ animation.play();
+ assert_equals(animation.currentTime, 0,
+ 'Zero current time is not affected by playbackRate.');
+ }, 'Zero current time is not affected by playbackRate set while the ' +
+ 'animation is in idle state.');
+
+ promise_test(async t => {
+ const animation = createWorkletAnimation(t);
+
+ animation.play();
+ animation.playbackRate = 0.5;
+ assert_equals(animation.currentTime, 0,
+ 'Zero current time is not affected by playbackRate.');
+ }, 'Zero current time is not affected by playbackRate set while the ' +
+ 'animation is in play-pending state.');
+
+ promise_test(async t => {
+ const animation = createWorkletAnimation(t);
+ const playbackRate = 2;
+
+ animation.play();
+
+ await waitForAnimationFrameWithCondition(_=> {
+ return animation.playState == "running"
+ });
+ // Make sure the current time is not Zero.
+ await waitForDocumentTimelineAdvance();
+
+ // Set playback rate while the animation is playing.
+ const prevCurrentTime = animation.currentTime;
+ animation.playbackRate = playbackRate;
+
+ assert_times_equal(animation.currentTime, prevCurrentTime,
+ 'The current time should stay unaffected by setting playback rate.');
+ }, 'Non zero current time is not affected by playbackRate set while the ' +
+ 'animation is in play state.');
+
+ promise_test(async t => {
+ const animation = createWorkletAnimation(t);
+ const playbackRate = 0.2;
+
+ animation.play();
+
+ await waitForAnimationFrameWithCondition(_=> {
+ return animation.playState == "running"
+ });
+
+ // Set playback rate while the animation is playing.
+ const prevCurrentTime = animation.currentTime;
+ const prevTimelineTime = document.timeline.currentTime;
+ animation.playbackRate = playbackRate;
+
+ // Play the animation some more.
+ await waitForDocumentTimelineAdvance();
+
+ const currentTime = animation.currentTime;
+ const currentTimelineTime = document.timeline.currentTime;
+
+ assert_times_equal(
+ currentTime - prevCurrentTime,
+ (currentTimelineTime - prevTimelineTime) * playbackRate,
+ 'The current time should increase 0.2 times faster than timeline.');
+ }, 'The playback rate affects the rate of progress of the current time.');
+
+ promise_test(async t => {
+ const animation = createWorkletAnimation(t);
+ const playbackRate = 2;
+
+ // Set playback rate while the animation is in 'idle' state.
+ animation.playbackRate = playbackRate;
+ const prevTimelineTime = document.timeline.currentTime;
+ animation.play();
+
+ await waitForAnimationFrameWithCondition(_=> {
+ return animation.playState == "running"
+ });
+ await waitForDocumentTimelineAdvance();
+
+ const currentTime = animation.currentTime;
+ const timelineTime = document.timeline.currentTime;
+ assert_times_equal(
+ currentTime,
+ (timelineTime - prevTimelineTime) * playbackRate,
+ 'The current time should increase two times faster than timeline.');
+ }, 'The playback rate set before the animation started playing affects ' +
+ 'the rate of progress of the current time');
+
+ promise_test(async t => {
+ const timing = { duration: 100,
+ easing: 'linear',
+ fill: 'none',
+ iterations: 1
+ };
+ // TODO(crbug.com/937382): Currently composited
+ // workletAnimation.currentTime and the corresponding
+ // effect.getComputedTiming().localTime are computed by main and
+ // compositing threads respectively and, as a result, don't match.
+ // To workaround this limitation we compare the output of two identical
+ // animations that only differ in playback rate. The expectation is that
+ // their output matches after taking their playback rates into
+ // consideration. This works since these two animations start at the same
+ // time on the same thread.
+ // Once the issue is fixed, this test needs to change so expected
+ // effect.getComputedTiming().localTime is compared against
+ // workletAnimation.currentTime.
+ const target = createDiv(t);
+ const targetRef = createDiv(t);
+ const keyframeEffect = new KeyframeEffect(
+ target, { opacity: [1, 0] }, timing);
+ const keyframeEffectRef = new KeyframeEffect(
+ targetRef, { opacity: [1, 0] }, timing);
+ const animation = new WorkletAnimation(
+ 'passthrough', keyframeEffect, document.timeline);
+ const animationRef = new WorkletAnimation(
+ 'passthrough', keyframeEffectRef, document.timeline);
+ const playbackRate = 2;
+ animation.playbackRate = playbackRate;
+ animation.play();
+ animationRef.play();
+
+ // wait until local times are synced back to the main thread.
+ await waitForAnimationFrameWithCondition(_ => {
+ return getComputedStyle(target).opacity != '1';
+ });
+
+ assert_times_equal(
+ keyframeEffect.getComputedTiming().localTime,
+ keyframeEffectRef.getComputedTiming().localTime * playbackRate,
+ 'When playback rate is set on WorkletAnimation, the underlying ' +
+ 'effect\'s timing should be properly updated.');
+
+ assert_approx_equals(
+ 1 - Number(getComputedStyle(target).opacity),
+ (1 - Number(getComputedStyle(targetRef).opacity)) * playbackRate,
+ 0.001,
+ 'When playback rate is set on WorkletAnimation, the underlying effect' +
+ ' should produce correct visual result.');
+ }, 'When playback rate is updated, the underlying effect is properly ' +
+ 'updated with the current time of its WorkletAnimation and produces ' +
+ 'correct visual result.');
+
+ promise_test(async t => {
+ const animation = createScrollLinkedWorkletAnimation(t);
+ const scroller = animation.timeline.scrollSource;
+ const maxScroll = scroller.scrollHeight - scroller.clientHeight;
+ scroller.scrollTop = 0.2 * maxScroll;
+
+ animation.playbackRate = 0.5;
+ animation.play();
+ await waitForAnimationFrameWithCondition(_=> {
+ return animation.playState == "running"
+ });
+ assert_percents_equal(animation.currentTime, 10,
+ 'Initial current time is scaled by playbackRate.');
+ }, 'Initial current time is scaled by playbackRate set while ' +
+ 'scroll-linked animation is in idle state.');
+
+ promise_test(async t => {
+ const animation = createScrollLinkedWorkletAnimation(t);
+ const scroller = animation.timeline.scrollSource;
+ const maxScroll = scroller.scrollHeight - scroller.clientHeight;
+ scroller.scrollTop = 0.2 * maxScroll;
+
+ animation.play();
+ animation.playbackRate = 0.5;
+
+ assert_percents_equal(animation.currentTime, 20,
+ 'Initial current time is not affected by playbackRate.');
+ }, 'Initial current time is not affected by playbackRate set while '+
+ 'scroll-linked animation is in play-pending state.');
+
+ promise_test(async t => {
+ const animation = createScrollLinkedWorkletAnimation(t);
+ const scroller = animation.timeline.scrollSource;
+ const maxScroll = scroller.scrollHeight - scroller.clientHeight;
+ const playbackRate = 2;
+
+ animation.play();
+ scroller.scrollTop = 0.2 * maxScroll;
+ await waitForAnimationFrameWithCondition(_=> {
+ return animation.playState == "running"
+ });
+ // Set playback rate while the animation is playing.
+ animation.playbackRate = playbackRate;
+ assert_percents_equal(animation.currentTime, 20,
+ 'The current time should stay unaffected by setting playback rate.');
+ }, 'The current time is not affected by playbackRate set while the ' +
+ 'scroll-linked animation is in play state.');
+
+ promise_test(async t => {
+ const animation = createScrollLinkedWorkletAnimation(t);
+ const scroller = animation.timeline.scrollSource;
+ const maxScroll = scroller.scrollHeight - scroller.clientHeight;
+ const playbackRate = 2;
+
+ animation.play();
+ await waitForAnimationFrameWithCondition(_=> {
+ return animation.playState == "running"
+ });
+ scroller.scrollTop = 0.1 * maxScroll;
+
+ // Set playback rate while the animation is playing.
+ animation.playbackRate = playbackRate;
+
+ scroller.scrollTop = 0.2 * maxScroll;
+
+ assert_equals(
+ animation.currentTime.value - 10, 10 * playbackRate,
+ 'The current time should increase twice faster than scroll timeline.');
+ }, 'Scroll-linked animation playback rate affects the rate of progress ' +
+ 'of the current time.');
+
+ promise_test(async t => {
+ const animation = createScrollLinkedWorkletAnimation(t);
+ const scroller = animation.timeline.scrollSource;
+ const maxScroll = scroller.scrollHeight - scroller.clientHeight;
+ const playbackRate = 2;
+
+ // Set playback rate while the animation is in 'idle' state.
+ animation.playbackRate = playbackRate;
+ animation.play();
+ await waitForAnimationFrameWithCondition(_=> {
+ return animation.playState == "running"
+ });
+ scroller.scrollTop = 0.2 * maxScroll;
+
+ assert_percents_equal(animation.currentTime, 20 * playbackRate,
+ 'The current time should increase two times faster than timeline.');
+ }, 'The playback rate set before scroll-linked animation started playing ' +
+ 'affects the rate of progress of the current time');
+
+ promise_test(async t => {
+ const scroller = createScroller(t);
+ const timeline = new ScrollTimeline({
+ scrollSource: scroller
+ });
+ const timing = { duration: 1000,
+ easing: 'linear',
+ fill: 'none',
+ iterations: 1
+ };
+ const target = createDiv(t);
+ const keyframeEffect = new KeyframeEffect(
+ target, { opacity: [1, 0] }, timing);
+ const animation = new WorkletAnimation(
+ 'passthrough', keyframeEffect, timeline);
+ const playbackRate = 2;
+ const maxScroll = scroller.scrollHeight - scroller.clientHeight;
+
+ animation.play();
+ animation.playbackRate = playbackRate;
+ await waitForAnimationFrameWithCondition(_=> {
+ return animation.playState == "running"
+ });
+
+ scroller.scrollTop = 0.2 * maxScroll;
+ // wait until local times are synced back to the main thread.
+ await waitForAnimationFrameWithCondition(_ => {
+ return getComputedStyle(target).opacity != '1';
+ });
+
+ assert_percents_equal(
+ keyframeEffect.getComputedTiming().localTime,
+ 20 * playbackRate,
+ 'When playback rate is set on WorkletAnimation, the underlying ' +
+ 'effect\'s timing should be properly updated.');
+ assert_approx_equals(
+ Number(getComputedStyle(target).opacity),
+ 1 - 20 * playbackRate / 1000, 0.001,
+ 'When playback rate is set on WorkletAnimation, the underlying ' +
+ 'effect should produce correct visual result.');
+ }, 'When playback rate is updated, the underlying effect is properly ' +
+ 'updated with the current time of its scroll-linked WorkletAnimation ' +
+ 'and produces correct visual result.');
+ done();
+ });
+}
+</script>
+</body> \ No newline at end of file