Bug 1436659 - Add tests for Animation.updatePlaybackRate; r=hiro
authorBrian Birtles <birtles@gmail.com>
Tue, 13 Feb 2018 15:04:18 +0900
changeset 403902 7465cb110ae5ec2e2ca73182caf5293f0efc8fd5
parent 403901 a1a5840a6bb53e305ba02bcbeb215659342d0edb
child 403903 5ec4adb145cfadbd26a906d04d0aa3d984280578
push id99885
push userapavel@mozilla.com
push dateThu, 15 Feb 2018 10:38:09 +0000
treeherdermozilla-inbound@99495614cba7 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewershiro
bugs1436659
milestone60.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 1436659 - Add tests for Animation.updatePlaybackRate; r=hiro These tests test the behavior introduced in the following changesets to the Web Animations specification: 1. https://github.com/w3c/csswg-drafts/commit/5af5e276badf4df0271bcfa0b8e7837fff24133a 2. https://github.com/w3c/csswg-drafts/commit/673f6fc1269829743c707c53dcb04092f958de35 which can be viewed as a merged diff at: https://gist.github.com/birtles/d147eb2e0e2d4d37fadf217abd709411 See the following spec issues: 1. https://github.com/w3c/csswg-drafts/issues/2059 2. https://github.com/w3c/csswg-drafts/issues/2266 MozReview-Commit-ID: 3XJHXIlgSwF
testing/web-platform/meta/MANIFEST.json
testing/web-platform/meta/web-animations/interfaces/Animation/idlharness.html.ini
testing/web-platform/meta/web-animations/timing-model/animations/finishing-an-animation.html.ini
testing/web-platform/meta/web-animations/timing-model/animations/pausing-an-animation.html.ini
testing/web-platform/meta/web-animations/timing-model/animations/playing-an-animation.html.ini
testing/web-platform/meta/web-animations/timing-model/animations/reversing-an-animation.html.ini
testing/web-platform/meta/web-animations/timing-model/animations/seamlessly-updating-the-playback-rate-of-an-animation.html.ini
testing/web-platform/meta/web-animations/timing-model/animations/setting-the-current-time-of-an-animation.html.ini
testing/web-platform/meta/web-animations/timing-model/animations/setting-the-playback-rate-of-an-animation.html.ini
testing/web-platform/meta/web-animations/timing-model/animations/setting-the-start-time-of-an-animation.html.ini
testing/web-platform/meta/web-animations/timing-model/animations/setting-the-target-effect-of-an-animation.html.ini
testing/web-platform/tests/web-animations/interfaces/Animation/idlharness.html
testing/web-platform/tests/web-animations/timing-model/animations/finishing-an-animation.html
testing/web-platform/tests/web-animations/timing-model/animations/pausing-an-animation.html
testing/web-platform/tests/web-animations/timing-model/animations/playing-an-animation.html
testing/web-platform/tests/web-animations/timing-model/animations/reversing-an-animation.html
testing/web-platform/tests/web-animations/timing-model/animations/seamlessly-updating-the-playback-rate-of-an-animation.html
testing/web-platform/tests/web-animations/timing-model/animations/setting-the-current-time-of-an-animation.html
testing/web-platform/tests/web-animations/timing-model/animations/setting-the-playback-rate-of-an-animation.html
testing/web-platform/tests/web-animations/timing-model/animations/setting-the-start-time-of-an-animation.html
testing/web-platform/tests/web-animations/timing-model/animations/setting-the-target-effect-of-an-animation.html
--- a/testing/web-platform/meta/MANIFEST.json
+++ b/testing/web-platform/meta/MANIFEST.json
@@ -358925,16 +358925,28 @@
     ]
    ],
    "web-animations/timing-model/animations/reversing-an-animation.html": [
     [
      "/web-animations/timing-model/animations/reversing-an-animation.html",
      {}
     ]
    ],
+   "web-animations/timing-model/animations/seamlessly-updating-the-playback-rate-of-an-animation.html": [
+    [
+     "/web-animations/timing-model/animations/seamlessly-updating-the-playback-rate-of-an-animation.html",
+     {}
+    ]
+   ],
+   "web-animations/timing-model/animations/setting-the-current-time-of-an-animation.html": [
+    [
+     "/web-animations/timing-model/animations/setting-the-current-time-of-an-animation.html",
+     {}
+    ]
+   ],
    "web-animations/timing-model/animations/setting-the-playback-rate-of-an-animation.html": [
     [
      "/web-animations/timing-model/animations/setting-the-playback-rate-of-an-animation.html",
      {}
     ]
    ],
    "web-animations/timing-model/animations/setting-the-start-time-of-an-animation.html": [
     [
@@ -591533,17 +591545,17 @@
    "ffcba3379db7094455a7798e4d5972d8e52caec5",
    "testharness"
   ],
   "web-animations/interfaces/Animation/id.html": [
    "4e3dd92351d76c5c7d09ddd1ca025520f4c8875d",
    "testharness"
   ],
   "web-animations/interfaces/Animation/idlharness.html": [
-   "989e773dbf3d7d57f26b41108bc3d7f0b3ea3168",
+   "d61aa2d95ea31809a275183408e822c8c1eec87d",
    "testharness"
   ],
   "web-animations/interfaces/Animation/oncancel.html": [
    "82abc08a0b416f5198239464fb4fc01d2edd6e1c",
    "testharness"
   ],
   "web-animations/interfaces/Animation/onfinish.html": [
    "db82fabeaf2b646647f134634fef30f05e5ec7f8",
@@ -591713,45 +591725,53 @@
    "602fe7e6880e0b18329262699872c696f451d744",
    "testharness"
   ],
   "web-animations/timing-model/animations/canceling-an-animation.html": [
    "e03baa30d438529a0ebe39f0f623563aa9850d74",
    "testharness"
   ],
   "web-animations/timing-model/animations/finishing-an-animation.html": [
-   "6bdfa6409b2409fea7e54c677d7008df153aabfb",
+   "4c1cf823a81e72541abcafaa08950cf87424ae55",
    "testharness"
   ],
   "web-animations/timing-model/animations/pausing-an-animation.html": [
-   "982440e635a0c5a437febac0e53a6eb7442db495",
+   "a4cb7b89c778ad5c294eeb55e94461e19ca8eb4b",
    "testharness"
   ],
   "web-animations/timing-model/animations/play-states.html": [
    "0ab2fa3a464001272d1af541ea769fa967490c3b",
    "testharness"
   ],
   "web-animations/timing-model/animations/playing-an-animation.html": [
-   "8a5f14ba7712e33bc2450d9ab9198f4865ef9244",
+   "10580a1e72892208a14c6fe55091e998edf0171c",
    "testharness"
   ],
   "web-animations/timing-model/animations/reversing-an-animation.html": [
-   "bba3a71bfcf0b2c9ab38af6a2d0bd98954becf95",
+   "72b89e78ca7dac261af8de370389d89c810b3718",
+   "testharness"
+  ],
+  "web-animations/timing-model/animations/seamlessly-updating-the-playback-rate-of-an-animation.html": [
+   "a7e28aa0b40a39b00da257e347cb6ecf8d1d2882",
+   "testharness"
+  ],
+  "web-animations/timing-model/animations/setting-the-current-time-of-an-animation.html": [
+   "a7da92b9624750eccb9dce1d32e522fdbb65176f",
    "testharness"
   ],
   "web-animations/timing-model/animations/setting-the-playback-rate-of-an-animation.html": [
-   "1e0a0e80e70ca0d71a118f6b958a3f24cfb50ca0",
+   "b2698d9a829a1eadb3ef3b6d8e0050e7a6315305",
    "testharness"
   ],
   "web-animations/timing-model/animations/setting-the-start-time-of-an-animation.html": [
-   "80bf466ba3d657200c2f2b6e783c56ba04b87390",
+   "cf6040eb52964f12b06a9e3cdf14948ce8141270",
    "testharness"
   ],
   "web-animations/timing-model/animations/setting-the-target-effect-of-an-animation.html": [
-   "13df24037116368b688d7d20c1dffdac027021b3",
+   "5575a251b9c265d98471e758b3cf9b218e381cba",
    "testharness"
   ],
   "web-animations/timing-model/animations/setting-the-timeline-of-an-animation.html": [
    "e4e134b566327c9d7316aee4f3e7fe4eeb2116ba",
    "testharness"
   ],
   "web-animations/timing-model/animations/the-current-time-of-an-animation.html": [
    "90ba3d81ee9e32b1f13845301c4ad1c8ad47f2f7",
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/meta/web-animations/interfaces/Animation/idlharness.html.ini
@@ -0,0 +1,10 @@
+[idlharness.html]
+  [Animation interface: operation updatePlaybackRate(double)]
+    expected: FAIL
+
+  [Animation interface: new Animation() must inherit property "updatePlaybackRate(double)" with the proper type]
+    expected: FAIL
+
+  [Animation interface: calling updatePlaybackRate(double) on new Animation() with too few arguments must throw TypeError]
+    expected: FAIL
+
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/meta/web-animations/timing-model/animations/finishing-an-animation.html.ini
@@ -0,0 +1,13 @@
+[finishing-an-animation.html]
+  [A pending playback rate should be applied immediately when an animation is finished]
+    expected: FAIL
+
+  [An exception should be thrown if the effective playback rate is zero]
+    expected: FAIL
+
+  [An exception should be thrown when finishing if the effective playback rate is positive and the target effect end is infinity]
+    expected: FAIL
+
+  [An exception is NOT thrown when finishing if the effective playback rate is negative and the target effect end is infinity]
+    expected: FAIL
+
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/meta/web-animations/timing-model/animations/pausing-an-animation.html.ini
@@ -0,0 +1,4 @@
+[pausing-an-animation.html]
+  [A pause-pending animation maintains the current time when applying a pending playback rate]
+    expected: FAIL
+
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/meta/web-animations/timing-model/animations/playing-an-animation.html.ini
@@ -0,0 +1,4 @@
+[playing-an-animation.html]
+  [A pending playback rate is used when determining auto-rewind behavior]
+    expected: FAIL
+
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/meta/web-animations/timing-model/animations/reversing-an-animation.html.ini
@@ -0,0 +1,10 @@
+[reversing-an-animation.html]
+  [Reversing an animation inverts the playback rate]
+    expected: FAIL
+
+  [Reversing should use the negative pending playback rate]
+    expected: FAIL
+
+  [When reversing fails, it should restore any previous pending playback rate]
+    expected: FAIL
+
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/meta/web-animations/timing-model/animations/seamlessly-updating-the-playback-rate-of-an-animation.html.ini
@@ -0,0 +1,28 @@
+[seamlessly-updating-the-playback-rate-of-an-animation.html]
+  [Updating the playback rate maintains the current time]
+    expected: FAIL
+
+  [Updating the playback rate while running makes the animation pending]
+    expected: FAIL
+
+  [Updating the playback rate on a play-pending animation maintains the current time]
+    expected: FAIL
+
+  [Updating the playback rate on a pause-pending animation maintains the current time]
+    expected: FAIL
+
+  [If a pending playback rate is set multiple times, the latest wins]
+    expected: FAIL
+
+  [In the idle state, the playback rate is applied immediately]
+    expected: FAIL
+
+  [In the paused state, the playback rate is applied immediately]
+    expected: FAIL
+
+  [Updating the playback rate on a finished animation maintains the current time]
+    expected: FAIL
+
+  [Updating the playback rate to zero on a finished animation maintains the current time]
+    expected: FAIL
+
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/meta/web-animations/timing-model/animations/setting-the-current-time-of-an-animation.html.ini
@@ -0,0 +1,4 @@
+[setting-the-current-time-of-an-animation.html]
+  [Setting the current time of a pausing animation applies a pending playback rate]
+    expected: FAIL
+
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/meta/web-animations/timing-model/animations/setting-the-playback-rate-of-an-animation.html.ini
@@ -0,0 +1,4 @@
+[setting-the-playback-rate-of-an-animation.html]
+  [Setting the playback rate should clear any pending playback rate]
+    expected: FAIL
+
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/meta/web-animations/timing-model/animations/setting-the-start-time-of-an-animation.html.ini
@@ -0,0 +1,7 @@
+[setting-the-start-time-of-an-animation.html]
+  [Setting the start time of a play-pending animation applies a pending playback rate]
+    expected: FAIL
+
+  [Setting the start time of a playing animation applies a pending playback rate]
+    expected: FAIL
+
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/meta/web-animations/timing-model/animations/setting-the-target-effect-of-an-animation.html.ini
@@ -0,0 +1,4 @@
+[setting-the-target-effect-of-an-animation.html]
+  [Setting the target effect to null causes a pending playback rate to be applied]
+    expected: FAIL
+
--- a/testing/web-platform/tests/web-animations/interfaces/Animation/idlharness.html
+++ b/testing/web-platform/tests/web-animations/interfaces/Animation/idlharness.html
@@ -24,16 +24,17 @@ interface Animation : EventTarget {
     readonly attribute Promise<Animation>       ready;
     readonly attribute Promise<Animation>       finished;
              attribute EventHandler             onfinish;
              attribute EventHandler             oncancel;
     void cancel ();
     void finish ();
     void play ();
     void pause ();
+    void updatePlaybackRate (double playbackRate);
     void reverse ();
 };
 </script>
 <script>
 'use strict';
 
 const idlArray = new IdlArray();
 
--- a/testing/web-platform/tests/web-animations/timing-model/animations/finishing-an-animation.html
+++ b/testing/web-platform/tests/web-animations/timing-model/animations/finishing-an-animation.html
@@ -223,10 +223,66 @@ promise_test(async t => {
   const promiseResult = await animation.finished;
 
   assert_equals(promiseResult, animation);
   assert_equals(animation.ready, promise);
   assert_true(readyResolved);
 }, 'A pending ready promise is resolved and not replaced when the animation'
    + ' is finished');
 
+promise_test(async t => {
+  const animation = createDiv(t).animate(null, 100 * MS_PER_SEC);
+  await animation.ready;
+
+  animation.updatePlaybackRate(2);
+  assert_true(animation.pending);
+
+  animation.finish();
+  assert_false(animation.pending);
+  assert_equals(animation.playbackRate, 2);
+  assert_time_equals_literal(animation.currentTime, 100 * MS_PER_SEC);
+}, 'A pending playback rate should be applied immediately when an animation'
+   + ' is finished');
+
+promise_test(async t => {
+  const animation = createDiv(t).animate(null, 100 * MS_PER_SEC);
+  await animation.ready;
+
+  animation.updatePlaybackRate(0);
+
+  assert_throws('InvalidStateError', () => {
+    animation.finish();
+  });
+}, 'An exception should be thrown if the effective playback rate is zero');
+
+promise_test(async t => {
+  const animation = createDiv(t).animate(null, {
+    duration: 100 * MS_PER_SEC,
+    iterations: Infinity
+  });
+  animation.currentTime = 50 * MS_PER_SEC;
+  animation.playbackRate = -1;
+  await animation.ready;
+
+  animation.updatePlaybackRate(1);
+
+  assert_throws('InvalidStateError', () => {
+    animation.finish();
+  });
+}, 'An exception should be thrown when finishing if the effective playback rate'
+   + ' is positive and the target effect end is infinity');
+
+promise_test(async t => {
+  const animation = createDiv(t).animate(null, {
+    duration: 100 * MS_PER_SEC,
+    iterations: Infinity
+  });
+  await animation.ready;
+
+  animation.updatePlaybackRate(-1);
+
+  animation.finish();
+  // Should not have thrown
+}, 'An exception is NOT thrown when finishing if the effective playback rate'
+   + ' is negative and the target effect end is infinity');
+
 </script>
 </body>
--- a/testing/web-platform/tests/web-animations/timing-model/animations/pausing-an-animation.html
+++ b/testing/web-platform/tests/web-animations/timing-model/animations/pausing-an-animation.html
@@ -19,10 +19,28 @@ promise_test(async t => {
   const promiseResult = await promise;
 
   assert_equals(promiseResult, animation);
   assert_equals(animation.ready, promise);
   assert_false(animation.pending, 'No longer pause-pending');
 }, 'A pending ready promise should be resolved and not replaced when the'
    + ' animation is paused');
 
+promise_test(async t => {
+  const animation = createDiv(t).animate(null, 100 * MS_PER_SEC);
+  // Let animation start roughly half-way through
+  animation.currentTime = 50 * MS_PER_SEC;
+  await animation.ready;
+
+  // Go pause-pending and also set a pending playback rate
+  animation.pause();
+  animation.updatePlaybackRate(0.5);
+
+  await animation.ready;
+  // If the current time was updated using the new playback rate it will jump
+  // back to 25s but if we correctly used the old playback rate the current time
+  // will be >50s.
+  assert_greater_than(animation.currentTime, 50 * MS_PER_SEC);
+}, 'A pause-pending animation maintains the current time when applying a'
+   + ' pending playback rate');
+
 </script>
 </body>
--- a/testing/web-platform/tests/web-animations/timing-model/animations/playing-an-animation.html
+++ b/testing/web-platform/tests/web-animations/timing-model/animations/playing-an-animation.html
@@ -49,10 +49,27 @@ promise_test(async t => {
   const animation = createDiv(t).animate(null, 100 * MS_PER_SEC);
   const promise = animation.ready;
   const promiseResult = await promise;
   assert_equals(promiseResult, animation);
   assert_equals(animation.ready, promise);
 }, 'A pending ready promise should be resolved and not replaced when the'
    + ' animation enters the running state');
 
+promise_test(async t => {
+  // Seek animation beyond target end
+  const animation = createDiv(t).animate(null, 100 * MS_PER_SEC);
+  animation.currentTime = -100 * MS_PER_SEC;
+  await animation.ready;
+
+  // Set pending playback rate to the opposite direction
+  animation.updatePlaybackRate(-1);
+  assert_true(animation.pending);
+  assert_equals(animation.playbackRate, 1);
+
+  // When we play, we should seek to the target end, NOT to zero (which
+  // is where we would seek to if we used the playbackRate of 1.
+  animation.play();
+  assert_time_equals_literal(animation.currentTime, 100 * MS_PER_SEC);
+}, 'A pending playback rate is used when determining auto-rewind behavior');
+
 </script>
 </body>
--- a/testing/web-platform/tests/web-animations/timing-model/animations/reversing-an-animation.html
+++ b/testing/web-platform/tests/web-animations/timing-model/animations/reversing-an-animation.html
@@ -21,18 +21,22 @@ promise_test(async t => {
   // reverse(), it will throw (per spec).
   await waitForAnimationFrames(1);
 
   assert_greater_than_equal(animation.currentTime, 0,
     'currentTime expected to be greater than 0, one frame after starting');
   animation.currentTime = 50 * MS_PER_SEC;
   const previousPlaybackRate = animation.playbackRate;
   animation.reverse();
+  assert_equals(animation.playbackRate, previousPlaybackRate,
+                'Playback rate should not have changed');
+  await animation.ready;
+
   assert_equals(animation.playbackRate, -previousPlaybackRate,
-    'playbackRate should be inverted');
+                'Playback rate should be inverted');
 }, 'Reversing an animation inverts the playback rate');
 
 promise_test(async t => {
   const div = createDiv(t);
   const animation = div.animate({}, { duration: 100 * MS_PER_SEC,
                                       iterations: Infinity });
   animation.currentTime = 50 * MS_PER_SEC;
   animation.pause();
@@ -144,23 +148,26 @@ test(t => {
   assert_throws('InvalidStateError',
     () => { animation.reverse(); },
     'reverse() should throw InvalidStateError ' +
     'if the playbackRate > 0 and the currentTime < 0 ' +
     'and the target effect is positive infinity');
 }, 'Reversing an animation when playbackRate > 0 and currentTime < 0 ' +
    'and the target effect end is positive infinity should throw an exception');
 
-test(t => {
+promise_test(async t => {
   const animation = createDiv(t).animate({}, { duration: 100 * MS_PER_SEC,
                                                iterations: Infinity });
   animation.currentTime = -200 * MS_PER_SEC;
 
   try { animation.reverse(); } catch(e) { }
 
+  assert_equals(animation.playbackRate, 1, 'playbackRate is unchanged');
+
+  await animation.ready;
   assert_equals(animation.playbackRate, 1, 'playbackRate remains unchanged');
 }, 'When reversing throws an exception, the playback rate remains unchanged');
 
 test(t => {
   const div = createDiv(t);
   const animation = div.animate({}, { duration: 100 * MS_PER_SEC,
                                       iterations: Infinity });
   animation.currentTime = -200 * MS_PER_SEC;
@@ -186,34 +193,62 @@ test(t => {
   assert_equals(animation.currentTime, 0,
     'reverse() should start playing from the start of animation time ' +
     'if the playbackRate < 0 and the currentTime < 0 ' +
     'and the target effect is positive infinity');
 }, 'Reversing an animation when playbackRate < 0 and currentTime < 0 ' +
    'and the target effect end is positive infinity should make it play ' +
    'from the start');
 
-test(t => {
+promise_test(async t => {
   const div = createDiv(t);
   const animation = div.animate({}, 100 * MS_PER_SEC);
   animation.playbackRate = 0;
   animation.currentTime = 50 * MS_PER_SEC;
   animation.reverse();
 
+  await animation.ready;
   assert_equals(animation.playbackRate, 0,
     'reverse() should preserve playbackRate if the playbackRate == 0');
   assert_equals(animation.currentTime, 50 * MS_PER_SEC,
     'reverse() should not affect the currentTime if the playbackRate == 0');
-  t.done();
 }, 'Reversing when when playbackRate == 0 should preserve the current ' +
    'time and playback rate');
 
 test(t => {
   const div = createDiv(t);
   const animation =
     new Animation(new KeyframeEffect(div, null, 100 * MS_PER_SEC), null);
 
   assert_throws('InvalidStateError', () => { animation.reverse(); });
 }, 'Reversing an animation without an active timeline throws an ' +
    'InvalidStateError');
 
+promise_test(async t => {
+  const animation = createDiv(t).animate(null, 100 * MS_PER_SEC);
+  await animation.ready;
+
+  animation.updatePlaybackRate(2);
+  animation.reverse();
+
+  await animation.ready;
+  assert_equals(animation.playbackRate, -2);
+}, 'Reversing should use the negative pending playback rate');
+
+promise_test(async t => {
+  const animation = createDiv(t).animate(null, {
+    duration: 100 * MS_PER_SEC,
+    iterations: Infinity,
+  });
+  animation.currentTime = -200 * MS_PER_SEC;
+  await animation.ready;
+
+  animation.updatePlaybackRate(2);
+  assert_throws('InvalidStateError', () => { animation.reverse(); });
+  assert_equals(animation.playbackRate, 1);
+
+  await animation.ready;
+  assert_equals(animation.playbackRate, 2);
+}, 'When reversing fails, it should restore any previous pending playback'
+   + ' rate');
+
 </script>
 </body>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/web-animations/timing-model/animations/seamlessly-updating-the-playback-rate-of-an-animation.html
@@ -0,0 +1,142 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>Seamlessly updating the playback rate of an animation</title>
+<link rel="help"
+  href="https://drafts.csswg.org/web-animations-1/#seamlessly-updating-the-playback-rate-of-an-animation">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../testcommon.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+promise_test(async t => {
+  const animation = createDiv(t).animate(null, 100 * MS_PER_SEC);
+  await animation.ready;
+
+  animation.currentTime = 50 * MS_PER_SEC;
+
+  animation.updatePlaybackRate(0.5);
+  await animation.ready;
+  // Since the animation is in motion (and we want to test it while it is in
+  // motion!) we can't assert that the current time == 50s but we can check
+  // that the current time is NOT re-calculated by simply substituting in the
+  // new playback rate (i.e. without adjusting the start time). If that were
+  // the case the currentTime would jump to 25s. So we just test the currentTime
+  // hasn't gone backwards.
+  assert_greater_than_equal(animation.currentTime, 50 * MS_PER_SEC,
+    'Reducing the playback rate should not change the current time ' +
+    'of a playing animation');
+
+  animation.updatePlaybackRate(2);
+  await animation.ready;
+  // Likewise, we test here that the current time does not jump to 100s as it
+  // would if we naively applied a playbackRate of 2 without adjusting the
+  // startTime.
+  assert_less_than(animation.currentTime, 100 * MS_PER_SEC,
+    'Increasing the playback rate should not change the current time ' +
+    'of a playing animation');
+}, 'Updating the playback rate maintains the current time');
+
+promise_test(async t => {
+  const animation = createDiv(t).animate(null, 100 * MS_PER_SEC);
+  await animation.ready;
+
+  assert_false(animation.pending);
+  animation.updatePlaybackRate(2);
+  assert_true(animation.pending);
+}, 'Updating the playback rate while running makes the animation pending');
+
+promise_test(async t => {
+  const animation = createDiv(t).animate(null, 100 * MS_PER_SEC);
+  animation.currentTime = 50 * MS_PER_SEC;
+  assert_true(animation.pending);
+
+  animation.updatePlaybackRate(0.5);
+
+  // Check that the hold time is updated as expected
+  assert_time_equals_literal(animation.currentTime, 50 * MS_PER_SEC);
+
+  await animation.ready;
+
+  // As above, check that the currentTime is not calculated by simply
+  // substituting in the updated playbackRate without updating the startTime.
+  assert_greater_than_equal(animation.currentTime, 50 * MS_PER_SEC,
+    'Reducing the playback rate should not change the current time ' +
+    'of a play-pending animation');
+}, 'Updating the playback rate on a play-pending animation maintains'
+   + ' the current time');
+
+promise_test(async t => {
+  const animation = createDiv(t).animate(null, 100 * MS_PER_SEC);
+  animation.currentTime = 50 * MS_PER_SEC;
+  await animation.ready;
+
+  animation.pause();
+  animation.updatePlaybackRate(0.5);
+
+  assert_greater_than_equal(animation.currentTime, 50 * MS_PER_SEC);
+}, 'Updating the playback rate on a pause-pending animation maintains'
+   + ' the current time');
+
+promise_test(async t => {
+  const animation = createDiv(t).animate(null, 100 * MS_PER_SEC);
+
+  animation.updatePlaybackRate(2);
+  animation.updatePlaybackRate(3);
+  animation.updatePlaybackRate(4);
+
+  assert_equals(animation.playbackRate, 1);
+  await animation.ready;
+
+  assert_equals(animation.playbackRate, 4);
+}, 'If a pending playback rate is set multiple times, the latest wins');
+
+test(t => {
+  const animation = createDiv(t).animate(null, 100 * MS_PER_SEC);
+  animation.cancel();
+
+  animation.updatePlaybackRate(2);
+  assert_equals(animation.playbackRate, 2);
+  assert_false(animation.pending);
+}, 'In the idle state, the playback rate is applied immediately');
+
+promise_test(async t => {
+  const animation = createDiv(t).animate(null, 100 * MS_PER_SEC);
+  animation.pause();
+  await animation.ready;
+
+  animation.updatePlaybackRate(2);
+  assert_equals(animation.playbackRate, 2);
+  assert_false(animation.pending);
+}, 'In the paused state, the playback rate is applied immediately');
+
+promise_test(async t => {
+  const animation = createDiv(t).animate(null, 100 * MS_PER_SEC);
+  animation.finish();
+  assert_time_equals_literal(animation.currentTime, 100 * MS_PER_SEC);
+  assert_false(animation.pending);
+
+  animation.updatePlaybackRate(2);
+  assert_equals(animation.playbackRate, 2);
+  assert_time_equals_literal(animation.currentTime, 100 * MS_PER_SEC);
+  assert_false(animation.pending);
+}, 'Updating the playback rate on a finished animation maintains'
+   + ' the current time');
+
+promise_test(async t => {
+  const animation = createDiv(t).animate(null, 100 * MS_PER_SEC);
+  animation.finish();
+  assert_time_equals_literal(animation.currentTime, 100 * MS_PER_SEC);
+  assert_false(animation.pending);
+
+  animation.updatePlaybackRate(0);
+  assert_equals(animation.playbackRate, 0);
+  assert_time_equals_literal(animation.currentTime, 100 * MS_PER_SEC);
+  assert_false(animation.pending);
+}, 'Updating the playback rate to zero on a finished animation maintains'
+   + ' the current time');
+
+</script>
+</body>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/web-animations/timing-model/animations/setting-the-current-time-of-an-animation.html
@@ -0,0 +1,38 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>Setting the current time of an animation</title>
+<link rel="help"
+  href="https://drafts.csswg.org/web-animations-1/#setting-the-current-time-of-an-animation">
+<script src='/resources/testharness.js'></script>
+<script src='/resources/testharnessreport.js'></script>
+<script src='../../testcommon.js'></script>
+<body>
+<div id='log'></div>
+<script>
+'use strict';
+
+promise_test(async t => {
+  const anim = createDiv(t).animate(null, 100 * MS_PER_SEC);
+  await anim.ready;
+  anim.pause();
+
+  // We should be pause-pending now
+  assert_true(anim.pending);
+  assert_equals(anim.playState, 'paused');
+
+  // Apply a pending playback rate
+  anim.updatePlaybackRate(2);
+  assert_equals(anim.playbackRate, 1);
+
+  // Setting the current time should apply the pending playback rate
+  anim.currentTime = 50 * MS_PER_SEC;
+  assert_equals(anim.playbackRate, 2);
+  assert_false(anim.pending);
+
+  // Sanity check that the current time is preserved
+  assert_time_equals_literal(anim.currentTime, 50 * MS_PER_SEC);
+}, 'Setting the current time of a pausing animation applies a pending playback'
+   + ' rate');
+
+</script>
+</body>
--- a/testing/web-platform/tests/web-animations/timing-model/animations/setting-the-playback-rate-of-an-animation.html
+++ b/testing/web-platform/tests/web-animations/timing-model/animations/setting-the-playback-rate-of-an-animation.html
@@ -43,10 +43,19 @@ promise_test(async t => {
   const animation = createDiv(t).animate(null, 100 * MS_PER_SEC);
   animation.currentTime = 50 * MS_PER_SEC;
   await animation.ready;
   animation.playbackRate = 2;
   assert_greater_than_equal(animation.currentTime, 50 * MS_PER_SEC);
   assert_less_than(animation.currentTime, 100 * MS_PER_SEC);
 }, 'Setting the playback rate while playing preserves the current time');
 
+promise_test(async t => {
+  const animation = createDiv(t).animate(null, 100 * MS_PER_SEC);
+  animation.currentTime = 50 * MS_PER_SEC;
+  animation.updatePlaybackRate(2);
+  animation.playbackRate = 1;
+  await animation.ready;
+  assert_equals(animation.playbackRate, 1);
+}, 'Setting the playback rate should clear any pending playback rate');
+
 </script>
 </body>
--- a/testing/web-platform/tests/web-animations/timing-model/animations/setting-the-start-time-of-an-animation.html
+++ b/testing/web-platform/tests/web-animations/timing-model/animations/setting-the-start-time-of-an-animation.html
@@ -189,10 +189,59 @@ promise_test(async t => {
   // the hold time
   const finishedCurrentTime = animation.currentTime;
   await waitForAnimationFrames(1);
   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 anim = createDiv(t).animate(null, 100 * MS_PER_SEC);
+
+  // We should be play-pending now
+  assert_true(anim.pending);
+  assert_equals(anim.playState, 'running');
+
+  // Apply a pending playback rate
+  anim.updatePlaybackRate(2);
+  assert_equals(anim.playbackRate, 1);
+  assert_true(anim.pending);
+
+  // Setting the start time should apply the pending playback rate
+  anim.startTime = anim.timeline.currentTime - 25 * MS_PER_SEC;
+  assert_equals(anim.playbackRate, 2);
+  assert_false(anim.pending);
+
+  // Sanity check that the start time is preserved and current time is
+  // calculated using the new playback rate
+  assert_times_equal(anim.startTime,
+                     anim.timeline.currentTime - 25 * MS_PER_SEC);
+  assert_time_equals_literal(anim.currentTime, 50 * MS_PER_SEC);
+}, 'Setting the start time of a play-pending animation applies a pending playback rate');
+
+promise_test(async t => {
+  const anim = createDiv(t).animate(null, 100 * MS_PER_SEC);
+  await anim.ready;
+
+  // We should be running now
+  assert_false(anim.pending);
+  assert_equals(anim.playState, 'running');
+
+  // Apply a pending playback rate
+  anim.updatePlaybackRate(2);
+  assert_equals(anim.playbackRate, 1);
+  assert_true(anim.pending);
+
+  // Setting the start time should apply the pending playback rate
+  anim.startTime = anim.timeline.currentTime - 25 * MS_PER_SEC;
+  assert_equals(anim.playbackRate, 2);
+  assert_false(anim.pending);
+
+  // Sanity check that the start time is preserved and current time is
+  // calculated using the new playback rate
+  assert_times_equal(anim.startTime,
+                     anim.timeline.currentTime - 25 * MS_PER_SEC);
+  assert_time_equals_literal(anim.currentTime, 50 * MS_PER_SEC);
+}, 'Setting the start time of a playing animation applies a pending playback rate');
+
 </script>
 </body>
--- a/testing/web-platform/tests/web-animations/timing-model/animations/setting-the-target-effect-of-an-animation.html
+++ b/testing/web-platform/tests/web-animations/timing-model/animations/setting-the-target-effect-of-an-animation.html
@@ -88,10 +88,20 @@ test(t => {
   animB.effect = effect;
   assert_equals(effect.getComputedTiming().progress, 0.2,
                 'After setting the effect on a different animation, ' +
                 'it uses the new animation\'s timing');
 }, 'After setting the target effect of animation to the target effect of an ' +
    'existing animation, the target effect\'s timing is updated to reflect ' +
    'the current time of the new animation.');
 
+test(t => {
+  const anim = createDiv(t).animate(null, 100 * MS_PER_SEC);
+  anim.updatePlaybackRate(2);
+  assert_equals(anim.playbackRate, 1);
+
+  anim.effect = null;
+  assert_equals(anim.playbackRate, 2);
+}, 'Setting the target effect to null causes a pending playback rate to be'
+   + ' applied');
+
 </script>
 </body>