Line data Source code
1 : /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 : /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 : /* This Source Code Form is subject to the terms of the Mozilla Public
4 : * License, v. 2.0. If a copy of the MPL was not distributed with this
5 : * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 :
7 : #ifndef mozilla_dom_Animation_h
8 : #define mozilla_dom_Animation_h
9 :
10 : #include "nsWrapperCache.h"
11 : #include "nsCycleCollectionParticipant.h"
12 : #include "mozilla/AnimationPerformanceWarning.h"
13 : #include "mozilla/Attributes.h"
14 : #include "mozilla/DOMEventTargetHelper.h"
15 : #include "mozilla/EffectCompositor.h" // For EffectCompositor::CascadeLevel
16 : #include "mozilla/LinkedList.h"
17 : #include "mozilla/TimeStamp.h" // for TimeStamp, TimeDuration
18 : #include "mozilla/dom/AnimationBinding.h" // for AnimationPlayState
19 : #include "mozilla/dom/AnimationEffectReadOnly.h"
20 : #include "mozilla/dom/AnimationTimeline.h"
21 : #include "mozilla/dom/Promise.h"
22 : #include "nsCSSPropertyID.h"
23 : #include "nsIGlobalObject.h"
24 :
25 : // X11 has a #define for CurrentTime.
26 : #ifdef CurrentTime
27 : #undef CurrentTime
28 : #endif
29 :
30 : // GetCurrentTime is defined in winbase.h as zero argument macro forwarding to
31 : // GetTickCount().
32 : #ifdef GetCurrentTime
33 : #undef GetCurrentTime
34 : #endif
35 :
36 : struct JSContext;
37 : class nsCSSPropertyIDSet;
38 : class nsIDocument;
39 : class nsIFrame;
40 :
41 : namespace mozilla {
42 :
43 : struct AnimationRule;
44 :
45 : namespace dom {
46 :
47 : class CSSAnimation;
48 : class CSSTransition;
49 :
50 : class Animation
51 : : public DOMEventTargetHelper
52 : , public LinkedListElement<Animation>
53 : {
54 : protected:
55 0 : virtual ~Animation() {}
56 :
57 : public:
58 2 : explicit Animation(nsIGlobalObject* aGlobal)
59 2 : : DOMEventTargetHelper(aGlobal)
60 : , mPlaybackRate(1.0)
61 : , mPendingState(PendingState::NotPending)
62 2 : , mAnimationIndex(sNextAnimationIndex++)
63 : , mFinishedAtLastComposeStyle(false)
64 : , mIsRelevant(false)
65 : , mFinishedIsResolved(false)
66 4 : , mSyncWithGeometricAnimations(false)
67 : {
68 2 : }
69 :
70 : NS_DECL_ISUPPORTS_INHERITED
71 2 : NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(Animation,
72 : DOMEventTargetHelper)
73 :
74 0 : nsIGlobalObject* GetParentObject() const { return GetOwnerGlobal(); }
75 : virtual JSObject* WrapObject(JSContext* aCx,
76 : JS::Handle<JSObject*> aGivenProto) override;
77 :
78 0 : virtual CSSAnimation* AsCSSAnimation() { return nullptr; }
79 0 : virtual const CSSAnimation* AsCSSAnimation() const { return nullptr; }
80 0 : virtual CSSTransition* AsCSSTransition() { return nullptr; }
81 0 : virtual const CSSTransition* AsCSSTransition() const { return nullptr; }
82 :
83 : /**
84 : * Flag to pass to Play to indicate whether or not it should automatically
85 : * rewind the current time to the start point if the animation is finished.
86 : * For regular calls to play() from script we should do this, but when a CSS
87 : * animation's animation-play-state changes we shouldn't rewind the animation.
88 : */
89 : enum class LimitBehavior {
90 : AutoRewind,
91 : Continue
92 : };
93 :
94 : // Animation interface methods
95 : static already_AddRefed<Animation>
96 : Constructor(const GlobalObject& aGlobal,
97 : AnimationEffectReadOnly* aEffect,
98 : const Optional<AnimationTimeline*>& aTimeline,
99 : ErrorResult& aRv);
100 0 : void GetId(nsAString& aResult) const { aResult = mId; }
101 : void SetId(const nsAString& aId);
102 102 : AnimationEffectReadOnly* GetEffect() const { return mEffect; }
103 : void SetEffect(AnimationEffectReadOnly* aEffect);
104 34 : AnimationTimeline* GetTimeline() const { return mTimeline; }
105 : void SetTimeline(AnimationTimeline* aTimeline);
106 0 : Nullable<TimeDuration> GetStartTime() const { return mStartTime; }
107 : void SetStartTime(const Nullable<TimeDuration>& aNewStartTime);
108 : Nullable<TimeDuration> GetCurrentTime() const;
109 : void SetCurrentTime(const TimeDuration& aNewCurrentTime);
110 186 : double PlaybackRate() const { return mPlaybackRate; }
111 : void SetPlaybackRate(double aPlaybackRate);
112 : AnimationPlayState PlayState() const;
113 : virtual Promise* GetReady(ErrorResult& aRv);
114 : virtual Promise* GetFinished(ErrorResult& aRv);
115 : void Cancel();
116 : virtual void Finish(ErrorResult& aRv);
117 : virtual void Play(ErrorResult& aRv, LimitBehavior aLimitBehavior);
118 : virtual void Pause(ErrorResult& aRv);
119 : virtual void Reverse(ErrorResult& aRv);
120 : bool IsRunningOnCompositor() const;
121 0 : IMPL_EVENT_HANDLER(finish);
122 0 : IMPL_EVENT_HANDLER(cancel);
123 :
124 : // Wrapper functions for Animation DOM methods when called
125 : // from script.
126 : //
127 : // We often use the same methods internally and from script but when called
128 : // from script we (or one of our subclasses) perform extra steps such as
129 : // flushing style or converting the return type.
130 : Nullable<double> GetStartTimeAsDouble() const;
131 : void SetStartTimeAsDouble(const Nullable<double>& aStartTime);
132 : Nullable<double> GetCurrentTimeAsDouble() const;
133 : void SetCurrentTimeAsDouble(const Nullable<double>& aCurrentTime,
134 : ErrorResult& aRv);
135 0 : virtual AnimationPlayState PlayStateFromJS() const { return PlayState(); }
136 0 : virtual void PlayFromJS(ErrorResult& aRv)
137 : {
138 0 : Play(aRv, LimitBehavior::AutoRewind);
139 0 : }
140 : /**
141 : * PauseFromJS is currently only here for symmetry with PlayFromJS but
142 : * in future we will likely have to flush style in
143 : * CSSAnimation::PauseFromJS so we leave it for now.
144 : */
145 0 : void PauseFromJS(ErrorResult& aRv) { Pause(aRv); }
146 :
147 : // Wrapper functions for Animation DOM methods when called from style.
148 :
149 0 : virtual void CancelFromStyle() { CancelNoUpdate(); }
150 : void SetTimelineNoUpdate(AnimationTimeline* aTimeline);
151 : void SetEffectNoUpdate(AnimationEffectReadOnly* aEffect);
152 :
153 : virtual void Tick();
154 26 : bool NeedsTicks() const
155 : {
156 26 : AnimationPlayState playState = PlayState();
157 26 : return playState == AnimationPlayState::Running ||
158 26 : playState == AnimationPlayState::Pending;
159 : }
160 :
161 : /**
162 : * Set the time to use for starting or pausing a pending animation.
163 : *
164 : * Typically, when an animation is played, it does not start immediately but
165 : * is added to a table of pending animations on the document of its effect.
166 : * In the meantime it sets its hold time to the time from which playback
167 : * should begin.
168 : *
169 : * When the document finishes painting, any pending animations in its table
170 : * are marked as being ready to start by calling StartOnNextTick.
171 : * The moment when the paint completed is also recorded, converted to a
172 : * timeline time, and passed to StartOnTick. This is so that when these
173 : * animations do start, they can be timed from the point when painting
174 : * completed.
175 : *
176 : * After calling TriggerOnNextTick, animations remain in the pending state
177 : * until the next refresh driver tick. At that time they transition out of
178 : * the pending state using the time passed to TriggerOnNextTick as the
179 : * effective time at which they resumed.
180 : *
181 : * This approach means that any setup time required for performing the
182 : * initial paint of an animation such as layerization is not deducted from
183 : * the running time of the animation. Without this we can easily drop the
184 : * first few frames of an animation, or, on slower devices, the whole
185 : * animation.
186 : *
187 : * Furthermore:
188 : *
189 : * - Starting the animation immediately when painting finishes is problematic
190 : * because the start time of the animation will be ahead of its timeline
191 : * (since the timeline time is based on the refresh driver time).
192 : * That's a problem because the animation is playing but its timing
193 : * suggests it starts in the future. We could update the timeline to match
194 : * the start time of the animation but then we'd also have to update the
195 : * timing and style of all animations connected to that timeline or else be
196 : * stuck in an inconsistent state until the next refresh driver tick.
197 : *
198 : * - If we simply use the refresh driver time on its next tick, the lag
199 : * between triggering an animation and its effective start is unacceptably
200 : * long.
201 : *
202 : * For pausing, we apply the same asynchronous approach. This is so that we
203 : * synchronize with animations that are running on the compositor. Otherwise
204 : * if the main thread lags behind the compositor there will be a noticeable
205 : * jump backwards when the main thread takes over. Even though main thread
206 : * animations could be paused immediately, we do it asynchronously for
207 : * consistency and so that animations paused together end up in step.
208 : *
209 : * Note that the caller of this method is responsible for removing the
210 : * animation from any PendingAnimationTracker it may have been added to.
211 : */
212 : void TriggerOnNextTick(const Nullable<TimeDuration>& aReadyTime);
213 : /**
214 : * Testing only: Start or pause a pending animation using the current
215 : * timeline time. This is used to support existing tests that expect
216 : * animations to begin immediately. Ideally we would rewrite the those tests
217 : * and get rid of this method, but there are a lot of them.
218 : *
219 : * As with TriggerOnNextTick, the caller of this method is responsible for
220 : * removing the animation from any PendingAnimationTracker it may have been
221 : * added to.
222 : */
223 : void TriggerNow();
224 : /**
225 : * When StartOnNextTick is called, we store the ready time but we don't apply
226 : * it until the next tick. In the meantime, GetStartTime() will return null.
227 : *
228 : * However, if we build layer animations again before the next tick, we
229 : * should initialize them with the start time that GetStartTime() will return
230 : * on the next tick.
231 : *
232 : * If we were to simply set the start time of layer animations to null, their
233 : * start time would be updated to the current wallclock time when rendering
234 : * finishes, thus making them out of sync with the start time stored here.
235 : * This, in turn, will make the animation jump backwards when we build
236 : * animations on the next tick and apply the start time stored here.
237 : *
238 : * This method returns the start time, if resolved. Otherwise, if we have
239 : * a pending ready time, it returns the corresponding start time. If neither
240 : * of those are available, it returns null.
241 : */
242 : Nullable<TimeDuration> GetCurrentOrPendingStartTime() const;
243 :
244 : /**
245 : * Calculates the corresponding start time to use for an animation that is
246 : * currently pending with current time |mHoldTime| but should behave
247 : * as if it began or resumed playback at timeline time |aReadyTime|.
248 : */
249 : TimeDuration StartTimeFromReadyTime(const TimeDuration& aReadyTime) const;
250 :
251 : /**
252 : * Converts a time in the timescale of this Animation's currentTime, to a
253 : * TimeStamp. Returns a null TimeStamp if the conversion cannot be performed
254 : * because of the current state of this Animation (e.g. it has no timeline, a
255 : * zero playbackRate, an unresolved start time etc.) or the value of the time
256 : * passed-in (e.g. an infinite time).
257 : */
258 : TimeStamp AnimationTimeToTimeStamp(const StickyTimeDuration& aTime) const;
259 :
260 : // Converts an AnimationEvent's elapsedTime value to an equivalent TimeStamp
261 : // that can be used to sort events by when they occurred.
262 : TimeStamp ElapsedTimeToTimeStamp(const StickyTimeDuration& aElapsedTime) const;
263 :
264 0 : bool IsPausedOrPausing() const
265 : {
266 0 : return PlayState() == AnimationPlayState::Paused ||
267 0 : mPendingState == PendingState::PausePending;
268 : }
269 :
270 32 : bool HasCurrentEffect() const
271 : {
272 32 : return GetEffect() && GetEffect()->IsCurrent();
273 : }
274 6 : bool IsInEffect() const
275 : {
276 6 : return GetEffect() && GetEffect()->IsInEffect();
277 : }
278 :
279 0 : bool IsPlaying() const
280 : {
281 0 : return mPlaybackRate != 0.0 &&
282 0 : mTimeline &&
283 0 : (PlayState() == AnimationPlayState::Running ||
284 0 : mPendingState == PendingState::PlayPending);
285 : }
286 :
287 : bool ShouldBeSynchronizedWithMainThread(
288 : nsCSSPropertyID aProperty,
289 : const nsIFrame* aFrame,
290 : AnimationPerformanceWarning::Type& aPerformanceWarning) const;
291 :
292 90 : bool IsRelevant() const { return mIsRelevant; }
293 : void UpdateRelevance();
294 :
295 : /**
296 : * Returns true if this Animation has a lower composite order than aOther.
297 : */
298 : bool HasLowerCompositeOrderThan(const Animation& aOther) const;
299 :
300 : /**
301 : * Returns the level at which the effect(s) associated with this Animation
302 : * are applied to the CSS cascade.
303 : */
304 0 : virtual EffectCompositor::CascadeLevel CascadeLevel() const
305 : {
306 0 : return EffectCompositor::CascadeLevel::Animations;
307 : }
308 :
309 : /**
310 : * Returns true if this animation does not currently need to update
311 : * style on the main thread (e.g. because it is empty, or is
312 : * running on the compositor).
313 : */
314 : bool CanThrottle() const;
315 :
316 : /**
317 : * Updates various bits of state that we need to update as the result of
318 : * running ComposeStyle().
319 : * See the comment of KeyframeEffectReadOnly::WillComposeStyle for more detail.
320 : */
321 : void WillComposeStyle();
322 :
323 : /**
324 : * Updates |aComposeResult| with the animation values of this animation's
325 : * effect, if any.
326 : * Any properties contained in |aPropertiesToSkip| will not be added or
327 : * updated in |aComposeResult|.
328 : */
329 : template<typename ComposeAnimationResult>
330 : void ComposeStyle(ComposeAnimationResult&& aComposeResult,
331 : const nsCSSPropertyIDSet& aPropertiesToSkip);
332 :
333 : void NotifyEffectTimingUpdated();
334 : void NotifyGeometricAnimationsStartingThisFrame();
335 :
336 : /**
337 : * Used by subclasses to synchronously queue a cancel event in situations
338 : * where the Animation may have been cancelled.
339 : *
340 : * We need to do this synchronously because after a CSS animation/transition
341 : * is canceled, it will be released by its owning element and may not still
342 : * exist when we would normally go to queue events on the next tick.
343 : */
344 0 : virtual void MaybeQueueCancelEvent(StickyTimeDuration aActiveTime) {};
345 :
346 : protected:
347 : void SilentlySetCurrentTime(const TimeDuration& aNewCurrentTime);
348 : void SilentlySetPlaybackRate(double aPlaybackRate);
349 : void CancelNoUpdate();
350 : void PlayNoUpdate(ErrorResult& aRv, LimitBehavior aLimitBehavior);
351 : void PauseNoUpdate(ErrorResult& aRv);
352 : void ResumeAt(const TimeDuration& aReadyTime);
353 : void PauseAt(const TimeDuration& aReadyTime);
354 2 : void FinishPendingAt(const TimeDuration& aReadyTime)
355 : {
356 2 : if (mPendingState == PendingState::PlayPending) {
357 2 : ResumeAt(aReadyTime);
358 0 : } else if (mPendingState == PendingState::PausePending) {
359 0 : PauseAt(aReadyTime);
360 : } else {
361 0 : NS_NOTREACHED("Can't finish pending if we're not in a pending state");
362 : }
363 2 : }
364 :
365 : /**
366 : * Finishing behavior depends on if changes to timing occurred due
367 : * to a seek or regular playback.
368 : */
369 : enum class SeekFlag {
370 : NoSeek,
371 : DidSeek
372 : };
373 :
374 : enum class SyncNotifyFlag {
375 : Sync,
376 : Async
377 : };
378 :
379 : virtual void UpdateTiming(SeekFlag aSeekFlag,
380 : SyncNotifyFlag aSyncNotifyFlag);
381 : void UpdateFinishedState(SeekFlag aSeekFlag,
382 : SyncNotifyFlag aSyncNotifyFlag);
383 : void UpdateEffect();
384 : void FlushStyle() const;
385 : void PostUpdate();
386 : void ResetFinishedPromise();
387 : void MaybeResolveFinishedPromise();
388 : void DoFinishNotification(SyncNotifyFlag aSyncNotifyFlag);
389 : void DoFinishNotificationImmediately();
390 : void DispatchPlaybackEvent(const nsAString& aName);
391 :
392 : /**
393 : * Remove this animation from the pending animation tracker and reset
394 : * mPendingState as necessary. The caller is responsible for resolving or
395 : * aborting the mReady promise as necessary.
396 : */
397 : void CancelPendingTasks();
398 :
399 : /**
400 : * Performs the same steps as CancelPendingTasks and also rejects and
401 : * recreates the ready promise if the animation was pending.
402 : */
403 : void ResetPendingTasks();
404 :
405 : /**
406 : * Returns true if this animation is not only play-pending, but has
407 : * yet to be given a pending ready time. This roughly corresponds to
408 : * animations that are waiting to be painted (since we set the pending
409 : * ready time at the end of painting). Identifying such animations is
410 : * useful because in some cases animations that are painted together
411 : * may need to be synchronized.
412 : */
413 0 : bool IsNewlyStarted() const {
414 0 : return mPendingState == PendingState::PlayPending &&
415 0 : mPendingReadyTime.IsNull();
416 : }
417 : bool IsPossiblyOrphanedPendingAnimation() const;
418 : StickyTimeDuration EffectEnd() const;
419 :
420 : nsIDocument* GetRenderedDocument() const;
421 :
422 : RefPtr<AnimationTimeline> mTimeline;
423 : RefPtr<AnimationEffectReadOnly> mEffect;
424 : // The beginning of the delay period.
425 : Nullable<TimeDuration> mStartTime; // Timeline timescale
426 : Nullable<TimeDuration> mHoldTime; // Animation timescale
427 : Nullable<TimeDuration> mPendingReadyTime; // Timeline timescale
428 : Nullable<TimeDuration> mPreviousCurrentTime; // Animation timescale
429 : double mPlaybackRate;
430 :
431 : // A Promise that is replaced on each call to Play()
432 : // and fulfilled when Play() is successfully completed.
433 : // This object is lazily created by GetReady.
434 : // See http://w3c.github.io/web-animations/#current-ready-promise
435 : RefPtr<Promise> mReady;
436 :
437 : // A Promise that is resolved when we reach the end of the effect, or
438 : // 0 when playing backwards. The Promise is replaced if the animation is
439 : // finished but then a state change makes it not finished.
440 : // This object is lazily created by GetFinished.
441 : // See http://w3c.github.io/web-animations/#current-finished-promise
442 : RefPtr<Promise> mFinished;
443 :
444 : // Indicates if the animation is in the pending state (and what state it is
445 : // waiting to enter when it finished pending). We use this rather than
446 : // checking if this animation is tracked by a PendingAnimationTracker because
447 : // the animation will continue to be pending even after it has been removed
448 : // from the PendingAnimationTracker while it is waiting for the next tick
449 : // (see TriggerOnNextTick for details).
450 : enum class PendingState { NotPending, PlayPending, PausePending };
451 : PendingState mPendingState;
452 :
453 : static uint64_t sNextAnimationIndex;
454 :
455 : // The relative position of this animation within the global animation list.
456 : // This is kNoIndex while the animation is in the idle state and is updated
457 : // each time the animation transitions out of the idle state.
458 : //
459 : // Note that subclasses such as CSSTransition and CSSAnimation may repurpose
460 : // this member to implement their own brand of sorting. As a result, it is
461 : // possible for two different objects to have the same index.
462 : uint64_t mAnimationIndex;
463 :
464 : bool mFinishedAtLastComposeStyle;
465 : // Indicates that the animation should be exposed in an element's
466 : // getAnimations() list.
467 : bool mIsRelevant;
468 :
469 : nsRevocableEventPtr<nsRunnableMethod<Animation>> mFinishNotificationTask;
470 : // True if mFinished is resolved or would be resolved if mFinished has
471 : // yet to be created. This is not set when mFinished is rejected since
472 : // in that case mFinished is immediately reset to represent a new current
473 : // finished promise.
474 : bool mFinishedIsResolved;
475 :
476 : // True if this animation was triggered at the same time as one or more
477 : // geometric animations and hence we should run any transform animations on
478 : // the main thread.
479 : bool mSyncWithGeometricAnimations;
480 :
481 : nsString mId;
482 : };
483 :
484 : } // namespace dom
485 : } // namespace mozilla
486 :
487 : #endif // mozilla_dom_Animation_h
|