Bug 1775327 - Part 2: Fix playing finished scroll animation on reversing scrolling. r=firefox-animation-reviewers,birtles

We have to make sure the scroll animations is still responsive at
boundaries even if it's playstate is finished.

This patch includes the update of UpdateFinishedState() to match the
spec, and make sure we still tick scroll animations at finished play state.

Getting a finished state might be strange for scroll animations, and this
might be a spec issue. However, for consistency with JS-generated animations,
we'd like to align the behaviors with other browsers, and make sure we are
still match the definition of finished state in the spec.

Besides, we have to use EndTime() on the compositor so
animation-iteration-count works properly.

Tests are in the last patch.

Differential Revision: https://phabricator.services.mozilla.com/D149940
This commit is contained in:
Boris Chiou 2022-07-07 18:33:41 +00:00
parent ee025d7686
commit db1b58d707
4 changed files with 25 additions and 22 deletions

View File

@ -1610,30 +1610,32 @@ void Animation::UpdateTiming(SeekFlag aSeekFlag,
// https://drafts.csswg.org/web-animations/#update-an-animations-finished-state
void Animation::UpdateFinishedState(SeekFlag aSeekFlag,
SyncNotifyFlag aSyncNotifyFlag) {
Nullable<TimeDuration> currentTime = GetCurrentTimeAsDuration();
Nullable<TimeDuration> unconstrainedCurrentTime =
aSeekFlag == SeekFlag::NoSeek ? GetUnconstrainedCurrentTime()
: GetCurrentTimeAsDuration();
TimeDuration effectEnd = TimeDuration(EffectEnd());
if (!mStartTime.IsNull() && mPendingState == PendingState::NotPending) {
if (mPlaybackRate > 0.0 && !currentTime.IsNull() &&
currentTime.Value() >= effectEnd) {
if (!unconstrainedCurrentTime.IsNull() && !mStartTime.IsNull() &&
mPendingState == PendingState::NotPending) {
if (mPlaybackRate > 0.0 && unconstrainedCurrentTime.Value() >= effectEnd) {
if (aSeekFlag == SeekFlag::DidSeek) {
mHoldTime = currentTime;
mHoldTime = unconstrainedCurrentTime;
} else if (!mPreviousCurrentTime.IsNull()) {
mHoldTime.SetValue(std::max(mPreviousCurrentTime.Value(), effectEnd));
} else {
mHoldTime.SetValue(effectEnd);
}
} else if (mPlaybackRate < 0.0 && !currentTime.IsNull() &&
currentTime.Value() <= TimeDuration()) {
} else if (mPlaybackRate < 0.0 &&
unconstrainedCurrentTime.Value() <= TimeDuration()) {
if (aSeekFlag == SeekFlag::DidSeek) {
mHoldTime = currentTime;
mHoldTime = unconstrainedCurrentTime;
} else if (!mPreviousCurrentTime.IsNull()) {
mHoldTime.SetValue(
std::min(mPreviousCurrentTime.Value(), TimeDuration(0)));
} else {
mHoldTime.SetValue(0);
}
} else if (mPlaybackRate != 0.0 && !currentTime.IsNull() && mTimeline &&
} else if (mPlaybackRate != 0.0 && mTimeline &&
!mTimeline->GetCurrentTimeAsDuration().IsNull()) {
if (aSeekFlag == SeekFlag::DidSeek && !mHoldTime.IsNull()) {
mStartTime = StartTimeFromTimelineTime(
@ -1644,15 +1646,16 @@ void Animation::UpdateFinishedState(SeekFlag aSeekFlag,
}
}
// We must recalculate the current time to take account of any mHoldTime
// changes the code above made.
mPreviousCurrentTime = GetCurrentTimeAsDuration();
bool currentFinishedState = PlayState() == AnimationPlayState::Finished;
if (currentFinishedState && !mFinishedIsResolved) {
DoFinishNotification(aSyncNotifyFlag);
} else if (!currentFinishedState && mFinishedIsResolved) {
ResetFinishedPromise();
}
// We must recalculate the current time to take account of any mHoldTime
// changes the code above made.
mPreviousCurrentTime = GetCurrentTimeAsDuration();
}
void Animation::UpdateEffect(PostRestyleMode aPostRestyle) {

View File

@ -164,7 +164,13 @@ class Animation : public DOMEventTargetHelper,
// won't be relevant and hence won't be returned by GetAnimations().
// We don't want its timeline to keep it alive (which would happen
// if we return true) since otherwise it will effectively be leaked.
PlaybackRate() != 0.0);
PlaybackRate() != 0.0) ||
// Always return true for not idle animations attached to not
// monotonically increasing timelines even if the animation is
// finished. This is required to accommodate cases where timeline
// ticks back in time.
(mTimeline && !mTimeline->IsMonotonicallyIncreasing() &&
PlayState() != AnimationPlayState::Idle);
}
/**

View File

@ -219,8 +219,6 @@ void TimingParams::Normalize() {
mDuration = Some(StickyTimeDuration::FromMilliseconds(
PROGRESS_TIMELINE_DURATION_MILLISEC));
mDelay = TimeDuration::FromMilliseconds(0);
mIterations = std::numeric_limits<double>::infinity();
mDirection = dom::PlaybackDirection::Alternate;
Update();
}

View File

@ -31,10 +31,8 @@ namespace layers {
static dom::Nullable<TimeDuration> CalculateElapsedTimeForScrollTimeline(
const Maybe<APZSampler::ScrollOffsetAndRange> aScrollMeta,
const ScrollTimelineOptions& aOptions, const Maybe<TimeDuration>& aDuration,
const ScrollTimelineOptions& aOptions, const StickyTimeDuration& aEndTime,
const TimeDuration& aStartTime, float aPlaybackRate) {
MOZ_ASSERT(aDuration);
// We return Nothing If the associated APZ controller is not available
// (because it may be destroyed but this animation is still alive).
if (!aScrollMeta) {
@ -59,8 +57,7 @@ static dom::Nullable<TimeDuration> CalculateElapsedTimeForScrollTimeline(
double progress = position / range;
// Just in case to avoid getting a progress more than 100%, for overscrolling.
progress = std::min(progress, 1.0);
auto timelineTime = aDuration->MultDouble(progress);
auto timelineTime = TimeDuration(aEndTime.MultDouble(progress));
return dom::Animation::CurrentTimeFromTimelineTime(timelineTime, aStartTime,
aPlaybackRate);
}
@ -82,8 +79,7 @@ static dom::Nullable<TimeDuration> CalculateElapsedTime(
aAPZSampler->GetCurrentScrollOffsetAndRange(
aLayersId, aAnimation.mScrollTimelineOptions.value().source(),
aProofOfMapLock),
aAnimation.mScrollTimelineOptions.value(),
aAnimation.mTiming.Duration(),
aAnimation.mScrollTimelineOptions.value(), aAnimation.mTiming.EndTime(),
aAnimation.mStartTime.refOr(aAnimation.mHoldTime),
aAnimation.mPlaybackRate);
}