This patch (finally!) introduces the delayed start behavior. It updates
AnimationPlayer::DoPlay to put animations in the PendingPlayerTracker from
where they are triggered.
This patch also updates nsTransitionManager to set the animation's source
before calling Play as otherwise the AnimationPlayer won't be able to access
the pending player tracker (which it locates by navigating AnimationPlayer ->
Animation (source content) -> target element -> composed doc -> pending player
tracker). In future, when we support setting the AnimationPlayer.source property
we will make this more robust so that the order in which these steps are
performed doesn't matter.
This patch also updates a couple of tests to reflect the fact that
AnimationPlayer will now return the pending state.
In the future we will want to specifically just update source content without
necessarily triggering any other actions that might take place on a tick (like
queuing events).
We want to time animations from when their first frame is painted. However,
interruptible reflow complicates this since, for a given set of pending
animations, some may be painted whilst others are not. To simplify this we
simply force an uninterruptible reflow when we have animations that are
waiting to start.
We would like to trigger animations from the point when the first frame of the
animation is painted. However, some animations will never trigger a paint (e.g.
animations with an empty keyframes rule). These animations should start start
however. To ensure this, whenever an animation is newly pending we schedule
a paint.
This patch adds a reference from PendingPlayerTracker back to the document
object that owns it. This is used in the next patch in this series to find the
document's root frame for scheduling a paint.
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.
ResolveStartTime is a bit hard to understand. Eventually, once we implement
SetStartTime, we can probably remove this method altogether and just use that.
This renaming moves us closer to that direction.
This patch also adjusts a comment about the preconditions for calling StartNow.
This is because in a subsequent patch in this series we will update the
assertion at the beginning of StartNow to simply check that the player is
pending.
This patch adds a means of terminating an animation so that is has no effect.
The procedure is defined by Web Animations:
http://w3c.github.io/web-animations/#cancelling-a-player-section
We don't implement all of this, however, since we don't currently support the
finished promise or custom effects.
In a later bug we will expose this as the cancel() method on AnimationPlayer.
We call this method for terminated animations in nsAnimationManager and
nsTransitionManager to ensure they get removed from the pending player tracker
and so that, for example, the ready promise of CSS Animation player objects is
rejected when the corresponding item is removed from animation-name.
In Bug 1104435 we made the initial ready promise be created lazily. This patch
takes this a step further and makes sure we don't create the promise at all
unless it is explicitly requested.
This patch updates the pause procedure for AnimationPlayer so that if there is
a pending play it will be cancelled.
At the same it removes the existing check for a redundant call to Pause when we
are already paused. This check is not necessary since if we are already paused
the method will have no effect anyway.
Finally, this patch updates the comment about going to the pending state while
pausing since this will happen in bug 1109390.
This patch adds a member for tracking if a player is currently pending or not
and uses this to return the "pending" state from PlayState(). We don't currently
set mIsPending to true yet, however.
Additionally, this patch updates AnimationPlayer::ComposeStyle to set
aNeedsRefreshes to true when the player is pending. This is used by the
appropriate animation/transition manager to determine if it should continue to
observe the refresh driver or not.
This patch adds a hashtable to nsDocument that stores all the animation players
that are currently waiting to start. In the future it may also be used to store
players that are waiting to pause which is why the methods are called
AddPlayPending/RemovePlayPending instead of just AddPlayer/RemovePlayer.
AnimationPlayer.ready will currently always be resolved but by updating these
tests to wait on it anyway they should continue to work once we introduce the
delayed animation start behavior.
Since bug 1104433, step_func now passes on the return value of its callback.
That means we can use it directly as a Promise callback function in Promise
chains where the return value of the function is another Promise.
This patch updates existing tests to eliminate the awkward handling we had
around wrapping some parts of Promise callbacks in step() but leaving the return
statement outside it.
This patch moves commonly used addDiv and waitForFrame test methods to
a separate testcommon.js support file.
It also takes advantage of the updates to testharness.js from bug 1104433 to
rework addDiv such that it automatically removes the created div at the end of
the test.
Every time we call play() (and in future pause()) we should create a new Promise
object and then fulfill it when we complete the play() operation.
This patch adds that step. This allows us to tweak test cases that currently
expect animations to start immediately so that by waiting on this promise they
will continue to work when we introduce the delayed start behavior in bug
927349.
We should also resolve the ready promise when we are in the pending state and
pause() is called. However, since we currently never enter the pending state we
don't need to add this just yet.
Web Animations defines the AnimationPlayer.ready Promise as type
Promise<AnimationPlayer>. This promise resolves to the AnimationPlayer object on
which it is defined. However, in order to be able to pass AnimationPlayer as
a resolution value it needs to implement nsISupports.
This patch makes AnimationPlayer derive from nsISupports.
In order to create a Promise object for AnimationPlayer.ready, we need an
nsIGlobalObject. Currently we can access this through the following chain:
AnimationPlayer -> AnimationTimeline -> Document -> nsIGlobalObject
Come bug 1096776 (Support AnimationPlayers without a timeline or with an
inactive timeline) we will no longer be able to rely on having an
AnimationTimeline so we will probably have to store the corresponding window
object on the AnimationPlayer. For now, however, we can look up the timeline as
above.
This patch makes this a little more straightforward by changing the return type
and value of AnimationTimeline::GetParentObject to return the nsIGlobalObject of
the document with which it is associated.
In preparation for deferring the start of animations in bug 927349, this patch
makes the code that resolves the start time a separate method. In this patch we
call the method immediately from AnimationPlayer::DoPlay. However, in the future
this will be called at a later point when the first frame of the animation has
been rendered.
Now that there is a public accessor for mStartTime, we can make it a protected
member of AnimationPlayer. The only time mStartTime is ever set is when playing
the animation so we can replace external modifications to mStartTime with calls
to Play(). This simplifies implementing deferred starting of animations
in bug 927349 by isolating the deferred playback logic to AnimationPlayer.
Note that even when we call PauseFromStyle immediately afterwards we still need
to call PlayFromStyle (or Play) first in order to resolve the time at which the
player should be paused. A newly created player doesn't have a current time so
if we were simply to call pause it wouldn't pause at the start of the animation
as we might expect. The call to Play(FromStyle) will cause the current time to
become zero and then we pause at that time.
In forthcoming patches we will encapsulate AnimationPlayer::mStartTime so we can
ensure that related state is updated appropriately. We would like to expose
mStartTime via GetStartTime() but currently a method of that name returns the
start time as a double.
This patch applies the pattern used for currentTime to startTime; specifically,
GetCurrentTime() returns the TimeDuration (since that's what C++ callers should
use) while GetCurrentTimeAsDouble() returns a double.
At the same time, this patch also removes the [Pure] extended attribute from
startTime in the WebIDL definition since subsequent patches either in this bug
or in bug 927349 will mean that startTime can be updated out-of-band.
Specifically, we will implement deferred playback of animation such that the
startTime remains null until we've finished rendering the first frame of the
animation.
This patch reworks AnimationPlayer to represent the paused state by a null start
time. This brings it into line with recent changes in the Web Animations spec
and removes the need for the mIsPaused member variable.
The idea is that in order for a player to play, it needs a start time and
an active timeline. The processing is roughly:
* If it is seeked to a particular time (through setting the currentTime or
calling pause() or finish()) but has no start time, it is paused.
* Otherwise, if it has no active timeline or no start time, it is idle.
By removing the mIsPaused flag the number of possible permutations of states is
reduced so the model is easier to reason about (see:
http://lists.w3.org/Archives/Public/public-fx/2014OctDec/0026.html).
This patch replaces the mIsPaused flag with checks for mStartTime.IsNull()
according to the rules outlined above.
The existing relationship between the particular versions of
AnimationPlayer::Play* (particularly in the CSSAnimationPlayer) subclass are
confusing because, for example, CSSAnimationPlayer::PlayFromStyle needs to be
careful to *not* call Play on CSSAnimationPlayer, but only on the parent
object (since otherwise we reset the sticky pause behavior).
This patch reworks this relationship by adding a protected DoPlay method that
performs the common pausing behavior. Play/PlayFromJS/PlayFromStyle then add
flushing, sticky pausing etc. as necessary.
This patch also removes the UpdateFlags enum and parameters previously used to
control whether we forced an update to style. This is no longer necessary since
we no longer call 'Play' from style. Instead we make Play always post restyles.
If we come across a case where we want to call Play and *not* post restyles, we
can re-add the flags then.
Roughly the same arrangement is true for Pause except that we don't currently
flush styles for CSS animations in PauseFromJS since it currently won't make any
observable difference.
This patch introduces an abstract method to AnimationPlayer to fetch the manager
object associated with the player. This method is implemented separate by
CSSAnimationPlayer and CSSTransitionPlayer to return the nsAnimationManager or
nsTransitionManager accordingly.
In order for AnimationPlayer objects to be able to notify their
collection/manager, the can either store an extra pointer member, or they can
navigate to the collection as follows:
player->source(animation)->target(element)->document
->presShell->presContext->manager->collection
This patch adds a getter for the first part of this journey up to the document.
Previously AnimationPlayer::Play() and AnimationPlayer::PlayState() would flush
styles as part of their operation. This, however, is only needed when the player
corresponds to a CSS Animation or CSS Transition. Now that we have concrete
subclasses for each of these cases we can move style flushing to the subclasses
and remove it from the base class (which is expected to be shared with
animations that are not dependent on style).
In order to be able to find the collection a player belongs to from its source
content, we first need to be able to determine which manager--the animation
manager or transition manager--to look up.
We eventually plan to push transition event dispatch down to a CSS
transitions-specific subclass of AnimationPlayer, so this seems like a suitable
point to introduce this class.
Using this subclass we can define a virtual GetManager method that will
return the appropriate animation/transition manager for the player.
This patch moves the code for queuing CSS animation events from
nsAnimationManager to CSSAnimationPlayer. In doing so, it also moves the
mLastNotification member and associated enum values.
AnimationPlayer::CanThrottle determines if an animation player has just finished
by inspecting the value of mLastNotification. This is problematic for two
reasons:
1. mLastNotification is intended to be used for events (as the XXX comment
notes)
2. mLastNotification is specific to CSS Animations and should be moved to
CSSAnimationPlayer.
To address this, this patch adds an extra member mIsPreviousStateFinished. The
Web Animations spec already defines animation players as having such a member:
http://w3c.github.io/web-animations/#previous-finished-state
We set it to true when we calculate the style for an animation that has
finished. This differs slightly from the code it is replacing as explained
below.
In the case of CSS Animations we perform the following sequence of steps on each
sample.
1. EnsureStyleRuleFor (calls CanThrottle, and maybe ComposeStyle)
2. GetEventsForCurrentTime
In the existing code, we update mLastNotification in (2) which happens on every
sample, even throttled samples.
In this patch, however, we update mIsPreviousStateFinished in (1) during the
ComposeStyle step which only happens for unthrottled samples. So, as of this
patch, in CanThrottle, we ask "have we newly entered the finished state since
the last *unthrottled* sample?", whereas previously we simply looked for
a change since the last sample, throttled or not. However, if the answer to the
question is "yes", then we'll run an unthrottled sample and update
mIsPreviousStateFinished so these should be functionally equivalent.
Another subtle difference is that this patch looks at the player's finished
state rather than the animation phase of its source content, and these will
produce different results in the case where the player is paused. However, since
paused animations are not run on the compositor, this should not matter.
In the case of CSS Transitions, AnimationPlayer::CanThrottle() is not currently
used and so mIsPreviousStateFinished is irrelevant.
Ultimately, both the existing and the new code is somewhat fragile but hopefully
this will be addressed by:
* Replacing mIsPreviousStateFinished with inspecting whether the finished
promise is settled (bug 1074630),
* Merging more of the code in nsAnimationManager and nsTransitionManager and
applying a unified approach to sampling that better accommodates these
considerations.
This patch takes the CSSAnimationPlayer object, currently defined in
dom/animation/AnimationPlayer.{cpp,h}, and moves it to
layout/style/nsAnimationManager.{cpp,h} where the rest of the CSS
Animations-specific code lives.
At the same time it extends the scope of the mozilla namespace block in
nsAnimationManager.h to also include the AnimationEventInfo and EventArray types
since these classes, which don't have an ns* prefix, probably should be in the
mozilla namespace anyway.
This patch extracts the logic for calculating animation styles from
AnimationPlayerCollection and puts the bulk of it into the Animation objects.
Some of the initial logic surrounding the animation player state (e.g. is it
paused or not, etc.) is put into AnimationPlayer.
In future we may shift this logic even further down to the AnimationEffect
objects but currently we don't create such objects unless necessary.