1 /* vim: set shiftwidth=2 tabstop=8 autoindent cindent expandtab: */ 2 /* This Source Code Form is subject to the terms of the Mozilla Public 3 * License, v. 2.0. If a copy of the MPL was not distributed with this 4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 5 #ifndef nsAnimationManager_h_ 6 #define nsAnimationManager_h_ 7 8 #include "mozilla/Attributes.h" 9 #include "mozilla/ContentEvents.h" 10 #include "mozilla/EventForwards.h" 11 #include "AnimationCommon.h" 12 #include "mozilla/dom/Animation.h" 13 #include "mozilla/MemoryReporting.h" 14 #include "mozilla/TimeStamp.h" 15 16 class nsIGlobalObject; 17 class nsStyleContext; 18 19 namespace mozilla { 20 namespace css { 21 class Declaration; 22 } /* namespace css */ 23 namespace dom { 24 class KeyframeEffectReadOnly; 25 class Promise; 26 } /* namespace dom */ 27 28 enum class CSSPseudoElementType : uint8_t; 29 30 struct AnimationEventInfo { 31 RefPtr<dom::Element> mElement; 32 RefPtr<dom::Animation> mAnimation; 33 InternalAnimationEvent mEvent; 34 TimeStamp mTimeStamp; 35 AnimationEventInfoAnimationEventInfo36 AnimationEventInfo(dom::Element* aElement, 37 CSSPseudoElementType aPseudoType, 38 EventMessage aMessage, 39 const nsSubstring& aAnimationName, 40 const StickyTimeDuration& aElapsedTime, 41 const TimeStamp& aTimeStamp, 42 dom::Animation* aAnimation) 43 : mElement(aElement) 44 , mAnimation(aAnimation) 45 , mEvent(true, aMessage) 46 , mTimeStamp(aTimeStamp) 47 { 48 // XXX Looks like nobody initialize WidgetEvent::time 49 mEvent.mAnimationName = aAnimationName; 50 mEvent.mElapsedTime = aElapsedTime.ToSeconds(); 51 mEvent.mPseudoElement = 52 AnimationCollection<dom::CSSAnimation>::PseudoTypeAsString(aPseudoType); 53 } 54 55 // InternalAnimationEvent doesn't support copy-construction, so we need 56 // to ourselves in order to work with nsTArray AnimationEventInfoAnimationEventInfo57 AnimationEventInfo(const AnimationEventInfo& aOther) 58 : mElement(aOther.mElement) 59 , mAnimation(aOther.mAnimation) 60 , mEvent(true, aOther.mEvent.mMessage) 61 , mTimeStamp(aOther.mTimeStamp) 62 { 63 mEvent.AssignAnimationEventData(aOther.mEvent, false); 64 } 65 }; 66 67 namespace dom { 68 69 class CSSAnimation final : public Animation 70 { 71 public: CSSAnimation(nsIGlobalObject * aGlobal,const nsSubstring & aAnimationName)72 explicit CSSAnimation(nsIGlobalObject* aGlobal, 73 const nsSubstring& aAnimationName) 74 : dom::Animation(aGlobal) 75 , mAnimationName(aAnimationName) 76 , mIsStylePaused(false) 77 , mPauseShouldStick(false) 78 , mNeedsNewAnimationIndexWhenRun(false) 79 , mPreviousPhaseOrIteration(PREVIOUS_PHASE_BEFORE) 80 { 81 // We might need to drop this assertion once we add a script-accessible 82 // constructor but for animations generated from CSS markup the 83 // animation-name should never be empty. 84 MOZ_ASSERT(!mAnimationName.IsEmpty(), "animation-name should not be empty"); 85 } 86 87 JSObject* WrapObject(JSContext* aCx, 88 JS::Handle<JSObject*> aGivenProto) override; 89 AsCSSAnimation()90 CSSAnimation* AsCSSAnimation() override { return this; } AsCSSAnimation()91 const CSSAnimation* AsCSSAnimation() const override { return this; } 92 93 // CSSAnimation interface GetAnimationName(nsString & aRetVal)94 void GetAnimationName(nsString& aRetVal) const { aRetVal = mAnimationName; } 95 96 // Alternative to GetAnimationName that returns a reference to the member 97 // for more efficient internal usage. AnimationName()98 const nsString& AnimationName() const { return mAnimationName; } 99 100 // Animation interface overrides 101 virtual Promise* GetReady(ErrorResult& aRv) override; 102 virtual void Play(ErrorResult& aRv, LimitBehavior aLimitBehavior) override; 103 virtual void Pause(ErrorResult& aRv) override; 104 105 virtual AnimationPlayState PlayStateFromJS() const override; 106 virtual void PlayFromJS(ErrorResult& aRv) override; 107 108 void PlayFromStyle(); 109 void PauseFromStyle(); CancelFromStyle()110 void CancelFromStyle() override 111 { 112 mOwningElement = OwningElementRef(); 113 114 // When an animation is disassociated with style it enters an odd state 115 // where its composite order is undefined until it first transitions 116 // out of the idle state. 117 // 118 // Even if the composite order isn't defined we don't want it to be random 119 // in case we need to determine the order to dispatch events associated 120 // with an animation in this state. To solve this we treat the animation as 121 // if it had been added to the end of the global animation list so that 122 // its sort order is defined. We'll update this index again once the 123 // animation leaves the idle state. 124 mAnimationIndex = sNextAnimationIndex++; 125 mNeedsNewAnimationIndexWhenRun = true; 126 127 Animation::CancelFromStyle(); 128 } 129 130 void Tick() override; 131 void QueueEvents(); 132 IsStylePaused()133 bool IsStylePaused() const { return mIsStylePaused; } 134 135 bool HasLowerCompositeOrderThan(const CSSAnimation& aOther) const; 136 SetAnimationIndex(uint64_t aIndex)137 void SetAnimationIndex(uint64_t aIndex) 138 { 139 MOZ_ASSERT(IsTiedToMarkup()); 140 if (IsRelevant() && 141 mAnimationIndex != aIndex) { 142 nsNodeUtils::AnimationChanged(this); 143 PostUpdate(); 144 } 145 mAnimationIndex = aIndex; 146 } 147 148 // Sets the owning element which is used for determining the composite 149 // order of CSSAnimation objects generated from CSS markup. 150 // 151 // @see mOwningElement SetOwningElement(const OwningElementRef & aElement)152 void SetOwningElement(const OwningElementRef& aElement) 153 { 154 mOwningElement = aElement; 155 } 156 // True for animations that are generated from CSS markup and continue to 157 // reflect changes to that markup. IsTiedToMarkup()158 bool IsTiedToMarkup() const { return mOwningElement.IsSet(); } 159 160 protected: ~CSSAnimation()161 virtual ~CSSAnimation() 162 { 163 MOZ_ASSERT(!mOwningElement.IsSet(), "Owning element should be cleared " 164 "before a CSS animation is destroyed"); 165 } 166 167 // Animation overrides 168 void UpdateTiming(SeekFlag aSeekFlag, 169 SyncNotifyFlag aSyncNotifyFlag) override; 170 171 // Returns the duration from the start of the animation's source effect's 172 // active interval to the point where the animation actually begins playback. 173 // This is zero unless the animation's source effect has a negative delay in 174 // which case it is the absolute value of that delay. 175 // This is used for setting the elapsedTime member of CSS AnimationEvents. InitialAdvance()176 TimeDuration InitialAdvance() const { 177 return mEffect ? 178 std::max(TimeDuration(), mEffect->SpecifiedTiming().mDelay * -1) : 179 TimeDuration(); 180 } 181 182 nsString mAnimationName; 183 184 // The (pseudo-)element whose computed animation-name refers to this 185 // animation (if any). 186 // 187 // This is used for determining the relative composite order of animations 188 // generated from CSS markup. 189 // 190 // Typically this will be the same as the target element of the keyframe 191 // effect associated with this animation. However, it can differ in the 192 // following circumstances: 193 // 194 // a) If script removes or replaces the effect of this animation, 195 // b) If this animation is cancelled (e.g. by updating the 196 // animation-name property or removing the owning element from the 197 // document), 198 // c) If this object is generated from script using the CSSAnimation 199 // constructor. 200 // 201 // For (b) and (c) the owning element will return !IsSet(). 202 OwningElementRef mOwningElement; 203 204 // When combining animation-play-state with play() / pause() the following 205 // behavior applies: 206 // 1. pause() is sticky and always overrides the underlying 207 // animation-play-state 208 // 2. If animation-play-state is 'paused', play() will temporarily override 209 // it until animation-play-state next becomes 'running'. 210 // 3. Calls to play() trigger finishing behavior but setting the 211 // animation-play-state to 'running' does not. 212 // 213 // This leads to five distinct states: 214 // 215 // A. Running 216 // B. Running and temporarily overriding animation-play-state: paused 217 // C. Paused and sticky overriding animation-play-state: running 218 // D. Paused and sticky overriding animation-play-state: paused 219 // E. Paused by animation-play-state 220 // 221 // C and D may seem redundant but they differ in how to respond to the 222 // sequence: call play(), set animation-play-state: paused. 223 // 224 // C will transition to A then E leaving the animation paused. 225 // D will transition to B then B leaving the animation running. 226 // 227 // A state transition chart is as follows: 228 // 229 // A | B | C | D | E 230 // --------------------------- 231 // play() A | B | A | B | B 232 // pause() C | D | C | D | D 233 // 'running' A | A | C | C | A 234 // 'paused' E | B | D | D | E 235 // 236 // The base class, Animation already provides a boolean value, 237 // mIsPaused which gives us two states. To this we add a further two booleans 238 // to represent the states as follows. 239 // 240 // A. Running 241 // (!mIsPaused; !mIsStylePaused; !mPauseShouldStick) 242 // B. Running and temporarily overriding animation-play-state: paused 243 // (!mIsPaused; mIsStylePaused; !mPauseShouldStick) 244 // C. Paused and sticky overriding animation-play-state: running 245 // (mIsPaused; !mIsStylePaused; mPauseShouldStick) 246 // D. Paused and sticky overriding animation-play-state: paused 247 // (mIsPaused; mIsStylePaused; mPauseShouldStick) 248 // E. Paused by animation-play-state 249 // (mIsPaused; mIsStylePaused; !mPauseShouldStick) 250 // 251 // (That leaves 3 combinations of the boolean values that we never set because 252 // they don't represent valid states.) 253 bool mIsStylePaused; 254 bool mPauseShouldStick; 255 256 // When true, indicates that when this animation next leaves the idle state, 257 // its animation index should be updated. 258 bool mNeedsNewAnimationIndexWhenRun; 259 260 enum { 261 PREVIOUS_PHASE_BEFORE = uint64_t(-1), 262 PREVIOUS_PHASE_AFTER = uint64_t(-2) 263 }; 264 // One of the PREVIOUS_PHASE_* constants, or an integer for the iteration 265 // whose start we last notified on. 266 uint64_t mPreviousPhaseOrIteration; 267 }; 268 269 } /* namespace dom */ 270 271 template <> 272 struct AnimationTypeTraits<dom::CSSAnimation> 273 { 274 static nsIAtom* ElementPropertyAtom() 275 { 276 return nsGkAtoms::animationsProperty; 277 } 278 static nsIAtom* BeforePropertyAtom() 279 { 280 return nsGkAtoms::animationsOfBeforeProperty; 281 } 282 static nsIAtom* AfterPropertyAtom() 283 { 284 return nsGkAtoms::animationsOfAfterProperty; 285 } 286 }; 287 288 } /* namespace mozilla */ 289 290 class nsAnimationManager final 291 : public mozilla::CommonAnimationManager<mozilla::dom::CSSAnimation> 292 { 293 public: 294 explicit nsAnimationManager(nsPresContext *aPresContext) 295 : mozilla::CommonAnimationManager<mozilla::dom::CSSAnimation>(aPresContext) 296 { 297 } 298 299 NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(nsAnimationManager) 300 NS_DECL_CYCLE_COLLECTION_NATIVE_CLASS(nsAnimationManager) 301 302 typedef mozilla::AnimationCollection<mozilla::dom::CSSAnimation> 303 CSSAnimationCollection; 304 typedef nsTArray<RefPtr<mozilla::dom::CSSAnimation>> 305 OwningCSSAnimationPtrArray; 306 307 /** 308 * Update the set of animations on |aElement| based on |aStyleContext|. 309 * If necessary, this will notify the corresponding EffectCompositor so 310 * that it can update its animation rule. 311 * 312 * aStyleContext may be a style context for aElement or for its 313 * :before or :after pseudo-element. 314 */ 315 void UpdateAnimations(nsStyleContext* aStyleContext, 316 mozilla::dom::Element* aElement); 317 318 /** 319 * Add a pending event. 320 */ 321 void QueueEvent(mozilla::AnimationEventInfo&& aEventInfo) 322 { 323 mEventDispatcher.QueueEvent( 324 mozilla::Forward<mozilla::AnimationEventInfo>(aEventInfo)); 325 } 326 327 /** 328 * Dispatch any pending events. We accumulate animationend and 329 * animationiteration events only during refresh driver notifications 330 * (and dispatch them at the end of such notifications), but we 331 * accumulate animationstart events at other points when style 332 * contexts are created. 333 */ 334 void DispatchEvents() 335 { 336 RefPtr<nsAnimationManager> kungFuDeathGrip(this); 337 mEventDispatcher.DispatchEvents(mPresContext); 338 } 339 void SortEvents() { mEventDispatcher.SortEvents(); } 340 void ClearEventQueue() { mEventDispatcher.ClearEventQueue(); } 341 342 // Stop animations on the element. This method takes the real element 343 // rather than the element for the generated content for animations on 344 // ::before and ::after. 345 void StopAnimationsForElement(mozilla::dom::Element* aElement, 346 mozilla::CSSPseudoElementType aPseudoType); 347 348 protected: 349 ~nsAnimationManager() override = default; 350 351 private: 352 353 void BuildAnimations(nsStyleContext* aStyleContext, 354 mozilla::dom::Element* aTarget, 355 CSSAnimationCollection* aCollection, 356 OwningCSSAnimationPtrArray& aAnimations); 357 358 mozilla::DelayedEventDispatcher<mozilla::AnimationEventInfo> mEventDispatcher; 359 }; 360 361 #endif /* !defined(nsAnimationManager_h_) */ 362