From 26a029d407be480d791972afb5975cf62c9360a6 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Fri, 19 Apr 2024 02:47:55 +0200 Subject: Adding upstream version 124.0.1. Signed-off-by: Daniel Baumann --- dom/animation/Animation.h | 592 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 592 insertions(+) create mode 100644 dom/animation/Animation.h (limited to 'dom/animation/Animation.h') diff --git a/dom/animation/Animation.h b/dom/animation/Animation.h new file mode 100644 index 0000000000..cb0859a4b6 --- /dev/null +++ b/dom/animation/Animation.h @@ -0,0 +1,592 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#ifndef mozilla_dom_Animation_h +#define mozilla_dom_Animation_h + +#include "X11UndefineNone.h" +#include "nsCycleCollectionParticipant.h" +#include "mozilla/AnimationPerformanceWarning.h" +#include "mozilla/Attributes.h" +#include "mozilla/BasePrincipal.h" +#include "mozilla/DOMEventTargetHelper.h" +#include "mozilla/EffectCompositor.h" // For EffectCompositor::CascadeLevel +#include "mozilla/LinkedList.h" +#include "mozilla/Maybe.h" +#include "mozilla/PostRestyleMode.h" +#include "mozilla/StickyTimeDuration.h" +#include "mozilla/TimeStamp.h" // for TimeStamp, TimeDuration +#include "mozilla/dom/AnimationBinding.h" // for AnimationPlayState +#include "mozilla/dom/AnimationTimeline.h" + +struct JSContext; +class nsCSSPropertyIDSet; +class nsIFrame; +class nsIGlobalObject; + +namespace mozilla { + +struct AnimationRule; +class MicroTaskRunnable; + +namespace dom { + +class AnimationEffect; +class AsyncFinishNotification; +class CSSAnimation; +class CSSTransition; +class Document; +class Promise; + +class Animation : public DOMEventTargetHelper, + public LinkedListElement { + protected: + virtual ~Animation(); + + public: + explicit Animation(nsIGlobalObject* aGlobal); + + // Constructs a copy of |aOther| with a new effect and timeline. + // This is only intended to be used while making a static clone of a document + // during printing, and does not assume that |aOther| is in the same document + // as any of the other arguments. + static already_AddRefed ClonePausedAnimation( + nsIGlobalObject* aGlobal, const Animation& aOther, + AnimationEffect& aEffect, AnimationTimeline& aTimeline); + + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(Animation, DOMEventTargetHelper) + + nsIGlobalObject* GetParentObject() const { return GetOwnerGlobal(); } + + /** + * Utility function to get the target (pseudo-)element associated with an + * animation. + */ + NonOwningAnimationTarget GetTargetForAnimation() const; + + virtual JSObject* WrapObject(JSContext* aCx, + JS::Handle aGivenProto) override; + + virtual CSSAnimation* AsCSSAnimation() { return nullptr; } + virtual const CSSAnimation* AsCSSAnimation() const { return nullptr; } + virtual CSSTransition* AsCSSTransition() { return nullptr; } + virtual const CSSTransition* AsCSSTransition() const { return nullptr; } + + /** + * Flag to pass to Play to indicate whether or not it should automatically + * rewind the current time to the start point if the animation is finished. + * For regular calls to play() from script we should do this, but when a CSS + * animation's animation-play-state changes we shouldn't rewind the animation. + */ + enum class LimitBehavior { AutoRewind, Continue }; + + // Animation interface methods + static already_AddRefed Constructor( + const GlobalObject& aGlobal, AnimationEffect* aEffect, + const Optional& aTimeline, ErrorResult& aRv); + + void GetId(nsAString& aResult) const { aResult = mId; } + void SetId(const nsAString& aId); + + AnimationEffect* GetEffect() const { return mEffect; } + virtual void SetEffect(AnimationEffect* aEffect); + void SetEffectNoUpdate(AnimationEffect* aEffect); + + // FIXME: Bug 1676794. This is a tentative solution before we implement + // ScrollTimeline interface. If the timeline is scroll/view timeline, we + // return null. Once we implement ScrollTimeline interface, we can drop this. + already_AddRefed GetTimelineFromJS() const { + return mTimeline && mTimeline->IsScrollTimeline() ? nullptr + : do_AddRef(mTimeline); + } + void SetTimelineFromJS(AnimationTimeline* aTimeline) { + SetTimeline(aTimeline); + } + + AnimationTimeline* GetTimeline() const { return mTimeline; } + void SetTimeline(AnimationTimeline* aTimeline); + void SetTimelineNoUpdate(AnimationTimeline* aTimeline); + + Nullable GetStartTime() const { return mStartTime; } + Nullable GetStartTimeAsDouble() const; + void SetStartTime(const Nullable& aNewStartTime); + void SetPendingReadyTime(const TimeStamp& aReadyTime) { + mPendingReadyTime = aReadyTime; + } + virtual void SetStartTimeAsDouble(const Nullable& aStartTime); + + // This is deliberately _not_ called GetCurrentTime since that would clash + // with a macro defined in winbase.h + Nullable GetCurrentTimeAsDuration() const { + return GetCurrentTimeForHoldTime(mHoldTime); + } + Nullable GetCurrentTimeAsDouble() const; + void SetCurrentTime(const TimeDuration& aSeekTime); + void SetCurrentTimeNoUpdate(const TimeDuration& aSeekTime); + void SetCurrentTimeAsDouble(const Nullable& aCurrentTime, + ErrorResult& aRv); + + double PlaybackRate() const { return mPlaybackRate; } + void SetPlaybackRate(double aPlaybackRate); + + AnimationPlayState PlayState() const; + virtual AnimationPlayState PlayStateFromJS() const { return PlayState(); } + + bool Pending() const { return mPendingState != PendingState::NotPending; } + virtual bool PendingFromJS() const { return Pending(); } + AnimationReplaceState ReplaceState() const { return mReplaceState; } + + virtual Promise* GetReady(ErrorResult& aRv); + Promise* GetFinished(ErrorResult& aRv); + + IMPL_EVENT_HANDLER(finish); + IMPL_EVENT_HANDLER(cancel); + IMPL_EVENT_HANDLER(remove); + + void Cancel(PostRestyleMode aPostRestyle = PostRestyleMode::IfNeeded); + + void Finish(ErrorResult& aRv); + + void Play(ErrorResult& aRv, LimitBehavior aLimitBehavior); + virtual void PlayFromJS(ErrorResult& aRv) { + Play(aRv, LimitBehavior::AutoRewind); + } + + void Pause(ErrorResult& aRv); + virtual void PauseFromJS(ErrorResult& aRv) { Pause(aRv); } + + void UpdatePlaybackRate(double aPlaybackRate); + virtual void Reverse(ErrorResult& aRv); + + void Persist(); + MOZ_CAN_RUN_SCRIPT void CommitStyles(ErrorResult& aRv); + + bool IsRunningOnCompositor() const; + + using TickState = AnimationTimeline::TickState; + virtual void Tick(TickState&); + bool NeedsTicks() const { + return Pending() || + (PlayState() == AnimationPlayState::Running && + // An animation with a zero playback rate doesn't need ticks even if + // it is running since it effectively behaves as if it is paused. + // + // It's important we return false in this case since a zero playback + // rate animation in the before or after phase that doesn't fill + // 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) || + // 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); + } + /** + * For the monotonically increasing timeline, we use this only for testing: + * Start or pause a pending animation using the current timeline time. This + * is used to support existing tests that expect animations to begin + * immediately. Ideally we would rewrite the those tests and get rid of this + * method, but there are a lot of them. + */ + bool TryTriggerNow(); + /** + * 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 + */ + 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; + } + + /** + * 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). + */ + TimeStamp AnimationTimeToTimeStamp(const StickyTimeDuration& aTime) const; + + // Converts an AnimationEvent's elapsedTime value to an equivalent TimeStamp + // that can be used to sort events by when they occurred. + TimeStamp ElapsedTimeToTimeStamp( + const StickyTimeDuration& aElapsedTime) const; + + bool IsPausedOrPausing() const { + return PlayState() == AnimationPlayState::Paused; + } + + bool HasCurrentEffect() const; + bool IsInEffect() const; + + bool IsPlaying() const { + return mPlaybackRate != 0.0 && mTimeline && + !mTimeline->GetCurrentTimeAsDuration().IsNull() && + PlayState() == AnimationPlayState::Running; + } + + bool ShouldBeSynchronizedWithMainThread( + const nsCSSPropertyIDSet& aPropertySet, const nsIFrame* aFrame, + AnimationPerformanceWarning::Type& aPerformanceWarning /* out */) const; + + bool IsRelevant() const { return mIsRelevant; } + void UpdateRelevance(); + + // https://drafts.csswg.org/web-animations-1/#replaceable-animation + bool IsReplaceable() const; + + /** + * Returns true if this Animation satisfies the requirements for being + * removed when it is replaced. + * + * Returning true does not imply this animation _should_ be removed. + * Determining that depends on the other effects in the same EffectSet to + * which this animation's effect, if any, contributes. + */ + bool IsRemovable() const; + + /** + * Make this animation's target effect no-longer part of the effect stack + * while preserving its timing information. + */ + void Remove(); + + /** + * Returns true if this Animation has a lower composite order than aOther. + */ + bool HasLowerCompositeOrderThan(const Animation& aOther) const; + + /** + * Returns the level at which the effect(s) associated with this Animation + * are applied to the CSS cascade. + */ + virtual EffectCompositor::CascadeLevel CascadeLevel() const { + return EffectCompositor::CascadeLevel::Animations; + } + + /** + * Returns true if this animation does not currently need to update + * style on the main thread (e.g. because it is empty, or is + * running on the compositor). + */ + bool CanThrottle() const; + + /** + * Updates various bits of state that we need to update as the result of + * running ComposeStyle(). + * See the comment of KeyframeEffect::WillComposeStyle for more detail. + */ + void WillComposeStyle(); + + /** + * Updates |aComposeResult| with the animation values of this animation's + * effect, if any. + * Any properties contained in |aPropertiesToSkip| will not be added or + * updated in |aComposeResult|. + */ + void ComposeStyle(StyleAnimationValueMap& aComposeResult, + const nsCSSPropertyIDSet& aPropertiesToSkip); + + void NotifyEffectTimingUpdated(); + void NotifyEffectPropertiesUpdated(); + void NotifyEffectTargetUpdated(); + + /** + * Used by subclasses to synchronously queue a cancel event in situations + * where the Animation may have been cancelled. + * + * 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){}; + + Maybe& CachedChildIndexRef() { return mCachedChildIndex; } + + void SetPartialPrerendered(uint64_t aIdOnCompositor) { + mIdOnCompositor = aIdOnCompositor; + mIsPartialPrerendered = true; + } + bool IsPartialPrerendered() const { return mIsPartialPrerendered; } + uint64_t IdOnCompositor() const { return mIdOnCompositor; } + /** + * Needs to be called when the pre-rendered animation is going to no longer + * run on the compositor. + */ + void ResetPartialPrerendered() { + MOZ_ASSERT(mIsPartialPrerendered); + mIsPartialPrerendered = false; + mIdOnCompositor = 0; + } + /** + * Called via NotifyJankedAnimations IPC call from the compositor to update + * pre-rendered area on the main-thread. + */ + void UpdatePartialPrerendered() { + ResetPartialPrerendered(); + PostUpdate(); + } + + bool UsingScrollTimeline() const { + 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& aTimelineDuration, + const Nullable& aCurrentTime, + const TimeDuration& aEffectStartTime, const double aPlaybackRate); + ProgressTimelinePosition AtProgressTimelineBoundary() const { + Nullable currentTime = GetUnconstrainedCurrentTime(); + return AtProgressTimelineBoundary( + mTimeline ? mTimeline->TimelineDuration() : nullptr, + // Set unlimited current time based on the first matching condition: + // 1. start time is resolved: + // (timeline time - start time) × playback rate + // 2. Otherwise: + // animation’s current time + !currentTime.IsNull() ? currentTime : GetCurrentTimeAsDuration(), + mStartTime.IsNull() ? TimeDuration() : mStartTime.Value(), + mPlaybackRate); + } + + void SetHiddenByContentVisibility(bool hidden); + bool IsHiddenByContentVisibility() const { + return mHiddenByContentVisibility; + } + void UpdateHiddenByContentVisibility(); + + DocGroup* GetDocGroup(); + + protected: + void SilentlySetCurrentTime(const TimeDuration& aNewCurrentTime); + void CancelNoUpdate(); + void PlayNoUpdate(ErrorResult& aRv, LimitBehavior aLimitBehavior); + 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 { + MOZ_ASSERT_UNREACHABLE( + "Can't finish pending if we're not in a pending state"); + } + } + void ApplyPendingPlaybackRate() { + if (mPendingPlaybackRate) { + mPlaybackRate = mPendingPlaybackRate.extract(); + } + } + + /** + * Finishing behavior depends on if changes to timing occurred due + * to a seek or regular playback. + */ + enum class SeekFlag { NoSeek, DidSeek }; + + enum class SyncNotifyFlag { Sync, Async }; + + virtual void UpdateTiming(SeekFlag aSeekFlag, SyncNotifyFlag aSyncNotifyFlag); + void UpdateFinishedState(SeekFlag aSeekFlag, SyncNotifyFlag aSyncNotifyFlag); + void UpdateEffect(PostRestyleMode aPostRestyle); + /** + * Flush all pending styles other than throttled animation styles (e.g. + * animations running on the compositor). + */ + void FlushUnanimatedStyle() const; + void PostUpdate(); + void ResetFinishedPromise(); + void MaybeResolveFinishedPromise(); + void DoFinishNotification(SyncNotifyFlag aSyncNotifyFlag); + friend class AsyncFinishNotification; + void DoFinishNotificationImmediately(MicroTaskRunnable* aAsync = nullptr); + void QueuePlaybackEvent(const nsAString& aName, + TimeStamp&& aScheduledEventTime); + + /** + * Remove this animation from the pending animation tracker and reset + * mPendingState as necessary. The caller is responsible for resolving or + * aborting the mReady promise as necessary. + */ + void CancelPendingTasks(); + + /** + * Performs the same steps as CancelPendingTasks and also rejects and + * recreates the ready promise if the animation was pending. + */ + void ResetPendingTasks(); + StickyTimeDuration EffectEnd() const; + + Nullable GetCurrentTimeForHoldTime( + const Nullable& aHoldTime) const; + Nullable GetUnconstrainedCurrentTime() const { + return GetCurrentTimeForHoldTime(Nullable()); + } + + void ScheduleReplacementCheck(); + void MaybeScheduleReplacementCheck(); + + // Earlier side of the elapsed time range reported in CSS Animations and CSS + // Transitions events. + // + // https://drafts.csswg.org/css-animations-2/#interval-start + // https://drafts.csswg.org/css-transitions-2/#interval-start + StickyTimeDuration IntervalStartTime( + const StickyTimeDuration& aActiveDuration) const; + + // Later side of the elapsed time range reported in CSS Animations and CSS + // Transitions events. + // + // https://drafts.csswg.org/css-animations-2/#interval-end + // https://drafts.csswg.org/css-transitions-2/#interval-end + StickyTimeDuration IntervalEndTime( + const StickyTimeDuration& aActiveDuration) const; + + TimeStamp GetTimelineCurrentTimeAsTimeStamp() const { + return mTimeline ? mTimeline->GetCurrentTimeAsTimeStamp() : TimeStamp(); + } + + Document* GetRenderedDocument() const; + Document* GetTimelineDocument() const; + + bool HasFiniteTimeline() const { + return mTimeline && !mTimeline->IsMonotonicallyIncreasing(); + } + + void UpdateScrollTimelineAnimationTracker(AnimationTimeline* aOldTimeline, + AnimationTimeline* aNewTimeline); + + RefPtr mTimeline; + RefPtr mEffect; + // The beginning of the delay period. + Nullable mStartTime; // Timeline timescale + Nullable mHoldTime; // Animation timescale + Nullable mPreviousCurrentTime; // Animation timescale + double mPlaybackRate = 1.0; + Maybe 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 mReady; + + // A Promise that is resolved when we reach the end of the effect, or + // 0 when playing backwards. The Promise is replaced if the animation is + // finished but then a state change makes it not finished. + // This object is lazily created by GetFinished. + // See http://drafts.csswg.org/web-animations/#current-finished-promise + RefPtr mFinished; + + static uint64_t sNextAnimationIndex; + + // The relative position of this animation within the global animation list. + // + // Note that subclasses such as CSSTransition and CSSAnimation may repurpose + // this member to implement their own brand of sorting. As a result, it is + // possible for two different objects to have the same index. + uint64_t mAnimationIndex; + + // While ordering Animation objects for event dispatch, the index of the + // target node in its parent may be cached in mCachedChildIndex. + Maybe mCachedChildIndex; + + // Indicates if the animation is in the pending state (and what state it is + // waiting to enter when it finished pending). + enum class PendingState : uint8_t { NotPending, PlayPending, PausePending }; + PendingState mPendingState = PendingState::NotPending; + + // Handling of this animation's target effect when filling while finished. + AnimationReplaceState mReplaceState = AnimationReplaceState::Active; + + bool mFinishedAtLastComposeStyle = false; + bool mWasReplaceableAtLastTick = false; + // When we create a new pending animation, this tracks whether we've seen at + // least one refresh driver tick. This is used to guarantee that a whole tick + // has run before triggering the animation, which guarantees (for most pages) + // that we've actually painted. + bool mSawTickWhilePending = false; + + bool mHiddenByContentVisibility = false; + + // Indicates that the animation should be exposed in an element's + // getAnimations() list. + bool mIsRelevant = false; + + // True if mFinished is resolved or would be resolved if mFinished has + // yet to be created. This is not set when mFinished is rejected since + // in that case mFinished is immediately reset to represent a new current + // finished promise. + bool mFinishedIsResolved = false; + + RefPtr mFinishNotificationTask; + + nsString mId; + + bool mResetCurrentTimeOnResume = false; + + // Whether the Animation is System, ResistFingerprinting, or neither + RTPCallerType mRTPCallerType; + + // The time at which our animation should be ready. + TimeStamp mPendingReadyTime; + + private: + // The id for this animation on the compositor. + uint64_t mIdOnCompositor = 0; + bool mIsPartialPrerendered = false; +}; + +} // namespace dom +} // namespace mozilla + +#endif // mozilla_dom_Animation_h -- cgit v1.2.3