Bug 1361260 - Incorporate playbackRate when calculating the start time of a pending compositor animation; r?hiro draft
authorBrian Birtles <birtles@gmail.com>
Tue, 02 May 2017 16:43:21 +0900
changeset 571259 3fb74baac33e09c242d422668fc12e33046b4b68
parent 571193 ad9c46175dccade97d6f5972ed75b0ddc8811c4c
child 626711 b058a7a5e508f562fae8471a9147e6c4443a28bc
push id56731
push userbbirtles@mozilla.com
push dateTue, 02 May 2017 09:36:05 +0000
reviewershiro
bugs1361260
milestone55.0a1
Bug 1361260 - Incorporate playbackRate when calculating the start time of a pending compositor animation; r?hiro MozReview-Commit-ID: FBmT5ImBcYJ
dom/animation/test/mozilla/file_deferred_start.html
gfx/layers/Layers.cpp
--- a/dom/animation/test/mozilla/file_deferred_start.html
+++ b/dom/animation/test/mozilla/file_deferred_start.html
@@ -74,23 +74,26 @@ promise_test(function(t) {
 }, 'Animation.ready is resolved for an empty animation');
 
 // Test that compositor animations with delays get synced correctly
 //
 // NOTE: It is important that we DON'T use
 // SpecialPowers.DOMWindowUtils.advanceTimeAndRefresh here since that takes
 // us through a different code path.
 promise_test(function(t) {
+  assert_false(SpecialPowers.DOMWindowUtils.isTestControllingRefreshes,
+               'Test should run without the refresh driver being under'
+               + ' test control');
+
   // This test only applies to compositor animations
   if (!isOMTAEnabled()) {
     return;
   }
 
-  const div = addDiv(t);
-  div.classList.add('target');
+  const div = addDiv(t, { class: 'target' });
 
   // As with the above test, any stray paints can cause this test to produce
   // a false negative (that is, pass when it should fail). To avoid this we
   // first wait for the document to load, then wait until it is idle, then wait
   // for paints and only then do we commence the test. Even doing that, this
   // test can sometimes pass when it should not due to a stray paint. Most of
   // the time, however, it will correctly fail so hopefully even if we do
   // occasionally produce a false negative on one platform, another platform
@@ -98,34 +101,81 @@ promise_test(function(t) {
   return waitForDocLoad().then(() => waitForIdle())
   .then(() => waitForPaints())
   .then(() => {
     div.animate({ transform: [ 'translate(0px)', 'translate(100px)' ] },
                 { duration: 400 * MS_PER_SEC,
                   delay: -200 * MS_PER_SEC });
     return waitForPaints();
   }).then(() => {
-    var transformStr =
+    const transformStr =
       SpecialPowers.DOMWindowUtils.getOMTAStyle(div, 'transform');
-
-    var matrixComponents =
-      transformStr.startsWith('matrix(')
-      ? transformStr.substring('matrix('.length, transformStr.length-1)
-                    .split(',')
-                    .map(component => Number(component))
-      : [];
-    assert_equals(matrixComponents.length, 6,
-                  'Got a valid transform matrix on the compositor'
-                  + ' (got: "' + transformStr + '")');
+    const translateX = getTranslateXFromTransform(transformStr);
 
     // If the delay has been applied we should be about half-way through
     // the animation. However, if we applied it twice we will be at the
     // end of the animation already so check that we are roughly half way
     // through.
-    const translateX = matrixComponents[4];
     assert_between_inclusive(translateX, 40, 75,
         'Animation is about half-way through on the compositor');
   });
 }, 'Starting an animation with a delay starts from the correct point');
 
+// Test that compositor animations with a playback rate start at the
+// appropriate point.
+//
+// NOTE: As with the previous test, it is important that we DON'T use
+// SpecialPowers.DOMWindowUtils.advanceTimeAndRefresh here since that takes
+// us through a different code path.
+promise_test(function(t) {
+  assert_false(SpecialPowers.DOMWindowUtils.isTestControllingRefreshes,
+               'Test should run without the refresh driver being under'
+               + ' test control');
+
+  // This test only applies to compositor animations
+  if (!isOMTAEnabled()) {
+    return;
+  }
+
+  const div = addDiv(t, { class: 'target' });
+
+  // Wait for idle state (see notes in previous test).
+  return waitForDocLoad().then(() => waitForIdle())
+  .then(() => waitForPaints())
+  .then(() => {
+    const animation =
+      div.animate({ transform: [ 'translate(0px)', 'translate(100px)' ] },
+                  200 * MS_PER_SEC);
+    animation.currentTime = 100 * MS_PER_SEC;
+    animation.playbackRate = 0.1;
+    return waitForPaints();
+  }).then(() => {
+    const transformStr =
+      SpecialPowers.DOMWindowUtils.getOMTAStyle(div, 'transform');
+    const translateX = getTranslateXFromTransform(transformStr);
+
+    // We pass the playback rate to the compositor independently and we have
+    // tests to ensure that it is correctly applied there. However, if, when
+    // we resolve the start time of the pending animation, we fail to
+    // incorporate the playback rate, we will end up starting from the wrong
+    // point and the current time calculated on the compositor will be wrong.
+    assert_between_inclusive(translateX, 25, 75,
+        'Animation is about half-way through on the compositor');
+  });
+}, 'Starting an animation with a playbackRate starts from the correct point');
+
+function getTranslateXFromTransform(transformStr) {
+  const matrixComponents =
+    transformStr.startsWith('matrix(')
+    ? transformStr.substring('matrix('.length, transformStr.length-1)
+                  .split(',')
+                  .map(component => Number(component))
+    : [];
+  assert_equals(matrixComponents.length, 6,
+                'Got a valid transform matrix on the compositor'
+                + ' (got: "' + transformStr + '")');
+
+  return matrixComponents[4];
+}
+
 done();
 </script>
 </body>
--- a/gfx/layers/Layers.cpp
+++ b/gfx/layers/Layers.cpp
@@ -287,18 +287,23 @@ Layer::StartPendingAnimations(const Time
       [&aReadyTime](Layer *layer)
       {
         bool updated = false;
         for (size_t animIdx = 0, animEnd = layer->mAnimations.Length();
              animIdx < animEnd; animIdx++) {
           Animation& anim = layer->mAnimations[animIdx];
 
           // If the animation is play-pending, resolve the start time.
+          // This mirrors the calculation in Animation::StartTimeFromReadyTime.
           if (anim.startTime().IsNull() && !anim.isNotPlaying()) {
-            anim.startTime() = aReadyTime - anim.holdTime();
+            anim.startTime() =
+              anim.playbackRate() == 0
+              ? aReadyTime
+              : aReadyTime - anim.holdTime().MultDouble(1.0 /
+                                                        anim.playbackRate());
             updated = true;
           }
         }
         if (updated) {
           layer->Mutated();
         }
       });
 }