Bug 1074630, part 1 - Implement Web Animations finishing behavior. r=birtles, r=smaug

This commit is contained in:
Jonathan Watt 2015-03-18 13:22:11 +00:00
parent 481edeac42
commit 78696876a2
6 changed files with 161 additions and 49 deletions

View File

@ -17,7 +17,7 @@ namespace mozilla {
namespace dom {
NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(AnimationPlayer, mTimeline,
mSource, mReady)
mSource, mReady, mFinished)
NS_IMPL_CYCLE_COLLECTING_ADDREF(AnimationPlayer)
NS_IMPL_CYCLE_COLLECTING_RELEASE(AnimationPlayer)
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(AnimationPlayer)
@ -68,12 +68,8 @@ AnimationPlayer::SetStartTime(const Nullable<TimeDuration>& aNewStartTime)
mReady->MaybeResolve(this);
}
UpdateSourceContent();
UpdateTiming();
PostUpdate();
// FIXME: Once bug 1074630 is fixed, run the procedure to update a player's
// finished state for player:
// http://w3c.github.io/web-animations/#update-a-players-finished-state
}
Nullable<TimeDuration>
@ -113,8 +109,7 @@ AnimationPlayer::SilentlySetCurrentTime(const TimeDuration& aSeekTime)
(aSeekTime / mPlaybackRate));
}
// Once AnimationPlayers store a previous current time, set that to
// unresolved.
mPreviousCurrentTime.SetNull();
}
// Implements http://w3c.github.io/web-animations/#set-the-current-time
@ -130,12 +125,9 @@ AnimationPlayer::SetCurrentTime(const TimeDuration& aSeekTime)
}
}
UpdateFinishedState(true);
UpdateSourceContent();
PostUpdate();
// FIXME: Once bug 1074630 is fixed, run the procedure to update a player's
// finished state for player:
// http://w3c.github.io/web-animations/#update-a-players-finished-state
}
void
@ -186,36 +178,56 @@ AnimationPlayer::PlayState() const
return AnimationPlayState::Running;
}
static inline already_AddRefed<Promise>
CreatePromise(AnimationTimeline* aTimeline, ErrorResult& aRv)
{
nsIGlobalObject* global = aTimeline->GetParentObject();
if (global) {
return Promise::Create(global, aRv);
}
return nullptr;
}
Promise*
AnimationPlayer::GetReady(ErrorResult& aRv)
{
// Lazily create the ready promise if it doesn't exist
if (!mReady) {
nsIGlobalObject* global = mTimeline->GetParentObject();
if (global) {
mReady = Promise::Create(global, aRv);
if (mReady && PlayState() != AnimationPlayState::Pending) {
mReady->MaybeResolve(this);
}
}
mReady = CreatePromise(mTimeline, aRv); // Lazily create on demand
}
if (!mReady) {
aRv.Throw(NS_ERROR_FAILURE);
} else if (PlayState() != AnimationPlayState::Pending) {
mReady->MaybeResolve(this);
}
return mReady;
}
void
AnimationPlayer::Play()
Promise*
AnimationPlayer::GetFinished(ErrorResult& aRv)
{
DoPlay();
if (!mFinished) {
mFinished = CreatePromise(mTimeline, aRv); // Lazily create on demand
}
if (!mFinished) {
aRv.Throw(NS_ERROR_FAILURE);
} else if (IsFinished()) {
mFinished->MaybeResolve(this);
}
return mFinished;
}
void
AnimationPlayer::Play(LimitBehavior aLimitBehavior)
{
DoPlay(aLimitBehavior);
PostUpdate();
}
void
AnimationPlayer::Pause()
{
// TODO: The DoPause() call should not be synchronous (bug 1109390). See
// http://w3c.github.io/web-animations/#pausing-an-animation-section
DoPause();
PostUpdate();
}
@ -285,7 +297,7 @@ AnimationPlayer::Tick()
ResumeAt(mTimeline->GetCurrentTime().Value());
}
UpdateSourceContent();
UpdateTiming();
}
void
@ -345,6 +357,12 @@ AnimationPlayer::Cancel()
}
}
if (mFinished) {
mFinished->MaybeReject(NS_ERROR_DOM_ABORT_ERR);
}
// Clear finished promise. We'll create a new one lazily.
mFinished = nullptr;
mHoldTime.SetNull();
mStartTime.SetNull();
@ -407,16 +425,12 @@ AnimationPlayer::ComposeStyle(nsRefPtr<css::AnimValuesStyleRule>& aStyleRule,
mSource->ComposeStyle(aStyleRule, aSetProperties);
mIsPreviousStateFinished = (playState == AnimationPlayState::Finished);
//XXXjwatt mIsPreviousStateFinished = (playState == AnimationPlayState::Finished);
}
void
AnimationPlayer::DoPlay()
AnimationPlayer::DoPlay(LimitBehavior aLimitBehavior)
{
// FIXME: When we implement finishing behavior (bug 1074630) we will
// need to pass a flag so that when we start playing due to a change in
// animation-play-state we *don't* trigger finishing behavior.
bool reuseReadyPromise = false;
if (mPendingState != PendingState::NotPending) {
CancelPendingTasks();
@ -425,10 +439,16 @@ AnimationPlayer::DoPlay()
Nullable<TimeDuration> currentTime = GetCurrentTime();
if (mPlaybackRate > 0.0 &&
(currentTime.IsNull())) {
(currentTime.IsNull() ||
(aLimitBehavior == LimitBehavior::AutoRewind &&
(currentTime.Value().ToMilliseconds() < 0.0 ||
currentTime.Value() >= SourceContentEnd())))) {
mHoldTime.SetValue(TimeDuration(0));
} else if (mPlaybackRate < 0.0 &&
(currentTime.IsNull())) {
(currentTime.IsNull() ||
(aLimitBehavior == LimitBehavior::AutoRewind &&
(currentTime.Value().ToMilliseconds() <= 0.0 ||
currentTime.Value() > SourceContentEnd())))) {
mHoldTime.SetValue(TimeDuration(SourceContentEnd()));
} else if (mPlaybackRate == 0.0 && currentTime.IsNull()) {
mHoldTime.SetValue(TimeDuration(0));
@ -457,9 +477,8 @@ AnimationPlayer::DoPlay()
PendingPlayerTracker* tracker = doc->GetOrCreatePendingPlayerTracker();
tracker->AddPlayPending(*this);
// We may have updated the current time when we set the hold time above
// so notify source content.
UpdateSourceContent();
// We may have updated the current time when we set the hold time above.
UpdateTiming();
}
void
@ -484,6 +503,8 @@ AnimationPlayer::DoPause()
// Bug 1109390 - check for null result here and go to pending state
mHoldTime = GetCurrentTime();
mStartTime.SetNull();
UpdateFinishedState();
}
void
@ -506,13 +527,72 @@ AnimationPlayer::ResumeAt(const TimeDuration& aResumeTime)
}
mPendingState = PendingState::NotPending;
UpdateSourceContent();
UpdateTiming();
if (mReady) {
mReady->MaybeResolve(this);
}
}
void
AnimationPlayer::UpdateTiming()
{
// We call UpdateFinishedState before UpdateSourceContent because the former
// can change the current time, which is used by the latter.
UpdateFinishedState();
UpdateSourceContent();
}
void
AnimationPlayer::UpdateFinishedState(bool aSeekFlag)
{
Nullable<TimeDuration> currentTime = GetCurrentTime();
TimeDuration targetEffectEnd = TimeDuration(SourceContentEnd());
if (!mStartTime.IsNull() &&
mPendingState == PendingState::NotPending) {
if (mPlaybackRate > 0.0 &&
!currentTime.IsNull() &&
currentTime.Value() >= targetEffectEnd) {
if (aSeekFlag) {
mHoldTime = currentTime;
} else if (!mPreviousCurrentTime.IsNull()) {
mHoldTime.SetValue(std::max(mPreviousCurrentTime.Value(),
targetEffectEnd));
} else {
mHoldTime.SetValue(targetEffectEnd);
}
} else if (mPlaybackRate < 0.0 &&
!currentTime.IsNull() &&
currentTime.Value().ToMilliseconds() <= 0.0) {
if (aSeekFlag) {
mHoldTime = currentTime;
} else {
mHoldTime.SetValue(0);
}
} else if (mPlaybackRate != 0.0 &&
!currentTime.IsNull()) {
if (aSeekFlag && !mHoldTime.IsNull()) {
mStartTime.SetValue(mTimeline->GetCurrentTime().Value() -
(mHoldTime.Value() / mPlaybackRate));
}
mHoldTime.SetNull();
}
}
bool currentFinishedState = IsFinished();
if (currentFinishedState && !mIsPreviousStateFinished) {
if (mFinished) {
mFinished->MaybeResolve(this);
}
} else if (!currentFinishedState && mIsPreviousStateFinished) {
// Clear finished promise. We'll create a new one lazily.
mFinished = nullptr;
}
mIsPreviousStateFinished = currentFinishedState;
mPreviousCurrentTime = currentTime;
}
void
AnimationPlayer::UpdateSourceContent()
{
@ -563,6 +643,19 @@ AnimationPlayer::CancelPendingTasks()
mPendingReadyTime.SetNull();
}
bool
AnimationPlayer::IsFinished() const
{
// Unfortunately there's some weirdness in the spec at the moment where if
// you're finished and paused, the playState is paused. This prevents us
// from just checking |PlayState() == AnimationPlayState::Finished| here,
// and we need this much more messy check to see if we're finished.
Nullable<TimeDuration> currentTime = GetCurrentTime();
return !currentTime.IsNull() &&
((mPlaybackRate > 0.0 && currentTime.Value() >= SourceContentEnd()) ||
(mPlaybackRate < 0.0 && currentTime.Value().ToMilliseconds() <= 0.0));
}
bool
AnimationPlayer::IsPossiblyOrphanedPendingPlayer() const
{

View File

@ -70,6 +70,13 @@ public:
virtual CSSAnimationPlayer* AsCSSAnimationPlayer() { return nullptr; }
virtual CSSTransitionPlayer* AsCSSTransitionPlayer() { return nullptr; }
// Flag to pass to DoPlay to indicate that it should not carry out finishing
// behavior (reset the current time to the beginning of the active duration).
enum LimitBehavior {
AutoRewind = 0,
Continue = 1
};
// AnimationPlayer methods
Animation* GetSource() const { return mSource; }
AnimationTimeline* Timeline() const { return mTimeline; }
@ -83,7 +90,8 @@ public:
void SilentlySetPlaybackRate(double aPlaybackRate);
AnimationPlayState PlayState() const;
virtual Promise* GetReady(ErrorResult& aRv);
virtual void Play();
virtual Promise* GetFinished(ErrorResult& aRv);
virtual void Play(LimitBehavior aLimitBehavior);
virtual void Pause();
bool IsRunningOnCompositor() const { return mIsRunningOnCompositor; }
@ -97,7 +105,7 @@ public:
void SetCurrentTimeAsDouble(const Nullable<double>& aCurrentTime,
ErrorResult& aRv);
virtual AnimationPlayState PlayStateFromJS() const { return PlayState(); }
virtual void PlayFromJS() { Play(); }
virtual void PlayFromJS() { Play(LimitBehavior::AutoRewind); }
// PauseFromJS is currently only here for symmetry with PlayFromJS but
// in future we will likely have to flush style in
// CSSAnimationPlayer::PauseFromJS so we leave it for now.
@ -252,10 +260,12 @@ public:
bool& aNeedsRefreshes);
protected:
void DoPlay();
void DoPlay(LimitBehavior aLimitBehavior);
void DoPause();
void ResumeAt(const TimeDuration& aResumeTime);
void UpdateTiming();
void UpdateFinishedState(bool aSeekFlag = false);
void UpdateSourceContent();
void FlushStyle() const;
void PostUpdate();
@ -266,6 +276,8 @@ protected:
*/
void CancelPendingTasks();
bool IsFinished() const;
bool IsPossiblyOrphanedPendingPlayer() const;
StickyTimeDuration SourceContentEnd() const;
@ -280,13 +292,22 @@ protected:
Nullable<TimeDuration> mStartTime; // Timeline timescale
Nullable<TimeDuration> mHoldTime; // Player timescale
Nullable<TimeDuration> mPendingReadyTime; // Timeline timescale
Nullable<TimeDuration> mPreviousCurrentTime; // Player timescale
double mPlaybackRate;
// A Promise that is replaced on each call to Play() (and in future Pause())
// and fulfilled when Play() is successfully completed.
// This object is lazily created by GetReady.
// See http://w3c.github.io/web-animations/#current-ready-promise
nsRefPtr<Promise> mReady;
// A Promise that is resolved when we reach the end of the source content, or
// 0 when playing backwards. The Promise is replaced if the animation is
// finished but then a state change makes it not finished.
// This object is lazily created by GetFinished.
// See http://w3c.github.io/web-animations/#current-finished-promise
nsRefPtr<Promise> mFinished;
// Indicates if the player is in the pending state (and what state it is
// waiting to enter when it finished pending). We use this rather than
// checking if this player is tracked by a PendingPlayerTracker because the
@ -299,9 +320,6 @@ protected:
bool mIsRunningOnCompositor;
// Indicates whether we were in the finished state during our
// most recent unthrottled sample (our last ComposeStyle call).
// FIXME: When we implement the finished promise (bug 1074630) we can
// probably remove this and check if the promise has been settled yet
// or not instead.
bool mIsPreviousStateFinished; // Spec calls this "previous finished state"
// Indicates that the player should be exposed in an element's
// getAnimationPlayers() list.

View File

@ -29,8 +29,9 @@ interface AnimationPlayer {
readonly attribute AnimationPlayState playState;
[Throws]
readonly attribute Promise<AnimationPlayer> ready;
/*
[Throws]
readonly attribute Promise<AnimationPlayer> finished;
/*
void cancel ();
void finish ();
*/

View File

@ -35,10 +35,10 @@ CSSAnimationPlayer::GetReady(ErrorResult& aRv)
}
void
CSSAnimationPlayer::Play()
CSSAnimationPlayer::Play(LimitBehavior aLimitBehavior)
{
mPauseShouldStick = false;
AnimationPlayer::Play();
AnimationPlayer::Play(aLimitBehavior);
}
void
@ -71,7 +71,7 @@ CSSAnimationPlayer::PlayFromStyle()
{
mIsStylePaused = false;
if (!mPauseShouldStick) {
DoPlay();
DoPlay(AnimationPlayer::LimitBehavior::Continue);
}
}

View File

@ -67,7 +67,7 @@ public:
AsCSSAnimationPlayer() override { return this; }
virtual dom::Promise* GetReady(ErrorResult& aRv) override;
virtual void Play() override;
virtual void Play(LimitBehavior aLimitBehavior) override;
virtual void Pause() override;
virtual dom::AnimationPlayState PlayStateFromJS() const override;

View File

@ -81,7 +81,7 @@ public:
// A variant of Play() that avoids posting style updates since this method
// is expected to be called whilst already updating style.
void PlayFromStyle() { DoPlay(); }
void PlayFromStyle() { DoPlay(AnimationPlayer::LimitBehavior::Continue); }
protected:
virtual ~CSSTransitionPlayer() { }