--- a/devtools/server/actors/animation.js +++ b/devtools/server/actors/animation.js @@ -385,26 +385,24 @@ var AnimationPlayerActor = protocol.Acto // Reset the local copy of the state on removal, since the animation can // be kept on the client and re-added, its state needs to be sent in // full. this.currentState = null; } if (hasCurrentAnimation(changedAnimations)) { // Only consider the state has having changed if any of delay, duration, - // iterationCount, iterationStart, or playbackRate has changed (for now - // at least). + // iterationcount or iterationStart has changed (for now at least). let newState = this.getState(); let oldState = this.currentState; hasChanged = newState.delay !== oldState.delay || newState.iterationCount !== oldState.iterationCount || newState.iterationStart !== oldState.iterationStart || newState.duration !== oldState.duration || - newState.endDelay !== oldState.endDelay || - newState.playbackRate !== oldState.playbackRate; + newState.endDelay !== oldState.endDelay; break; } } if (hasChanged) { this.emit("changed", this.getCurrentState()); } }, @@ -461,18 +459,17 @@ var AnimationPlayerActor = protocol.Acto } this.player.currentTime = currentTime * this.player.playbackRate; }, /** * Set the playback rate of the animation player. */ setPlaybackRate: function (playbackRate) { - this.player.updatePlaybackRate(playbackRate); - return this.player.ready; + this.player.playbackRate = playbackRate; }, /** * Get data about the keyframes of this animation player. * @return {Object} Returns a list of frames, each frame containing the list * animated properties as well as the frame's offset. */ getFrames: function () { @@ -868,13 +865,13 @@ exports.AnimationsActor = protocol.Actor }, /** * Set the playback rate of several animations at the same time. * @param {Array} players A list of AnimationPlayerActor. * @param {Number} rate The new rate. */ setPlaybackRates: function (players, rate) { - return promise.all( - players.map(player => player.setPlaybackRate(rate)) - ); + for (let player of players) { + player.setPlaybackRate(rate); + } } });
--- a/dom/animation/Animation.cpp +++ b/dom/animation/Animation.cpp @@ -268,20 +268,17 @@ Animation::SetStartTime(const Nullable<T // the already null time to null. timelineTime = mTimeline->GetCurrentTime(); } if (timelineTime.IsNull() && !aNewStartTime.IsNull()) { mHoldTime.SetNull(); } Nullable<TimeDuration> previousCurrentTime = GetCurrentTime(); - - ApplyPendingPlaybackRate(); mStartTime = aNewStartTime; - if (!aNewStartTime.IsNull()) { if (mPlaybackRate != 0.0) { mHoldTime.SetNull(); } } else { mHoldTime = previousCurrentTime; } @@ -296,30 +293,29 @@ Animation::SetStartTime(const Nullable<T if (IsRelevant()) { nsNodeUtils::AnimationChanged(this); } PostUpdate(); } // https://drafts.csswg.org/web-animations/#current-time Nullable<TimeDuration> -Animation::GetCurrentTimeForHoldTime( - const Nullable<TimeDuration>& aHoldTime) const +Animation::GetCurrentTime() const { Nullable<TimeDuration> result; - if (!aHoldTime.IsNull()) { - result = aHoldTime; + if (!mHoldTime.IsNull()) { + result = mHoldTime; return result; } if (mTimeline && !mStartTime.IsNull()) { Nullable<TimeDuration> timelineTime = mTimeline->GetCurrentTime(); if (!timelineTime.IsNull()) { - result = CurrentTimeFromTimelineTime( - timelineTime.Value(), mStartTime.Value(), mPlaybackRate); + result.SetValue((timelineTime.Value() - mStartTime.Value()) + .MultDouble(mPlaybackRate)); } } return result; } // https://drafts.csswg.org/web-animations/#set-the-current-time void Animation::SetCurrentTime(const TimeDuration& aSeekTime) @@ -335,39 +331,35 @@ Animation::SetCurrentTime(const TimeDura AutoMutationBatchForAnimation mb(*this); SilentlySetCurrentTime(aSeekTime); if (mPendingState == PendingState::PausePending) { // Finish the pause operation mHoldTime.SetValue(aSeekTime); - - ApplyPendingPlaybackRate(); mStartTime.SetNull(); if (mReady) { mReady->MaybeResolve(this); } CancelPendingTasks(); } UpdateTiming(SeekFlag::DidSeek, SyncNotifyFlag::Async); if (IsRelevant()) { nsNodeUtils::AnimationChanged(this); } PostUpdate(); } -// https://drafts.csswg.org/web-animations/#set-the-playback-rate +// https://drafts.csswg.org/web-animations/#set-the-animation-playback-rate void Animation::SetPlaybackRate(double aPlaybackRate) { - mPendingPlaybackRate.reset(); - if (aPlaybackRate == mPlaybackRate) { return; } AutoMutationBatchForAnimation mb(*this); Nullable<TimeDuration> previousTime = GetCurrentTime(); mPlaybackRate = aPlaybackRate; @@ -385,93 +377,16 @@ Animation::SetPlaybackRate(double aPlayb // - update the playback rate on animations on layers. UpdateTiming(SeekFlag::DidSeek, SyncNotifyFlag::Async); if (IsRelevant()) { nsNodeUtils::AnimationChanged(this); } PostUpdate(); } -// https://drafts.csswg.org/web-animations/#seamlessly-update-the-playback-rate -void -Animation::UpdatePlaybackRate(double aPlaybackRate) -{ - if (mPendingPlaybackRate && mPendingPlaybackRate.value() == aPlaybackRate) { - return; - } - - mPendingPlaybackRate = Some(aPlaybackRate); - - // If we already have a pending task, there is nothing more to do since the - // playback rate will be applied then. - if (Pending()) { - return; - } - - AutoMutationBatchForAnimation mb(*this); - - AnimationPlayState playState = PlayState(); - if (playState == AnimationPlayState::Idle || - playState == AnimationPlayState::Paused) { - // We are either idle or paused. In either case we can apply the pending - // playback rate immediately. - ApplyPendingPlaybackRate(); - - // We don't need to update timing or post an update here because: - // - // * the current time hasn't changed -- it's either unresolved or fixed - // with a hold time -- so the output won't have changed - // * the finished state won't have changed even if the sign of the - // playback rate changed since we're not finished (we're paused or idle) - // * the playback rate on layers doesn't need to be updated since we're not - // moving. Once we get a start time etc. we'll update the playback rate - // then. - // - // All we need to do is update observers so that, e.g. DevTools, report the - // right information. - if (IsRelevant()) { - nsNodeUtils::AnimationChanged(this); - } - } else if (playState == AnimationPlayState::Finished) { - MOZ_ASSERT(mTimeline && !mTimeline->GetCurrentTime().IsNull(), - "If we have no active timeline, we should be idle or paused"); - if (aPlaybackRate != 0) { - // The unconstrained current time can only be unresolved if either we - // don't have an active timeline (and we already asserted that is not - // true) or we have an unresolved start time (in which case we should be - // paused). - MOZ_ASSERT(!GetUnconstrainedCurrentTime().IsNull(), - "Unconstrained current time should be resolved"); - TimeDuration unconstrainedCurrentTime = - GetUnconstrainedCurrentTime().Value(); - TimeDuration timelineTime = mTimeline->GetCurrentTime().Value(); - mStartTime = StartTimeFromTimelineTime( - timelineTime, unconstrainedCurrentTime, aPlaybackRate); - } else { - mStartTime = mTimeline->GetCurrentTime(); - } - - ApplyPendingPlaybackRate(); - - // Even though we preserve the current time, we might now leave the finished - // state (e.g. if the playback rate changes sign) so we need to update - // timing. - UpdateTiming(SeekFlag::NoSeek, SyncNotifyFlag::Async); - if (IsRelevant()) { - nsNodeUtils::AnimationChanged(this); - } - PostUpdate(); - } else { - ErrorResult rv; - Play(rv, LimitBehavior::Continue); - MOZ_ASSERT(!rv.Failed(), - "We should only fail to play when using auto-rewind behavior"); - } -} - // https://drafts.csswg.org/web-animations/#play-state AnimationPlayState Animation::PlayState() const { if (!nsContentUtils::AnimationsAPIPendingMemberEnabled() && Pending()) { return AnimationPlayState::Pending; } @@ -534,46 +449,42 @@ Animation::Cancel() CancelNoUpdate(); PostUpdate(); } // https://drafts.csswg.org/web-animations/#finish-an-animation void Animation::Finish(ErrorResult& aRv) { - double effectivePlaybackRate = CurrentOrPendingPlaybackRate(); - - if (effectivePlaybackRate == 0 || - (effectivePlaybackRate > 0 && EffectEnd() == TimeDuration::Forever())) { + if (mPlaybackRate == 0 || + (mPlaybackRate > 0 && EffectEnd() == TimeDuration::Forever())) { aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); return; } AutoMutationBatchForAnimation mb(*this); - ApplyPendingPlaybackRate(); - // Seek to the end TimeDuration limit = mPlaybackRate > 0 ? TimeDuration(EffectEnd()) : TimeDuration(0); bool didChange = GetCurrentTime() != Nullable<TimeDuration>(limit); SilentlySetCurrentTime(limit); // If we are paused or play-pending we need to fill in the start time in // order to transition to the finished state. // // We only do this, however, if we have an active timeline. If we have an // inactive timeline we can't transition into the finished state just like // we can't transition to the running state (this finished state is really // a substate of the running state). if (mStartTime.IsNull() && mTimeline && !mTimeline->GetCurrentTime().IsNull()) { - mStartTime = StartTimeFromTimelineTime( - mTimeline->GetCurrentTime().Value(), limit, mPlaybackRate); + mStartTime.SetValue(mTimeline->GetCurrentTime().Value() - + limit.MultDouble(1.0 / mPlaybackRate)); didChange = true; } // If we just resolved the start time for a pause or play-pending // animation, we need to clear the task. We don't do this as a branch of // the above however since we can have a play-pending animation with a // resolved start time if we aborted a pause operation. if (!mStartTime.IsNull() && @@ -613,34 +524,35 @@ Animation::Pause(ErrorResult& aRv) void Animation::Reverse(ErrorResult& aRv) { if (!mTimeline || mTimeline->GetCurrentTime().IsNull()) { aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); return; } - double effectivePlaybackRate = CurrentOrPendingPlaybackRate(); - - if (effectivePlaybackRate == 0.0) { + if (mPlaybackRate == 0.0) { return; } - Maybe<double> originalPendingPlaybackRate = mPendingPlaybackRate; + AutoMutationBatchForAnimation mb(*this); - mPendingPlaybackRate = Some(-effectivePlaybackRate); - + SilentlySetPlaybackRate(-mPlaybackRate); Play(aRv, LimitBehavior::AutoRewind); // If Play() threw, restore state and don't report anything to mutation // observers. if (aRv.Failed()) { - mPendingPlaybackRate = originalPendingPlaybackRate; + SilentlySetPlaybackRate(-mPlaybackRate); + return; } + if (IsRelevant()) { + nsNodeUtils::AnimationChanged(this); + } // Play(), above, unconditionally calls PostUpdate so we don't need to do // it here. } // --------------------------------------------------------------------------- // // JS wrappers for Animation interface: // @@ -760,53 +672,42 @@ Animation::TriggerNow() FinishPendingAt(mTimeline->GetCurrentTime().Value()); } Nullable<TimeDuration> Animation::GetCurrentOrPendingStartTime() const { Nullable<TimeDuration> result; - // If we have a pending playback rate, work out what start time we will use - // when we come to updating that playback rate. - // - // This logic roughly shadows that in ResumeAt but is just different enough - // that it is difficult to extract out the common functionality (and - // extracting that functionality out would make it harder to match ResumeAt up - // against the spec). - if (mPendingPlaybackRate && !mPendingReadyTime.IsNull() && - !mStartTime.IsNull()) { - // If we have a hold time, use it as the current time to match. - TimeDuration currentTimeToMatch = - !mHoldTime.IsNull() - ? mHoldTime.Value() - : CurrentTimeFromTimelineTime( - mPendingReadyTime.Value(), mStartTime.Value(), mPlaybackRate); - - result = StartTimeFromTimelineTime( - mPendingReadyTime.Value(), currentTimeToMatch, *mPendingPlaybackRate); - return result; - } - if (!mStartTime.IsNull()) { result = mStartTime; return result; } if (mPendingReadyTime.IsNull() || mHoldTime.IsNull()) { return result; } // Calculate the equivalent start time from the pending ready time. - result = StartTimeFromTimelineTime( - mPendingReadyTime.Value(), mHoldTime.Value(), mPlaybackRate); + result = StartTimeFromReadyTime(mPendingReadyTime.Value()); return result; } +TimeDuration +Animation::StartTimeFromReadyTime(const TimeDuration& aReadyTime) const +{ + MOZ_ASSERT(!mHoldTime.IsNull(), "Hold time should be set in order to" + " convert a ready time to a start time"); + if (mPlaybackRate == 0) { + return aReadyTime; + } + return aReadyTime - mHoldTime.Value().MultDouble(1 / mPlaybackRate); +} + TimeStamp Animation::AnimationTimeToTimeStamp(const StickyTimeDuration& aTime) const { // Initializes to null. Return the same object every time to benefit from // return-value-optimization. TimeStamp result; // We *don't* check for mTimeline->TracksWallclockTime() here because that @@ -827,17 +728,17 @@ Animation::AnimationTimeToTimeStamp(cons // Check the time is convertible to a timestamp if (aTime == TimeDuration::Forever() || mPlaybackRate == 0.0 || mStartTime.IsNull()) { return result; } // Invert the standard relation: - // current time = (timeline time - start time) * playback rate + // animation time = (timeline time - start time) * playback rate TimeDuration timelineTime = TimeDuration(aTime).MultDouble(1.0 / mPlaybackRate) + mStartTime.Value(); result = mTimeline->ToTimeStamp(timelineTime); return result; } TimeStamp @@ -859,23 +760,33 @@ Animation::SilentlySetCurrentTime(const !mTimeline || mTimeline->GetCurrentTime().IsNull() || mPlaybackRate == 0.0) { mHoldTime.SetValue(aSeekTime); if (!mTimeline || mTimeline->GetCurrentTime().IsNull()) { mStartTime.SetNull(); } } else { - mStartTime = StartTimeFromTimelineTime( - mTimeline->GetCurrentTime().Value(), aSeekTime, mPlaybackRate); + mStartTime.SetValue(mTimeline->GetCurrentTime().Value() - + (aSeekTime.MultDouble(1 / mPlaybackRate))); } mPreviousCurrentTime.SetNull(); } +void +Animation::SilentlySetPlaybackRate(double aPlaybackRate) +{ + Nullable<TimeDuration> previousTime = GetCurrentTime(); + mPlaybackRate = aPlaybackRate; + if (!previousTime.IsNull()) { + SilentlySetCurrentTime(previousTime.Value()); + } +} + // https://drafts.csswg.org/web-animations/#cancel-an-animation void Animation::CancelNoUpdate() { if (PlayState() != AnimationPlayState::Idle) { ResetPendingTasks(); if (mFinished) { @@ -1085,18 +996,18 @@ Animation::ComposeStyle(ComposeAnimation if (pending && mHoldTime.IsNull() && !mStartTime.IsNull()) { Nullable<TimeDuration> timeToUse = mPendingReadyTime; if (timeToUse.IsNull() && mTimeline && mTimeline->TracksWallclockTime()) { timeToUse = mTimeline->ToTimelineTime(TimeStamp::Now()); } if (!timeToUse.IsNull()) { - mHoldTime = CurrentTimeFromTimelineTime( - timeToUse.Value(), mStartTime.Value(), mPlaybackRate); + mHoldTime.SetValue((timeToUse.Value() - mStartTime.Value()) + .MultDouble(mPlaybackRate)); } } KeyframeEffectReadOnly* keyframeEffect = mEffect->AsKeyframeEffect(); if (keyframeEffect) { keyframeEffect->ComposeStyle(Forward<ComposeAnimationResult>(aComposeResult), aPropertiesToSkip); } @@ -1129,57 +1040,48 @@ Animation::NotifyGeometricAnimationsStar // https://drafts.csswg.org/web-animations/#play-an-animation void Animation::PlayNoUpdate(ErrorResult& aRv, LimitBehavior aLimitBehavior) { AutoMutationBatchForAnimation mb(*this); bool abortedPause = mPendingState == PendingState::PausePending; - double effectivePlaybackRate = CurrentOrPendingPlaybackRate(); - Nullable<TimeDuration> currentTime = GetCurrentTime(); - if (effectivePlaybackRate > 0.0 && + if (mPlaybackRate > 0.0 && (currentTime.IsNull() || (aLimitBehavior == LimitBehavior::AutoRewind && (currentTime.Value() < TimeDuration() || currentTime.Value() >= EffectEnd())))) { mHoldTime.SetValue(TimeDuration(0)); - } else if (effectivePlaybackRate < 0.0 && + } else if (mPlaybackRate < 0.0 && (currentTime.IsNull() || (aLimitBehavior == LimitBehavior::AutoRewind && (currentTime.Value() <= TimeDuration() || currentTime.Value() > EffectEnd())))) { if (EffectEnd() == TimeDuration::Forever()) { aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); return; } mHoldTime.SetValue(TimeDuration(EffectEnd())); - } else if (effectivePlaybackRate == 0.0 && currentTime.IsNull()) { + } else if (mPlaybackRate == 0.0 && currentTime.IsNull()) { mHoldTime.SetValue(TimeDuration(0)); } bool reuseReadyPromise = false; if (mPendingState != PendingState::NotPending) { CancelPendingTasks(); reuseReadyPromise = true; } - // If the hold time is null then we're already playing normally and, - // typically, we can bail out here. - // - // However, there are two cases where we can't do that: - // - // (a) If we just aborted a pause. In this case, for consistency, we need to - // go through the motions of doing an asynchronous start. - // - // (b) If we have timing changes (specifically a change to the playbackRate) - // that should be applied asynchronously. - // - if (mHoldTime.IsNull() && !abortedPause && !mPendingPlaybackRate) { + // If the hold time is null then we're either already playing normally (and + // we can ignore this call) or we aborted a pending pause operation (in which + // case, for consistency, we need to go through the motions of doing an + // asynchronous start even though we already have a resolved start time). + if (mHoldTime.IsNull() && !abortedPause) { return; } // Clear the start time until we resolve a new one. We do this except // for the case where we are aborting a pause and don't have a hold time. // // If we're aborting a pause and *do* have a hold time (e.g. because // the animation is finished or we just applied the auto-rewind behavior @@ -1266,79 +1168,55 @@ Animation::PauseNoUpdate(ErrorResult& aR } UpdateTiming(SeekFlag::NoSeek, SyncNotifyFlag::Async); if (IsRelevant()) { nsNodeUtils::AnimationChanged(this); } } -// https://drafts.csswg.org/web-animations/#play-an-animation void Animation::ResumeAt(const TimeDuration& aReadyTime) { // This method is only expected to be called for an animation that is // waiting to play. We can easily adapt it to handle other states // but it's currently not necessary. MOZ_ASSERT(mPendingState == PendingState::PlayPending, "Expected to resume a play-pending animation"); MOZ_ASSERT(!mHoldTime.IsNull() || !mStartTime.IsNull(), "An animation in the play-pending state should have either a" " resolved hold time or resolved start time"); - AutoMutationBatchForAnimation mb(*this); - bool hadPendingPlaybackRate = mPendingPlaybackRate.isSome(); - - if (!mHoldTime.IsNull()) { - // The hold time is set, so we don't need any special handling to preserve - // the current time. - ApplyPendingPlaybackRate(); - mStartTime = - StartTimeFromTimelineTime(aReadyTime, mHoldTime.Value(), mPlaybackRate); + // If we aborted a pending pause operation we will already have a start time + // we should use. In all other cases, we resolve it from the ready time. + if (mStartTime.IsNull()) { + mStartTime = StartTimeFromReadyTime(aReadyTime); if (mPlaybackRate != 0) { mHoldTime.SetNull(); } - } else if (!mStartTime.IsNull() && mPendingPlaybackRate) { - // Apply any pending playback rate, preserving the current time. - TimeDuration currentTimeToMatch = CurrentTimeFromTimelineTime( - aReadyTime, mStartTime.Value(), mPlaybackRate); - ApplyPendingPlaybackRate(); - mStartTime = - StartTimeFromTimelineTime(aReadyTime, currentTimeToMatch, mPlaybackRate); - if (mPlaybackRate == 0) { - mHoldTime.SetValue(currentTimeToMatch); - } } - mPendingState = PendingState::NotPending; UpdateTiming(SeekFlag::NoSeek, SyncNotifyFlag::Async); - // If we had a pending playback rate, we will have now applied it so we need - // to notify observers. - if (hadPendingPlaybackRate && IsRelevant()) { - nsNodeUtils::AnimationChanged(this); - } - if (mReady) { mReady->MaybeResolve(this); } } void Animation::PauseAt(const TimeDuration& aReadyTime) { MOZ_ASSERT(mPendingState == PendingState::PausePending, "Expected to pause a pause-pending animation"); if (!mStartTime.IsNull() && mHoldTime.IsNull()) { - mHoldTime = CurrentTimeFromTimelineTime( - aReadyTime, mStartTime.Value(), mPlaybackRate); + mHoldTime.SetValue((aReadyTime - mStartTime.Value()) + .MultDouble(mPlaybackRate)); } - ApplyPendingPlaybackRate(); mStartTime.SetNull(); mPendingState = PendingState::NotPending; UpdateTiming(SeekFlag::NoSeek, SyncNotifyFlag::Async); if (mReady) { mReady->MaybeResolve(this); } @@ -1388,20 +1266,18 @@ Animation::UpdateFinishedState(SeekFlag } else { mHoldTime.SetValue(0); } } else if (mPlaybackRate != 0.0 && !currentTime.IsNull() && mTimeline && !mTimeline->GetCurrentTime().IsNull()) { if (aSeekFlag == SeekFlag::DidSeek && !mHoldTime.IsNull()) { - mStartTime = - StartTimeFromTimelineTime(mTimeline->GetCurrentTime().Value(), - mHoldTime.Value(), - mPlaybackRate); + mStartTime.SetValue(mTimeline->GetCurrentTime().Value() - + (mHoldTime.Value().MultDouble(1 / mPlaybackRate))); } mHoldTime.SetNull(); } } bool currentFinishedState = PlayState() == AnimationPlayState::Finished; if (currentFinishedState && !mFinishedIsResolved) { DoFinishNotification(aSyncNotifyFlag); @@ -1476,18 +1352,16 @@ Animation::CancelPendingTasks() void Animation::ResetPendingTasks() { if (mPendingState == PendingState::NotPending) { return; } CancelPendingTasks(); - ApplyPendingPlaybackRate(); - if (mReady) { mReady->MaybeReject(NS_ERROR_DOM_ABORT_ERR); mReady = nullptr; } } bool Animation::IsPossiblyOrphanedPendingAnimation() const
--- a/dom/animation/Animation.h +++ b/dom/animation/Animation.h @@ -100,32 +100,29 @@ public: void GetId(nsAString& aResult) const { aResult = mId; } void SetId(const nsAString& aId); AnimationEffectReadOnly* GetEffect() const { return mEffect; } void SetEffect(AnimationEffectReadOnly* aEffect); AnimationTimeline* GetTimeline() const { return mTimeline; } void SetTimeline(AnimationTimeline* aTimeline); Nullable<TimeDuration> GetStartTime() const { return mStartTime; } void SetStartTime(const Nullable<TimeDuration>& aNewStartTime); - Nullable<TimeDuration> GetCurrentTime() const { - return GetCurrentTimeForHoldTime(mHoldTime); - } + Nullable<TimeDuration> GetCurrentTime() const; void SetCurrentTime(const TimeDuration& aNewCurrentTime); double PlaybackRate() const { return mPlaybackRate; } void SetPlaybackRate(double aPlaybackRate); AnimationPlayState PlayState() const; bool Pending() const { return mPendingState != PendingState::NotPending; } virtual Promise* GetReady(ErrorResult& aRv); Promise* GetFinished(ErrorResult& aRv); void Cancel(); void Finish(ErrorResult& aRv); virtual void Play(ErrorResult& aRv, LimitBehavior aLimitBehavior); virtual void Pause(ErrorResult& aRv); void Reverse(ErrorResult& aRv); - void UpdatePlaybackRate(double aPlaybackRate); bool IsRunningOnCompositor() const; IMPL_EVENT_HANDLER(finish); IMPL_EVENT_HANDLER(cancel); // Wrapper functions for Animation DOM methods when called // from script. // // We often use the same methods internally and from script but when called @@ -240,64 +237,21 @@ public: * * This method returns the start time, if resolved. Otherwise, if we have * a pending ready time, it returns the corresponding start time. If neither * of those are available, it returns null. */ Nullable<TimeDuration> GetCurrentOrPendingStartTime() const; /** - * As with the start time, we should use the pending playback rate when - * producing layer animations. - */ - double CurrentOrPendingPlaybackRate() const - { - return mPendingPlaybackRate.valueOr(mPlaybackRate); - } - bool HasPendingPlaybackRate() const { return mPendingPlaybackRate.isSome(); } - - /** - * The following relationship from the definition of the 'current time' is - * re-used in many algorithms so we extract it here into a static method that - * can be re-used: - * - * current time = (timeline time - start time) * playback rate - * - * As per https://drafts.csswg.org/web-animations-1/#current-time + * Calculates the corresponding start time to use for an animation that is + * currently pending with current time |mHoldTime| but should behave + * as if it began or resumed playback at timeline time |aReadyTime|. */ - static TimeDuration CurrentTimeFromTimelineTime( - const TimeDuration& aTimelineTime, - const TimeDuration& aStartTime, - float aPlaybackRate) - { - return (aTimelineTime - aStartTime).MultDouble(aPlaybackRate); - } - - /** - * As with calculating the current time, we often need to calculate a start - * time from a current time. The following method simply inverts the current - * time relationship. - * - * In each case where this is used, the desired behavior for playbackRate == - * 0 is to return the specified timeline time (often referred to as the ready - * time). - */ - static TimeDuration StartTimeFromTimelineTime( - const TimeDuration& aTimelineTime, - const TimeDuration& aCurrentTime, - float aPlaybackRate) - { - TimeDuration result = aTimelineTime; - if (aPlaybackRate == 0) { - return result; - } - - result -= aCurrentTime.MultDouble(1.0 / aPlaybackRate); - return result; - } + TimeDuration StartTimeFromReadyTime(const TimeDuration& aReadyTime) const; /** * Converts a time in the timescale of this Animation's currentTime, to a * TimeStamp. Returns a null TimeStamp if the conversion cannot be performed * because of the current state of this Animation (e.g. it has no timeline, a * zero playbackRate, an unresolved start time etc.) or the value of the time * passed-in (e.g. an infinite time). */ @@ -397,38 +351,32 @@ public: * We need to do this synchronously because after a CSS animation/transition * is canceled, it will be released by its owning element and may not still * exist when we would normally go to queue events on the next tick. */ virtual void MaybeQueueCancelEvent(const StickyTimeDuration& aActiveTime) {}; protected: void SilentlySetCurrentTime(const TimeDuration& aNewCurrentTime); + void SilentlySetPlaybackRate(double aPlaybackRate); void CancelNoUpdate(); void PlayNoUpdate(ErrorResult& aRv, LimitBehavior aLimitBehavior); void PauseNoUpdate(ErrorResult& aRv); void ResumeAt(const TimeDuration& aReadyTime); void PauseAt(const TimeDuration& aReadyTime); void FinishPendingAt(const TimeDuration& aReadyTime) { if (mPendingState == PendingState::PlayPending) { ResumeAt(aReadyTime); } else if (mPendingState == PendingState::PausePending) { PauseAt(aReadyTime); } else { NS_NOTREACHED("Can't finish pending if we're not in a pending state"); } } - void ApplyPendingPlaybackRate() - { - if (mPendingPlaybackRate) { - mPlaybackRate = *mPendingPlaybackRate; - mPendingPlaybackRate.reset(); - } - } /** * Finishing behavior depends on if changes to timing occurred due * to a seek or regular playback. */ enum class SeekFlag { NoSeek, DidSeek @@ -467,47 +415,34 @@ protected: /** * Returns true if this animation is not only play-pending, but has * yet to be given a pending ready time. This roughly corresponds to * animations that are waiting to be painted (since we set the pending * ready time at the end of painting). Identifying such animations is * useful because in some cases animations that are painted together * may need to be synchronized. - * - * We don't, however, want to include animations with a fixed start time such - * as animations that are simply having their playbackRate updated or which - * are resuming from an aborted pause. */ bool IsNewlyStarted() const { return mPendingState == PendingState::PlayPending && - mPendingReadyTime.IsNull() && - mStartTime.IsNull(); + mPendingReadyTime.IsNull(); } bool IsPossiblyOrphanedPendingAnimation() const; StickyTimeDuration EffectEnd() const; - Nullable<TimeDuration> GetCurrentTimeForHoldTime( - const Nullable<TimeDuration>& aHoldTime) const; - Nullable<TimeDuration> GetUnconstrainedCurrentTime() const - { - return GetCurrentTimeForHoldTime(Nullable<TimeDuration>()); - } - nsIDocument* GetRenderedDocument() const; RefPtr<AnimationTimeline> mTimeline; RefPtr<AnimationEffectReadOnly> mEffect; // The beginning of the delay period. Nullable<TimeDuration> mStartTime; // Timeline timescale Nullable<TimeDuration> mHoldTime; // Animation timescale Nullable<TimeDuration> mPendingReadyTime; // Timeline timescale Nullable<TimeDuration> mPreviousCurrentTime; // Animation timescale double mPlaybackRate; - Maybe<double> mPendingPlaybackRate; // A Promise that is replaced on each call to Play() // and fulfilled when Play() is successfully completed. // This object is lazily created by GetReady. // See http://drafts.csswg.org/web-animations/#current-ready-promise RefPtr<Promise> mReady; // A Promise that is resolved when we reach the end of the effect, or
--- a/dom/webidl/Animation.webidl +++ b/dom/webidl/Animation.webidl @@ -39,17 +39,16 @@ interface Animation : EventTarget { attribute EventHandler oncancel; void cancel (); [Throws] void finish (); [Throws, BinaryName="playFromJS"] void play (); [Throws, BinaryName="pauseFromJS"] void pause (); - void updatePlaybackRate (double playbackRate); [Throws] void reverse (); }; // Non-standard extensions partial interface Animation { [ChromeOnly] readonly attribute boolean isRunningOnCompositor; };
--- a/gfx/layers/AnimationInfo.cpp +++ b/gfx/layers/AnimationInfo.cpp @@ -2,17 +2,16 @@ /* vim: set ts=8 sts=2 et sw=2 tw=80: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "AnimationInfo.h" #include "mozilla/layers/WebRenderLayerManager.h" #include "mozilla/layers/AnimationHelper.h" -#include "mozilla/dom/Animation.h" namespace mozilla { namespace layers { AnimationInfo::AnimationInfo(LayerManager* aManager) : mManager(aManager), mCompositorAnimationsId(0), mAnimationGeneration(0), @@ -99,37 +98,27 @@ AnimationInfo::SetCompositorAnimations(c bool AnimationInfo::StartPendingAnimations(const TimeStamp& aReadyTime) { bool updated = false; for (size_t animIdx = 0, animEnd = mAnimations.Length(); animIdx < animEnd; animIdx++) { Animation& anim = mAnimations[animIdx]; - // If the animation is doing an async update of its playback rate, then we - // want to match whatever its current time would be at *aReadyTime*. - if (!std::isnan(anim.previousPlaybackRate()) && - anim.startTime().type() == MaybeTimeDuration::TTimeDuration && - !anim.originTime().IsNull() && !anim.isNotPlaying()) { - TimeDuration readyTime = aReadyTime - anim.originTime(); - anim.holdTime() = dom::Animation::CurrentTimeFromTimelineTime( - readyTime, - anim.startTime().get_TimeDuration(), - anim.previousPlaybackRate()); - // Make start time null so that we know to update it below. - anim.startTime() = null_t(); - } - // If the animation is play-pending, resolve the start time. + // This mirrors the calculation in Animation::StartTimeFromReadyTime. if (anim.startTime().type() == MaybeTimeDuration::Tnull_t && !anim.originTime().IsNull() && !anim.isNotPlaying()) { TimeDuration readyTime = aReadyTime - anim.originTime(); - anim.startTime() = dom::Animation::StartTimeFromTimelineTime( - readyTime, anim.holdTime(), anim.playbackRate()); + anim.startTime() = + anim.playbackRate() == 0 + ? readyTime + : readyTime - anim.holdTime().MultDouble(1.0 / + anim.playbackRate()); updated = true; } } return updated; } void AnimationInfo::TransferMutatedFlagToLayer(Layer* aLayer)
--- a/gfx/layers/ipc/LayersMessages.ipdlh +++ b/gfx/layers/ipc/LayersMessages.ipdlh @@ -236,25 +236,16 @@ struct Animation { float iterationStart; // This uses the NS_STYLE_ANIMATION_DIRECTION_* constants. uint8_t direction; // This uses dom::FillMode. uint8_t fillMode; nsCSSPropertyID property; AnimationData data; float playbackRate; - // When performing an asynchronous update to the playbackRate, |playbackRate| - // above is the updated playbackRate while |previousPlaybackRate| is the - // existing playbackRate. This is used by AnimationInfo to update the - // startTime based on the 'readyTime' (timestamp at the end of painting) - // and is not used beyond that point. - // - // It is set to numeric_limits<float>::quiet_NaN() when no asynchronous update - // to the playbackRate is being performed. - float previousPlaybackRate; // This is used in the transformed progress calculation. TimingFunction easingFunction; uint8_t iterationComposite; // True if the animation has a fixed current time (e.g. paused and // forward-filling animations). bool isNotPlaying; // The base style that animations should composite with. This is only set for // animations with a composite mode of additive or accumulate, and only for
--- a/layout/painting/nsDisplayList.cpp +++ b/layout/painting/nsDisplayList.cpp @@ -566,21 +566,17 @@ AddAnimationForProperty(nsIFrame* aFrame animation->delay() = timing.Delay(); animation->endDelay() = timing.EndDelay(); animation->duration() = computedTiming.mDuration; animation->iterations() = computedTiming.mIterations; animation->iterationStart() = computedTiming.mIterationStart; animation->direction() = static_cast<uint8_t>(timing.Direction()); animation->fillMode() = static_cast<uint8_t>(computedTiming.mFill); animation->property() = aProperty.mProperty; - animation->playbackRate() = aAnimation->CurrentOrPendingPlaybackRate(); - animation->previousPlaybackRate() = - aAnimation->HasPendingPlaybackRate() - ? aAnimation->PlaybackRate() - : std::numeric_limits<float>::quiet_NaN(); + animation->playbackRate() = aAnimation->PlaybackRate(); animation->data() = aData; animation->easingFunction() = ToTimingFunction(timing.TimingFunction()); animation->iterationComposite() = static_cast<uint8_t>(aAnimation->GetEffect()-> AsKeyframeEffect()->IterationComposite()); animation->isNotPlaying() = !aAnimation->IsPlaying(); TransformReferenceBox refBox(aFrame);
--- a/testing/web-platform/meta/MANIFEST.json +++ b/testing/web-platform/meta/MANIFEST.json @@ -358577,22 +358577,16 @@ ] ], "web-animations/animation-model/animation-types/visibility.html": [ [ "/web-animations/animation-model/animation-types/visibility.html", {} ] ], - "web-animations/animation-model/combining-effects/applying-the-composited-result.html": [ - [ - "/web-animations/animation-model/combining-effects/applying-the-composited-result.html", - {} - ] - ], "web-animations/animation-model/combining-effects/effect-composition.html": [ [ "/web-animations/animation-model/combining-effects/effect-composition.html", {} ] ], "web-animations/animation-model/keyframe-effects/effect-value-context.html": [ [ @@ -358649,16 +358643,22 @@ ] ], "web-animations/interfaces/Animation/effect.html": [ [ "/web-animations/interfaces/Animation/effect.html", {} ] ], + "web-animations/interfaces/Animation/finish.html": [ + [ + "/web-animations/interfaces/Animation/finish.html", + {} + ] + ], "web-animations/interfaces/Animation/finished.html": [ [ "/web-animations/interfaces/Animation/finished.html", {} ] ], "web-animations/interfaces/Animation/id.html": [ [ @@ -358697,16 +358697,22 @@ ] ], "web-animations/interfaces/Animation/play.html": [ [ "/web-animations/interfaces/Animation/play.html", {} ] ], + "web-animations/interfaces/Animation/playbackRate.html": [ + [ + "/web-animations/interfaces/Animation/playbackRate.html", + {} + ] + ], "web-animations/interfaces/Animation/ready.html": [ [ "/web-animations/interfaces/Animation/ready.html", {} ] ], "web-animations/interfaces/Animation/startTime.html": [ [ @@ -358895,16 +358901,22 @@ ] ], "web-animations/timing-model/animations/canceling-an-animation.html": [ [ "/web-animations/timing-model/animations/canceling-an-animation.html", {} ] ], + "web-animations/timing-model/animations/current-time.html": [ + [ + "/web-animations/timing-model/animations/current-time.html", + {} + ] + ], "web-animations/timing-model/animations/finishing-an-animation.html": [ [ "/web-animations/timing-model/animations/finishing-an-animation.html", {} ] ], "web-animations/timing-model/animations/pausing-an-animation.html": [ [ @@ -358925,55 +358937,31 @@ ] ], "web-animations/timing-model/animations/reversing-an-animation.html": [ [ "/web-animations/timing-model/animations/reversing-an-animation.html", {} ] ], - "web-animations/timing-model/animations/seamlessly-updating-the-playback-rate-of-an-animation.html": [ - [ - "/web-animations/timing-model/animations/seamlessly-updating-the-playback-rate-of-an-animation.html", - {} - ] - ], - "web-animations/timing-model/animations/setting-the-current-time-of-an-animation.html": [ - [ - "/web-animations/timing-model/animations/setting-the-current-time-of-an-animation.html", - {} - ] - ], - "web-animations/timing-model/animations/setting-the-playback-rate-of-an-animation.html": [ - [ - "/web-animations/timing-model/animations/setting-the-playback-rate-of-an-animation.html", - {} - ] - ], - "web-animations/timing-model/animations/setting-the-start-time-of-an-animation.html": [ - [ - "/web-animations/timing-model/animations/setting-the-start-time-of-an-animation.html", - {} - ] - ], - "web-animations/timing-model/animations/setting-the-target-effect-of-an-animation.html": [ - [ - "/web-animations/timing-model/animations/setting-the-target-effect-of-an-animation.html", - {} - ] - ], - "web-animations/timing-model/animations/setting-the-timeline-of-an-animation.html": [ - [ - "/web-animations/timing-model/animations/setting-the-timeline-of-an-animation.html", - {} - ] - ], - "web-animations/timing-model/animations/the-current-time-of-an-animation.html": [ - [ - "/web-animations/timing-model/animations/the-current-time-of-an-animation.html", + "web-animations/timing-model/animations/set-the-animation-start-time.html": [ + [ + "/web-animations/timing-model/animations/set-the-animation-start-time.html", + {} + ] + ], + "web-animations/timing-model/animations/set-the-target-effect-of-an-animation.html": [ + [ + "/web-animations/timing-model/animations/set-the-target-effect-of-an-animation.html", + {} + ] + ], + "web-animations/timing-model/animations/set-the-timeline-of-an-animation.html": [ + [ + "/web-animations/timing-model/animations/set-the-timeline-of-an-animation.html", {} ] ], "web-animations/timing-model/animations/updating-the-finished-state.html": [ [ "/web-animations/timing-model/animations/updating-the-finished-state.html", {} ] @@ -591488,20 +591476,16 @@ "web-animations/animation-model/animation-types/property-types.js": [ "d5ccae3dd857f15eab45de2e70b332aaa126a575", "support" ], "web-animations/animation-model/animation-types/visibility.html": [ "da3370704ca9e83a1171a64320a240e3145fab2c", "testharness" ], - "web-animations/animation-model/combining-effects/applying-the-composited-result.html": [ - "5262331e6a2f957dd70e9a9888825250de65fd5b", - "testharness" - ], "web-animations/animation-model/combining-effects/effect-composition.html": [ "5d6c3904de02eb3b6c890163ccdc6b8cb6499e56", "testharness" ], "web-animations/animation-model/keyframe-effects/effect-value-context.html": [ "da405e4bfc35d5d0b4c151706b09eb1a84d2f0da", "testharness" ], @@ -591536,26 +591520,30 @@ "web-animations/interfaces/Animation/constructor.html": [ "f4dc4fdca61255557ed346412e134745bce1a3ed", "testharness" ], "web-animations/interfaces/Animation/effect.html": [ "4445fc8bd2120fb1e212dfc6a1fcf786a531ee6f", "testharness" ], + "web-animations/interfaces/Animation/finish.html": [ + "64acecec8528b4d241d5dcb9248ef82eafa02810", + "testharness" + ], "web-animations/interfaces/Animation/finished.html": [ "ffcba3379db7094455a7798e4d5972d8e52caec5", "testharness" ], "web-animations/interfaces/Animation/id.html": [ "4e3dd92351d76c5c7d09ddd1ca025520f4c8875d", "testharness" ], "web-animations/interfaces/Animation/idlharness.html": [ - "d61aa2d95ea31809a275183408e822c8c1eec87d", + "989e773dbf3d7d57f26b41108bc3d7f0b3ea3168", "testharness" ], "web-animations/interfaces/Animation/oncancel.html": [ "82abc08a0b416f5198239464fb4fc01d2edd6e1c", "testharness" ], "web-animations/interfaces/Animation/onfinish.html": [ "db82fabeaf2b646647f134634fef30f05e5ec7f8", @@ -591568,16 +591556,20 @@ "web-animations/interfaces/Animation/pending.html": [ "5677f7e8b076dc096d636aaaa4d4191c286f1d90", "testharness" ], "web-animations/interfaces/Animation/play.html": [ "54edbdd6c0e1953f8d0e2bfbb92bfe318114ab74", "testharness" ], + "web-animations/interfaces/Animation/playbackRate.html": [ + "a298a65aaeb5a337fe894f0160493693f309c2a1", + "testharness" + ], "web-animations/interfaces/Animation/ready.html": [ "bd4a18205791b2b0271a6266dba3ebc8482c835b", "testharness" ], "web-animations/interfaces/Animation/startTime.html": [ "01f669542434f03d37e9f148a4f3135fe3122d46", "testharness" ], @@ -591721,69 +591713,57 @@ "3edd2c4bdd8409c2c12f08bc998dd8d532e0fd7d", "testharness" ], "web-animations/timing-model/animation-effects/simple-iteration-progress.html": [ "602fe7e6880e0b18329262699872c696f451d744", "testharness" ], "web-animations/timing-model/animations/canceling-an-animation.html": [ - "e03baa30d438529a0ebe39f0f623563aa9850d74", + "d82cbc5caf654b9811c90d5165fb0429891cb149", + "testharness" + ], + "web-animations/timing-model/animations/current-time.html": [ + "52d23e752878c821754b2c2b752e7393882609e2", "testharness" ], "web-animations/timing-model/animations/finishing-an-animation.html": [ - "4c1cf823a81e72541abcafaa08950cf87424ae55", + "8d430adcb97bf3dab9703bc2d31be23e1adaec85", "testharness" ], "web-animations/timing-model/animations/pausing-an-animation.html": [ - "a4cb7b89c778ad5c294eeb55e94461e19ca8eb4b", + "c46fbcb8bc40fc3ee26e10802a205926ab97a84f", "testharness" ], "web-animations/timing-model/animations/play-states.html": [ - "0ab2fa3a464001272d1af541ea769fa967490c3b", + "3ec76eff991e306699b21fb03bc1f346ffd9cee3", "testharness" ], "web-animations/timing-model/animations/playing-an-animation.html": [ - "10580a1e72892208a14c6fe55091e998edf0171c", + "1ae05a904e5b4fbcf1d904f02825f836da7b4c18", "testharness" ], "web-animations/timing-model/animations/reversing-an-animation.html": [ - "72b89e78ca7dac261af8de370389d89c810b3718", - "testharness" - ], - "web-animations/timing-model/animations/seamlessly-updating-the-playback-rate-of-an-animation.html": [ - "a7e28aa0b40a39b00da257e347cb6ecf8d1d2882", - "testharness" - ], - "web-animations/timing-model/animations/setting-the-current-time-of-an-animation.html": [ - "a7da92b9624750eccb9dce1d32e522fdbb65176f", - "testharness" - ], - "web-animations/timing-model/animations/setting-the-playback-rate-of-an-animation.html": [ - "b2698d9a829a1eadb3ef3b6d8e0050e7a6315305", - "testharness" - ], - "web-animations/timing-model/animations/setting-the-start-time-of-an-animation.html": [ - "cf6040eb52964f12b06a9e3cdf14948ce8141270", - "testharness" - ], - "web-animations/timing-model/animations/setting-the-target-effect-of-an-animation.html": [ - "5575a251b9c265d98471e758b3cf9b218e381cba", - "testharness" - ], - "web-animations/timing-model/animations/setting-the-timeline-of-an-animation.html": [ - "e4e134b566327c9d7316aee4f3e7fe4eeb2116ba", - "testharness" - ], - "web-animations/timing-model/animations/the-current-time-of-an-animation.html": [ - "90ba3d81ee9e32b1f13845301c4ad1c8ad47f2f7", + "3afdb3cc9a9dafb28ebe46902276c19c24aeb9a8", + "testharness" + ], + "web-animations/timing-model/animations/set-the-animation-start-time.html": [ + "fa26feebcde00a5b0b63f8f3587acc313a58f26a", + "testharness" + ], + "web-animations/timing-model/animations/set-the-target-effect-of-an-animation.html": [ + "3b7f1f60cd771ff8587daf7ab76ccbecff59f781", + "testharness" + ], + "web-animations/timing-model/animations/set-the-timeline-of-an-animation.html": [ + "bd33cb8638aa373b17cda20906af5aea2f5a7503", "testharness" ], "web-animations/timing-model/animations/updating-the-finished-state.html": [ - "59e7ed8e4eac5c9edf2526ef748b22e1877b7016", + "c30161f7d5a20db616ade354133ae6a8989d149f", "testharness" ], "web-animations/timing-model/time-transformations/transformed-progress.html": [ "2e55f43def584a67eeb313f050154cd146002938", "testharness" ], "web-animations/timing-model/timelines/document-timelines.html": [ "d0fcb390c19c9ede7288278dc11ea5b3d33671cb",
deleted file mode 100644 --- a/testing/web-platform/tests/web-animations/animation-model/combining-effects/applying-the-composited-result.html +++ /dev/null @@ -1,29 +0,0 @@ -<!doctype html> -<meta charset=utf-8> -<title>Applying the composited result</title> -<link rel="help" - href="https://drafts.csswg.org/web-animations-1/#applying-the-composited-result"> -<script src=/resources/testharness.js></script> -<script src=/resources/testharnessreport.js></script> -<script src="../../testcommon.js"></script> -<div id="log"></div> -<script> -'use strict'; - -promise_test(async t => { - const div = createDiv(t); - div.style.marginLeft = '10px'; - const animation = div.animate( - { marginLeft: ['100px', '200px'] }, - 100 * MS_PER_SEC - ); - await animation.ready; - - animation.finish(); - - const marginLeft = parseFloat(getComputedStyle(div).marginLeft); - assert_equals(marginLeft, 10, 'The computed style should be reset'); -}, 'Finishing an animation that does not fill forwards causes its animation' - + ' style to be cleared'); - -</script>
new file mode 100644 --- /dev/null +++ b/testing/web-platform/tests/web-animations/interfaces/Animation/finish.html @@ -0,0 +1,246 @@ +<!DOCTYPE html> +<meta charset=utf-8> +<title>Animation.finish</title> +<link rel="help" href="https://drafts.csswg.org/web-animations/#dom-animation-finish"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="../../testcommon.js"></script> +<body> +<div id="log"></div> +<script> +'use strict'; + +const gKeyFrames = { 'marginLeft': ['100px', '200px'] }; + +test(t => { + const div = createDiv(t); + const animation = div.animate(gKeyFrames, 100 * MS_PER_SEC); + animation.playbackRate = 0; + + assert_throws({name: 'InvalidStateError'}, () => { + animation.finish(); + }); +}, 'Test exceptions when finishing non-running animation'); + +test(t => { + const div = createDiv(t); + const animation = div.animate(gKeyFrames, + { duration : 100 * MS_PER_SEC, + iterations : Infinity }); + + assert_throws({name: 'InvalidStateError'}, () => { + animation.finish(); + }); +}, 'Test exceptions when finishing infinite animation'); + +test(t => { + const div = createDiv(t); + const animation = div.animate(gKeyFrames, 100 * MS_PER_SEC); + animation.finish(); + + assert_equals(animation.currentTime, 100 * MS_PER_SEC, + 'After finishing, the currentTime should be set to the end ' + + 'of the active duration'); +}, 'Test finishing of animation'); + +test(t => { + const div = createDiv(t); + const animation = div.animate(gKeyFrames, 100 * MS_PER_SEC); + // 1s past effect end + animation.currentTime = + animation.effect.getComputedTiming().endTime + 1 * MS_PER_SEC; + animation.finish(); + + assert_equals(animation.currentTime, 100 * MS_PER_SEC, + 'After finishing, the currentTime should be set back to the ' + + 'end of the active duration'); +}, 'Test finishing of animation with a current time past the effect end'); + +promise_test(t => { + const div = createDiv(t); + const animation = div.animate(gKeyFrames, 100 * MS_PER_SEC); + animation.currentTime = 100 * MS_PER_SEC; + return animation.finished.then(() => { + animation.playbackRate = -1; + animation.finish(); + + assert_equals(animation.currentTime, 0, + 'After finishing a reversed animation the currentTime ' + + 'should be set to zero'); + }); +}, 'Test finishing of reversed animation'); + +promise_test(t => { + const div = createDiv(t); + const animation = div.animate(gKeyFrames, 100 * MS_PER_SEC); + animation.currentTime = 100 * MS_PER_SEC; + return animation.finished.then(() => { + animation.playbackRate = -1; + animation.currentTime = -1000; + animation.finish(); + + assert_equals(animation.currentTime, 0, + 'After finishing a reversed animation the currentTime ' + + 'should be set back to zero'); + }); +}, 'Test finishing of reversed animation with a current time less than zero'); + +promise_test(t => { + const div = createDiv(t); + const animation = div.animate(gKeyFrames, 100 * MS_PER_SEC); + animation.pause(); + return animation.ready.then(() => { + animation.finish(); + + assert_equals(animation.playState, 'finished', + 'The play state of a paused animation should become ' + + '"finished" after finish() is called'); + assert_times_equal(animation.startTime, + animation.timeline.currentTime - 100 * MS_PER_SEC, + 'The start time of a paused animation should be set ' + + 'after calling finish()'); + }); +}, 'Test finish() while paused'); + +test(t => { + const div = createDiv(t); + const animation = div.animate(gKeyFrames, 100 * MS_PER_SEC); + animation.pause(); + // Update playbackRate so we can test that the calculated startTime + // respects it + animation.playbackRate = 2; + // While animation is still pause-pending call finish() + animation.finish(); + + assert_equals(animation.playState, 'finished', + 'The play state of a pause-pending animation should become ' + + '"finished" after finish() is called'); + assert_times_equal(animation.startTime, + animation.timeline.currentTime - 100 * MS_PER_SEC / 2, + 'The start time of a pause-pending animation should ' + + 'be set after calling finish()'); +}, 'Test finish() while pause-pending with positive playbackRate'); + +test(t => { + const div = createDiv(t); + const animation = div.animate(gKeyFrames, 100 * MS_PER_SEC); + animation.pause(); + animation.playbackRate = -2; + animation.finish(); + + assert_equals(animation.playState, 'finished', + 'The play state of a pause-pending animation should become ' + + '"finished" after finish() is called'); + assert_equals(animation.startTime, animation.timeline.currentTime, + 'The start time of a pause-pending animation should be ' + + 'set after calling finish()'); +}, 'Test finish() while pause-pending with negative playbackRate'); + +test(t => { + const div = createDiv(t); + const animation = div.animate(gKeyFrames, 100 * MS_PER_SEC); + animation.playbackRate = 0.5; + animation.finish(); + + assert_equals(animation.playState, 'finished', + 'The play state of a play-pending animation should become ' + + '"finished" after finish() is called'); + assert_times_equal(animation.startTime, + animation.timeline.currentTime - 100 * MS_PER_SEC / 0.5, + 'The start time of a play-pending animation should ' + + 'be set after calling finish()'); +}, 'Test finish() while play-pending'); + +// FIXME: Add a test for when we are play-pending without an active timeline. +// - In that case even after calling finish() we should still be pending but +// the current time should be updated + +promise_test(t => { + const div = createDiv(t); + const animation = div.animate(gKeyFrames, 100 * MS_PER_SEC); + return animation.ready.then(() => { + animation.pause(); + animation.play(); + // We are now in the unusual situation of being play-pending whilst having + // a resolved start time. Check that finish() still triggers a transition + // to the finished state immediately. + animation.finish(); + + assert_equals(animation.playState, 'finished', + 'After aborting a pause then calling finish() the play ' + + 'state of an animation should become "finished" immediately'); + }); +}, 'Test finish() during aborted pause'); + +promise_test(t => { + const div = createDiv(t); + div.style.marginLeft = '10px'; + const animation = div.animate(gKeyFrames, 100 * MS_PER_SEC); + return animation.ready.then(() => { + animation.finish(); + const marginLeft = parseFloat(getComputedStyle(div).marginLeft); + + assert_equals(marginLeft, 10, + 'The computed style should be reset when finish() is ' + + 'called'); + }); +}, 'Test resetting of computed style'); + +promise_test(t => { + const div = createDiv(t); + const animation = div.animate(gKeyFrames, 100 * MS_PER_SEC); + let resolvedFinished = false; + animation.finished.then(() => { + resolvedFinished = true; + }); + + return animation.ready.then(() => { + animation.finish(); + }).then(() => { + assert_true(resolvedFinished, + 'Animation.finished should be resolved soon after ' + + 'Animation.finish()'); + }); +}, 'Test finish() resolves finished promise synchronously'); + +promise_test(t => { + const effect = new KeyframeEffectReadOnly(null, gKeyFrames, 100 * MS_PER_SEC); + const animation = new Animation(effect, document.timeline); + let resolvedFinished = false; + animation.finished.then(() => { + resolvedFinished = true; + }); + + return animation.ready.then(() => { + animation.finish(); + }).then(() => { + assert_true(resolvedFinished, + 'Animation.finished should be resolved soon after ' + + 'Animation.finish()'); + }); +}, 'Test finish() resolves finished promise synchronously with an animation ' + + 'without a target'); + +promise_test(t => { + const effect = new KeyframeEffectReadOnly(null, gKeyFrames, 100 * MS_PER_SEC); + const animation = new Animation(effect, document.timeline); + animation.play(); + + let resolvedFinished = false; + animation.finished.then(() => { + resolvedFinished = true; + }); + + return animation.ready.then(() => { + animation.currentTime = animation.effect.getComputedTiming().endTime - 1; + return waitForAnimationFrames(2); + }).then(() => { + assert_true(resolvedFinished, + 'Animation.finished should be resolved soon after ' + + 'Animation finishes normally'); + }); +}, 'Test normally finished animation resolves finished promise synchronously ' + + 'with an animation without a target'); + +</script> +</body>
--- a/testing/web-platform/tests/web-animations/interfaces/Animation/idlharness.html +++ b/testing/web-platform/tests/web-animations/interfaces/Animation/idlharness.html @@ -24,17 +24,16 @@ interface Animation : EventTarget { readonly attribute Promise<Animation> ready; readonly attribute Promise<Animation> finished; attribute EventHandler onfinish; attribute EventHandler oncancel; void cancel (); void finish (); void play (); void pause (); - void updatePlaybackRate (double playbackRate); void reverse (); }; </script> <script> 'use strict'; const idlArray = new IdlArray();
rename from testing/web-platform/tests/web-animations/timing-model/animations/setting-the-playback-rate-of-an-animation.html rename to testing/web-platform/tests/web-animations/interfaces/Animation/playbackRate.html --- a/testing/web-platform/tests/web-animations/timing-model/animations/setting-the-playback-rate-of-an-animation.html +++ b/testing/web-platform/tests/web-animations/interfaces/Animation/playbackRate.html @@ -1,61 +1,84 @@ <!DOCTYPE html> <meta charset=utf-8> -<title>Setting the playback rate of an animation</title> -<link rel="help" href="https://drafts.csswg.org/web-animations/#setting-the-playback-rate-of-an-animation"> +<title>Animation.playbackRate</title> +<link rel="help" href="https://drafts.csswg.org/web-animations/#dom-animation-playbackrate"> <script src="/resources/testharness.js"></script> <script src="/resources/testharnessreport.js"></script> <script src="../../testcommon.js"></script> <body> <div id="log"></div> <script> 'use strict'; -promise_test(async t => { - const animation = createDiv(t).animate(null, 100 * MS_PER_SEC); - animation.playbackRate = 2; - await animation.ready; - - const previousAnimationCurrentTime = animation.currentTime; - const previousTimelineCurrentTime = animation.timeline.currentTime; - - await waitForAnimationFrames(1); - +function assert_playbackrate(animation, + previousAnimationCurrentTime, + previousTimelineCurrentTime, + description) { const animationCurrentTimeDifference = animation.currentTime - previousAnimationCurrentTime; const timelineCurrentTimeDifference = animation.timeline.currentTime - previousTimelineCurrentTime; - assert_times_equal( - animationCurrentTimeDifference, - timelineCurrentTimeDifference * animation.playbackRate, - 'The current time should increase two times faster than timeline' - ); -}, 'The playback rate affects the rate of progress of the current time'); + assert_times_equal(animationCurrentTimeDifference, + timelineCurrentTimeDifference * animation.playbackRate, + description); +} -test(t => { - const animation = createDiv(t).animate(null, 100 * MS_PER_SEC); - animation.currentTime = 50 * MS_PER_SEC; - animation.playbackRate = 2; - assert_time_equals_literal(animation.currentTime, 50 * MS_PER_SEC); -}, 'Setting the playback rate while play-pending preserves the current time'); +promise_test(t => { + const div = createDiv(t); + const animation = div.animate(null, 100 * MS_PER_SEC); + return animation.ready.then(() => { + animation.currentTime = 7 * MS_PER_SEC; // ms + animation.playbackRate = 0.5; + + assert_equals(animation.currentTime, 7 * MS_PER_SEC, + 'Reducing Animation.playbackRate should not change the currentTime ' + + 'of a playing animation'); + animation.playbackRate = 2; + assert_equals(animation.currentTime, 7 * MS_PER_SEC, + 'Increasing Animation.playbackRate should not change the currentTime ' + + 'of a playing animation'); + }); +}, 'Test the initial effect of setting playbackRate on currentTime'); -promise_test(async t => { - const animation = createDiv(t).animate(null, 100 * MS_PER_SEC); - animation.currentTime = 50 * MS_PER_SEC; - await animation.ready; +promise_test(t => { + const div = createDiv(t); + const animation = div.animate(null, 100 * MS_PER_SEC); animation.playbackRate = 2; - assert_greater_than_equal(animation.currentTime, 50 * MS_PER_SEC); - assert_less_than(animation.currentTime, 100 * MS_PER_SEC); -}, 'Setting the playback rate while playing preserves the current time'); + let previousTimelineCurrentTime; + let previousAnimationCurrentTime; + return animation.ready.then(() => { + previousAnimationCurrentTime = animation.currentTime; + previousTimelineCurrentTime = animation.timeline.currentTime; + return waitForAnimationFrames(1); + }).then(() => { + assert_playbackrate(animation, + previousAnimationCurrentTime, + previousTimelineCurrentTime, + 'animation.currentTime should be 2 times faster than timeline.'); + }); +}, 'Test the effect of setting playbackRate on currentTime'); -promise_test(async t => { - const animation = createDiv(t).animate(null, 100 * MS_PER_SEC); - animation.currentTime = 50 * MS_PER_SEC; - animation.updatePlaybackRate(2); - animation.playbackRate = 1; - await animation.ready; - assert_equals(animation.playbackRate, 1); -}, 'Setting the playback rate should clear any pending playback rate'); +promise_test(t => { + const div = createDiv(t); + const animation = div.animate(null, 100 * MS_PER_SEC); + animation.playbackRate = 2; + let previousTimelineCurrentTime; + let previousAnimationCurrentTime; + return animation.ready.then(() => { + previousAnimationCurrentTime = animation.currentTime; + previousTimelineCurrentTime = animation.timeline.currentTime; + animation.playbackRate = 1; + return waitForAnimationFrames(1); + }).then(() => { + assert_equals(animation.playbackRate, 1, + 'sanity check: animation.playbackRate is still 1.'); + assert_playbackrate(animation, + previousAnimationCurrentTime, + previousTimelineCurrentTime, + 'animation.currentTime should be the same speed as timeline now.'); + }); +}, 'Test the effect of setting playbackRate while playing animation'); </script> </body>
--- a/testing/web-platform/tests/web-animations/timing-model/animations/canceling-an-animation.html +++ b/testing/web-platform/tests/web-animations/timing-model/animations/canceling-an-animation.html @@ -21,38 +21,39 @@ promise_test(t => { }); animation.cancel(); return retPromise; }, 'A play-pending ready promise should be rejected when the animation is' + ' canceled'); -promise_test(async t => { +promise_test(t => { const animation = createDiv(t).animate(null, 100 * MS_PER_SEC); - await animation.ready; - - // Make it pause-pending - animation.pause(); - - // We need to store the original ready promise since cancel() will - // replace it - const originalPromise = animation.ready; - animation.cancel(); - - await promise_rejects(t, 'AbortError', originalPromise, - 'Cancel should abort ready promise'); + return animation.ready.then(() => { + animation.pause(); + // Set up listeners on pause-pending ready promise + const retPromise = animation.ready.then(() => { + assert_unreached('ready promise was fulfilled'); + }).catch(err => { + assert_equals(err.name, 'AbortError', + 'ready promise is rejected with AbortError'); + }); + animation.cancel(); + return retPromise; + }); }, 'A pause-pending ready promise should be rejected when the animation is' + ' canceled'); -promise_test(async t => { +promise_test(t => { const animation = createDiv(t).animate(null); animation.cancel(); - const promiseResult = await animation.ready; - assert_equals(promiseResult, animation); + return animation.ready.then(p => { + assert_equals(p, animation); + }); }, 'When an animation is canceled, it should create a resolved Promise'); test(t => { const animation = createDiv(t).animate(null); const promise = animation.ready; animation.cancel(); assert_not_equals(animation.ready, promise); }, 'The ready promise should be replaced when the animation is canceled');
rename from testing/web-platform/tests/web-animations/timing-model/animations/the-current-time-of-an-animation.html rename to testing/web-platform/tests/web-animations/timing-model/animations/current-time.html --- a/testing/web-platform/tests/web-animations/timing-model/animations/the-current-time-of-an-animation.html +++ b/testing/web-platform/tests/web-animations/timing-model/animations/current-time.html @@ -1,12 +1,12 @@ <!DOCTYPE html> <meta charset=utf-8> -<title>The current time of an animation</title> -<link rel="help" href="https://drafts.csswg.org/web-animations/#the-current-time-of-an-animation"> +<title>Current time</title> +<link rel="help" href="https://drafts.csswg.org/web-animations/#current-time"> <script src="/resources/testharness.js"></script> <script src="/resources/testharnessreport.js"></script> <script src="../../testcommon.js"></script> <body> <div id="log"></div> <script> 'use strict'; @@ -16,23 +16,24 @@ test(t => { document.timeline); animation.play(); assert_equals(animation.currentTime, 0, 'Current time returns the hold time set when entering the play-pending ' + 'state'); }, 'The current time returns the hold time when set'); -promise_test(async t => { +promise_test(t => { const animation = new Animation(new KeyframeEffect(createDiv(t), null, 100 * MS_PER_SEC), null); - await animation.ready; - assert_equals(animation.currentTime, null); + return animation.ready.then(() => { + assert_equals(animation.currentTime, null); + }); }, 'The current time is unresolved when there is no associated timeline ' + '(and no hold time is set)'); // FIXME: Test that the current time is unresolved when we have an inactive // timeline if we find a way of creating an inactive timeline! test(t => { const animation = @@ -56,19 +57,20 @@ test(t => { const startTime = animation.startTime; const playbackRate = animation.playbackRate; assert_times_equal(animation.currentTime, (timelineTime - startTime) * playbackRate, 'Animation has a unresolved start time'); }, 'The current time is calculated from the timeline time, start time and ' + 'playback rate'); -promise_test(async t => { +promise_test(t => { const animation = createDiv(t).animate(null, 100 * MS_PER_SEC); animation.playbackRate = 0; - await animation.ready; - await waitForAnimationFrames(1); - assert_time_equals_literal(animation.currentTime, 0); + return animation.ready.then(() => waitForAnimationFrames(1)) + .then(() => { + assert_time_equals_literal(animation.currentTime, 0); + }); }, 'The current time does not progress if playback rate is 0'); </script> </body>
--- a/testing/web-platform/tests/web-animations/timing-model/animations/finishing-an-animation.html +++ b/testing/web-platform/tests/web-animations/timing-model/animations/finishing-an-animation.html @@ -6,283 +6,26 @@ <script src="/resources/testharness.js"></script> <script src="/resources/testharnessreport.js"></script> <script src="../../testcommon.js"></script> <body> <div id="log"></div> <script> 'use strict'; -test(t => { - const div = createDiv(t); - const animation = div.animate(null, 100 * MS_PER_SEC); - animation.playbackRate = 0; - - assert_throws({name: 'InvalidStateError'}, () => { - animation.finish(); - }); -}, 'Finishing an animation with a zero playback rate throws'); - -test(t => { - const div = createDiv(t); - const animation = div.animate(null, - { duration : 100 * MS_PER_SEC, - iterations : Infinity }); - - assert_throws({name: 'InvalidStateError'}, () => { - animation.finish(); - }); -}, 'Finishing an infinite animation throws'); - -test(t => { - const div = createDiv(t); - const animation = div.animate(null, 100 * MS_PER_SEC); - animation.finish(); - - assert_time_equals_literal(animation.currentTime, 100 * MS_PER_SEC, - 'After finishing, the currentTime should be set to the end of the' - + ' active duration'); -}, 'Finishing an animation seeks to the end time'); - -test(t => { - const div = createDiv(t); - const animation = div.animate(null, 100 * MS_PER_SEC); - // 1s past effect end - animation.currentTime = - animation.effect.getComputedTiming().endTime + 1 * MS_PER_SEC; - animation.finish(); - - assert_time_equals_literal(animation.currentTime, 100 * MS_PER_SEC, - 'After finishing, the currentTime should be set back to the end of the' - + ' active duration'); -}, 'Finishing an animation with a current time past the effect end jumps' - + ' back to the end'); - -promise_test(async t => { - const div = createDiv(t); - const animation = div.animate(null, 100 * MS_PER_SEC); - animation.currentTime = 100 * MS_PER_SEC; - await animation.finished; - - animation.playbackRate = -1; - animation.finish(); - - assert_equals(animation.currentTime, 0, - 'After finishing a reversed animation the currentTime ' + - 'should be set to zero'); -}, 'Finishing a reversed animation jumps to zero time'); - -promise_test(async t => { - const div = createDiv(t); - const animation = div.animate(null, 100 * MS_PER_SEC); - animation.currentTime = 100 * MS_PER_SEC; - await animation.finished; - - animation.playbackRate = -1; - animation.currentTime = -1000; - animation.finish(); - - assert_equals(animation.currentTime, 0, - 'After finishing a reversed animation the currentTime ' + - 'should be set back to zero'); -}, 'Finishing a reversed animation with a current time less than zero' - + ' makes it jump back to zero'); - -promise_test(async t => { - const div = createDiv(t); - const animation = div.animate(null, 100 * MS_PER_SEC); - animation.pause(); - await animation.ready; - - animation.finish(); - - assert_equals(animation.playState, 'finished', - 'The play state of a paused animation should become ' + - '"finished"'); - assert_times_equal(animation.startTime, - animation.timeline.currentTime - 100 * MS_PER_SEC, - 'The start time of a paused animation should be set'); -}, 'Finishing a paused animation resolves the start time'); - -test(t => { - const div = createDiv(t); - const animation = div.animate(null, 100 * MS_PER_SEC); - // Update playbackRate so we can test that the calculated startTime - // respects it - animation.playbackRate = 2; - animation.pause(); - // While animation is still pause-pending call finish() - animation.finish(); - - assert_false(animation.pending); - assert_equals(animation.playState, 'finished', - 'The play state of a pause-pending animation should become ' + - '"finished"'); - assert_times_equal(animation.startTime, - animation.timeline.currentTime - 100 * MS_PER_SEC / 2, - 'The start time of a pause-pending animation should ' + - 'be set'); -}, 'Finishing a pause-pending animation resolves the pending task' - + ' immediately and update the start time'); - -test(t => { - const div = createDiv(t); - const animation = div.animate(null, 100 * MS_PER_SEC); - animation.playbackRate = -2; - animation.pause(); - animation.finish(); - - assert_false(animation.pending); - assert_equals(animation.playState, 'finished', - 'The play state of a pause-pending animation should become ' + - '"finished"'); - assert_times_equal(animation.startTime, animation.timeline.currentTime, - 'The start time of a pause-pending animation should be ' + - 'set'); -}, 'Finishing a pause-pending animation with negative playback rate' - + ' resolves the pending task immediately'); - -test(t => { - const div = createDiv(t); - const animation = div.animate(null, 100 * MS_PER_SEC); - animation.playbackRate = 0.5; - animation.finish(); - - assert_false(animation.pending); - assert_equals(animation.playState, 'finished', - 'The play state of a play-pending animation should become ' + - '"finished"'); - assert_times_equal(animation.startTime, - animation.timeline.currentTime - 100 * MS_PER_SEC / 0.5, - 'The start time of a play-pending animation should ' + - 'be set'); -}, 'Finishing an animation while play-pending resolves the pending' - + ' task immediately'); - -// FIXME: Add a test for when we are play-pending without an active timeline. -// - In that case even after calling finish() we should still be pending but -// the current time should be updated - -promise_test(async t => { - const div = createDiv(t); - const animation = div.animate(null, 100 * MS_PER_SEC); - await animation.ready; - - animation.pause(); - animation.play(); - // We are now in the unusual situation of being play-pending whilst having - // a resolved start time. Check that finish() still triggers a transition - // to the finished state immediately. - animation.finish(); - - assert_equals(animation.playState, 'finished', - 'After aborting a pause then finishing an animation its play ' + - 'state should become "finished" immediately'); -}, 'Finishing an animation during an aborted pause makes it finished' - + ' immediately'); - -promise_test(async t => { - const div = createDiv(t); - const animation = div.animate(null, 100 * MS_PER_SEC); - let resolvedFinished = false; - animation.finished.then(() => { - resolvedFinished = true; - }); - - await animation.ready; - - animation.finish(); - await Promise.resolve(); - - assert_true(resolvedFinished, 'finished promise should be resolved'); -}, 'Finishing an animation resolves the finished promise synchronously'); - -promise_test(async t => { - const effect = new KeyframeEffectReadOnly(null, null, 100 * MS_PER_SEC); - const animation = new Animation(effect, document.timeline); - let resolvedFinished = false; - animation.finished.then(() => { - resolvedFinished = true; - }); - - await animation.ready; - - animation.finish(); - await Promise.resolve(); - - assert_true(resolvedFinished, 'finished promise should be resolved'); -}, 'Finishing an animation without a target resolves the finished promise' - + ' synchronously'); - -promise_test(async t => { +promise_test(t => { const animation = createDiv(t).animate(null, 100 * MS_PER_SEC); const promise = animation.ready; let readyResolved = false; animation.finish(); animation.ready.then(() => { readyResolved = true; }); - const promiseResult = await animation.finished; - - assert_equals(promiseResult, animation); - assert_equals(animation.ready, promise); - assert_true(readyResolved); -}, 'A pending ready promise is resolved and not replaced when the animation' - + ' is finished'); - -promise_test(async t => { - const animation = createDiv(t).animate(null, 100 * MS_PER_SEC); - await animation.ready; - - animation.updatePlaybackRate(2); - assert_true(animation.pending); - - animation.finish(); - assert_false(animation.pending); - assert_equals(animation.playbackRate, 2); - assert_time_equals_literal(animation.currentTime, 100 * MS_PER_SEC); -}, 'A pending playback rate should be applied immediately when an animation' - + ' is finished'); - -promise_test(async t => { - const animation = createDiv(t).animate(null, 100 * MS_PER_SEC); - await animation.ready; - - animation.updatePlaybackRate(0); - - assert_throws('InvalidStateError', () => { - animation.finish(); + return animation.finished.then(p => { + assert_equals(p, animation); + assert_equals(animation.ready, promise); + assert_true(readyResolved); }); -}, 'An exception should be thrown if the effective playback rate is zero'); - -promise_test(async t => { - const animation = createDiv(t).animate(null, { - duration: 100 * MS_PER_SEC, - iterations: Infinity - }); - animation.currentTime = 50 * MS_PER_SEC; - animation.playbackRate = -1; - await animation.ready; - - animation.updatePlaybackRate(1); - - assert_throws('InvalidStateError', () => { - animation.finish(); - }); -}, 'An exception should be thrown when finishing if the effective playback rate' - + ' is positive and the target effect end is infinity'); - -promise_test(async t => { - const animation = createDiv(t).animate(null, { - duration: 100 * MS_PER_SEC, - iterations: Infinity - }); - await animation.ready; - - animation.updatePlaybackRate(-1); - - animation.finish(); - // Should not have thrown -}, 'An exception is NOT thrown when finishing if the effective playback rate' - + ' is negative and the target effect end is infinity'); +}, 'A pending ready promise should be resolved and not replaced when the' + + ' animation is finished'); </script> </body>
--- a/testing/web-platform/tests/web-animations/timing-model/animations/pausing-an-animation.html +++ b/testing/web-platform/tests/web-animations/timing-model/animations/pausing-an-animation.html @@ -6,41 +6,22 @@ <script src="/resources/testharness.js"></script> <script src="/resources/testharnessreport.js"></script> <script src="../../testcommon.js"></script> <body> <div id="log"></div> <script> 'use strict'; -promise_test(async t => { +promise_test(t => { const animation = createDiv(t).animate(null, 100 * MS_PER_SEC); const promise = animation.ready; animation.pause(); - - const promiseResult = await promise; - - assert_equals(promiseResult, animation); - assert_equals(animation.ready, promise); - assert_false(animation.pending, 'No longer pause-pending'); + return promise.then(p => { + assert_equals(p, animation); + assert_equals(animation.ready, promise); + assert_false(animation.pending, 'No longer pause-pending'); + }); }, 'A pending ready promise should be resolved and not replaced when the' + ' animation is paused'); -promise_test(async t => { - const animation = createDiv(t).animate(null, 100 * MS_PER_SEC); - // Let animation start roughly half-way through - animation.currentTime = 50 * MS_PER_SEC; - await animation.ready; - - // Go pause-pending and also set a pending playback rate - animation.pause(); - animation.updatePlaybackRate(0.5); - - await animation.ready; - // If the current time was updated using the new playback rate it will jump - // back to 25s but if we correctly used the old playback rate the current time - // will be >50s. - assert_greater_than(animation.currentTime, 50 * MS_PER_SEC); -}, 'A pause-pending animation maintains the current time when applying a' - + ' pending playback rate'); - </script> </body>
--- a/testing/web-platform/tests/web-animations/timing-model/animations/play-states.html +++ b/testing/web-platform/tests/web-animations/timing-model/animations/play-states.html @@ -1,12 +1,12 @@ <!DOCTYPE html> <meta charset=utf-8> <title>Play states</title> -<link rel="help" href="https://drafts.csswg.org/web-animations/#play-states"> +<link rel="help" href="https://drafts.csswg.org/web-animations/#play-state"> <script src="/resources/testharness.js"></script> <script src="/resources/testharnessreport.js"></script> <script src="../../testcommon.js"></script> <body> <div id="log"></div> <script> 'use strict';
--- a/testing/web-platform/tests/web-animations/timing-model/animations/playing-an-animation.html +++ b/testing/web-platform/tests/web-animations/timing-model/animations/playing-an-animation.html @@ -40,36 +40,20 @@ test(t => { const animation = createDiv(t).animate(null, 100 * MS_PER_SEC); animation.cancel(); const promise = animation.ready; animation.play(); assert_not_equals(animation.ready, promise); }, 'The ready promise should be replaced if the animation is not already' + ' pending'); -promise_test(async t => { +promise_test(t => { const animation = createDiv(t).animate(null, 100 * MS_PER_SEC); const promise = animation.ready; - const promiseResult = await promise; - assert_equals(promiseResult, animation); - assert_equals(animation.ready, promise); + return promise.then(p => { + assert_equals(p, animation); + assert_equals(animation.ready, promise); + }); }, 'A pending ready promise should be resolved and not replaced when the' + ' animation enters the running state'); -promise_test(async t => { - // Seek animation beyond target end - const animation = createDiv(t).animate(null, 100 * MS_PER_SEC); - animation.currentTime = -100 * MS_PER_SEC; - await animation.ready; - - // Set pending playback rate to the opposite direction - animation.updatePlaybackRate(-1); - assert_true(animation.pending); - assert_equals(animation.playbackRate, 1); - - // When we play, we should seek to the target end, NOT to zero (which - // is where we would seek to if we used the playbackRate of 1. - animation.play(); - assert_time_equals_literal(animation.currentTime, 100 * MS_PER_SEC); -}, 'A pending playback rate is used when determining auto-rewind behavior'); - </script> </body>
--- a/testing/web-platform/tests/web-animations/timing-model/animations/reversing-an-animation.html +++ b/testing/web-platform/tests/web-animations/timing-model/animations/reversing-an-animation.html @@ -1,58 +1,53 @@ <!DOCTYPE html> <meta charset=utf-8> -<title>Reversing an animation</title> +<title>Reverse an animation</title> <link rel="help" - href="https://drafts.csswg.org/web-animations/#reversing-an-animation-section"> + href="https://drafts.csswg.org/web-animations/#reverse-an-animation"> <script src="/resources/testharness.js"></script> <script src="/resources/testharnessreport.js"></script> <script src="../../testcommon.js"></script> <body> <div id="log"></div> <script> 'use strict'; -promise_test(async t => { +promise_test(t => { const div = createDiv(t); const animation = div.animate({}, { duration: 100 * MS_PER_SEC, iterations: Infinity }); - await animation.ready; // Wait a frame because if currentTime is still 0 when we call // reverse(), it will throw (per spec). - await waitForAnimationFrames(1); - - assert_greater_than_equal(animation.currentTime, 0, - 'currentTime expected to be greater than 0, one frame after starting'); - animation.currentTime = 50 * MS_PER_SEC; - const previousPlaybackRate = animation.playbackRate; - animation.reverse(); - assert_equals(animation.playbackRate, previousPlaybackRate, - 'Playback rate should not have changed'); - await animation.ready; - - assert_equals(animation.playbackRate, -previousPlaybackRate, - 'Playback rate should be inverted'); + return animation.ready.then(waitForAnimationFrames(1)).then(() => { + assert_greater_than_equal(animation.currentTime, 0, + 'currentTime expected to be greater than 0, one frame after starting'); + animation.currentTime = 50 * MS_PER_SEC; + const previousPlaybackRate = animation.playbackRate; + animation.reverse(); + assert_equals(animation.playbackRate, -previousPlaybackRate, + 'playbackRate should be inverted'); + }); }, 'Reversing an animation inverts the playback rate'); -promise_test(async t => { +promise_test(t => { const div = createDiv(t); const animation = div.animate({}, { duration: 100 * MS_PER_SEC, iterations: Infinity }); animation.currentTime = 50 * MS_PER_SEC; animation.pause(); - await animation.ready; - - animation.reverse(); - await animation.ready; - - assert_equals(animation.playState, 'running', - 'Animation.playState should be "running" after reverse()'); + return animation.ready.then(() => { + animation.reverse(); + return animation.ready; + }).then(() => { + assert_equals(animation.playState, 'running', + 'Animation.playState should be "running" after reverse()'); + }); }, 'Reversing an animation plays a pausing animation'); test(t => { const div = createDiv(t); const animation = div.animate({}, 100 * MS_PER_SEC); animation.currentTime = 50 * MS_PER_SEC; animation.reverse(); @@ -69,28 +64,29 @@ test(t => { 'The animation is pending before we call reverse'); animation.reverse(); assert_true(animation.pending, 'The animation is still pending after calling reverse'); }, 'Reversing an animation does not cause it to leave the pending state'); -promise_test(async t => { +promise_test(t => { const div = createDiv(t); const animation = div.animate({}, { duration: 200 * MS_PER_SEC, delay: -100 * MS_PER_SEC }); let readyResolved = false; animation.ready.then(() => { readyResolved = true; }); animation.reverse(); - await Promise.resolve(); - assert_false(readyResolved, - 'ready promise should not have been resolved yet'); + return Promise.resolve(() => { + assert_false(readyResolved, + 'ready promise should not have been resolved yet'); + }); }, 'Reversing an animation does not cause it to resolve the ready promise'); test(t => { const div = createDiv(t); const animation = div.animate({}, 100 * MS_PER_SEC); animation.currentTime = 200 * MS_PER_SEC; animation.reverse(); @@ -148,26 +144,23 @@ test(t => { assert_throws('InvalidStateError', () => { animation.reverse(); }, 'reverse() should throw InvalidStateError ' + 'if the playbackRate > 0 and the currentTime < 0 ' + 'and the target effect is positive infinity'); }, 'Reversing an animation when playbackRate > 0 and currentTime < 0 ' + 'and the target effect end is positive infinity should throw an exception'); -promise_test(async t => { +test(t => { const animation = createDiv(t).animate({}, { duration: 100 * MS_PER_SEC, iterations: Infinity }); animation.currentTime = -200 * MS_PER_SEC; try { animation.reverse(); } catch(e) { } - assert_equals(animation.playbackRate, 1, 'playbackRate is unchanged'); - - await animation.ready; assert_equals(animation.playbackRate, 1, 'playbackRate remains unchanged'); }, 'When reversing throws an exception, the playback rate remains unchanged'); test(t => { const div = createDiv(t); const animation = div.animate({}, { duration: 100 * MS_PER_SEC, iterations: Infinity }); animation.currentTime = -200 * MS_PER_SEC; @@ -193,62 +186,34 @@ test(t => { assert_equals(animation.currentTime, 0, 'reverse() should start playing from the start of animation time ' + 'if the playbackRate < 0 and the currentTime < 0 ' + 'and the target effect is positive infinity'); }, 'Reversing an animation when playbackRate < 0 and currentTime < 0 ' + 'and the target effect end is positive infinity should make it play ' + 'from the start'); -promise_test(async t => { +test(t => { const div = createDiv(t); const animation = div.animate({}, 100 * MS_PER_SEC); animation.playbackRate = 0; animation.currentTime = 50 * MS_PER_SEC; animation.reverse(); - await animation.ready; assert_equals(animation.playbackRate, 0, 'reverse() should preserve playbackRate if the playbackRate == 0'); assert_equals(animation.currentTime, 50 * MS_PER_SEC, 'reverse() should not affect the currentTime if the playbackRate == 0'); + t.done(); }, 'Reversing when when playbackRate == 0 should preserve the current ' + 'time and playback rate'); test(t => { const div = createDiv(t); const animation = new Animation(new KeyframeEffect(div, null, 100 * MS_PER_SEC), null); assert_throws('InvalidStateError', () => { animation.reverse(); }); }, 'Reversing an animation without an active timeline throws an ' + 'InvalidStateError'); -promise_test(async t => { - const animation = createDiv(t).animate(null, 100 * MS_PER_SEC); - await animation.ready; - - animation.updatePlaybackRate(2); - animation.reverse(); - - await animation.ready; - assert_equals(animation.playbackRate, -2); -}, 'Reversing should use the negative pending playback rate'); - -promise_test(async t => { - const animation = createDiv(t).animate(null, { - duration: 100 * MS_PER_SEC, - iterations: Infinity, - }); - animation.currentTime = -200 * MS_PER_SEC; - await animation.ready; - - animation.updatePlaybackRate(2); - assert_throws('InvalidStateError', () => { animation.reverse(); }); - assert_equals(animation.playbackRate, 1); - - await animation.ready; - assert_equals(animation.playbackRate, 2); -}, 'When reversing fails, it should restore any previous pending playback' - + ' rate'); - </script> </body>
deleted file mode 100644 --- a/testing/web-platform/tests/web-animations/timing-model/animations/seamlessly-updating-the-playback-rate-of-an-animation.html +++ /dev/null @@ -1,142 +0,0 @@ -<!doctype html> -<meta charset=utf-8> -<title>Seamlessly updating the playback rate of an animation</title> -<link rel="help" - href="https://drafts.csswg.org/web-animations-1/#seamlessly-updating-the-playback-rate-of-an-animation"> -<script src="/resources/testharness.js"></script> -<script src="/resources/testharnessreport.js"></script> -<script src="../../testcommon.js"></script> -<body> -<div id="log"></div> -<script> -'use strict'; - -promise_test(async t => { - const animation = createDiv(t).animate(null, 100 * MS_PER_SEC); - await animation.ready; - - animation.currentTime = 50 * MS_PER_SEC; - - animation.updatePlaybackRate(0.5); - await animation.ready; - // Since the animation is in motion (and we want to test it while it is in - // motion!) we can't assert that the current time == 50s but we can check - // that the current time is NOT re-calculated by simply substituting in the - // new playback rate (i.e. without adjusting the start time). If that were - // the case the currentTime would jump to 25s. So we just test the currentTime - // hasn't gone backwards. - assert_greater_than_equal(animation.currentTime, 50 * MS_PER_SEC, - 'Reducing the playback rate should not change the current time ' + - 'of a playing animation'); - - animation.updatePlaybackRate(2); - await animation.ready; - // Likewise, we test here that the current time does not jump to 100s as it - // would if we naively applied a playbackRate of 2 without adjusting the - // startTime. - assert_less_than(animation.currentTime, 100 * MS_PER_SEC, - 'Increasing the playback rate should not change the current time ' + - 'of a playing animation'); -}, 'Updating the playback rate maintains the current time'); - -promise_test(async t => { - const animation = createDiv(t).animate(null, 100 * MS_PER_SEC); - await animation.ready; - - assert_false(animation.pending); - animation.updatePlaybackRate(2); - assert_true(animation.pending); -}, 'Updating the playback rate while running makes the animation pending'); - -promise_test(async t => { - const animation = createDiv(t).animate(null, 100 * MS_PER_SEC); - animation.currentTime = 50 * MS_PER_SEC; - assert_true(animation.pending); - - animation.updatePlaybackRate(0.5); - - // Check that the hold time is updated as expected - assert_time_equals_literal(animation.currentTime, 50 * MS_PER_SEC); - - await animation.ready; - - // As above, check that the currentTime is not calculated by simply - // substituting in the updated playbackRate without updating the startTime. - assert_greater_than_equal(animation.currentTime, 50 * MS_PER_SEC, - 'Reducing the playback rate should not change the current time ' + - 'of a play-pending animation'); -}, 'Updating the playback rate on a play-pending animation maintains' - + ' the current time'); - -promise_test(async t => { - const animation = createDiv(t).animate(null, 100 * MS_PER_SEC); - animation.currentTime = 50 * MS_PER_SEC; - await animation.ready; - - animation.pause(); - animation.updatePlaybackRate(0.5); - - assert_greater_than_equal(animation.currentTime, 50 * MS_PER_SEC); -}, 'Updating the playback rate on a pause-pending animation maintains' - + ' the current time'); - -promise_test(async t => { - const animation = createDiv(t).animate(null, 100 * MS_PER_SEC); - - animation.updatePlaybackRate(2); - animation.updatePlaybackRate(3); - animation.updatePlaybackRate(4); - - assert_equals(animation.playbackRate, 1); - await animation.ready; - - assert_equals(animation.playbackRate, 4); -}, 'If a pending playback rate is set multiple times, the latest wins'); - -test(t => { - const animation = createDiv(t).animate(null, 100 * MS_PER_SEC); - animation.cancel(); - - animation.updatePlaybackRate(2); - assert_equals(animation.playbackRate, 2); - assert_false(animation.pending); -}, 'In the idle state, the playback rate is applied immediately'); - -promise_test(async t => { - const animation = createDiv(t).animate(null, 100 * MS_PER_SEC); - animation.pause(); - await animation.ready; - - animation.updatePlaybackRate(2); - assert_equals(animation.playbackRate, 2); - assert_false(animation.pending); -}, 'In the paused state, the playback rate is applied immediately'); - -promise_test(async t => { - const animation = createDiv(t).animate(null, 100 * MS_PER_SEC); - animation.finish(); - assert_time_equals_literal(animation.currentTime, 100 * MS_PER_SEC); - assert_false(animation.pending); - - animation.updatePlaybackRate(2); - assert_equals(animation.playbackRate, 2); - assert_time_equals_literal(animation.currentTime, 100 * MS_PER_SEC); - assert_false(animation.pending); -}, 'Updating the playback rate on a finished animation maintains' - + ' the current time'); - -promise_test(async t => { - const animation = createDiv(t).animate(null, 100 * MS_PER_SEC); - animation.finish(); - assert_time_equals_literal(animation.currentTime, 100 * MS_PER_SEC); - assert_false(animation.pending); - - animation.updatePlaybackRate(0); - assert_equals(animation.playbackRate, 0); - assert_time_equals_literal(animation.currentTime, 100 * MS_PER_SEC); - assert_false(animation.pending); -}, 'Updating the playback rate to zero on a finished animation maintains' - + ' the current time'); - -</script> -</body>
rename from testing/web-platform/tests/web-animations/timing-model/animations/setting-the-start-time-of-an-animation.html rename to testing/web-platform/tests/web-animations/timing-model/animations/set-the-animation-start-time.html --- a/testing/web-platform/tests/web-animations/timing-model/animations/setting-the-start-time-of-an-animation.html +++ b/testing/web-platform/tests/web-animations/timing-model/animations/set-the-animation-start-time.html @@ -1,12 +1,12 @@ <!DOCTYPE html> <meta charset=utf-8> -<title>Setting the start time of an animation</title> -<link rel="help" href="https://drafts.csswg.org/web-animations/#setting-the-start-time-of-an-animation"> +<title>Set the animation start time</title> +<link rel="help" href="https://drafts.csswg.org/web-animations/#set-the-animation-start-time"> <script src="/resources/testharness.js"></script> <script src="/resources/testharnessreport.js"></script> <script src="../../testcommon.js"></script> <body> <div id="log"></div> <script> 'use strict'; @@ -107,17 +107,17 @@ test(t => { assert_time_equals_literal(animation.currentTime, 1000, 'Hold time is set after start time is made' + ' unresolved'); assert_equals(animation.playState, 'paused', 'Animation reports it is paused after setting an unresolved' + ' start time'); }, 'Setting an unresolved start time sets the hold time'); -promise_test(async t => { +promise_test(t => { const animation = new Animation(new KeyframeEffect(createDiv(t), null, 100 * MS_PER_SEC), document.timeline); let readyPromiseCallbackCalled = false; animation.ready.then(() => { readyPromiseCallbackCalled = true; } ); // Put the animation in the play-pending state @@ -130,22 +130,23 @@ promise_test(async t => { // Setting the start time should resolve the 'ready' promise, i.e. // it should schedule a microtask to run the promise callbacks. animation.startTime = document.timeline.currentTime; assert_false(readyPromiseCallbackCalled, 'Ready promise callback is not called synchronously'); // If we schedule another microtask then it should run immediately after // the ready promise resolution microtask. - await Promise.resolve(); - assert_true(readyPromiseCallbackCalled, - 'Ready promise callback called after setting startTime'); + return Promise.resolve().then(() => { + assert_true(readyPromiseCallbackCalled, + 'Ready promise callback called after setting startTime'); + }); }, 'Setting the start time resolves a pending ready promise'); -promise_test(async t => { +promise_test(t => { const animation = new Animation(new KeyframeEffect(createDiv(t), null, 100 * MS_PER_SEC), document.timeline); let readyPromiseCallbackCalled = false; animation.ready.then(() => { readyPromiseCallbackCalled = true; } ); // Put the animation in the pause-pending state @@ -157,22 +158,23 @@ promise_test(async t => { 'Animation is in pause-pending state'); // Setting the start time should resolve the 'ready' promise although // the resolution callbacks when be run in a separate microtask. animation.startTime = null; assert_false(readyPromiseCallbackCalled, 'Ready promise callback is not called synchronously'); - await Promise.resolve(); - assert_true(readyPromiseCallbackCalled, - 'Ready promise callback called after setting startTime'); + return Promise.resolve().then(() => { + assert_true(readyPromiseCallbackCalled, + 'Ready promise callback called after setting startTime'); + }); }, 'Setting the start time resolves a pending pause task'); -promise_test(async t => { +promise_test(t => { const animation = new Animation(new KeyframeEffect(createDiv(t), null, 100 * MS_PER_SEC), document.timeline); // Set start time such that the current time is past the end time animation.startTime = document.timeline.currentTime - 110 * MS_PER_SEC; assert_equals(animation.playState, 'finished', @@ -183,65 +185,17 @@ promise_test(async t => { assert_greater_than(animation.currentTime, animation.effect.getComputedTiming().endTime, 'Setting the start time updated the finished state with' + ' the \'did seek\' flag set to true'); // Furthermore, that time should persist if we have correctly updated // the hold time const finishedCurrentTime = animation.currentTime; - await waitForAnimationFrames(1); - assert_equals(animation.currentTime, finishedCurrentTime, - 'Current time does not change after seeking past the effect' - + ' end time by setting the current time'); + return waitForAnimationFrames(1).then(() => { + assert_equals(animation.currentTime, finishedCurrentTime, + 'Current time does not change after seeking past the effect' + + ' end time by setting the current time'); + }); }, 'Setting the start time updates the finished state'); -promise_test(async t => { - const anim = createDiv(t).animate(null, 100 * MS_PER_SEC); - - // We should be play-pending now - assert_true(anim.pending); - assert_equals(anim.playState, 'running'); - - // Apply a pending playback rate - anim.updatePlaybackRate(2); - assert_equals(anim.playbackRate, 1); - assert_true(anim.pending); - - // Setting the start time should apply the pending playback rate - anim.startTime = anim.timeline.currentTime - 25 * MS_PER_SEC; - assert_equals(anim.playbackRate, 2); - assert_false(anim.pending); - - // Sanity check that the start time is preserved and current time is - // calculated using the new playback rate - assert_times_equal(anim.startTime, - anim.timeline.currentTime - 25 * MS_PER_SEC); - assert_time_equals_literal(anim.currentTime, 50 * MS_PER_SEC); -}, 'Setting the start time of a play-pending animation applies a pending playback rate'); - -promise_test(async t => { - const anim = createDiv(t).animate(null, 100 * MS_PER_SEC); - await anim.ready; - - // We should be running now - assert_false(anim.pending); - assert_equals(anim.playState, 'running'); - - // Apply a pending playback rate - anim.updatePlaybackRate(2); - assert_equals(anim.playbackRate, 1); - assert_true(anim.pending); - - // Setting the start time should apply the pending playback rate - anim.startTime = anim.timeline.currentTime - 25 * MS_PER_SEC; - assert_equals(anim.playbackRate, 2); - assert_false(anim.pending); - - // Sanity check that the start time is preserved and current time is - // calculated using the new playback rate - assert_times_equal(anim.startTime, - anim.timeline.currentTime - 25 * MS_PER_SEC); - assert_time_equals_literal(anim.currentTime, 50 * MS_PER_SEC); -}, 'Setting the start time of a playing animation applies a pending playback rate'); - </script> </body>
rename from testing/web-platform/tests/web-animations/timing-model/animations/setting-the-target-effect-of-an-animation.html rename to testing/web-platform/tests/web-animations/timing-model/animations/set-the-target-effect-of-an-animation.html --- a/testing/web-platform/tests/web-animations/timing-model/animations/setting-the-target-effect-of-an-animation.html +++ b/testing/web-platform/tests/web-animations/timing-model/animations/set-the-target-effect-of-an-animation.html @@ -1,11 +1,11 @@ <!DOCTYPE html> <meta charset=utf-8> -<title>Setting the target effect of an animation</title> +<title>Setting the target effect</title> <link rel='help' href='https://drafts.csswg.org/web-animations/#setting-the-target-effect'> <script src='/resources/testharness.js'></script> <script src='/resources/testharnessreport.js'></script> <script src='../../testcommon.js'></script> <body> <div id='log'></div> <script> 'use strict'; @@ -26,58 +26,60 @@ promise_test(t => { // This is a bit odd, see: https://github.com/w3c/web-animations/issues/207 assert_equals(anim.playState, 'paused'); assert_false(anim.pending); return retPromise; }, 'If new effect is null and old effect is not null, we reset the pending ' + 'tasks and ready promise is rejected'); -promise_test(async t => { +promise_test(t => { const anim = new Animation(); anim.pause(); assert_true(anim.pending); anim.effect = new KeyframeEffectReadOnly(createDiv(t), { marginLeft: [ '0px', '100px' ] }, 100 * MS_PER_SEC); assert_true(anim.pending); - await anim.ready; - assert_false(anim.pending); - assert_equals(anim.playState, 'paused'); + return anim.ready.then(() => { + assert_false(anim.pending); + assert_equals(anim.playState, 'paused'); + }); }, 'If animation has a pending pause task, reschedule that task to run ' + 'as soon as animation is ready.'); -promise_test(async t => { +promise_test(t => { const anim = new Animation(); anim.play(); assert_true(anim.pending); anim.effect = new KeyframeEffectReadOnly(createDiv(t), { marginLeft: [ '0px', '100px' ] }, 100 * MS_PER_SEC); assert_true(anim.pending); - await anim.ready; - assert_false(anim.pending); - assert_equals(anim.playState, 'running'); + return anim.ready.then(() => { + assert_false(anim.pending); + assert_equals(anim.playState, 'running'); + }); }, 'If animation has a pending play task, reschedule that task to run ' + 'as soon as animation is ready to play new effect.'); -promise_test(async t => { +promise_test(t => { const animA = createDiv(t).animate({ marginLeft: [ '0px', '100px' ] }, 100 * MS_PER_SEC); const animB = new Animation(); - await animA.ready; - - animB.effect = animA.effect; - assert_equals(animA.effect, null); - assert_equals(animA.playState, 'finished'); + return animA.ready.then(() => { + animB.effect = animA.effect; + assert_equals(animA.effect, null); + assert_equals(animA.playState, 'finished'); + }); }, 'When setting the effect of an animation to the effect of an existing ' + 'animation, the existing animation\'s target effect should be set to null.'); test(t => { const animA = createDiv(t).animate({ marginLeft: [ '0px', '100px' ] }, 100 * MS_PER_SEC); const animB = new Animation(); const effect = animA.effect; @@ -88,20 +90,10 @@ test(t => { animB.effect = effect; assert_equals(effect.getComputedTiming().progress, 0.2, 'After setting the effect on a different animation, ' + 'it uses the new animation\'s timing'); }, 'After setting the target effect of animation to the target effect of an ' + 'existing animation, the target effect\'s timing is updated to reflect ' + 'the current time of the new animation.'); -test(t => { - const anim = createDiv(t).animate(null, 100 * MS_PER_SEC); - anim.updatePlaybackRate(2); - assert_equals(anim.playbackRate, 1); - - anim.effect = null; - assert_equals(anim.playbackRate, 2); -}, 'Setting the target effect to null causes a pending playback rate to be' - + ' applied'); - </script> </body>
rename from testing/web-platform/tests/web-animations/timing-model/animations/setting-the-timeline-of-an-animation.html rename to testing/web-platform/tests/web-animations/timing-model/animations/set-the-timeline-of-an-animation.html --- a/testing/web-platform/tests/web-animations/timing-model/animations/setting-the-timeline-of-an-animation.html +++ b/testing/web-platform/tests/web-animations/timing-model/animations/set-the-timeline-of-an-animation.html @@ -1,11 +1,11 @@ <!DOCTYPE html> <meta charset=utf-8> -<title>Setting the timeline of an animation</title> +<title>Setting the timeline</title> <link rel="help" href="https://drafts.csswg.org/web-animations/#setting-the-timeline"> <script src="/resources/testharness.js"></script> <script src="/resources/testharnessreport.js"></script> <script src="../../testcommon.js"></script> <body> <div id="log"></div> <script> 'use strict'; @@ -76,53 +76,55 @@ test(t => { assert_equals(animation.playState, 'idle'); animation.timeline = document.timeline; assert_equals(animation.playState, 'finished'); }, 'After setting timeline on an idle animation with a sufficiently ancient' + ' start time it is finished'); -promise_test(async t => { +promise_test(t => { const animation = new Animation(new KeyframeEffect(createDiv(t), null, 100 * MS_PER_SEC), null); animation.play(); assert_true(animation.pending && animation.playState === 'running', 'Animation is initially play-pending'); animation.timeline = document.timeline; assert_true(animation.pending && animation.playState === 'running', 'Animation is still play-pending after setting timeline'); - await animation.ready; - assert_true(!animation.pending && animation.playState === 'running', - 'Animation plays after it finishes pending'); + return animation.ready.then(() => { + assert_true(!animation.pending && animation.playState === 'running', + 'Animation plays after it finishes pending'); + }); }, 'After setting timeline on a play-pending animation it begins playing' + ' after pending'); -promise_test(async t => { +promise_test(t => { const animation = new Animation(new KeyframeEffect(createDiv(t), null, 100 * MS_PER_SEC), null); animation.startTime = document.timeline.currentTime; animation.pause(); animation.timeline = null; assert_true(animation.pending && animation.playState === 'paused', 'Animation is initially pause-pending'); animation.timeline = document.timeline; assert_true(animation.pending && animation.playState === 'paused', 'Animation is still pause-pending after setting timeline'); - await animation.ready; - assert_true(!animation.pending && animation.playState === 'paused', - 'Animation pauses after it finishes pending'); + return animation.ready.then(() => { + assert_true(!animation.pending && animation.playState === 'paused', + 'Animation pauses after it finishes pending'); + }); }, 'After setting timeline on a pause-pending animation it becomes paused' + ' after pending'); // --------------------------------------------------------------------- // // Tests from timeline to no timeline // // --------------------------------------------------------------------- @@ -186,70 +188,73 @@ test(t => { const animation = createDiv(t).animate(null, 100 * MS_PER_SEC); assert_true(animation.pending && animation.playState === 'running'); animation.timeline = null; assert_true(animation.pending && animation.playState === 'running'); }, 'After clearing timeline on play-pending animation it is still pending'); -promise_test(async t => { +promise_test(t => { const animation = createDiv(t).animate(null, 100 * MS_PER_SEC); assert_true(animation.pending && animation.playState === 'running'); animation.timeline = null; animation.timeline = document.timeline; assert_true(animation.pending && animation.playState === 'running'); - await animation.ready; - assert_true(!animation.pending && animation.playState === 'running'); + return animation.ready.then(() => { + assert_true(!animation.pending && animation.playState === 'running'); + }); }, 'After clearing and re-setting timeline on play-pending animation it' + ' begins to play'); test(t => { const animation = new Animation(new KeyframeEffect(createDiv(t), null, 100 * MS_PER_SEC), document.timeline); animation.startTime = document.timeline.currentTime; animation.pause(); assert_true(animation.pending && animation.playState === 'paused'); animation.timeline = null; assert_true(animation.pending && animation.playState === 'paused'); }, 'After clearing timeline on a pause-pending animation it is still pending'); -promise_test(async t => { +promise_test(t => { const animation = new Animation(new KeyframeEffect(createDiv(t), null, 100 * MS_PER_SEC), document.timeline); animation.startTime = document.timeline.currentTime; animation.pause(); assert_true(animation.pending && animation.playState === 'paused'); animation.timeline = null; animation.timeline = document.timeline; assert_true(animation.pending && animation.playState === 'paused'); - await animation.ready; - assert_true(!animation.pending && animation.playState === 'paused'); + return animation.ready.then(() => { + assert_true(!animation.pending && animation.playState === 'paused'); + }); }, 'After clearing and re-setting timeline on a pause-pending animation it' + ' completes pausing'); -promise_test(async t => { +promise_test(t => { const animation = new Animation(new KeyframeEffect(createDiv(t), null, 100 * MS_PER_SEC), document.timeline); const initialStartTime = document.timeline.currentTime - 50 * MS_PER_SEC; animation.startTime = initialStartTime; animation.pause(); animation.play(); animation.timeline = null; animation.timeline = document.timeline; - await animation.ready; - assert_times_equal(animation.startTime, initialStartTime); + return animation.ready.then(() => { + assert_times_equal(animation.startTime, initialStartTime); + }); }, 'After clearing and re-setting timeline on an animation in the middle of' + ' an aborted pause, it continues playing using the same start time'); </script> </body>
deleted file mode 100644 --- a/testing/web-platform/tests/web-animations/timing-model/animations/setting-the-current-time-of-an-animation.html +++ /dev/null @@ -1,38 +0,0 @@ -<!doctype html> -<meta charset=utf-8> -<title>Setting the current time of an animation</title> -<link rel="help" - href="https://drafts.csswg.org/web-animations-1/#setting-the-current-time-of-an-animation"> -<script src='/resources/testharness.js'></script> -<script src='/resources/testharnessreport.js'></script> -<script src='../../testcommon.js'></script> -<body> -<div id='log'></div> -<script> -'use strict'; - -promise_test(async t => { - const anim = createDiv(t).animate(null, 100 * MS_PER_SEC); - await anim.ready; - anim.pause(); - - // We should be pause-pending now - assert_true(anim.pending); - assert_equals(anim.playState, 'paused'); - - // Apply a pending playback rate - anim.updatePlaybackRate(2); - assert_equals(anim.playbackRate, 1); - - // Setting the current time should apply the pending playback rate - anim.currentTime = 50 * MS_PER_SEC; - assert_equals(anim.playbackRate, 2); - assert_false(anim.pending); - - // Sanity check that the current time is preserved - assert_time_equals_literal(anim.currentTime, 50 * MS_PER_SEC); -}, 'Setting the current time of a pausing animation applies a pending playback' - + ' rate'); - -</script> -</body>
--- a/testing/web-platform/tests/web-animations/timing-model/animations/updating-the-finished-state.html +++ b/testing/web-platform/tests/web-animations/timing-model/animations/updating-the-finished-state.html @@ -15,254 +15,251 @@ // TESTS FOR UPDATING THE HOLD TIME // // -------------------------------------------------------------------- // CASE 1: playback rate > 0 and current time >= target effect end // (Also the start time is resolved and there is pending task) // Did seek = false -promise_test(async t => { +promise_test(t => { const anim = createDiv(t).animate(null, 100 * MS_PER_SEC); // Here and in the following tests we wait until ready resolves as // otherwise we don't have a resolved start time. We test the case // where the start time is unresolved in a subsequent test. - await anim.ready; - - // Seek to 1ms before the target end and then wait 1ms - anim.currentTime = 100 * MS_PER_SEC - 1; - await waitForAnimationFramesWithDelay(1); - - assert_equals(anim.currentTime, 100 * MS_PER_SEC, - 'Hold time is set to target end clamping current time'); + return anim.ready.then(() => { + // Seek to 1ms before the target end and then wait 1ms + anim.currentTime = 100 * MS_PER_SEC - 1; + return waitForAnimationFramesWithDelay(1); + }).then(() => { + assert_equals(anim.currentTime, 100 * MS_PER_SEC, + 'Hold time is set to target end clamping current time'); + }); }, 'Updating the finished state when playing past end'); // Did seek = true -promise_test(async t => { +promise_test(t => { const anim = createDiv(t).animate(null, 100 * MS_PER_SEC); - - await anim.ready; - - anim.currentTime = 200 * MS_PER_SEC; - await waitForNextFrame(); - - assert_equals(anim.currentTime, 200 * MS_PER_SEC, - 'Hold time is set so current time should NOT change'); + return anim.ready.then(() => { + anim.currentTime = 200 * MS_PER_SEC; + return waitForNextFrame(); + }).then(() => { + assert_equals(anim.currentTime, 200 * MS_PER_SEC, + 'Hold time is set so current time should NOT change'); + }); }, 'Updating the finished state when seeking past end'); // Test current time == target end // // We can't really write a test for current time == target end with // did seek = false since that would imply setting up an animation where // the next animation frame time happens to exactly align with the target end. // // Fortunately, we don't need to test that case since even if the implementation // fails to set the hold time on such a tick, it should be mostly unobservable // (on the subsequent tick the hold time will be set to the same value anyway). // Did seek = true -promise_test(async t => { +promise_test(t => { const anim = createDiv(t).animate(null, 100 * MS_PER_SEC); - await anim.ready; - - anim.currentTime = 100 * MS_PER_SEC; - await waitForNextFrame(); - - assert_equals(anim.currentTime, 100 * MS_PER_SEC, - 'Hold time is set so current time should NOT change'); + return anim.ready.then(() => { + anim.currentTime = 100 * MS_PER_SEC; + return waitForNextFrame(); + }).then(() => { + assert_equals(anim.currentTime, 100 * MS_PER_SEC, + 'Hold time is set so current time should NOT change'); + }); }, 'Updating the finished state when seeking exactly to end'); // CASE 2: playback rate < 0 and current time <= 0 // (Also the start time is resolved and there is pending task) // Did seek = false -promise_test(async t => { +promise_test(t => { const anim = createDiv(t).animate(null, 100 * MS_PER_SEC); anim.playbackRate = -1; anim.play(); // Make sure animation is not initially finished - - await anim.ready; - - // Seek to 1ms before 0 and then wait 1ms - anim.currentTime = 1; - await waitForAnimationFramesWithDelay(1); - - assert_equals(anim.currentTime, 0 * MS_PER_SEC, - 'Hold time is set to zero clamping current time'); + return anim.ready.then(() => { + // Seek to 1ms before 0 and then wait 1ms + anim.currentTime = 1; + return waitForAnimationFramesWithDelay(1); + }).then(() => { + assert_equals(anim.currentTime, 0 * MS_PER_SEC, + 'Hold time is set to zero clamping current time'); + }); }, 'Updating the finished state when playing in reverse past zero'); // Did seek = true -promise_test(async t => { +promise_test(t => { const anim = createDiv(t).animate(null, 100 * MS_PER_SEC); anim.playbackRate = -1; anim.play(); - - await anim.ready; - - anim.currentTime = -100 * MS_PER_SEC; - await waitForNextFrame(); - - assert_equals(anim.currentTime, -100 * MS_PER_SEC, - 'Hold time is set so current time should NOT change'); + return anim.ready.then(() => { + anim.currentTime = -100 * MS_PER_SEC; + return waitForNextFrame(); + }).then(() => { + assert_equals(anim.currentTime, -100 * MS_PER_SEC, + 'Hold time is set so current time should NOT change'); + }); }, 'Updating the finished state when seeking a reversed animation past zero'); // As before, it's difficult to test current time == 0 for did seek = false but // it doesn't really matter. // Did seek = true -promise_test(async t => { +promise_test(t => { const anim = createDiv(t).animate(null, 100 * MS_PER_SEC); anim.playbackRate = -1; anim.play(); - await anim.ready; - - anim.currentTime = 0; - await waitForNextFrame(); - - assert_equals(anim.currentTime, 0 * MS_PER_SEC, - 'Hold time is set so current time should NOT change'); + return anim.ready.then(() => { + anim.currentTime = 0; + return waitForNextFrame(); + }).then(() => { + assert_equals(anim.currentTime, 0 * MS_PER_SEC, + 'Hold time is set so current time should NOT change'); + }); }, 'Updating the finished state when seeking a reversed animation exactly' + ' to zero'); // CASE 3: playback rate > 0 and current time < target end OR // playback rate < 0 and current time > 0 // (Also the start time is resolved and there is pending task) // Did seek = false; playback rate > 0 -promise_test(async t => { +promise_test(t => { const anim = createDiv(t).animate(null, 100 * MS_PER_SEC); // We want to test that the hold time is cleared so first we need to // put the animation in a state where the hold time is set. anim.finish(); - await anim.ready; - - assert_equals(anim.currentTime, 100 * MS_PER_SEC, - 'Hold time is initially set'); - // Then extend the duration so that the hold time is cleared and on - // the next tick the current time will increase. - anim.effect.timing.duration *= 2; - await waitForNextFrame(); - - assert_greater_than(anim.currentTime, 100 * MS_PER_SEC, - 'Hold time is not set so current time should increase'); + return anim.ready.then(() => { + assert_equals(anim.currentTime, 100 * MS_PER_SEC, + 'Hold time is initially set'); + // Then extend the duration so that the hold time is cleared and on + // the next tick the current time will increase. + anim.effect.timing.duration *= 2; + return waitForNextFrame(); + }).then(() => { + assert_greater_than(anim.currentTime, 100 * MS_PER_SEC, + 'Hold time is not set so current time should increase'); + }); }, 'Updating the finished state when playing before end'); // Did seek = true; playback rate > 0 -promise_test(async t => { +promise_test(t => { const anim = createDiv(t).animate(null, 100 * MS_PER_SEC); anim.finish(); - await anim.ready; - - anim.currentTime = 50 * MS_PER_SEC; - // When did seek = true, updating the finished state: (i) updates - // the animation's start time and (ii) clears the hold time. - // We can test both by checking that the currentTime is initially - // updated and then increases. - assert_equals(anim.currentTime, 50 * MS_PER_SEC, 'Start time is updated'); - await waitForNextFrame(); - - assert_greater_than(anim.currentTime, 50 * MS_PER_SEC, - 'Hold time is not set so current time should increase'); + return anim.ready.then(() => { + anim.currentTime = 50 * MS_PER_SEC; + // When did seek = true, updating the finished state: (i) updates + // the animation's start time and (ii) clears the hold time. + // We can test both by checking that the currentTime is initially + // updated and then increases. + assert_equals(anim.currentTime, 50 * MS_PER_SEC, 'Start time is updated'); + return waitForNextFrame(); + }).then(() => { + assert_greater_than(anim.currentTime, 50 * MS_PER_SEC, + 'Hold time is not set so current time should increase'); + }); }, 'Updating the finished state when seeking before end'); // Did seek = false; playback rate < 0 // // Unfortunately it is not possible to test this case. We need to have // a hold time set, a resolved start time, and then perform some // operation that updates the finished state with did seek set to true. // // However, the only situation where this could arrive is when we // replace the timeline and that procedure is likely to change. For all // other cases we either have an unresolved start time (e.g. when // paused), we don't have a set hold time (e.g. regular playback), or // the current time is zero (and anything that gets us out of that state // will set did seek = true). // Did seek = true; playback rate < 0 -promise_test(async t => { +promise_test(t => { const anim = createDiv(t).animate(null, 100 * MS_PER_SEC); anim.playbackRate = -1; - await anim.ready; - - anim.currentTime = 50 * MS_PER_SEC; - assert_equals(anim.currentTime, 50 * MS_PER_SEC, 'Start time is updated'); - await waitForNextFrame(); - - assert_less_than(anim.currentTime, 50 * MS_PER_SEC, - 'Hold time is not set so current time should decrease'); + return anim.ready.then(() => { + anim.currentTime = 50 * MS_PER_SEC; + assert_equals(anim.currentTime, 50 * MS_PER_SEC, 'Start time is updated'); + return waitForNextFrame(); + }).then(() => { + assert_less_than(anim.currentTime, 50 * MS_PER_SEC, + 'Hold time is not set so current time should decrease'); + }); }, 'Updating the finished state when seeking a reversed animation before end'); // CASE 4: playback rate == 0 // current time < 0 -promise_test(async t => { +promise_test(t => { const anim = createDiv(t).animate(null, 100 * MS_PER_SEC); anim.playbackRate = 0; - await anim.ready; - - anim.currentTime = -100 * MS_PER_SEC; - await waitForNextFrame(); - - assert_equals(anim.currentTime, -100 * MS_PER_SEC, - 'Hold time should not be cleared so current time should' - + ' NOT change'); + return anim.ready.then(() => { + anim.currentTime = -100 * MS_PER_SEC; + return waitForNextFrame(); + }).then(() => { + assert_equals(anim.currentTime, -100 * MS_PER_SEC, + 'Hold time should not be cleared so current time should' + + ' NOT change'); + }); }, 'Updating the finished state when playback rate is zero and the' + ' current time is less than zero'); // current time < target end -promise_test(async t => { +promise_test(t => { const anim = createDiv(t).animate(null, 100 * MS_PER_SEC); anim.playbackRate = 0; - await anim.ready; - - anim.currentTime = 50 * MS_PER_SEC; - await waitForNextFrame(); - - assert_equals(anim.currentTime, 50 * MS_PER_SEC, - 'Hold time should not be cleared so current time should' - + ' NOT change'); + return anim.ready.then(() => { + anim.currentTime = 50 * MS_PER_SEC; + return waitForNextFrame(); + }).then(() => { + assert_equals(anim.currentTime, 50 * MS_PER_SEC, + 'Hold time should not be cleared so current time should' + + ' NOT change'); + }); }, 'Updating the finished state when playback rate is zero and the' + ' current time is less than end'); // current time > target end -promise_test(async t => { +promise_test(t => { const anim = createDiv(t).animate(null, 100 * MS_PER_SEC); anim.playbackRate = 0; - await anim.ready; - - anim.currentTime = 200 * MS_PER_SEC; - await waitForNextFrame(); - - assert_equals(anim.currentTime, 200 * MS_PER_SEC, - 'Hold time should not be cleared so current time should' - + ' NOT change'); + return anim.ready.then(() => { + anim.currentTime = 200 * MS_PER_SEC; + return waitForNextFrame(); + }).then(() => { + assert_equals(anim.currentTime, 200 * MS_PER_SEC, + 'Hold time should not be cleared so current time should' + + ' NOT change'); + }); }, 'Updating the finished state when playback rate is zero and the' + ' current time is greater than end'); // CASE 5: current time unresolved -promise_test(async t => { +promise_test(t => { const anim = createDiv(t).animate(null, 100 * MS_PER_SEC); anim.cancel(); // Trigger a change that will cause the "update the finished state" // procedure to run. anim.effect.timing.duration = 200 * MS_PER_SEC; assert_equals(anim.currentTime, null, 'The animation hold time / start time should not be updated'); // The "update the finished state" procedure is supposed to run after any // change to timing, but just in case an implementation defers that, let's // wait a frame and check that the hold time / start time has still not been // updated. - await waitForAnimationFrames(1); - - assert_equals(anim.currentTime, null, - 'The animation hold time / start time should not be updated'); + return waitForAnimationFrames(1).then(() => { + assert_equals(anim.currentTime, null, + 'The animation hold time / start time should not be updated'); + }); }, 'Updating the finished state when current time is unresolved'); // CASE 6: has a pending task test(t => { const anim = createDiv(t).animate(null, 100 * MS_PER_SEC); anim.cancel(); anim.currentTime = 75 * MS_PER_SEC; @@ -276,31 +273,31 @@ test(t => { anim.effect.timing.duration = 50 * MS_PER_SEC; assert_equals(anim.currentTime, 75 * MS_PER_SEC, 'Hold time should not be updated'); }, 'Updating the finished state when there is a pending task'); // CASE 7: start time unresolved // Did seek = false -promise_test(async t => { +promise_test(t => { const anim = createDiv(t).animate(null, 100 * MS_PER_SEC); anim.cancel(); // Make it so that only the start time is unresolved (to avoid overlapping // with the test case where current time is unresolved) anim.currentTime = 150 * MS_PER_SEC; // Trigger a change that will cause the "update the finished state" // procedure to run (did seek = false). anim.effect.timing.duration = 200 * MS_PER_SEC; - await waitForAnimationFrames(1); - - assert_equals(anim.currentTime, 150 * MS_PER_SEC, - 'The animation hold time should not be updated'); - assert_equals(anim.startTime, null, - 'The animation start time should not be updated'); + return waitForAnimationFrames(1).then(() => { + assert_equals(anim.currentTime, 150 * MS_PER_SEC, + 'The animation hold time should not be updated'); + assert_equals(anim.startTime, null, + 'The animation start time should not be updated'); + }); }, 'Updating the finished state when start time is unresolved and' + ' did seek = false'); // Did seek = true test(t => { const anim = createDiv(t).animate(null, 100 * MS_PER_SEC); anim.cancel(); anim.currentTime = 150 * MS_PER_SEC; @@ -335,71 +332,63 @@ promise_test(t => { t.unreached_func('Seeking to finish should not resolve finished promise')); animation.currentTime = 1; animation.currentTime = 0; animation.pause(); return waitForAnimationFrames(3); }, 'Finish notification steps don\'t run when the animation seeks to finish' + ' and then seeks back again'); -promise_test(async t => { +promise_test(t => { const animation = createDiv(t).animate(null, 1); - await animation.ready; - - return waitForFinishEventAndPromise(animation); + return animation.ready.then(() => { + return waitForFinishEventAndPromise(animation); + }); }, 'Finish notification steps run when the animation completes normally'); -promise_test(async t => { - const effect = new KeyframeEffectReadOnly(null, null, 1); - const animation = new Animation(effect, document.timeline); - animation.play(); - await animation.ready; - - return waitForFinishEventAndPromise(animation); -}, 'Finish notification steps run when an animation without a target' - + ' effect completes normally'); - -promise_test(async t => { +promise_test(t => { const animation = createDiv(t).animate(null, 1); - await animation.ready; - - animation.currentTime = 10; - return waitForFinishEventAndPromise(animation); + return animation.ready.then(() => { + animation.currentTime = 10; + return waitForFinishEventAndPromise(animation); + }); }, 'Finish notification steps run when the animation seeks past finish'); -promise_test(async t => { +promise_test(t => { const animation = createDiv(t).animate(null, 1); - await animation.ready; - - // Register for notifications now since once we seek away from being - // finished the 'finished' promise will be replaced. - const finishNotificationSteps = waitForFinishEventAndPromise(animation); - animation.finish(); - animation.currentTime = 0; - animation.pause(); - return finishNotificationSteps; + return animation.ready.then(() => { + // Register for notifications now since once we seek away from being + // finished the 'finished' promise will be replaced. + const finishNotificationSteps = waitForFinishEventAndPromise(animation); + animation.finish(); + animation.currentTime = 0; + animation.pause(); + return finishNotificationSteps; + }); }, 'Finish notification steps run when the animation completes with .finish(),' + ' even if we then seek away'); -promise_test(async t => { +promise_test(t => { const animation = createDiv(t).animate(null, 1); const initialFinishedPromise = animation.finished; - await animation.finished; - animation.currentTime = 0; - assert_not_equals(initialFinishedPromise, animation.finished); + return animation.finished.then(target => { + animation.currentTime = 0; + assert_not_equals(initialFinishedPromise, animation.finished); + }); }, 'Animation finished promise is replaced after seeking back to start'); -promise_test(async t => { +promise_test(t => { const animation = createDiv(t).animate(null, 1); const initialFinishedPromise = animation.finished; - await animation.finished; - animation.play(); - assert_not_equals(initialFinishedPromise, animation.finished); + return animation.finished.then(target => { + animation.play(); + assert_not_equals(initialFinishedPromise, animation.finished); + }); }, 'Animation finished promise is replaced after replaying from start'); async_test(t => { const animation = createDiv(t).animate(null, 1); animation.onfinish = event => { animation.currentTime = 0; animation.onfinish = event => { t.done();