Bug 1361260 - Incorporate playbackRate when calculating the start time of a pending compositor animation; r=hiro
authorBrian Birtles <birtles@gmail.com>
Tue, 02 May 2017 16:43:21 +0900
changeset 355982 fc0edea49a7028885bfcb2f0556d5324576a8a1b
parent 355981 438c1291a77ab7b753fac99123ea3a7de6b57fb2
child 355983 095c3fdfc66234228388ba5c0ebaf3d35f2a9868
push id89800
push userbbirtles@mozilla.com
push dateTue, 02 May 2017 10:23:55 +0000
treeherdermozilla-inbound@095c3fdfc662 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewershiro
bugs1361260
milestone55.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 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();
         }
       });
 }