Bug 1266257 - Revise timing model calculations to use fraction-based approach draft
authorBrian Birtles <birtles@gmail.com>
Thu, 21 Apr 2016 09:55:28 +0900
changeset 354520 c7694ad5adccbbc55355980e361825c4032c688d
parent 354495 149106214df5f4c7feb211ea1ea251af559ccd7b
child 354521 dca11c772de1f8dda7d2889830a17bb5ad24fd61
push id16105
push userbbirtles@mozilla.com
push dateThu, 21 Apr 2016 02:22:18 +0000
bugs1266257
milestone48.0a1
Bug 1266257 - Revise timing model calculations to use fraction-based approach As per updates to Web Animations spec: https://github.com/w3c/web-animations/compare/10fc97366041%5E...b9a721e6536ae27c37df1e3948b013c6a52d5f4c MozReview-Commit-ID: 5NHnF56aETJ
dom/animation/KeyframeEffect.cpp
--- a/dom/animation/KeyframeEffect.cpp
+++ b/dom/animation/KeyframeEffect.cpp
@@ -213,46 +213,48 @@ KeyframeEffectReadOnly::GetLocalTime() c
   Nullable<TimeDuration> result;
   if (mAnimation) {
     result = mAnimation->GetCurrentTime();
   }
   return result;
 }
 
 void
-KeyframeEffectReadOnly::GetComputedTimingAsDict(ComputedTimingProperties& aRetVal) const
+KeyframeEffectReadOnly::GetComputedTimingAsDict(
+    ComputedTimingProperties& aRetVal) const
 {
   const Nullable<TimeDuration> currentTime = GetLocalTime();
   GetComputedTimingDictionary(GetComputedTimingAt(currentTime,
                                                   SpecifiedTiming()),
                               currentTime,
                               SpecifiedTiming(),
                               aRetVal);
 }
 
 ComputedTiming
 KeyframeEffectReadOnly::GetComputedTimingAt(
-                          const Nullable<TimeDuration>& aLocalTime,
-                          const TimingParams& aTiming)
+    const Nullable<TimeDuration>& aLocalTime,
+    const TimingParams& aTiming)
 {
   const StickyTimeDuration zeroDuration;
 
   // Always return the same object to benefit from return-value optimization.
   ComputedTiming result;
 
   if (aTiming.mDuration) {
     MOZ_ASSERT(aTiming.mDuration.ref() >= zeroDuration,
                "Iteration duration should be positive");
     result.mDuration = aTiming.mDuration.ref();
   }
 
   MOZ_ASSERT(aTiming.mIterations >= 0.0 && !IsNaN(aTiming.mIterations),
              "mIterations should be nonnegative & finite, as ensured by "
              "ValidateIterations or CSSParser");
   result.mIterations = aTiming.mIterations;
+
   MOZ_ASSERT(aTiming.mIterationStart >= 0.0,
              "mIterationStart should be nonnegative, as ensured by "
              "ValidateIterationStart");
   result.mIterationStart = aTiming.mIterationStart;
 
   result.mActiveDuration = aTiming.ActiveDuration();
   result.mEndTime = aTiming.EndTime();
   result.mFill = aTiming.mFill == dom::FillMode::Auto ?
@@ -261,118 +263,83 @@ KeyframeEffectReadOnly::GetComputedTimin
 
   // The default constructor for ComputedTiming sets all other members to
   // values consistent with an animation that has not been sampled.
   if (aLocalTime.IsNull()) {
     return result;
   }
   const TimeDuration& localTime = aLocalTime.Value();
 
-  // 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.
+  // Calculate the time within the active interval.
   StickyTimeDuration activeTime;
   if (localTime >=
         std::min(StickyTimeDuration(aTiming.mDelay + result.mActiveDuration),
                  result.mEndTime)) {
     result.mPhase = ComputedTiming::AnimationPhase::After;
     if (!result.FillsForwards()) {
       // The animation isn't active or filling at this time.
-      result.mProgress.SetNull();
       return result;
     }
     activeTime = result.mActiveDuration;
-    double finiteProgress =
-      (IsInfinite(result.mIterations) ? 0.0 : result.mIterations)
-      + result.mIterationStart;
-    isEndOfFinalIteration = result.mIterations != 0.0 &&
-                            fmod(finiteProgress, 1.0) == 0;
   } else if (localTime <
                std::min(StickyTimeDuration(aTiming.mDelay), result.mEndTime)) {
     result.mPhase = ComputedTiming::AnimationPhase::Before;
     if (!result.FillsBackwards()) {
       // The animation isn't active or filling at this time.
-      result.mProgress.SetNull();
       return result;
     }
     // activeTime is zero
   } else {
     MOZ_ASSERT(result.mActiveDuration != zeroDuration,
                "How can we be in the middle of a zero-duration interval?");
     result.mPhase = ComputedTiming::AnimationPhase::Active;
     activeTime = localTime - aTiming.mDelay;
   }
 
-  // Calculate the scaled active time
-  // (We handle the case where the iterationStart is zero separately in case
-  // the duration is infinity, since 0 * Infinity is undefined.)
-  StickyTimeDuration startOffset =
-    result.mIterationStart == 0.0
-    ? StickyTimeDuration(0)
-    : result.mDuration.MultDouble(result.mIterationStart);
-  StickyTimeDuration scaledActiveTime = activeTime + startOffset;
+  // Convert active time to a multiple of iterations.
+  double overallProgress;
+  if (result.mDuration == zeroDuration) {
+    overallProgress = result.mPhase == ComputedTiming::AnimationPhase::Before
+                      ? 0.0
+                      : result.mIterations;
+  } else {
+    overallProgress = activeTime / result.mDuration;
+  }
 
-  // Get the position within the current iteration.
-  StickyTimeDuration iterationTime;
-  if (result.mDuration != zeroDuration &&
-      scaledActiveTime != StickyTimeDuration::Forever()) {
-    iterationTime = isEndOfFinalIteration
-                    ? result.mDuration
-      : scaledActiveTime % result.mDuration;
-  } /* else, either the duration is zero and iterationTime is zero,
-       or the scaledActiveTime is infinity in which case the iterationTime
-       should become infinity but we will not use the iterationTime in that
-       case so we just leave it as zero */
+  // Factor in iteration start offset.
+  if (IsFinite(overallProgress)) {
+    overallProgress += result.mIterationStart;
+  }
 
   // Determine the 0-based index of the current iteration.
-  if (result.mPhase == ComputedTiming::AnimationPhase::Before ||
-      result.mIterations == 0) {
-    result.mCurrentIteration = static_cast<uint64_t>(result.mIterationStart);
-  } else if (result.mPhase == ComputedTiming::AnimationPhase::After) {
-    result.mCurrentIteration =
-      IsInfinite(result.mIterations)
-      ? UINT64_MAX // In GetComputedTimingDictionary(), we will convert this
-                   // into Infinity.
-      : static_cast<uint64_t>(ceil(result.mIterations +
-                              result.mIterationStart)) - 1;
-  } else if (result.mDuration == StickyTimeDuration::Forever()) {
-    result.mCurrentIteration = static_cast<uint64_t>(result.mIterationStart);
-  } else {
-    result.mCurrentIteration =
-      static_cast<uint64_t>(scaledActiveTime / result.mDuration); // floor
+  result.mCurrentIteration =
+    IsInfinite(result.mIterations) &&
+      result.mPhase == ComputedTiming::AnimationPhase::After
+    ? UINT64_MAX // In GetComputedTimingDictionary(),
+                 // we will convert this into Infinity
+    : static_cast<uint64_t>(overallProgress);
+
+  // Convert the overall progress to a fraction of a single iteration--the
+  // simply iteration progress.
+  double progress = IsFinite(overallProgress)
+                    ? fmod(overallProgress, 1.0)
+                    : fmod(result.mIterationStart, 1.0);
+
+  // 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.
+  if (result.mPhase == ComputedTiming::AnimationPhase::After &&
+      progress == 0.0 &&
+      result.mIterations != 0.0) {
+    progress = 1.0;
+    if (result.mCurrentIteration != UINT64_MAX) {
+      result.mCurrentIteration--;
+    }
   }
 
-  // Normalize the iteration time into a fraction of the iteration duration.
-  if (result.mPhase == ComputedTiming::AnimationPhase::Before ||
-      result.mIterations == 0) {
-    double progress = fmod(result.mIterationStart, 1.0);
-    result.mProgress.SetValue(progress);
-  } else if (result.mPhase == ComputedTiming::AnimationPhase::After) {
-    double progress;
-    if (isEndOfFinalIteration) {
-      progress = 1.0;
-    } else if (IsInfinite(result.mIterations)) {
-      progress = fmod(result.mIterationStart, 1.0);
-    } else {
-      progress = fmod(result.mIterations + result.mIterationStart, 1.0);
-    }
-    result.mProgress.SetValue(progress);
-  } else {
-    // We are in the active phase so the iteration duration can't be zero.
-    MOZ_ASSERT(result.mDuration != zeroDuration,
-               "In the active phase of a zero-duration animation?");
-    double progress = result.mDuration == StickyTimeDuration::Forever()
-                      ? fmod(result.mIterationStart, 1.0)
-                      : iterationTime / result.mDuration;
-    result.mProgress.SetValue(progress);
-  }
-
+  // Factor in the direction.
   bool thisIterationReverse = false;
   switch (aTiming.mDirection) {
     case PlaybackDirection::Normal:
       thisIterationReverse = false;
       break;
     case PlaybackDirection::Reverse:
       thisIterationReverse = true;
       break;
@@ -381,32 +348,35 @@ KeyframeEffectReadOnly::GetComputedTimin
       break;
     case PlaybackDirection::Alternate_reverse:
       thisIterationReverse = (result.mCurrentIteration & 1) == 0;
       break;
     default:
       MOZ_ASSERT(true, "Unknown PlaybackDirection type");
   }
   if (thisIterationReverse) {
-    result.mProgress.SetValue(1.0 - result.mProgress.Value());
+    progress = 1.0 - progress;
   }
 
+  // Calculate the 'before flag' which we use when applying step timing
+  // functions.
   if ((result.mPhase == ComputedTiming::AnimationPhase::After &&
        thisIterationReverse) ||
       (result.mPhase == ComputedTiming::AnimationPhase::Before &&
        !thisIterationReverse)) {
     result.mBeforeFlag = ComputedTimingFunction::BeforeFlag::Set;
   }
 
+  // Apply the easing.
   if (aTiming.mFunction) {
-    result.mProgress.SetValue(
-      aTiming.mFunction->GetValue(result.mProgress.Value(),
-                                  result.mBeforeFlag));
+    progress = aTiming.mFunction->GetValue(progress, result.mBeforeFlag);
   }
 
+  MOZ_ASSERT(IsFinite(progress), "Progress value should be finite");
+  result.mProgress.SetValue(progress);
   return result;
 }
 
 // https://w3c.github.io/web-animations/#in-play
 bool
 KeyframeEffectReadOnly::IsInPlay() const
 {
   if (!mAnimation || mAnimation->PlayState() == AnimationPlayState::Finished) {