Bug 1004365 part 3b.2 - Make ElementAnimation::GetComputedTimingAt handle zero-duration animations; r=dholbert
authorBrian Birtles <birtles@gmail.com>
Wed, 11 Jun 2014 14:19:08 +0900
changeset 188067 e097537a5589a3265dcc75669362781abc5ea633
parent 188066 25494b32627f15afb3db4ce5bf4b8f3394db0ac4
child 188068 7e75b68e61f354d4899aada03f0280c6f969f604
push id26944
push useremorley@mozilla.com
push dateWed, 11 Jun 2014 15:14:00 +0000
treeherdermozilla-central@c7391b84d9c2 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersdholbert
bugs1004365
milestone33.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 1004365 part 3b.2 - Make ElementAnimation::GetComputedTimingAt handle zero-duration animations; r=dholbert This patch adjusts GetComputedTimingAt to set the time fraction and current iteration fields of the output computed timing correctly for animations with zero iteration duration. Care must be taken to handle cases such as animations that have zero duration but repeat infinitely. The code is significantly re-arranged to more closely align with the naming and algorithms defined in Web Animations. A couple of tests in test_animations.html have been tweaked to account for floating-point error. This is not because the new code is less precise but actually the opposite. These tests fall on the transition point of step-timing functions. The new code uses the closest possible floating-point representation of these times which happens to cause them to fall on the opposite side of the transition point. For example, in evaluating a point 3s into a reversed interval the old code would give us an intermediate time fraction of: 0.29999999999999982 When we reverse that by subtracting from 1.0 we get: 0.70000000000000018 With the code in this patch we get an intermediate time fraction of: 0.29999999999999999 When we reverse that by subtracting from 1.0 we get: 0.69999999999999996 Hence we fall on the opposite side of the transition boundary.
layout/style/AnimationCommon.cpp
layout/style/test/test_animations.html
layout/style/test/test_animations_omta.html
--- a/layout/style/AnimationCommon.cpp
+++ b/layout/style/AnimationCommon.cpp
@@ -395,88 +395,118 @@ ElementAnimation::HasAnimationOfProperty
 
 ComputedTiming
 ElementAnimation::GetComputedTimingAt(TimeDuration aElapsedDuration,
                                       const AnimationTiming& aTiming)
 {
   // Always return the same object to benefit from return-value optimization.
   ComputedTiming result;
 
-  // Set |currentIterationCount| to the (fractional) number of
-  // iterations we've completed up to the current position.
-  double currentIterationCount = aElapsedDuration / aTiming.mIterationDuration;
-  if (currentIterationCount >= aTiming.mIterationCount) {
+  TimeDuration activeDuration = ActiveDuration(aTiming);
+
+  // When we finish exactly at the end of an iteration we need to report
+  // the end of the final iteration and not the start of the next iteration
+  // so we set up a flag for that case.
+  bool isEndOfFinalIteration = false;
+
+  // Get the normalized time within the active interval.
+  TimeDuration activeTime;
+  if (aElapsedDuration >= activeDuration) {
     result.mPhase = ComputedTiming::AnimationPhase_After;
     if (!aTiming.FillsForwards()) {
       // The animation isn't active or filling at this time.
       result.mTimeFraction = ComputedTiming::kNullTimeFraction;
       return result;
     }
-    currentIterationCount = aTiming.mIterationCount;
-  } else if (currentIterationCount < 0.0) {
+    activeTime = activeDuration;
+    // Note that infinity == floor(infinity) so this will also be true when we
+    // have finished an infinitely repeating animation of zero duration.
+    isEndOfFinalIteration =
+      aTiming.mIterationCount != 0.0 &&
+      aTiming.mIterationCount == floor(aTiming.mIterationCount);
+  } else if (aElapsedDuration < TimeDuration()) {
     result.mPhase = ComputedTiming::AnimationPhase_Before;
     if (!aTiming.FillsBackwards()) {
       // The animation isn't active or filling at this time.
       result.mTimeFraction = ComputedTiming::kNullTimeFraction;
       return result;
     }
-    currentIterationCount = 0.0;
+    // activeTime is zero
   } else {
+    MOZ_ASSERT(activeDuration != TimeDuration(),
+               "How can we be in the middle of a zero-duration interval?");
     result.mPhase = ComputedTiming::AnimationPhase_Active;
+    activeTime = aElapsedDuration;
   }
 
-  // Set |positionInIteration| to the position from 0% to 100% along
-  // the keyframes.
-  NS_ABORT_IF_FALSE(currentIterationCount >= 0.0, "must be positive");
-  double positionInIteration = fmod(currentIterationCount, 1);
+  // Get the position within the current iteration.
+  TimeDuration iterationTime;
+  if (aTiming.mIterationDuration != TimeDuration()) {
+    iterationTime = isEndOfFinalIteration
+                    ? aTiming.mIterationDuration
+                    : activeTime % aTiming.mIterationDuration;
+  } /* else, iterationTime is zero */
 
-  // Set |whichIteration| to the integral index of the current iteration.
-  // Casting to an integer here gives us floor(currentIterationCount).
-  // We don't check for overflow here since the range of an unsigned 64-bit
-  // integer is more than enough (i.e. we could handle an animation that
-  // iterates every *microsecond* for about 580,000 years).
-  uint64_t whichIteration = static_cast<uint64_t>(currentIterationCount);
+  // Determine the 0-based index of the current iteration.
+  if (isEndOfFinalIteration) {
+    result.mCurrentIteration =
+      aTiming.mIterationCount == NS_IEEEPositiveInfinity()
+      ? UINT64_MAX // FIXME: When we return this via the API we'll need
+                   // to make sure it ends up being infinity.
+      : static_cast<uint64_t>(aTiming.mIterationCount) - 1;
+  } else if (activeTime == TimeDuration(0)) {
+    // If the active time is zero we're either in the first iteration
+    // (including filling backwards) or we have finished an animation with an
+    // iteration duration of zero that is filling forwards (but we're not at
+    // the exact end of an iteration since we deal with that above).
+    result.mCurrentIteration =
+      result.mPhase == ComputedTiming::AnimationPhase_After
+      ? static_cast<uint64_t>(aTiming.mIterationCount) // floor
+      : 0;
+  } else {
+    result.mCurrentIteration =
+      static_cast<uint64_t>(activeTime / aTiming.mIterationDuration); // floor
+  }
 
-  // Check for the end of the final iteration.
-  if (whichIteration != 0 &&
-      result.mPhase == ComputedTiming::AnimationPhase_After &&
-      aTiming.mIterationCount == floor(aTiming.mIterationCount)) {
-    // When the animation's iteration count is an integer (as it
-    // normally is), we need to end at 100% of its final iteration
-    // rather than 0% of the next one (unless it's zero).
-    whichIteration -= 1;
-    positionInIteration = 1.0;
+  // Normalize the iteration time into a fraction of the iteration duration.
+  if (result.mPhase == ComputedTiming::AnimationPhase_Before) {
+    result.mTimeFraction = 0.0;
+  } else if (result.mPhase == ComputedTiming::AnimationPhase_After) {
+    result.mTimeFraction = isEndOfFinalIteration
+                         ? 1.0
+                         : fmod(aTiming.mIterationCount, 1.0f);
+  } else {
+    // We are in the active phase so the iteration duration can't be zero.
+    MOZ_ASSERT(aTiming.mIterationDuration != TimeDuration(0),
+               "In the active phase of a zero-duration animation?");
+    result.mTimeFraction =
+      aTiming.mIterationDuration == TimeDuration::Forever()
+      ? 0.0
+      : iterationTime / aTiming.mIterationDuration;
   }
 
   bool thisIterationReverse = false;
   switch (aTiming.mDirection) {
     case NS_STYLE_ANIMATION_DIRECTION_NORMAL:
       thisIterationReverse = false;
       break;
     case NS_STYLE_ANIMATION_DIRECTION_REVERSE:
       thisIterationReverse = true;
       break;
     case NS_STYLE_ANIMATION_DIRECTION_ALTERNATE:
-      // uint64_t has more integer precision than double does, so if
-      // whichIteration is that large, we've already lost and we're just
-      // guessing.  But the animation is presumably oscillating so fast
-      // it doesn't matter anyway.
-      thisIterationReverse = (whichIteration & 1) == 1;
+      thisIterationReverse = (result.mCurrentIteration & 1) == 1;
       break;
     case NS_STYLE_ANIMATION_DIRECTION_ALTERNATE_REVERSE:
-      // see as previous case
-      thisIterationReverse = (whichIteration & 1) == 0;
+      thisIterationReverse = (result.mCurrentIteration & 1) == 0;
       break;
   }
   if (thisIterationReverse) {
-    positionInIteration = 1.0 - positionInIteration;
+    result.mTimeFraction = 1.0 - result.mTimeFraction;
   }
 
-  result.mTimeFraction = positionInIteration;
-  result.mCurrentIteration = whichIteration;
   return result;
 }
 
 namespace css {
 
 bool
 CommonElementAnimationData::CanAnimatePropertyOnCompositor(const dom::Element *aElement,
                                                            nsCSSProperty aProperty,
--- a/layout/style/test/test_animations.html
+++ b/layout/style/test/test_animations.html
@@ -672,17 +672,17 @@ advance_clock(1000);
 is_approx(px_to_num(cs.paddingBottom), 60 + 100 * gTF.ease_in(0.2), 0.01,
           "keyframe timing functions test at 3s");
 advance_clock(1000);
 is_approx(px_to_num(cs.paddingBottom), 60 + 100 * gTF.ease_in(0.6), 0.01,
           "keyframe timing functions test at 4s");
 advance_clock(1000);
 is(cs.paddingBottom, "160px",
    "keyframe timing functions test at 5s");
-advance_clock(1010); // avoid floating point error
+advance_clock(1010); // avoid floating-point error
 is_approx(px_to_num(cs.paddingBottom), 160 - 40 * step_end(5)(0.4), 0.01,
           "keyframe timing functions test at 6s");
 advance_clock(1000);
 is_approx(px_to_num(cs.paddingBottom), 160 - 40 * step_end(5)(0.8), 0.01,
           "keyframe timing functions test at 7s");
 advance_clock(990);
 is_approx(px_to_num(cs.paddingBottom), 120 - 100 * gTF.linear(0.2), 0.01,
           "keyframe timing functions test at 8s");
@@ -696,23 +696,23 @@ advance_clock(20000);
 is(cs.paddingBottom, "20px",
    "keyframe timing functions test at 30s");
 advance_clock(1000);
 is_approx(px_to_num(cs.paddingBottom), 120 - 100 * gTF.linear(0.6), 0.01,
           "keyframe timing functions test at 31s");
 advance_clock(1000);
 is_approx(px_to_num(cs.paddingBottom), 120 - 100 * gTF.linear(0.2), 0.01,
           "keyframe timing functions test at 32s");
-advance_clock(1000);
+advance_clock(990); // avoid floating-point error
 is_approx(px_to_num(cs.paddingBottom), 160 - 40 * step_end(5)(0.8), 0.01,
           "keyframe timing functions test at 33s");
 advance_clock(1000);
 is_approx(px_to_num(cs.paddingBottom), 160 - 40 * step_end(5)(0.4), 0.01,
           "keyframe timing functions test at 34s");
-advance_clock(1000);
+advance_clock(1010);
 is(cs.paddingBottom, "160px",
    "keyframe timing functions test at 35s");
 advance_clock(1000);
 is_approx(px_to_num(cs.paddingBottom), 60 + 100 * gTF.ease_in(0.6), 0.01,
           "keyframe timing functions test at 36s");
 advance_clock(1000);
 is_approx(px_to_num(cs.paddingBottom), 60 + 100 * gTF.ease_in(0.2), 0.01,
           "keyframe timing functions test at 37s");
@@ -732,20 +732,20 @@ new_div("animation: kf_tf1 ease-in 10s i
 is(cs.paddingBottom, "20px",
    "keyframe timing functions test at 0s (test needed for flush)");
 advance_clock(11000);
 is_approx(px_to_num(cs.paddingBottom), 20 + 40 * gTF.ease(0.4), 0.01,
           "keyframe timing functions test at 11s");
 advance_clock(3000);
 is_approx(px_to_num(cs.paddingBottom), 60 + 100 * gTF.ease_in(0.6), 0.01,
           "keyframe timing functions test at 14s");
-advance_clock(2000);
+advance_clock(2010); // avoid floating-point error
 is_approx(px_to_num(cs.paddingBottom), 160 - 40 * step_end(5)(0.4), 0.01,
           "keyframe timing functions test at 16s");
-advance_clock(2000);
+advance_clock(1990);
 is_approx(px_to_num(cs.paddingBottom), 120 - 100 * gTF.linear(0.2), 0.01,
           "keyframe timing functions test at 18s");
 done_div();
 
 /*
  * css3-animations:  3.2. The 'animation-name' Property
  * http://dev.w3.org/csswg/css3-animations/#the-animation-name-property-
  */
--- a/layout/style/test/test_animations_omta.html
+++ b/layout/style/test/test_animations_omta.html
@@ -737,17 +737,17 @@ addAsyncTest(function *() {
                  "keyframe timing functions test at 3s");
   advance_clock(1000);
   omta_is_approx("transform", { tx: 60 + 100 * gTF.ease_in(0.6) },
                  RunningOn.Compositor, 0.01,
                  "keyframe timing functions test at 4s");
   advance_clock(1000);
   omta_is("transform", { tx: 160 }, RunningOn.Compositor,
           "keyframe timing functions test at 5s");
-  advance_clock(1010); // avoid floating point error
+  advance_clock(1010); // avoid floating-point error
   omta_is_approx("transform", { tx: 160 - 40 * step_end(5)(0.4) },
                  RunningOn.Compositor, 0.01,
                  "keyframe timing functions test at 6s");
   advance_clock(1000);
   omta_is_approx("transform", { tx: 160 - 40 * step_end(5)(0.8) },
                  RunningOn.Compositor, 0.01,
                  "keyframe timing functions test at 7s");
   advance_clock(990);
@@ -767,25 +767,25 @@ addAsyncTest(function *() {
   advance_clock(1000);
   omta_is_approx("transform", { tx: 120 - 100 * gTF.linear(0.6) },
                  RunningOn.Compositor, 0.01,
                  "keyframe timing functions test at 31s");
   advance_clock(1000);
   omta_is_approx("transform", { tx: 120 - 100 * gTF.linear(0.2) },
                  RunningOn.Compositor, 0.01,
                  "keyframe timing functions test at 32s");
-  advance_clock(1000);
+  advance_clock(990); // avoid floating-point error
   omta_is_approx("transform", { tx: 160 - 40 * step_end(5)(0.8) },
                  RunningOn.Compositor, 0.01,
                  "keyframe timing functions test at 33s");
   advance_clock(1000);
   omta_is_approx("transform", { tx: 160 - 40 * step_end(5)(0.4) },
                  RunningOn.Compositor, 0.01,
                  "keyframe timing functions test at 34s");
-  advance_clock(1000);
+  advance_clock(1010);
   omta_is("transform", { tx: 160 }, RunningOn.Compositor,
           "keyframe timing functions test at 35s");
   advance_clock(1000);
   omta_is_approx("transform", { tx: 60 + 100 * gTF.ease_in(0.6) },
                  RunningOn.Compositor, 0.01,
                  "keyframe timing functions test at 36s");
   advance_clock(1000);
   omta_is_approx("transform", { tx: 60 + 100 * gTF.ease_in(0.2) },
@@ -812,21 +812,21 @@ addAsyncTest(function *() {
   advance_clock(11000);
   omta_is_approx("transform", { tx: 20 + 40 * gTF.ease(0.4) },
                  RunningOn.Compositor, 0.01,
                  "keyframe timing functions test at 11s");
   advance_clock(3000);
   omta_is_approx("transform", { tx: 60 + 100 * gTF.ease_in(0.6) },
                  RunningOn.Compositor, 0.01,
                  "keyframe timing functions test at 14s");
-  advance_clock(2000);
+  advance_clock(2010); // avoid floating-point error
   omta_is_approx("transform", { tx: 160 - 40 * step_end(5)(0.4) },
                  RunningOn.Compositor, 0.01,
                  "keyframe timing functions test at 16s");
-  advance_clock(2000);
+  advance_clock(1990);
   omta_is_approx("transform", { tx: 120 - 100 * gTF.linear(0.2) },
                  RunningOn.Compositor, 0.01,
                  "keyframe timing functions test at 18s");
   done_div();
 });
 
 /*
  * css3-animations:  3.2. The 'animation-name' Property