mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-24 05:11:16 +00:00
Bug 1775327 - Part 1: Make sure the scroll animation is still in active phase at boundaries. r=firefox-animation-reviewers,birtles
We have to introduce "at progress timeline boundary" which is defined in web-animations-2 [1]. We need this to make sure the scroll animations do not go into before phase or after phase. The test of fill-mode should be together with delay, so I'd like to add the tests in the last patch. [1] https://drafts.csswg.org/web-animations-2/#at-progress-timeline-boundary Differential Revision: https://phabricator.services.mozilla.com/D149684
This commit is contained in:
parent
309272c871
commit
ee025d7686
@ -1741,6 +1741,53 @@ void Animation::ReschedulePendingTasks() {
|
||||
}
|
||||
}
|
||||
|
||||
// https://drafts.csswg.org/web-animations-2/#at-progress-timeline-boundary
|
||||
/* static*/ Animation::ProgressTimelinePosition
|
||||
Animation::AtProgressTimelineBoundary(
|
||||
const Nullable<TimeDuration>& aTimelineDuration,
|
||||
const Nullable<TimeDuration>& aCurrentTime,
|
||||
const TimeDuration& aEffectStartTime, const double aPlaybackRate) {
|
||||
// Based on changed defined in: https://github.com/w3c/csswg-drafts/pull/6702
|
||||
// 1. If any of the following conditions are true:
|
||||
// * the associated animation's timeline is not a progress-based timeline,
|
||||
// or
|
||||
// * the associated animation's timeline duration is unresolved or zero,
|
||||
// or
|
||||
// * the animation's playback rate is zero
|
||||
// return false
|
||||
// Note: We can detect a progress-based timeline by relying on the fact that
|
||||
// monotonic timelines (i.e. non-progress-based timelines) have an unresolved
|
||||
// timeline duration.
|
||||
if (aTimelineDuration.IsNull() || aTimelineDuration.Value().IsZero() ||
|
||||
aPlaybackRate == 0.0) {
|
||||
return ProgressTimelinePosition::NotBoundary;
|
||||
}
|
||||
|
||||
// 2. Let effective start time be the animation's start time if resolved, or
|
||||
// zero otherwise.
|
||||
const TimeDuration& effectiveStartTime = aEffectStartTime;
|
||||
|
||||
// 3. Let effective timeline time be (animation's current time / animation's
|
||||
// playback rate) + effective start time.
|
||||
// Note: we use zero if the current time is unresolved. See the spec issue:
|
||||
// https://github.com/w3c/csswg-drafts/issues/7458
|
||||
const TimeDuration effectiveTimelineTime =
|
||||
(aCurrentTime.IsNull()
|
||||
? TimeDuration()
|
||||
: aCurrentTime.Value().MultDouble(1.0 / aPlaybackRate)) +
|
||||
effectiveStartTime;
|
||||
|
||||
// 4. Let effective timeline progress be (effective timeline time / timeline
|
||||
// duration)
|
||||
// 5. If effective timeline progress is 0 or 1, return true,
|
||||
// We avoid the division here but it is effectively the same as 4 & 5 above.
|
||||
return effectiveTimelineTime.IsZero() ||
|
||||
(AnimationUtils::IsWithinAnimationTimeTolerance(
|
||||
effectiveTimelineTime, aTimelineDuration.Value()))
|
||||
? ProgressTimelinePosition::Boundary
|
||||
: ProgressTimelinePosition::NotBoundary;
|
||||
}
|
||||
|
||||
bool Animation::IsPossiblyOrphanedPendingAnimation() const {
|
||||
// Check if we are pending but might never start because we are not being
|
||||
// tracked.
|
||||
|
@ -451,6 +451,23 @@ class Animation : public DOMEventTargetHelper,
|
||||
return mTimeline && mTimeline->IsScrollTimeline();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if this is at the progress timeline boundary.
|
||||
* https://drafts.csswg.org/web-animations-2/#at-progress-timeline-boundary
|
||||
*/
|
||||
enum class ProgressTimelinePosition : uint8_t { Boundary, NotBoundary };
|
||||
static ProgressTimelinePosition AtProgressTimelineBoundary(
|
||||
const Nullable<TimeDuration>& aTimelineDuration,
|
||||
const Nullable<TimeDuration>& aCurrentTime,
|
||||
const TimeDuration& aEffectStartTime, const double aPlaybackRate);
|
||||
ProgressTimelinePosition AtProgressTimelineBoundary() const {
|
||||
return AtProgressTimelineBoundary(
|
||||
mTimeline ? mTimeline->TimelineDuration() : nullptr,
|
||||
GetCurrentTimeAsDuration(),
|
||||
mStartTime.IsNull() ? TimeDuration() : mStartTime.Value(),
|
||||
mPlaybackRate);
|
||||
}
|
||||
|
||||
protected:
|
||||
void SilentlySetCurrentTime(const TimeDuration& aNewCurrentTime);
|
||||
void CancelNoUpdate();
|
||||
|
@ -101,7 +101,8 @@ void AnimationEffect::SetSpecifiedTiming(TimingParams&& aTiming) {
|
||||
|
||||
ComputedTiming AnimationEffect::GetComputedTimingAt(
|
||||
const Nullable<TimeDuration>& aLocalTime, const TimingParams& aTiming,
|
||||
double aPlaybackRate) {
|
||||
double aPlaybackRate,
|
||||
Animation::ProgressTimelinePosition aProgressTimelinePosition) {
|
||||
static const StickyTimeDuration zeroDuration;
|
||||
|
||||
// Always return the same object to benefit from return-value optimization.
|
||||
@ -134,6 +135,9 @@ ComputedTiming AnimationEffect::GetComputedTimingAt(
|
||||
return result;
|
||||
}
|
||||
const TimeDuration& localTime = aLocalTime.Value();
|
||||
const bool atProgressTimelineBoundary =
|
||||
aProgressTimelinePosition ==
|
||||
Animation::ProgressTimelinePosition::Boundary;
|
||||
|
||||
StickyTimeDuration beforeActiveBoundary =
|
||||
std::max(std::min(StickyTimeDuration(aTiming.Delay()), result.mEndTime),
|
||||
@ -145,7 +149,8 @@ ComputedTiming AnimationEffect::GetComputedTimingAt(
|
||||
zeroDuration);
|
||||
|
||||
if (localTime > activeAfterBoundary ||
|
||||
(aPlaybackRate >= 0 && localTime == activeAfterBoundary)) {
|
||||
(aPlaybackRate >= 0 && localTime == activeAfterBoundary &&
|
||||
!atProgressTimelineBoundary)) {
|
||||
result.mPhase = ComputedTiming::AnimationPhase::After;
|
||||
if (!result.FillsForwards()) {
|
||||
// The animation isn't active or filling at this time.
|
||||
@ -156,7 +161,8 @@ ComputedTiming AnimationEffect::GetComputedTimingAt(
|
||||
result.mActiveDuration),
|
||||
zeroDuration);
|
||||
} else if (localTime < beforeActiveBoundary ||
|
||||
(aPlaybackRate < 0 && localTime == beforeActiveBoundary)) {
|
||||
(aPlaybackRate < 0 && localTime == beforeActiveBoundary &&
|
||||
!atProgressTimelineBoundary)) {
|
||||
result.mPhase = ComputedTiming::AnimationPhase::Before;
|
||||
if (!result.FillsBackwards()) {
|
||||
// The animation isn't active or filling at this time.
|
||||
@ -165,8 +171,8 @@ ComputedTiming AnimationEffect::GetComputedTimingAt(
|
||||
result.mActiveTime =
|
||||
std::max(StickyTimeDuration(localTime - aTiming.Delay()), zeroDuration);
|
||||
} else {
|
||||
MOZ_ASSERT(result.mActiveDuration,
|
||||
"How can we be in the middle of a zero-duration interval?");
|
||||
// Note: For progress-based timeline, it's possible to have a zero active
|
||||
// duration with active phase.
|
||||
result.mPhase = ComputedTiming::AnimationPhase::Active;
|
||||
result.mActiveTime = localTime - aTiming.Delay();
|
||||
}
|
||||
@ -267,9 +273,13 @@ ComputedTiming AnimationEffect::GetComputedTimingAt(
|
||||
|
||||
ComputedTiming AnimationEffect::GetComputedTiming(
|
||||
const TimingParams* aTiming) const {
|
||||
double playbackRate = mAnimation ? mAnimation->PlaybackRate() : 1;
|
||||
return GetComputedTimingAt(
|
||||
GetLocalTime(), aTiming ? *aTiming : NormalizedTiming(), playbackRate);
|
||||
const double playbackRate = mAnimation ? mAnimation->PlaybackRate() : 1;
|
||||
const auto progressTimelinePosition =
|
||||
mAnimation ? mAnimation->AtProgressTimelineBoundary()
|
||||
: Animation::ProgressTimelinePosition::NotBoundary;
|
||||
return GetComputedTimingAt(GetLocalTime(),
|
||||
aTiming ? *aTiming : NormalizedTiming(),
|
||||
playbackRate, progressTimelinePosition);
|
||||
}
|
||||
|
||||
// Helper function for generating an (Computed)EffectTiming dictionary
|
||||
@ -303,8 +313,11 @@ void AnimationEffect::GetComputedTimingAsDict(
|
||||
// Computed timing
|
||||
double playbackRate = mAnimation ? mAnimation->PlaybackRate() : 1;
|
||||
const Nullable<TimeDuration> currentTime = GetLocalTime();
|
||||
ComputedTiming computedTiming =
|
||||
GetComputedTimingAt(currentTime, SpecifiedTiming(), playbackRate);
|
||||
const auto progressTimelinePosition =
|
||||
mAnimation ? mAnimation->AtProgressTimelineBoundary()
|
||||
: Animation::ProgressTimelinePosition::NotBoundary;
|
||||
ComputedTiming computedTiming = GetComputedTimingAt(
|
||||
currentTime, SpecifiedTiming(), playbackRate, progressTimelinePosition);
|
||||
|
||||
aRetVal.mDuration.SetAsUnrestrictedDouble() =
|
||||
computedTiming.mDuration.ToMilliseconds();
|
||||
|
@ -79,7 +79,8 @@ class AnimationEffect : public nsISupports, public nsWrapperCache {
|
||||
// (because it is not currently active and is not filling at this time).
|
||||
static ComputedTiming GetComputedTimingAt(
|
||||
const Nullable<TimeDuration>& aLocalTime, const TimingParams& aTiming,
|
||||
double aPlaybackRate);
|
||||
double aPlaybackRate,
|
||||
Animation::ProgressTimelinePosition aProgressTimelinePosition);
|
||||
// Shortcut that gets the computed timing using the current local time as
|
||||
// calculated from the timeline time.
|
||||
ComputedTiming GetComputedTiming(const TimingParams* aTiming = nullptr) const;
|
||||
|
@ -106,6 +106,13 @@ class AnimationTimeline : public nsISupports, public nsWrapperCache {
|
||||
virtual bool IsScrollTimeline() const { return false; }
|
||||
virtual const ScrollTimeline* AsScrollTimeline() const { return nullptr; }
|
||||
|
||||
// For a monotonic timeline, there is no upper bound on current time, and
|
||||
// timeline duration is unresolved. For a non-monotonic (e.g. scroll)
|
||||
// timeline, the duration has a fixed upper bound.
|
||||
//
|
||||
// https://drafts.csswg.org/web-animations-2/#timeline-duration
|
||||
virtual Nullable<TimeDuration> TimelineDuration() const { return nullptr; }
|
||||
|
||||
protected:
|
||||
nsCOMPtr<nsIGlobalObject> mWindow;
|
||||
|
||||
|
@ -94,6 +94,21 @@ class AnimationUtils {
|
||||
return aType == PseudoStyleType::before ||
|
||||
aType == PseudoStyleType::after || aType == PseudoStyleType::marker;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the difference between |aFirst| and |aSecond| is within
|
||||
* the animation time tolerance (i.e. 1 microsecond).
|
||||
*/
|
||||
static bool IsWithinAnimationTimeTolerance(const TimeDuration& aFirst,
|
||||
const TimeDuration& aSecond) {
|
||||
if (aFirst == TimeDuration::Forever() ||
|
||||
aSecond == TimeDuration::Forever()) {
|
||||
return aFirst == aSecond;
|
||||
}
|
||||
|
||||
TimeDuration diff = aFirst >= aSecond ? aFirst - aSecond : aSecond - aFirst;
|
||||
return diff <= TimeDuration::FromMicroseconds(1);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace mozilla
|
||||
|
@ -299,7 +299,8 @@ void CSSTransition::UpdateStartValueFromReplacedTransition() {
|
||||
CSSTransition::GetCurrentTimeAt(*mTimeline, TimeStamp::Now(),
|
||||
mReplacedTransition->mStartTime,
|
||||
mReplacedTransition->mPlaybackRate),
|
||||
mReplacedTransition->mTiming, mReplacedTransition->mPlaybackRate);
|
||||
mReplacedTransition->mTiming, mReplacedTransition->mPlaybackRate,
|
||||
Animation::ProgressTimelinePosition::NotBoundary);
|
||||
|
||||
if (!computedTiming.mProgress.IsNull()) {
|
||||
double valuePosition = ComputedTimingFunction::GetPortion(
|
||||
|
@ -12,6 +12,7 @@
|
||||
#include "mozilla/HashTable.h"
|
||||
#include "mozilla/PairHash.h"
|
||||
#include "mozilla/ServoStyleConsts.h"
|
||||
#include "mozilla/TimingParams.h"
|
||||
#include "mozilla/WritingModes.h"
|
||||
|
||||
class nsIScrollableFrame;
|
||||
@ -144,6 +145,11 @@ class ScrollTimeline final : public AnimationTimeline {
|
||||
bool IsMonotonicallyIncreasing() const override { return false; }
|
||||
bool IsScrollTimeline() const override { return true; }
|
||||
const ScrollTimeline* AsScrollTimeline() const override { return this; }
|
||||
Nullable<TimeDuration> TimelineDuration() const override {
|
||||
// We are using this magic number for progress-based timeline duration
|
||||
// because we don't support percentage for duration.
|
||||
return TimeDuration::FromMilliseconds(PROGRESS_TIMELINE_DURATION_MILLISEC);
|
||||
}
|
||||
|
||||
void ScheduleAnimations() {
|
||||
// FIXME: Bug 1737927: Need to check the animation mutation observers for
|
||||
|
@ -221,7 +221,6 @@ void TimingParams::Normalize() {
|
||||
mDelay = TimeDuration::FromMilliseconds(0);
|
||||
mIterations = std::numeric_limits<double>::infinity();
|
||||
mDirection = dom::PlaybackDirection::Alternate;
|
||||
mFill = dom::FillMode::Both;
|
||||
|
||||
Update();
|
||||
}
|
||||
|
@ -31,8 +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 Maybe<TimeDuration>& aDuration,
|
||||
const TimeDuration& aStartTime, float aPlaybackRate) {
|
||||
MOZ_ASSERT(aDuration);
|
||||
|
||||
// We return Nothing If the associated APZ controller is not available
|
||||
@ -60,10 +60,9 @@ static dom::Nullable<TimeDuration> CalculateElapsedTimeForScrollTimeline(
|
||||
// Just in case to avoid getting a progress more than 100%, for overscrolling.
|
||||
progress = std::min(progress, 1.0);
|
||||
|
||||
// FIXME: Bug 1744850: should we take the playback rate into account? For now
|
||||
// it is always 1.0 from ScrollTimeline::Timing(). We may have to update here
|
||||
// in Bug 1744850.
|
||||
return TimeDuration::FromMilliseconds(progress * aDuration->ToMilliseconds());
|
||||
auto timelineTime = aDuration->MultDouble(progress);
|
||||
return dom::Animation::CurrentTimeFromTimelineTime(timelineTime, aStartTime,
|
||||
aPlaybackRate);
|
||||
}
|
||||
|
||||
static dom::Nullable<TimeDuration> CalculateElapsedTime(
|
||||
@ -84,7 +83,9 @@ static dom::Nullable<TimeDuration> CalculateElapsedTime(
|
||||
aLayersId, aAnimation.mScrollTimelineOptions.value().source(),
|
||||
aProofOfMapLock),
|
||||
aAnimation.mScrollTimelineOptions.value(),
|
||||
aAnimation.mTiming.Duration());
|
||||
aAnimation.mTiming.Duration(),
|
||||
aAnimation.mStartTime.refOr(aAnimation.mHoldTime),
|
||||
aAnimation.mPlaybackRate);
|
||||
}
|
||||
|
||||
// -------------------------------------
|
||||
@ -173,9 +174,24 @@ static AnimationHelper::SampleResult SampleAnimationForProperty(
|
||||
aAPZSampler, aLayersId, aProofOfMapLock, animation, aPreviousFrameTime,
|
||||
aCurrentFrameTime, aPreviousValue);
|
||||
|
||||
ComputedTiming computedTiming = dom::AnimationEffect::GetComputedTimingAt(
|
||||
elapsedDuration, animation.mTiming, animation.mPlaybackRate);
|
||||
const auto progressTimelinePosition =
|
||||
animation.mScrollTimelineOptions
|
||||
? dom::Animation::AtProgressTimelineBoundary(
|
||||
TimeDuration::FromMilliseconds(
|
||||
PROGRESS_TIMELINE_DURATION_MILLISEC),
|
||||
elapsedDuration, animation.mStartTime.refOr(TimeDuration()),
|
||||
animation.mPlaybackRate)
|
||||
: dom::Animation::ProgressTimelinePosition::NotBoundary;
|
||||
|
||||
ComputedTiming computedTiming = dom::AnimationEffect::GetComputedTimingAt(
|
||||
elapsedDuration, animation.mTiming, animation.mPlaybackRate,
|
||||
progressTimelinePosition);
|
||||
|
||||
// FIXME: Bug 1776077, for the scroll-linked animations, it's possible to
|
||||
// let it go from the active phase to the before phase, and its progress
|
||||
// becomes null. In this case, we shouldn't just skip this animation.
|
||||
// Instead, we have to reset the sampled result to that without this
|
||||
// animation.
|
||||
if (computedTiming.mProgress.IsNull()) {
|
||||
continue;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user