summaryrefslogtreecommitdiffstats
path: root/layout/style/nsTransitionManager.h
diff options
context:
space:
mode:
Diffstat (limited to 'layout/style/nsTransitionManager.h')
-rw-r--r--layout/style/nsTransitionManager.h447
1 files changed, 447 insertions, 0 deletions
diff --git a/layout/style/nsTransitionManager.h b/layout/style/nsTransitionManager.h
new file mode 100644
index 000000000..56ec61572
--- /dev/null
+++ b/layout/style/nsTransitionManager.h
@@ -0,0 +1,447 @@
+/* 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/. */
+
+/* Code to start and animate CSS transitions. */
+
+#ifndef nsTransitionManager_h_
+#define nsTransitionManager_h_
+
+#include "mozilla/ComputedTiming.h"
+#include "mozilla/ContentEvents.h"
+#include "mozilla/EffectCompositor.h" // For EffectCompositor::CascadeLevel
+#include "mozilla/MemoryReporting.h"
+#include "mozilla/dom/Animation.h"
+#include "mozilla/dom/KeyframeEffectReadOnly.h"
+#include "AnimationCommon.h"
+#include "nsCSSProps.h"
+
+class nsIGlobalObject;
+class nsStyleContext;
+class nsPresContext;
+class nsCSSPropertyIDSet;
+
+namespace mozilla {
+enum class CSSPseudoElementType : uint8_t;
+struct Keyframe;
+struct StyleTransition;
+} // namespace mozilla
+
+/*****************************************************************************
+ * Per-Element data *
+ *****************************************************************************/
+
+namespace mozilla {
+
+struct ElementPropertyTransition : public dom::KeyframeEffectReadOnly
+{
+ ElementPropertyTransition(nsIDocument* aDocument,
+ Maybe<OwningAnimationTarget>& aTarget,
+ const TimingParams &aTiming,
+ StyleAnimationValue aStartForReversingTest,
+ double aReversePortion,
+ const KeyframeEffectParams& aEffectOptions)
+ : dom::KeyframeEffectReadOnly(aDocument, aTarget, 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;
+ }
+
+ StyleAnimationValue 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 StyleAnimationValue();
+ }
+ 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.
+ StyleAnimationValue 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<ComputedTimingFunction> mTimingFunction;
+ StyleAnimationValue mFromValue, mToValue;
+ };
+ Maybe<ReplacedTransitionProperties> 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<JSObject*> aGivenProto) override;
+
+ CSSTransition* AsCSSTransition() override { return this; }
+ const CSSTransition* AsCSSTransition() const override { return this; }
+
+ // CSSTransition interface
+ void GetTransitionProperty(nsString& aRetVal) const;
+
+ // Animation interface overrides
+ virtual AnimationPlayState PlayStateFromJS() const override;
+ virtual 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() override
+ {
+ // 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::CancelFromStyle();
+
+ // It is important we do this *after* calling CancelFromStyle().
+ // This is because CancelFromStyle() 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(AnimationEffectReadOnly* aEffect);
+
+ void Tick() override;
+
+ nsCSSPropertyID TransitionProperty() const;
+ StyleAnimationValue 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<TimeDuration> GetCurrentTimeAt(
+ const DocumentTimeline& aTimeline,
+ const TimeStamp& aBaseTime,
+ const TimeDuration& aStartTime,
+ double aPlaybackRate);
+
+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();
+
+ // 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<int>(ComputedTiming::AnimationPhase::Null),
+ Before = static_cast<int>(ComputedTiming::AnimationPhase::Before),
+ Active = static_cast<int>(ComputedTiming::AnimationPhase::Active),
+ After = static_cast<int>(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;
+ StyleAnimationValue mTransitionToValue;
+};
+
+} // namespace dom
+
+template <>
+struct AnimationTypeTraits<dom::CSSTransition>
+{
+ static nsIAtom* ElementPropertyAtom()
+ {
+ return nsGkAtoms::transitionsProperty;
+ }
+ static nsIAtom* BeforePropertyAtom()
+ {
+ return nsGkAtoms::transitionsOfBeforeProperty;
+ }
+ static nsIAtom* AfterPropertyAtom()
+ {
+ return nsGkAtoms::transitionsOfAfterProperty;
+ }
+};
+
+struct TransitionEventInfo {
+ RefPtr<dom::Element> mElement;
+ RefPtr<dom::Animation> mAnimation;
+ InternalTransitionEvent mEvent;
+ TimeStamp mTimeStamp;
+
+ TransitionEventInfo(dom::Element* aElement,
+ CSSPseudoElementType aPseudoType,
+ EventMessage aMessage,
+ nsCSSPropertyID aProperty,
+ StickyTimeDuration aDuration,
+ const TimeStamp& aTimeStamp,
+ dom::Animation* aAnimation)
+ : mElement(aElement)
+ , mAnimation(aAnimation)
+ , mEvent(true, aMessage)
+ , mTimeStamp(aTimeStamp)
+ {
+ // XXX Looks like nobody initialize WidgetEvent::time
+ mEvent.mPropertyName =
+ NS_ConvertUTF8toUTF16(nsCSSProps::GetStringValue(aProperty));
+ mEvent.mElapsedTime = aDuration.ToSeconds();
+ mEvent.mPseudoElement =
+ AnimationCollection<dom::CSSTransition>::PseudoTypeAsString(aPseudoType);
+ }
+
+ // InternalTransitionEvent doesn't support copy-construction, so we need
+ // to ourselves in order to work with nsTArray
+ TransitionEventInfo(const TransitionEventInfo& aOther)
+ : mElement(aOther.mElement)
+ , mAnimation(aOther.mAnimation)
+ , mEvent(aOther.mEvent)
+ , mTimeStamp(aOther.mTimeStamp)
+ {
+ mEvent.AssignTransitionEventData(aOther.mEvent, false);
+ }
+};
+
+} // namespace mozilla
+
+class nsTransitionManager final
+ : public mozilla::CommonAnimationManager<mozilla::dom::CSSTransition>
+{
+public:
+ explicit nsTransitionManager(nsPresContext *aPresContext)
+ : mozilla::CommonAnimationManager<mozilla::dom::CSSTransition>(aPresContext)
+ , mInAnimationOnlyStyleUpdate(false)
+ {
+ }
+
+ NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(nsTransitionManager)
+ NS_DECL_CYCLE_COLLECTION_NATIVE_CLASS(nsTransitionManager)
+
+ typedef mozilla::AnimationCollection<mozilla::dom::CSSTransition>
+ CSSTransitionCollection;
+
+ /**
+ * StyleContextChanged
+ *
+ * To be called from RestyleManager::TryInitiatingTransition when the
+ * style of an element has changed, to initiate transitions from
+ * that style change. For style contexts with :before and :after
+ * pseudos, aElement is expected to be the generated before/after
+ * element.
+ *
+ * It may modify the new style context (by replacing
+ * *aNewStyleContext) to cover up some of the changes for the duration
+ * of the restyling of descendants. If it does, this function will
+ * take care of causing the necessary restyle afterwards.
+ */
+ void StyleContextChanged(mozilla::dom::Element *aElement,
+ nsStyleContext *aOldStyleContext,
+ RefPtr<nsStyleContext>* aNewStyleContext /* inout */);
+
+ /**
+ * When we're resolving style for an element that previously didn't have
+ * style, we might have some old finished transitions for it, if,
+ * say, it was display:none for a while, but previously displayed.
+ *
+ * This method removes any finished transitions that don't match the
+ * new style.
+ */
+ void PruneCompletedTransitions(mozilla::dom::Element* aElement,
+ mozilla::CSSPseudoElementType aPseudoType,
+ nsStyleContext* aNewStyleContext);
+
+ void SetInAnimationOnlyStyleUpdate(bool aInAnimationOnlyUpdate) {
+ mInAnimationOnlyStyleUpdate = aInAnimationOnlyUpdate;
+ }
+
+ bool InAnimationOnlyStyleUpdate() const {
+ return mInAnimationOnlyStyleUpdate;
+ }
+
+ void QueueEvent(mozilla::TransitionEventInfo&& aEventInfo)
+ {
+ mEventDispatcher.QueueEvent(
+ mozilla::Forward<mozilla::TransitionEventInfo>(aEventInfo));
+ }
+
+ void DispatchEvents()
+ {
+ RefPtr<nsTransitionManager> kungFuDeathGrip(this);
+ mEventDispatcher.DispatchEvents(mPresContext);
+ }
+ void SortEvents() { mEventDispatcher.SortEvents(); }
+ void ClearEventQueue() { mEventDispatcher.ClearEventQueue(); }
+
+ // Stop transitions on the element. This method takes the real element
+ // rather than the element for the generated content for transitions on
+ // ::before and ::after.
+ void StopTransitionsForElement(mozilla::dom::Element* aElement,
+ mozilla::CSSPseudoElementType aPseudoType);
+
+protected:
+ virtual ~nsTransitionManager() {}
+
+ typedef nsTArray<RefPtr<mozilla::dom::CSSTransition>>
+ OwningCSSTransitionPtrArray;
+
+ // Update the transitions. It'd start new, replace, or stop current
+ // transitions if need. aDisp and aElement shouldn't be nullptr.
+ // aElementTransitions is the collection of current transitions, and it
+ // could be a nullptr if we don't have any transitions.
+ bool
+ UpdateTransitions(const nsStyleDisplay* aDisp,
+ mozilla::dom::Element* aElement,
+ CSSTransitionCollection*& aElementTransitions,
+ nsStyleContext* aOldStyleContext,
+ nsStyleContext* aNewStyleContext);
+
+ void
+ ConsiderInitiatingTransition(nsCSSPropertyID aProperty,
+ const mozilla::StyleTransition& aTransition,
+ mozilla::dom::Element* aElement,
+ CSSTransitionCollection*& aElementTransitions,
+ nsStyleContext* aOldStyleContext,
+ nsStyleContext* aNewStyleContext,
+ bool* aStartedAny,
+ nsCSSPropertyIDSet* aWhichStarted);
+
+ nsTArray<mozilla::Keyframe> GetTransitionKeyframes(
+ nsStyleContext* aStyleContext,
+ nsCSSPropertyID aProperty,
+ mozilla::StyleAnimationValue&& aStartValue,
+ mozilla::StyleAnimationValue&& aEndValue,
+ const nsTimingFunction& aTimingFunction);
+
+ bool mInAnimationOnlyStyleUpdate;
+
+ mozilla::DelayedEventDispatcher<mozilla::TransitionEventInfo>
+ mEventDispatcher;
+};
+
+#endif /* !defined(nsTransitionManager_h_) */