diff --git a/dom/animation/AnimationTimeline.cpp b/dom/animation/AnimationTimeline.cpp index 550fc766c66a..b20cee214dc9 100644 --- a/dom/animation/AnimationTimeline.cpp +++ b/dom/animation/AnimationTimeline.cpp @@ -38,34 +38,104 @@ AnimationTimeline::GetCurrentTimeAsDouble() const return AnimationUtils::TimeDurationToDouble(GetCurrentTime()); } +void +AnimationTimeline::FastForward(const TimeStamp& aTimeStamp) +{ + // If we have already been fast-forwarded to an equally or more + // recent time, ignore this call. + if (!mFastForwardTime.IsNull() && aTimeStamp <= mFastForwardTime) { + return; + } + + // If the refresh driver is under test control then its values have little + // connection to TimeStamp values and it doesn't make sense to fast-forward + // the timeline to a TimeStamp value. + // + // Furthermore, when the refresh driver is under test control, + // nsDOMWindowUtils::AdvanceTimeAndRefresh automatically starts any + // pending animation players so we don't need to fast-forward the timeline + // anyway. + nsRefreshDriver* refreshDriver = GetRefreshDriver(); + if (refreshDriver && refreshDriver->IsTestControllingRefreshesEnabled()) { + return; + } + + // Bug 1113413: If the refresh driver has just been restored from test + // control it's possible that aTimeStamp could be before the most recent + // refresh. + if (refreshDriver && + aTimeStamp < refreshDriver->MostRecentRefresh()) { + mFastForwardTime = refreshDriver->MostRecentRefresh(); + return; + } + + // FIXME: For all animations attached to this timeline, we should mark + // their target elements as needing restyling. Otherwise, tasks that run + // in between now and the next refresh driver tick might see inconsistencies + // between the timing of an animation and the computed style of its target. + + mFastForwardTime = aTimeStamp; +} + TimeStamp AnimationTimeline::GetCurrentTimeStamp() const { - // Always return the same object to benefit from return-value optimization. - TimeStamp result = mLastCurrentTime; + nsRefreshDriver* refreshDriver = GetRefreshDriver(); + TimeStamp refreshTime = refreshDriver + ? refreshDriver->MostRecentRefresh() + : TimeStamp(); - // If we've never been sampled, initialize the current time to the timeline's - // zero time since that is the time we'll use if we don't have a refresh - // driver. + // Always return the same object to benefit from return-value optimization. + TimeStamp result = !refreshTime.IsNull() + ? refreshTime + : mLastRefreshDriverTime; + + // If we don't have a refresh driver and we've never had one use the + // timeline's zero time. if (result.IsNull()) { nsRefPtr timing = mDocument->GetNavigationTiming(); - if (!timing) { - return result; + if (timing) { + result = timing->GetNavigationStartTimeStamp(); + // Also, let this time represent the current refresh time. This way + // we'll save it as the last refresh time and skip looking up + // navigation timing each time. + refreshTime = result; } - result = timing->GetNavigationStartTimeStamp(); } - nsRefreshDriver* refreshDriver = GetRefreshDriver(); - if (!refreshDriver) { - return result; + // The timeline may have been fast-forwarded to account for animations + // that begin playing between ticks of the refresh driver. If so, we should + // use the fast-forward time unless we've already gone past that time. + // + // (If the refresh driver were ever to go backwards then we would need to + // ignore the fast-forward time in that case to prevent the timeline getting + // "stuck" until the refresh driver caught up. However, the only time the + // refresh driver goes backwards is when it is restored from test control + // and FastForward makes sure we don't set the fast foward time when we + // are under test control.) + MOZ_ASSERT(refreshTime.IsNull() || mLastRefreshDriverTime.IsNull() || + refreshTime >= mLastRefreshDriverTime || + mFastForwardTime.IsNull(), + "The refresh driver time should not go backwards when the" + " fast-forward time is set"); + + // We need to check if mFastForwardTime is ahead of the refresh driver + // time. This is because mFastForwardTime can still be set after the next + // refresh driver tick since we don't clear mFastForwardTime on a call to + // Tick() as we aren't currently guaranteed to get only one call to Tick() + // per refresh-driver tick. + if (result.IsNull() || + (!mFastForwardTime.IsNull() && mFastForwardTime > result)) { + result = mFastForwardTime; + } else { + // Make sure we continue to ignore the fast-forward time. + mFastForwardTime = TimeStamp(); + } + + if (!refreshTime.IsNull()) { + mLastRefreshDriverTime = refreshTime; } - result = refreshDriver->MostRecentRefresh(); - // FIXME: We would like to assert that: - // mLastCurrentTime.IsNull() || result >= mLastCurrentTime - // but due to bug 1043078 this will not be the case when the refresh driver - // is restored from test control. - mLastCurrentTime = result; return result; } diff --git a/dom/animation/AnimationTimeline.h b/dom/animation/AnimationTimeline.h index 21d63e86efb5..85b371255696 100644 --- a/dom/animation/AnimationTimeline.h +++ b/dom/animation/AnimationTimeline.h @@ -50,16 +50,35 @@ public: Nullable ToTimelineTime(const TimeStamp& aTimeStamp) const; TimeStamp ToTimeStamp(const TimeDuration& aTimelineTime) const; + // Force the timeline to advance to |aTimeStamp|. + // + // Normally the timeline uses the refresh driver time but when we have + // animations that are timed from when their first frame is rendered we need + // to bring the timeline forward to that moment. If we don't, calling + // IsRunning() will incorrectly return false (because GetCurrentTime() will + // return a negative time) until the next refresh driver tick causes the + // timeline to catch up. + // + // |aTimeStamp| must be greater or equal to the current refresh driver + // time for the document with which this timeline is associated unless the + // refresh driver is under test control, in which case this method will + // be a no-op. + void FastForward(const TimeStamp& aTimeStamp); + protected: TimeStamp GetCurrentTimeStamp() const; nsRefreshDriver* GetRefreshDriver() const; nsCOMPtr mDocument; - // Store the most recently returned value of current time. This is used - // in cases where we don't have a refresh driver (e.g. because we are in - // a display:none iframe). - mutable TimeStamp mLastCurrentTime; + // The most recently used refresh driver time. This is used in cases where + // we don't have a refresh driver (e.g. because we are in a display:none + // iframe). + mutable TimeStamp mLastRefreshDriverTime; + + // The time to which the timeline has been forced-to in order to account for + // animations that are started in-between frames. + mutable TimeStamp mFastForwardTime; }; } // namespace dom