Bug 927349 part 8 - Fast-forward the timeline before resolving start times; r=jwatt
authorBrian Birtles <birtles@gmail.com>
Mon, 22 Dec 2014 09:35:41 +0900
changeset 220803 d7da8aa9ec743fd3eedad18e071aa0aa71fce920
parent 220802 f699e122b007ff90a2dcc5bf238b083c832c06d8
child 220804 f77e3d03e7e3444e07ebd17a2c8ed48d01dfb7f5
push id28000
push usercbook@mozilla.com
push dateMon, 22 Dec 2014 12:13:57 +0000
treeherdermozilla-central@c82d5fcdc416 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjwatt
bugs927349, 1112480
milestone37.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 927349 part 8 - Fast-forward the timeline before resolving start times; r=jwatt Normally animation players get times from their timeline which is based on the refresh driver for their associated document. However, for animations that we time from when their first frame has been rendered, we want to record the actual time when painting finished as their start time. If we wait until the next refresh driver tick then the delay between playing an animation and its actual start will be too great. In this patch, we introduce a mechanism for fast-forwarding a timeline to a time between the current refresh driver time and the next refresh driver tick. By adjusting the timeline rather than the player we maintain a consistent state (in fact, if we just naively set the animation player start time to the timestamp value we recorded when painting finished it will appear to start in the future and the animation will temporarily jump from playing, to waiting to start, then back to playing again on the next refresh driver tick). To be completely consistent, however, when we fast-forward the timeline we should tell all animation players listening to the timeline to mark their target element as needing a style flush. Otherwise we may be able to observe an inconsistency between some animation players' current time and the computed style of their targets. We don't, however, currently know which players are observing a given timeline. We will likely introduce that in the near future (in order to implement AnimationTimeline.getAnimationPlayers) and fix the inconsistency in timing then. A test later in the patch series verifies this inconsistency so it is easy to fix in future. An alternative approach would be to simply record the time when animation should start, send that time to the compositor but don't actually update the animation start time on the main thread until the subsequent refresh driver tick. Such an approach is complex as it introduces an additional state--"finished pending but not yet started". We will attempt to switch to that approach in bug 1112480.
dom/animation/AnimationTimeline.cpp
dom/animation/AnimationTimeline.h
--- a/dom/animation/AnimationTimeline.cpp
+++ b/dom/animation/AnimationTimeline.cpp
@@ -33,44 +33,114 @@ AnimationTimeline::GetCurrentTime() cons
 }
 
 Nullable<double>
 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<nsDOMNavigationTiming> 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();
   }
 
-  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;
+  if (!refreshTime.IsNull()) {
+    mLastRefreshDriverTime = refreshTime;
+  }
+
   return result;
 }
 
 Nullable<TimeDuration>
 AnimationTimeline::ToTimelineTime(const TimeStamp& aTimeStamp) const
 {
   Nullable<TimeDuration> result; // Initializes to null
   if (aTimeStamp.IsNull()) {
--- a/dom/animation/AnimationTimeline.h
+++ b/dom/animation/AnimationTimeline.h
@@ -45,24 +45,43 @@ public:
 
   // Wrapper functions for AnimationTimeline DOM methods when called from
   // script.
   Nullable<double> GetCurrentTimeAsDouble() const;
 
   Nullable<TimeDuration> 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<nsIDocument> 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
 } // namespace mozilla
 
 #endif // mozilla_dom_AnimationTimeline_h