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