/* -*- 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/. */ /* Code to start and animate CSS transitions. */ #ifndef nsTransitionManager_h_ #define nsTransitionManager_h_ #include "mozilla/ComputedTiming.h" #include "mozilla/EffectCompositor.h" // For EffectCompositor::CascadeLevel #include "mozilla/dom/Animation.h" #include "mozilla/dom/KeyframeEffect.h" #include "AnimationCommon.h" #include "nsISupportsImpl.h" class nsIGlobalObject; class nsPresContext; class nsCSSPropertyIDSet; namespace mozilla { class ComputedStyle; enum class PseudoStyleType : uint8_t; struct Keyframe; struct StyleTransition; } // namespace mozilla /***************************************************************************** * Per-Element data * *****************************************************************************/ namespace mozilla { struct ElementPropertyTransition : public dom::KeyframeEffect { ElementPropertyTransition(dom::Document* aDocument, Maybe& aTarget, TimingParams&& aTiming, AnimationValue aStartForReversingTest, double aReversePortion, const KeyframeEffectParams& aEffectOptions) : dom::KeyframeEffect(aDocument, aTarget, std::move(aTiming), aEffectOptions), mStartForReversingTest(aStartForReversingTest), mReversePortion(aReversePortion) {} ElementPropertyTransition* AsTransition() override { return this; } const ElementPropertyTransition* AsTransition() const override { return this; } nsCSSPropertyID TransitionProperty() const { MOZ_ASSERT(mKeyframes.Length() == 2, "Transitions should have exactly two animation keyframes. " "Perhaps we are using an un-initialized transition?"); MOZ_ASSERT(mKeyframes[0].mPropertyValues.Length() == 1, "Transitions should have exactly one property in their first " "frame"); return mKeyframes[0].mPropertyValues[0].mProperty; } AnimationValue ToValue() const { // If we failed to generate properties from the transition frames, // return a null value but also show a warning since we should be // detecting that kind of situation in advance and not generating a // transition in the first place. if (mProperties.Length() < 1 || mProperties[0].mSegments.Length() < 1) { NS_WARNING("Failed to generate transition property values"); return AnimationValue(); } return mProperties[0].mSegments[0].mToValue; } // This is the start value to be used for a check for whether a // transition is being reversed. Normally the same as // mProperties[0].mSegments[0].mFromValue, except when this transition // started as the reversal of another in-progress transition. // Needed so we can handle two reverses in a row. AnimationValue mStartForReversingTest; // Likewise, the portion (in value space) of the "full" reversed // transition that we're actually covering. For example, if a :hover // effect has a transition that moves the element 10px to the right // (by changing 'left' from 0px to 10px), and the mouse moves in to // the element (starting the transition) but then moves out after the // transition has advanced 4px, the second transition (from 10px/4px // to 0px) will have mReversePortion of 0.4. (If the mouse then moves // in again when the transition is back to 2px, the mReversePortion // for the third transition (from 0px/2px to 10px) will be 0.8. double mReversePortion; // Compute the portion of the *value* space that we should be through // at the current time. (The input to the transition timing function // has time units, the output has value units.) double CurrentValuePortion() const; // For a new transition interrupting an existing transition on the // compositor, update the start value to match the value of the replaced // transitions at the current time. void UpdateStartValueFromReplacedTransition(); struct ReplacedTransitionProperties { TimeDuration mStartTime; double mPlaybackRate; TimingParams mTiming; Maybe mTimingFunction; AnimationValue mFromValue, mToValue; }; Maybe mReplacedTransition; }; namespace dom { class CSSTransition final : public Animation { public: explicit CSSTransition(nsIGlobalObject* aGlobal) : dom::Animation(aGlobal), mPreviousTransitionPhase(TransitionPhase::Idle), mNeedsNewAnimationIndexWhenRun(false), mTransitionProperty(eCSSProperty_UNKNOWN) {} JSObject* WrapObject(JSContext* aCx, JS::Handle aGivenProto) override; CSSTransition* AsCSSTransition() override { return this; } const CSSTransition* AsCSSTransition() const override { return this; } // CSSTransition interface void GetTransitionProperty(nsString& aRetVal) const; // Animation interface overrides AnimationPlayState PlayStateFromJS() const override; bool PendingFromJS() const override; void PlayFromJS(ErrorResult& aRv) override; // A variant of Play() that avoids posting style updates since this method // is expected to be called whilst already updating style. void PlayFromStyle() { ErrorResult rv; PlayNoUpdate(rv, Animation::LimitBehavior::Continue); // play() should not throw when LimitBehavior is Continue MOZ_ASSERT(!rv.Failed(), "Unexpected exception playing transition"); } void CancelFromStyle(PostRestyleMode aPostRestyle) { // The animation index to use for compositing will be established when // this transition next transitions out of the idle state but we still // update it now so that the sort order of this transition remains // defined until that moment. // // See longer explanation in CSSAnimation::CancelFromStyle. mAnimationIndex = sNextAnimationIndex++; mNeedsNewAnimationIndexWhenRun = true; Animation::Cancel(aPostRestyle); // It is important we do this *after* calling Cancel(). // This is because Cancel() will end up posting a restyle and // that restyle should target the *transitions* level of the cascade. // However, once we clear the owning element, CascadeLevel() will begin // returning CascadeLevel::Animations. mOwningElement = OwningElementRef(); } void SetEffectFromStyle(AnimationEffect* aEffect); void Tick() override; nsCSSPropertyID TransitionProperty() const; AnimationValue ToValue() const; bool HasLowerCompositeOrderThan(const CSSTransition& aOther) const; EffectCompositor::CascadeLevel CascadeLevel() const override { return IsTiedToMarkup() ? EffectCompositor::CascadeLevel::Transitions : EffectCompositor::CascadeLevel::Animations; } void SetCreationSequence(uint64_t aIndex) { MOZ_ASSERT(IsTiedToMarkup()); mAnimationIndex = aIndex; } // Sets the owning element which is used for determining the composite // oder of CSSTransition objects generated from CSS markup. // // @see mOwningElement void SetOwningElement(const OwningElementRef& aElement) { mOwningElement = aElement; } // True for transitions that are generated from CSS markup and continue to // reflect changes to that markup. bool IsTiedToMarkup() const { return mOwningElement.IsSet(); } // Return the animation current time based on a given TimeStamp, a given // start time and a given playbackRate on a given timeline. This is useful // when we estimate the current animated value running on the compositor // because the animation on the compositor may be running ahead while // main-thread is busy. static Nullable GetCurrentTimeAt( const DocumentTimeline& aTimeline, const TimeStamp& aBaseTime, const TimeDuration& aStartTime, double aPlaybackRate); void MaybeQueueCancelEvent(const StickyTimeDuration& aActiveTime) override { QueueEvents(aActiveTime); } protected: virtual ~CSSTransition() { MOZ_ASSERT(!mOwningElement.IsSet(), "Owning element should be cleared " "before a CSS transition is destroyed"); } // Animation overrides void UpdateTiming(SeekFlag aSeekFlag, SyncNotifyFlag aSyncNotifyFlag) override; void QueueEvents(const StickyTimeDuration& activeTime = StickyTimeDuration()); enum class TransitionPhase; // The (pseudo-)element whose computed transition-property refers to this // transition (if any). // // This is used for determining the relative composite order of transitions // generated from CSS markup. // // Typically this will be the same as the target element of the keyframe // effect associated with this transition. However, it can differ in the // following circumstances: // // a) If script removes or replaces the effect of this transition, // b) If this transition is cancelled (e.g. by updating the // transition-property or removing the owning element from the document), // c) If this object is generated from script using the CSSTransition // constructor. // // For (b) and (c) the owning element will return !IsSet(). OwningElementRef mOwningElement; // The 'transition phase' used to determine which transition events need // to be queued on this tick. // See: https://drafts.csswg.org/css-transitions-2/#transition-phase enum class TransitionPhase { Idle = static_cast(ComputedTiming::AnimationPhase::Idle), Before = static_cast(ComputedTiming::AnimationPhase::Before), Active = static_cast(ComputedTiming::AnimationPhase::Active), After = static_cast(ComputedTiming::AnimationPhase::After), Pending }; TransitionPhase mPreviousTransitionPhase; // When true, indicates that when this transition next leaves the idle state, // its animation index should be updated. bool mNeedsNewAnimationIndexWhenRun; // Store the transition property and to-value here since we need that // information in order to determine if there is an existing transition // for a given style change. We can't store that information on the // ElementPropertyTransition (effect) however since it can be replaced // using the Web Animations API. nsCSSPropertyID mTransitionProperty; AnimationValue mTransitionToValue; }; } // namespace dom template <> struct AnimationTypeTraits { static nsAtom* ElementPropertyAtom() { return nsGkAtoms::transitionsProperty; } static nsAtom* BeforePropertyAtom() { return nsGkAtoms::transitionsOfBeforeProperty; } static nsAtom* AfterPropertyAtom() { return nsGkAtoms::transitionsOfAfterProperty; } static nsAtom* MarkerPropertyAtom() { return nsGkAtoms::transitionsOfMarkerProperty; } }; } // namespace mozilla class nsTransitionManager final : public mozilla::CommonAnimationManager { public: explicit nsTransitionManager(nsPresContext* aPresContext) : mozilla::CommonAnimationManager( aPresContext) {} ~nsTransitionManager() final = default; typedef mozilla::AnimationCollection CSSTransitionCollection; /** * Update transitions for stylo. */ bool UpdateTransitions(mozilla::dom::Element* aElement, mozilla::PseudoStyleType aPseudoType, const mozilla::ComputedStyle& aOldStyle, const mozilla::ComputedStyle& aNewStyle); protected: typedef nsTArray> OwningCSSTransitionPtrArray; // Update transitions. This will start new transitions, // replace existing transitions, and stop existing transitions // as needed. aDisp and aElement must be non-null. // aElementTransitions is the collection of current transitions, and it // could be a nullptr if we don't have any transitions. bool DoUpdateTransitions(const nsStyleDisplay& aDisp, mozilla::dom::Element* aElement, mozilla::PseudoStyleType aPseudoType, CSSTransitionCollection*& aElementTransitions, const mozilla::ComputedStyle& aOldStyle, const mozilla::ComputedStyle& aNewStyle); // Returns whether the transition actually started. bool ConsiderInitiatingTransition( nsCSSPropertyID aProperty, const nsStyleDisplay& aStyleDisplay, uint32_t transitionIdx, mozilla::dom::Element* aElement, mozilla::PseudoStyleType aPseudoType, CSSTransitionCollection*& aElementTransitions, const mozilla::ComputedStyle& aOldStyle, const mozilla::ComputedStyle& aNewStyle, nsCSSPropertyIDSet& aPropertiesChecked); }; #endif /* !defined(nsTransitionManager_h_) */