Bug 1635336 [wpt PR 23398] - Enabled setStartTime API for scroll animations, a=testonly
authorOlga Gerchikov <gerchiko@microsoft.com>
Wed, 13 May 2020 09:49:07 +0000
changeset 531167 d3455a129c4eb6504ab34995763eae573451573d
parent 531166 0ff5e06c6a796fa76f9de07fd495c61973ef422a
child 531168 bdc280dfa2195718f2557bb7900a1554c5eb523b
push id37435
push userapavel@mozilla.com
push dateWed, 20 May 2020 15:28:23 +0000
treeherdermozilla-central@5415da14ec9a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerstestonly
bugs1635336, 23398, 916117, 2180509, 767041
milestone78.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1635336 [wpt PR 23398] - Enabled setStartTime API for scroll animations, a=testonly Automatic update from web-platform-tests Enabled setStartTime API for scroll animations - Updated Animation::setStartTime to set animation outdated if start time changes from unresolved to resolved. This is to ensure animation is tracked by its timeline for future time updates. - Imported tests from [1] and updated to run for scroll animations. Some tests that use not enabled APIs are skipped and will be added as these APIs are enabled. - Added tests verifying setting current time when the timeline is inactive. [1] https://source.chromium.org/chromium/chromium/src/+/master:third_party/blink/web_tests/external/wpt/web-animations/timing-model/animations/setting-the-start-time-of-an-animation.html;l=1?originalUrl=https:%2F%2Fcs.chromium.org%2F Bug: 916117 Change-Id: Ic03e84ad56cf8a538f0cf3d138d1e20067455dea Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2180509 Commit-Queue: Olga Gerchikov <gerchiko@microsoft.com> Reviewed-by: Majid Valipour <majidvp@chromium.org> Reviewed-by: Kevin Ellis <kevers@chromium.org> Cr-Commit-Position: refs/heads/master@{#767041} -- wpt-commits: 5d4122e5caa8fd324a4ae0133b771384b1f025ca wpt-pr: 23398
testing/web-platform/tests/scroll-animations/scroll-animation-inactive-timeline.html
testing/web-platform/tests/scroll-animations/setting-start-time.html
--- a/testing/web-platform/tests/scroll-animations/scroll-animation-inactive-timeline.html
+++ b/testing/web-platform/tests/scroll-animations/scroll-animation-inactive-timeline.html
@@ -61,16 +61,48 @@ promise_test(async t => {
     assert_equals(animation.startTime, 0,
       'Animation start time is resolved when the animation is ready.');
 }, 'Animation start and current times are correct if scroll timeline is ' +
    'activated after animation.play call.');
 
 promise_test(async t => {
     const animation = createScrollLinkedAnimation(t);
     const scroller = animation.timeline.scrollSource;
+    const target = animation.effect.target;
+    // Make the scroll timeline inactive.
+    scroller.style.overflow = 'visible';
+    // Wait for new animation frame which allows the timeline to compute new
+    // current time.
+    await waitForNextFrame();
+    // Set start time when the timeline is inactive.
+    animation.startTime = 0;
+    assert_equals(animation.currentTime, null,
+      'Sanity check current time is unresolved when the timeline is inactive.');
+
+    // Make the scroll timeline active.
+    scroller.style.overflow = 'auto';
+    // Wait for new animation frame which allows the timeline to compute new
+    // current time.
+    await waitForNextFrame();
+
+    assert_equals(animation.currentTime, 0,
+      'Animation current time is resolved when the timeline is active.');
+    assert_equals(animation.startTime, 0,
+      'Animation start time is resolved.');
+    assert_times_equal(
+      animation.effect.getComputedTiming().localTime, 0,
+      'Effect local time is resolved when the timeline is active.');
+    assert_equals(Number(getComputedStyle(target).opacity), 0,
+      'Animation has an effect when the timeline is active.');
+}, 'Animation start and current times are correct if scroll timeline is ' +
+   'activated after setting start time.');
+
+promise_test(async t => {
+    const animation = createScrollLinkedAnimation(t);
+    const scroller = animation.timeline.scrollSource;
     const maxScroll = scroller.scrollHeight - scroller.clientHeight;
     const target = animation.effect.target;
     // Advance the scroller.
     scroller.scrollTop = 0.2 * maxScroll;
 
     // Wait for new animation frame which allows the timeline to compute new
     // current time.
     await waitForNextFrame();
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/scroll-animations/setting-start-time.html
@@ -0,0 +1,339 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Setting the start time of scroll animation</title>
+<link rel="help" href="https://drafts.csswg.org/web-animations/#setting-the-start-time-of-an-animation">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/web-animations/testcommon.js"></script>
+<script src="testcommon.js"></script>
+<style>
+.scroller {
+  overflow: auto;
+  height: 200px;
+  width: 100px;
+}
+.contents {
+  height: 1000px;
+  width: 100%;
+}
+</style>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+promise_test(async t => {
+  const animation = createScrollLinkedAnimation(t);
+  const scroller = animation.timeline.scrollSource;
+  const maxScroll = scroller.scrollHeight - scroller.clientHeight;
+  scroller.scrollTop = 0.2 * maxScroll;
+  // Wait for new animation frame  which allows the timeline to compute new
+  // current time.
+  await waitForNextFrame();
+
+  // So long as a hold time is set, querying the current time will return
+  // the hold time.
+
+  // Since the start time is unresolved at this point, setting the current time
+  // will set the hold time
+  animation.currentTime = 300;
+  assert_equals(animation.startTime, null, 'The start time stays unresolved');
+  assert_times_equal(animation.currentTime, 300,
+                    'The current time is calculated from the hold time');
+
+  // If we set the start time, however, we should clear the hold time.
+  animation.startTime = 0;
+  assert_times_equal(animation.startTime, 0,
+                    'The start time is set to the requested value');
+  assert_times_equal(animation.currentTime, 200,
+                    'The current time is calculated from the start time, not' +
+                    ' the hold time');
+  // Sanity check
+  assert_equals(animation.playState, 'running',
+                'Animation reports it is running after setting a resolved ' +
+                'start time');
+}, 'Setting the start time clears the hold time');
+
+promise_test(async t => {
+  const animation = createScrollLinkedAnimation(t);
+  const scroller = animation.timeline.scrollSource;
+  // Make the scroll timeline inactive.
+  scroller.style.overflow = 'visible';
+  // Wait for new animation frame  which allows the timeline to compute new
+  // current time.
+  await waitForNextFrame();
+  assert_equals(animation.timeline.currentTime, null,
+                'Sanity check the timeline is inactive');
+
+  // So long as a hold time is set, querying the current time will return
+  // the hold time.
+
+  // Since the start time is unresolved at this point, setting the current time
+  // will set the hold time
+  animation.currentTime = 300;
+  assert_equals(animation.startTime, null, 'The start time stays unresolved');
+  assert_times_equal(animation.currentTime, 300,
+                    'The current time is calculated from the hold time');
+
+  // If we set the start time, however, we should clear the hold time.
+  animation.startTime = 0;
+  assert_times_equal(animation.startTime, 0,
+                    'The start time is set to the requested value');
+  assert_equals(animation.currentTime, null,
+                'The current time is calculated from the start time, not' +
+                ' the hold time');
+  // Sanity check
+  assert_equals(animation.playState, 'running',
+                'Animation reports it is running after setting a resolved ' +
+                'start time');
+}, 'Setting the start time clears the hold time when the timeline is inactive');
+
+promise_test(async t => {
+  const animation = createScrollLinkedAnimation(t);
+  const scroller = animation.timeline.scrollSource;
+  const maxScroll = scroller.scrollHeight - scroller.clientHeight;
+  scroller.scrollTop = 0.2 * maxScroll;
+
+  // Wait for new animation frame  which allows the timeline to compute new
+  // current time.
+  await waitForNextFrame();
+
+  // Set up a running animation (i.e. both start time and current time
+  // are resolved).
+  animation.startTime = 50;
+  assert_equals(animation.playState, 'running');
+  assert_times_equal(animation.startTime, 50,
+                     'The start time is set to the requested value');
+  assert_times_equal(animation.currentTime, 150,
+                     'Current time is resolved for a running animation');
+
+  // Clear start time
+  animation.startTime = null;
+  assert_equals(animation.startTime, null,
+                'The start time is set to the requested value');
+  assert_times_equal(animation.currentTime, 150,
+                    'Hold time is set after start time is made unresolved');
+  assert_equals(animation.playState, 'paused',
+                'Animation reports it is paused after setting an unresolved'
+                + ' start time');
+}, 'Setting an unresolved start time sets the hold time');
+
+promise_test(async t => {
+  const animation = createScrollLinkedAnimation(t);
+  const scroller = animation.timeline.scrollSource;
+  // Make the scroll timeline inactive.
+  scroller.style.overflow = 'visible';
+  // Wait for new animation frame  which allows the timeline to compute new
+  // current time.
+  await waitForNextFrame();
+  assert_equals(animation.timeline.currentTime, null,
+                'Sanity check the timeline is inactive');
+
+  // Set up a running animation (i.e. both start time and current time
+  // are resolved).
+  animation.startTime = 50;
+  assert_equals(animation.playState, 'running');
+  assert_times_equal(animation.startTime, 50,
+                     'The start time is set to the requested value');
+  assert_equals(animation.currentTime, null,
+                'Current time is unresolved for a running animation when the ' +
+                'timeline is inactive');
+
+  // Clear start time
+  animation.startTime = null;
+  assert_equals(animation.startTime, null,
+                'The start time is set to the requested value');
+  assert_equals(animation.currentTime, null,
+                'Hold time is set to unresolved after start time is made ' +
+                'unresolved');
+  assert_equals(animation.playState, 'idle',
+                'Animation reports it is idle after setting an unresolved'
+                + ' start time');
+}, 'Setting an unresolved start time sets the hold time to unresolved when ' +
+   'the timeline is inactive');
+
+promise_test(async t => {
+  const animation = createScrollLinkedAnimation(t);
+
+  // Wait for new animation frame  which allows the timeline to compute new
+  // current time.
+  await waitForNextFrame();
+
+  let readyPromiseCallbackCalled = false;
+  animation.ready.then(() => { readyPromiseCallbackCalled = true; } );
+
+  // Put the animation in the play-pending state
+  animation.play();
+
+  // Sanity check
+  assert_true(animation.pending && animation.playState === 'running',
+              'Animation is in play-pending state');
+
+  // Setting the start time should resolve the 'ready' promise, i.e.
+  // it should schedule a microtask to run the promise callbacks.
+  animation.startTime = 100;
+  assert_times_equal(animation.startTime, 100,
+                     'The start time is set to the requested value');
+  assert_false(readyPromiseCallbackCalled,
+               'Ready promise callback is not called synchronously');
+
+  // If we schedule another microtask then it should run immediately after
+  // the ready promise resolution microtask.
+  await Promise.resolve();
+  assert_true(readyPromiseCallbackCalled,
+              'Ready promise callback called after setting startTime');
+}, 'Setting the start time resolves a pending ready promise');
+
+promise_test(async t => {
+  const animation = createScrollLinkedAnimation(t);
+  const scroller = animation.timeline.scrollSource;
+  // Make the scroll timeline inactive.
+  scroller.style.overflow = 'visible';
+  // Wait for new animation frame  which allows the timeline to compute new
+  // current time.
+  await waitForNextFrame();
+  assert_equals(animation.timeline.currentTime, null,
+                'Sanity check the timeline is inactive');
+
+  let readyPromiseCallbackCalled = false;
+  animation.ready.then(() => { readyPromiseCallbackCalled = true; } );
+
+  // Put the animation in the play-pending state
+  animation.play();
+
+  // Sanity check
+  assert_true(animation.pending && animation.playState === 'running',
+              'Animation is in play-pending state');
+
+  // Setting the start time should resolve the 'ready' promise, i.e.
+  // it should schedule a microtask to run the promise callbacks.
+  animation.startTime = 100;
+  assert_times_equal(animation.startTime, 100,
+                     'The start time is set to the requested value');
+  assert_false(readyPromiseCallbackCalled,
+               'Ready promise callback is not called synchronously');
+
+  // If we schedule another microtask then it should run immediately after
+  // the ready promise resolution microtask.
+  await Promise.resolve();
+  assert_true(readyPromiseCallbackCalled,
+              'Ready promise callback called after setting startTime');
+}, 'Setting the start time resolves a pending ready promise when the timeline' +
+   'is inactive');
+
+promise_test(async t => {
+  const animation = createScrollLinkedAnimation(t);
+
+  // Wait for new animation frame  which allows the timeline to compute new
+  // current time.
+  await waitForNextFrame();
+
+  // Put the animation in the play-pending state
+  animation.play();
+
+  // Sanity check
+  assert_true(animation.pending, 'Animation is pending');
+  assert_equals(animation.playState, 'running', 'Animation is play-pending');
+  assert_times_equal(animation.startTime, 0, 'Start time is zero');
+
+  // Setting start time should cancel the pending task.
+  animation.startTime = null;
+  assert_false(animation.pending, 'Animation is no longer pending');
+  assert_equals(animation.playState, 'paused', 'Animation is paused');
+}, 'Setting an unresolved start time on a play-pending animation makes it'
+   + ' paused');
+
+promise_test(async t => {
+  const animation = createScrollLinkedAnimation(t);
+  const scroller = animation.timeline.scrollSource;
+  // Make the scroll timeline inactive.
+  scroller.style.overflow = 'visible';
+  // Wait for new animation frame  which allows the timeline to compute new
+  // current time.
+  await waitForNextFrame();
+  assert_equals(animation.timeline.currentTime, null,
+                'Sanity check the timeline is inactive');
+
+  // Put the animation in the play-pending state
+  animation.play();
+
+  // Sanity check
+  assert_true(animation.pending, 'Animation is pending');
+  assert_equals(animation.playState, 'running', 'Animation is play-pending');
+  assert_times_equal(animation.startTime, 0, 'Start time is zero');
+
+  // Setting start time should cancel the pending task.
+  animation.startTime = null;
+  assert_false(animation.pending, 'Animation is no longer pending');
+  assert_equals(animation.playState, 'idle', 'Animation is idle');
+}, 'Setting an unresolved start time on a play-pending animation makes it'
+   + ' idle when the timeline is inactive');
+
+promise_test(async t => {
+  const animation = createScrollLinkedAnimation(t);
+  // Wait for new animation frame  which allows the timeline to compute new
+  // current time.
+  await waitForNextFrame();
+
+  // Set start time such that the current time is past the end time
+  animation.startTime = -1100;
+  assert_times_equal(animation.startTime, -1100,
+                     'The start time is set to the requested value');
+  assert_equals(animation.playState, 'finished',
+                'Seeked to finished state using the startTime');
+
+  // If the 'did seek' flag is true, the current time should be greater than
+  // the effect end.
+  assert_greater_than(animation.currentTime,
+                      animation.effect.getComputedTiming().endTime,
+                      'Setting the start time updated the finished state with'
+                      + ' the \'did seek\' flag set to true');
+
+  // Furthermore, that time should persist if we have correctly updated
+  // the hold time
+  const finishedCurrentTime = animation.currentTime;
+  await waitForNextFrame();
+  assert_equals(animation.currentTime, finishedCurrentTime,
+                'Current time does not change after seeking past the effect'
+                + ' end time by setting the current time');
+}, 'Setting the start time updates the finished state');
+
+promise_test(async t => {
+  const animation = createScrollLinkedAnimation(t);
+  // Wait for new animation frame  which allows the timeline to compute new
+  // current time.
+  await waitForNextFrame();
+  animation.play();
+
+  await animation.ready;
+  assert_equals(animation.playState, 'running');
+
+  // Setting the start time updates the finished state. The hold time is not
+  // constrained by the effect end time.
+  animation.startTime = -1100;
+  assert_equals(animation.playState, 'finished');
+
+  assert_times_equal(animation.currentTime, 1100);
+}, 'Setting the start time on a running animation updates the play state');
+
+promise_test(async t => {
+  const animation = createScrollLinkedAnimation(t);
+  // Wait for new animation frame  which allows the timeline to compute new
+  // current time.
+  await waitForNextFrame();
+  animation.play();
+  await animation.ready;
+
+  // Setting the start time updates the finished state. The hold time is not
+  // constrained by the normal range of the animation time.
+  animation.currentTime = 1000;
+  assert_equals(animation.playState, 'finished', 'Animation is finished');
+  animation.playbackRate = -1;
+  assert_equals(animation.playState, 'running', 'Animation is running');
+  animation.startTime = -2000;
+  assert_equals(animation.playState, 'finished', 'Animation is finished');
+  assert_times_equal(animation.currentTime, -2000);
+}, 'Setting the start time on a reverse running animation updates the play '
+   + 'state');
+</script>
+</body>