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