diff options
Diffstat (limited to 'layout/style/nsAnimationManager.h')
-rw-r--r-- | layout/style/nsAnimationManager.h | 361 |
1 files changed, 361 insertions, 0 deletions
diff --git a/layout/style/nsAnimationManager.h b/layout/style/nsAnimationManager.h new file mode 100644 index 000000000..abe3aeeb8 --- /dev/null +++ b/layout/style/nsAnimationManager.h @@ -0,0 +1,361 @@ +/* vim: set shiftwidth=2 tabstop=8 autoindent cindent expandtab: */ +/* 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 nsAnimationManager_h_ +#define nsAnimationManager_h_ + +#include "mozilla/Attributes.h" +#include "mozilla/ContentEvents.h" +#include "mozilla/EventForwards.h" +#include "AnimationCommon.h" +#include "mozilla/dom/Animation.h" +#include "mozilla/MemoryReporting.h" +#include "mozilla/TimeStamp.h" + +class nsIGlobalObject; +class nsStyleContext; + +namespace mozilla { +namespace css { +class Declaration; +} /* namespace css */ +namespace dom { +class KeyframeEffectReadOnly; +class Promise; +} /* namespace dom */ + +enum class CSSPseudoElementType : uint8_t; + +struct AnimationEventInfo { + RefPtr<dom::Element> mElement; + RefPtr<dom::Animation> mAnimation; + InternalAnimationEvent mEvent; + TimeStamp mTimeStamp; + + AnimationEventInfo(dom::Element* aElement, + CSSPseudoElementType aPseudoType, + EventMessage aMessage, + const nsSubstring& aAnimationName, + const StickyTimeDuration& aElapsedTime, + const TimeStamp& aTimeStamp, + dom::Animation* aAnimation) + : mElement(aElement) + , mAnimation(aAnimation) + , mEvent(true, aMessage) + , mTimeStamp(aTimeStamp) + { + // XXX Looks like nobody initialize WidgetEvent::time + mEvent.mAnimationName = aAnimationName; + mEvent.mElapsedTime = aElapsedTime.ToSeconds(); + mEvent.mPseudoElement = + AnimationCollection<dom::CSSAnimation>::PseudoTypeAsString(aPseudoType); + } + + // InternalAnimationEvent doesn't support copy-construction, so we need + // to ourselves in order to work with nsTArray + AnimationEventInfo(const AnimationEventInfo& aOther) + : mElement(aOther.mElement) + , mAnimation(aOther.mAnimation) + , mEvent(true, aOther.mEvent.mMessage) + , mTimeStamp(aOther.mTimeStamp) + { + mEvent.AssignAnimationEventData(aOther.mEvent, false); + } +}; + +namespace dom { + +class CSSAnimation final : public Animation +{ +public: + explicit CSSAnimation(nsIGlobalObject* aGlobal, + const nsSubstring& aAnimationName) + : dom::Animation(aGlobal) + , mAnimationName(aAnimationName) + , mIsStylePaused(false) + , mPauseShouldStick(false) + , mNeedsNewAnimationIndexWhenRun(false) + , mPreviousPhaseOrIteration(PREVIOUS_PHASE_BEFORE) + { + // We might need to drop this assertion once we add a script-accessible + // constructor but for animations generated from CSS markup the + // animation-name should never be empty. + MOZ_ASSERT(!mAnimationName.IsEmpty(), "animation-name should not be empty"); + } + + JSObject* WrapObject(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) override; + + CSSAnimation* AsCSSAnimation() override { return this; } + const CSSAnimation* AsCSSAnimation() const override { return this; } + + // CSSAnimation interface + void GetAnimationName(nsString& aRetVal) const { aRetVal = mAnimationName; } + + // Alternative to GetAnimationName that returns a reference to the member + // for more efficient internal usage. + const nsString& AnimationName() const { return mAnimationName; } + + // Animation interface overrides + virtual Promise* GetReady(ErrorResult& aRv) override; + virtual void Play(ErrorResult& aRv, LimitBehavior aLimitBehavior) override; + virtual void Pause(ErrorResult& aRv) override; + + virtual AnimationPlayState PlayStateFromJS() const override; + virtual void PlayFromJS(ErrorResult& aRv) override; + + void PlayFromStyle(); + void PauseFromStyle(); + void CancelFromStyle() override + { + mOwningElement = OwningElementRef(); + + // When an animation is disassociated with style it enters an odd state + // where its composite order is undefined until it first transitions + // out of the idle state. + // + // Even if the composite order isn't defined we don't want it to be random + // in case we need to determine the order to dispatch events associated + // with an animation in this state. To solve this we treat the animation as + // if it had been added to the end of the global animation list so that + // its sort order is defined. We'll update this index again once the + // animation leaves the idle state. + mAnimationIndex = sNextAnimationIndex++; + mNeedsNewAnimationIndexWhenRun = true; + + Animation::CancelFromStyle(); + } + + void Tick() override; + void QueueEvents(); + + bool IsStylePaused() const { return mIsStylePaused; } + + bool HasLowerCompositeOrderThan(const CSSAnimation& aOther) const; + + void SetAnimationIndex(uint64_t aIndex) + { + MOZ_ASSERT(IsTiedToMarkup()); + if (IsRelevant() && + mAnimationIndex != aIndex) { + nsNodeUtils::AnimationChanged(this); + PostUpdate(); + } + mAnimationIndex = aIndex; + } + + // Sets the owning element which is used for determining the composite + // order of CSSAnimation objects generated from CSS markup. + // + // @see mOwningElement + void SetOwningElement(const OwningElementRef& aElement) + { + mOwningElement = aElement; + } + // True for animations that are generated from CSS markup and continue to + // reflect changes to that markup. + bool IsTiedToMarkup() const { return mOwningElement.IsSet(); } + +protected: + virtual ~CSSAnimation() + { + MOZ_ASSERT(!mOwningElement.IsSet(), "Owning element should be cleared " + "before a CSS animation is destroyed"); + } + + // Animation overrides + void UpdateTiming(SeekFlag aSeekFlag, + SyncNotifyFlag aSyncNotifyFlag) override; + + // Returns the duration from the start of the animation's source effect's + // active interval to the point where the animation actually begins playback. + // This is zero unless the animation's source effect has a negative delay in + // which case it is the absolute value of that delay. + // This is used for setting the elapsedTime member of CSS AnimationEvents. + TimeDuration InitialAdvance() const { + return mEffect ? + std::max(TimeDuration(), mEffect->SpecifiedTiming().mDelay * -1) : + TimeDuration(); + } + + nsString mAnimationName; + + // The (pseudo-)element whose computed animation-name refers to this + // animation (if any). + // + // This is used for determining the relative composite order of animations + // generated from CSS markup. + // + // Typically this will be the same as the target element of the keyframe + // effect associated with this animation. However, it can differ in the + // following circumstances: + // + // a) If script removes or replaces the effect of this animation, + // b) If this animation is cancelled (e.g. by updating the + // animation-name property or removing the owning element from the + // document), + // c) If this object is generated from script using the CSSAnimation + // constructor. + // + // For (b) and (c) the owning element will return !IsSet(). + OwningElementRef mOwningElement; + + // When combining animation-play-state with play() / pause() the following + // behavior applies: + // 1. pause() is sticky and always overrides the underlying + // animation-play-state + // 2. If animation-play-state is 'paused', play() will temporarily override + // it until animation-play-state next becomes 'running'. + // 3. Calls to play() trigger finishing behavior but setting the + // animation-play-state to 'running' does not. + // + // This leads to five distinct states: + // + // A. Running + // B. Running and temporarily overriding animation-play-state: paused + // C. Paused and sticky overriding animation-play-state: running + // D. Paused and sticky overriding animation-play-state: paused + // E. Paused by animation-play-state + // + // C and D may seem redundant but they differ in how to respond to the + // sequence: call play(), set animation-play-state: paused. + // + // C will transition to A then E leaving the animation paused. + // D will transition to B then B leaving the animation running. + // + // A state transition chart is as follows: + // + // A | B | C | D | E + // --------------------------- + // play() A | B | A | B | B + // pause() C | D | C | D | D + // 'running' A | A | C | C | A + // 'paused' E | B | D | D | E + // + // The base class, Animation already provides a boolean value, + // mIsPaused which gives us two states. To this we add a further two booleans + // to represent the states as follows. + // + // A. Running + // (!mIsPaused; !mIsStylePaused; !mPauseShouldStick) + // B. Running and temporarily overriding animation-play-state: paused + // (!mIsPaused; mIsStylePaused; !mPauseShouldStick) + // C. Paused and sticky overriding animation-play-state: running + // (mIsPaused; !mIsStylePaused; mPauseShouldStick) + // D. Paused and sticky overriding animation-play-state: paused + // (mIsPaused; mIsStylePaused; mPauseShouldStick) + // E. Paused by animation-play-state + // (mIsPaused; mIsStylePaused; !mPauseShouldStick) + // + // (That leaves 3 combinations of the boolean values that we never set because + // they don't represent valid states.) + bool mIsStylePaused; + bool mPauseShouldStick; + + // When true, indicates that when this animation next leaves the idle state, + // its animation index should be updated. + bool mNeedsNewAnimationIndexWhenRun; + + enum { + PREVIOUS_PHASE_BEFORE = uint64_t(-1), + PREVIOUS_PHASE_AFTER = uint64_t(-2) + }; + // One of the PREVIOUS_PHASE_* constants, or an integer for the iteration + // whose start we last notified on. + uint64_t mPreviousPhaseOrIteration; +}; + +} /* namespace dom */ + +template <> +struct AnimationTypeTraits<dom::CSSAnimation> +{ + static nsIAtom* ElementPropertyAtom() + { + return nsGkAtoms::animationsProperty; + } + static nsIAtom* BeforePropertyAtom() + { + return nsGkAtoms::animationsOfBeforeProperty; + } + static nsIAtom* AfterPropertyAtom() + { + return nsGkAtoms::animationsOfAfterProperty; + } +}; + +} /* namespace mozilla */ + +class nsAnimationManager final + : public mozilla::CommonAnimationManager<mozilla::dom::CSSAnimation> +{ +public: + explicit nsAnimationManager(nsPresContext *aPresContext) + : mozilla::CommonAnimationManager<mozilla::dom::CSSAnimation>(aPresContext) + { + } + + NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(nsAnimationManager) + NS_DECL_CYCLE_COLLECTION_NATIVE_CLASS(nsAnimationManager) + + typedef mozilla::AnimationCollection<mozilla::dom::CSSAnimation> + CSSAnimationCollection; + typedef nsTArray<RefPtr<mozilla::dom::CSSAnimation>> + OwningCSSAnimationPtrArray; + + /** + * Update the set of animations on |aElement| based on |aStyleContext|. + * If necessary, this will notify the corresponding EffectCompositor so + * that it can update its animation rule. + * + * aStyleContext may be a style context for aElement or for its + * :before or :after pseudo-element. + */ + void UpdateAnimations(nsStyleContext* aStyleContext, + mozilla::dom::Element* aElement); + + /** + * Add a pending event. + */ + void QueueEvent(mozilla::AnimationEventInfo&& aEventInfo) + { + mEventDispatcher.QueueEvent( + mozilla::Forward<mozilla::AnimationEventInfo>(aEventInfo)); + } + + /** + * Dispatch any pending events. We accumulate animationend and + * animationiteration events only during refresh driver notifications + * (and dispatch them at the end of such notifications), but we + * accumulate animationstart events at other points when style + * contexts are created. + */ + void DispatchEvents() + { + RefPtr<nsAnimationManager> kungFuDeathGrip(this); + mEventDispatcher.DispatchEvents(mPresContext); + } + void SortEvents() { mEventDispatcher.SortEvents(); } + void ClearEventQueue() { mEventDispatcher.ClearEventQueue(); } + + // Stop animations on the element. This method takes the real element + // rather than the element for the generated content for animations on + // ::before and ::after. + void StopAnimationsForElement(mozilla::dom::Element* aElement, + mozilla::CSSPseudoElementType aPseudoType); + +protected: + ~nsAnimationManager() override = default; + +private: + + void BuildAnimations(nsStyleContext* aStyleContext, + mozilla::dom::Element* aTarget, + CSSAnimationCollection* aCollection, + OwningCSSAnimationPtrArray& aAnimations); + + mozilla::DelayedEventDispatcher<mozilla::AnimationEventInfo> mEventDispatcher; +}; + +#endif /* !defined(nsAnimationManager_h_) */ |