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
--- 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) {