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.
This commit is contained in:
Brian Birtles 2014-12-22 09:35:41 +09:00
Родитель d803a0513e
Коммит fb0d9d7187
2 изменённых файлов: 110 добавлений и 21 удалений

Просмотреть файл

@ -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<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();
}
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;
}

Просмотреть файл

@ -50,16 +50,35 @@ public:
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