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 : #include "Animation.h"
8 : #include "AnimationUtils.h"
9 : #include "mozilla/dom/AnimationBinding.h"
10 : #include "mozilla/dom/AnimationPlaybackEvent.h"
11 : #include "mozilla/dom/DocumentTimeline.h"
12 : #include "mozilla/AnimationTarget.h"
13 : #include "mozilla/AutoRestore.h"
14 : #include "mozilla/AsyncEventDispatcher.h" // For AsyncEventDispatcher
15 : #include "mozilla/Maybe.h" // For Maybe
16 : #include "mozilla/TypeTraits.h" // For Forward<>
17 : #include "nsAnimationManager.h" // For CSSAnimation
18 : #include "nsDOMMutationObserver.h" // For nsAutoAnimationMutationBatch
19 : #include "nsIDocument.h" // For nsIDocument
20 : #include "nsIPresShell.h" // For nsIPresShell
21 : #include "nsThreadUtils.h" // For nsRunnableMethod and nsRevocableEventPtr
22 : #include "nsTransitionManager.h" // For CSSTransition
23 : #include "PendingAnimationTracker.h" // For PendingAnimationTracker
24 :
25 : namespace mozilla {
26 : namespace dom {
27 :
28 : // Static members
29 : uint64_t Animation::sNextAnimationIndex = 0;
30 :
31 0 : NS_IMPL_CYCLE_COLLECTION_INHERITED(Animation, DOMEventTargetHelper,
32 : mTimeline,
33 : mEffect,
34 : mReady,
35 : mFinished)
36 :
37 73 : NS_IMPL_ADDREF_INHERITED(Animation, DOMEventTargetHelper)
38 69 : NS_IMPL_RELEASE_INHERITED(Animation, DOMEventTargetHelper)
39 :
40 26 : NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(Animation)
41 24 : NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
42 :
43 : JSObject*
44 0 : Animation::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
45 : {
46 0 : return dom::AnimationBinding::Wrap(aCx, this, aGivenProto);
47 : }
48 :
49 : // ---------------------------------------------------------------------------
50 : //
51 : // Utility methods
52 : //
53 : // ---------------------------------------------------------------------------
54 :
55 : namespace {
56 : // A wrapper around nsAutoAnimationMutationBatch that looks up the
57 : // appropriate document from the supplied animation.
58 4 : class MOZ_RAII AutoMutationBatchForAnimation {
59 : public:
60 4 : explicit AutoMutationBatchForAnimation(const Animation& aAnimation
61 4 : MOZ_GUARD_OBJECT_NOTIFIER_PARAM) {
62 4 : MOZ_GUARD_OBJECT_NOTIFIER_INIT;
63 : Maybe<NonOwningAnimationTarget> target =
64 6 : nsNodeUtils::GetTargetForAnimation(&aAnimation);
65 4 : if (!target) {
66 2 : return;
67 : }
68 :
69 : // For mutation observers, we use the OwnerDoc.
70 2 : nsIDocument* doc = target->mElement->OwnerDoc();
71 2 : if (!doc) {
72 0 : return;
73 : }
74 :
75 2 : mAutoBatch.emplace(doc);
76 : }
77 :
78 : private:
79 : MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
80 : Maybe<nsAutoAnimationMutationBatch> mAutoBatch;
81 : };
82 : }
83 :
84 : // ---------------------------------------------------------------------------
85 : //
86 : // Animation interface:
87 : //
88 : // ---------------------------------------------------------------------------
89 : /* static */ already_AddRefed<Animation>
90 0 : Animation::Constructor(const GlobalObject& aGlobal,
91 : AnimationEffectReadOnly* aEffect,
92 : const Optional<AnimationTimeline*>& aTimeline,
93 : ErrorResult& aRv)
94 : {
95 0 : nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports());
96 0 : RefPtr<Animation> animation = new Animation(global);
97 :
98 : AnimationTimeline* timeline;
99 0 : if (aTimeline.WasPassed()) {
100 0 : timeline = aTimeline.Value();
101 : } else {
102 : nsIDocument* document =
103 0 : AnimationUtils::GetCurrentRealmDocument(aGlobal.Context());
104 0 : if (!document) {
105 0 : aRv.Throw(NS_ERROR_FAILURE);
106 0 : return nullptr;
107 : }
108 0 : timeline = document->Timeline();
109 : }
110 :
111 0 : animation->SetTimelineNoUpdate(timeline);
112 0 : animation->SetEffectNoUpdate(aEffect);
113 :
114 0 : return animation.forget();
115 : }
116 :
117 : void
118 0 : Animation::SetId(const nsAString& aId)
119 : {
120 0 : if (mId == aId) {
121 0 : return;
122 : }
123 0 : mId = aId;
124 0 : nsNodeUtils::AnimationChanged(this);
125 : }
126 :
127 : void
128 0 : Animation::SetEffect(AnimationEffectReadOnly* aEffect)
129 : {
130 0 : SetEffectNoUpdate(aEffect);
131 0 : PostUpdate();
132 0 : }
133 :
134 : // https://w3c.github.io/web-animations/#setting-the-target-effect
135 : void
136 2 : Animation::SetEffectNoUpdate(AnimationEffectReadOnly* aEffect)
137 : {
138 4 : RefPtr<Animation> kungFuDeathGrip(this);
139 :
140 2 : if (mEffect == aEffect) {
141 0 : return;
142 : }
143 :
144 4 : AutoMutationBatchForAnimation mb(*this);
145 2 : bool wasRelevant = mIsRelevant;
146 :
147 2 : if (mEffect) {
148 0 : if (!aEffect) {
149 : // If the new effect is null, call ResetPendingTasks before clearing
150 : // mEffect since ResetPendingTasks needs it to get the appropriate
151 : // PendingAnimationTracker.
152 0 : ResetPendingTasks();
153 : }
154 :
155 : // We need to notify observers now because once we set mEffect to null
156 : // we won't be able to find the target element to notify.
157 0 : if (mIsRelevant) {
158 0 : nsNodeUtils::AnimationRemoved(this);
159 : }
160 :
161 : // Break links with the old effect and then drop it.
162 0 : RefPtr<AnimationEffectReadOnly> oldEffect = mEffect;
163 0 : mEffect = nullptr;
164 0 : oldEffect->SetAnimation(nullptr);
165 :
166 : // The following will not do any notification because mEffect is null.
167 0 : UpdateRelevance();
168 : }
169 :
170 2 : if (aEffect) {
171 : // Break links from the new effect to its previous animation, if any.
172 4 : RefPtr<AnimationEffectReadOnly> newEffect = aEffect;
173 2 : Animation* prevAnim = aEffect->GetAnimation();
174 2 : if (prevAnim) {
175 0 : prevAnim->SetEffect(nullptr);
176 : }
177 :
178 : // Create links with the new effect. SetAnimation(this) will also update
179 : // mIsRelevant of this animation, and then notify mutation observer if
180 : // needed by calling Animation::UpdateRelevance(), so we don't need to
181 : // call it again.
182 2 : mEffect = newEffect;
183 2 : mEffect->SetAnimation(this);
184 :
185 : // Notify possible add or change.
186 : // If the target is different, the change notification will be ignored by
187 : // AutoMutationBatchForAnimation.
188 2 : if (wasRelevant && mIsRelevant) {
189 0 : nsNodeUtils::AnimationChanged(this);
190 : }
191 :
192 : // Reschedule pending pause or pending play tasks.
193 : // If we have a pending animation, it will either be registered
194 : // in the pending animation tracker and have a null pending ready time,
195 : // or, after it has been painted, it will be removed from the tracker
196 : // and assigned a pending ready time.
197 : // After updating the effect we'll typically need to repaint so if we've
198 : // already been assigned a pending ready time, we should clear it and put
199 : // the animation back in the tracker.
200 2 : if (!mPendingReadyTime.IsNull()) {
201 0 : mPendingReadyTime.SetNull();
202 :
203 0 : nsIDocument* doc = GetRenderedDocument();
204 0 : if (doc) {
205 : PendingAnimationTracker* tracker =
206 0 : doc->GetOrCreatePendingAnimationTracker();
207 0 : if (mPendingState == PendingState::PlayPending) {
208 0 : tracker->AddPlayPending(*this);
209 : } else {
210 0 : tracker->AddPausePending(*this);
211 : }
212 : }
213 : }
214 : }
215 :
216 2 : UpdateTiming(SeekFlag::NoSeek, SyncNotifyFlag::Async);
217 : }
218 :
219 : void
220 0 : Animation::SetTimeline(AnimationTimeline* aTimeline)
221 : {
222 0 : SetTimelineNoUpdate(aTimeline);
223 0 : PostUpdate();
224 0 : }
225 :
226 : // https://w3c.github.io/web-animations/#setting-the-timeline
227 : void
228 2 : Animation::SetTimelineNoUpdate(AnimationTimeline* aTimeline)
229 : {
230 2 : if (mTimeline == aTimeline) {
231 0 : return;
232 : }
233 :
234 : StickyTimeDuration activeTime = mEffect
235 2 : ? mEffect->GetComputedTiming().mActiveTime
236 4 : : StickyTimeDuration();
237 :
238 4 : RefPtr<AnimationTimeline> oldTimeline = mTimeline;
239 2 : if (oldTimeline) {
240 0 : oldTimeline->RemoveAnimation(this);
241 : }
242 :
243 2 : mTimeline = aTimeline;
244 2 : if (!mStartTime.IsNull()) {
245 0 : mHoldTime.SetNull();
246 : }
247 :
248 2 : if (!aTimeline) {
249 0 : MaybeQueueCancelEvent(activeTime);
250 : }
251 2 : UpdateTiming(SeekFlag::NoSeek, SyncNotifyFlag::Async);
252 : }
253 :
254 : // https://w3c.github.io/web-animations/#set-the-animation-start-time
255 : void
256 0 : Animation::SetStartTime(const Nullable<TimeDuration>& aNewStartTime)
257 : {
258 0 : if (aNewStartTime == mStartTime) {
259 0 : return;
260 : }
261 :
262 0 : AutoMutationBatchForAnimation mb(*this);
263 :
264 0 : Nullable<TimeDuration> timelineTime;
265 0 : if (mTimeline) {
266 : // The spec says to check if the timeline is active (has a resolved time)
267 : // before using it here, but we don't need to since it's harmless to set
268 : // the already null time to null.
269 0 : timelineTime = mTimeline->GetCurrentTime();
270 : }
271 0 : if (timelineTime.IsNull() && !aNewStartTime.IsNull()) {
272 0 : mHoldTime.SetNull();
273 : }
274 :
275 0 : Nullable<TimeDuration> previousCurrentTime = GetCurrentTime();
276 0 : mStartTime = aNewStartTime;
277 0 : if (!aNewStartTime.IsNull()) {
278 0 : if (mPlaybackRate != 0.0) {
279 0 : mHoldTime.SetNull();
280 : }
281 : } else {
282 0 : mHoldTime = previousCurrentTime;
283 : }
284 :
285 0 : CancelPendingTasks();
286 0 : if (mReady) {
287 : // We may have already resolved mReady, but in that case calling
288 : // MaybeResolve is a no-op, so that's okay.
289 0 : mReady->MaybeResolve(this);
290 : }
291 :
292 0 : UpdateTiming(SeekFlag::DidSeek, SyncNotifyFlag::Async);
293 0 : if (IsRelevant()) {
294 0 : nsNodeUtils::AnimationChanged(this);
295 : }
296 0 : PostUpdate();
297 : }
298 :
299 : // https://w3c.github.io/web-animations/#current-time
300 : Nullable<TimeDuration>
301 312 : Animation::GetCurrentTime() const
302 : {
303 312 : Nullable<TimeDuration> result;
304 312 : if (!mHoldTime.IsNull()) {
305 184 : result = mHoldTime;
306 184 : return result;
307 : }
308 :
309 128 : if (mTimeline && !mStartTime.IsNull()) {
310 172 : Nullable<TimeDuration> timelineTime = mTimeline->GetCurrentTime();
311 86 : if (!timelineTime.IsNull()) {
312 172 : result.SetValue((timelineTime.Value() - mStartTime.Value())
313 172 : .MultDouble(mPlaybackRate));
314 : }
315 : }
316 128 : return result;
317 : }
318 :
319 : // https://w3c.github.io/web-animations/#set-the-current-time
320 : void
321 0 : Animation::SetCurrentTime(const TimeDuration& aSeekTime)
322 : {
323 : // Return early if the current time has not changed. However, if we
324 : // are pause-pending, then setting the current time to any value
325 : // including the current value has the effect of aborting the
326 : // pause so we should not return early in that case.
327 0 : if (mPendingState != PendingState::PausePending &&
328 0 : Nullable<TimeDuration>(aSeekTime) == GetCurrentTime()) {
329 0 : return;
330 : }
331 :
332 0 : AutoMutationBatchForAnimation mb(*this);
333 :
334 0 : SilentlySetCurrentTime(aSeekTime);
335 :
336 0 : if (mPendingState == PendingState::PausePending) {
337 : // Finish the pause operation
338 0 : mHoldTime.SetValue(aSeekTime);
339 0 : mStartTime.SetNull();
340 :
341 0 : if (mReady) {
342 0 : mReady->MaybeResolve(this);
343 : }
344 0 : CancelPendingTasks();
345 : }
346 :
347 0 : UpdateTiming(SeekFlag::DidSeek, SyncNotifyFlag::Async);
348 0 : if (IsRelevant()) {
349 0 : nsNodeUtils::AnimationChanged(this);
350 : }
351 0 : PostUpdate();
352 : }
353 :
354 : // https://w3c.github.io/web-animations/#set-the-animation-playback-rate
355 : void
356 0 : Animation::SetPlaybackRate(double aPlaybackRate)
357 : {
358 0 : if (aPlaybackRate == mPlaybackRate) {
359 0 : return;
360 : }
361 :
362 0 : AutoMutationBatchForAnimation mb(*this);
363 :
364 0 : Nullable<TimeDuration> previousTime = GetCurrentTime();
365 0 : mPlaybackRate = aPlaybackRate;
366 0 : if (!previousTime.IsNull()) {
367 0 : SetCurrentTime(previousTime.Value());
368 : }
369 :
370 : // In the case where GetCurrentTime() returns the same result before and
371 : // after updating mPlaybackRate, SetCurrentTime will return early since,
372 : // as far as it can tell, nothing has changed.
373 : // As a result, we need to perform the following updates here:
374 : // - update timing (since, if the sign of the playback rate has changed, our
375 : // finished state may have changed),
376 : // - dispatch a change notification for the changed playback rate, and
377 : // - update the playback rate on animations on layers.
378 0 : UpdateTiming(SeekFlag::DidSeek, SyncNotifyFlag::Async);
379 0 : if (IsRelevant()) {
380 0 : nsNodeUtils::AnimationChanged(this);
381 : }
382 0 : PostUpdate();
383 : }
384 :
385 : // https://w3c.github.io/web-animations/#play-state
386 : AnimationPlayState
387 166 : Animation::PlayState() const
388 : {
389 166 : if (mPendingState != PendingState::NotPending) {
390 108 : return AnimationPlayState::Pending;
391 : }
392 :
393 116 : Nullable<TimeDuration> currentTime = GetCurrentTime();
394 58 : if (currentTime.IsNull()) {
395 12 : return AnimationPlayState::Idle;
396 : }
397 :
398 46 : if (mStartTime.IsNull()) {
399 0 : return AnimationPlayState::Paused;
400 : }
401 :
402 138 : if ((mPlaybackRate > 0.0 && currentTime.Value() >= EffectEnd()) ||
403 78 : (mPlaybackRate < 0.0 && currentTime.Value() <= TimeDuration())) {
404 14 : return AnimationPlayState::Finished;
405 : }
406 :
407 32 : return AnimationPlayState::Running;
408 : }
409 :
410 : Promise*
411 0 : Animation::GetReady(ErrorResult& aRv)
412 : {
413 0 : nsCOMPtr<nsIGlobalObject> global = GetOwnerGlobal();
414 0 : if (!mReady && global) {
415 0 : mReady = Promise::Create(global, aRv); // Lazily create on demand
416 : }
417 0 : if (!mReady) {
418 0 : aRv.Throw(NS_ERROR_FAILURE);
419 0 : return nullptr;
420 : }
421 0 : if (PlayState() != AnimationPlayState::Pending) {
422 0 : mReady->MaybeResolve(this);
423 : }
424 0 : return mReady;
425 : }
426 :
427 : Promise*
428 0 : Animation::GetFinished(ErrorResult& aRv)
429 : {
430 0 : nsCOMPtr<nsIGlobalObject> global = GetOwnerGlobal();
431 0 : if (!mFinished && global) {
432 0 : mFinished = Promise::Create(global, aRv); // Lazily create on demand
433 : }
434 0 : if (!mFinished) {
435 0 : aRv.Throw(NS_ERROR_FAILURE);
436 0 : return nullptr;
437 : }
438 0 : if (mFinishedIsResolved) {
439 0 : MaybeResolveFinishedPromise();
440 : }
441 0 : return mFinished;
442 : }
443 :
444 : void
445 0 : Animation::Cancel()
446 : {
447 0 : CancelNoUpdate();
448 0 : PostUpdate();
449 0 : }
450 :
451 : // https://w3c.github.io/web-animations/#finish-an-animation
452 : void
453 0 : Animation::Finish(ErrorResult& aRv)
454 : {
455 0 : if (mPlaybackRate == 0 ||
456 0 : (mPlaybackRate > 0 && EffectEnd() == TimeDuration::Forever())) {
457 0 : aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
458 0 : return;
459 : }
460 :
461 0 : AutoMutationBatchForAnimation mb(*this);
462 :
463 : // Seek to the end
464 : TimeDuration limit =
465 0 : mPlaybackRate > 0 ? TimeDuration(EffectEnd()) : TimeDuration(0);
466 0 : bool didChange = GetCurrentTime() != Nullable<TimeDuration>(limit);
467 0 : SilentlySetCurrentTime(limit);
468 :
469 : // If we are paused or play-pending we need to fill in the start time in
470 : // order to transition to the finished state.
471 : //
472 : // We only do this, however, if we have an active timeline. If we have an
473 : // inactive timeline we can't transition into the finished state just like
474 : // we can't transition to the running state (this finished state is really
475 : // a substate of the running state).
476 0 : if (mStartTime.IsNull() &&
477 0 : mTimeline &&
478 0 : !mTimeline->GetCurrentTime().IsNull()) {
479 0 : mStartTime.SetValue(mTimeline->GetCurrentTime().Value() -
480 0 : limit.MultDouble(1.0 / mPlaybackRate));
481 0 : didChange = true;
482 : }
483 :
484 : // If we just resolved the start time for a pause or play-pending
485 : // animation, we need to clear the task. We don't do this as a branch of
486 : // the above however since we can have a play-pending animation with a
487 : // resolved start time if we aborted a pause operation.
488 0 : if (!mStartTime.IsNull() &&
489 0 : (mPendingState == PendingState::PlayPending ||
490 0 : mPendingState == PendingState::PausePending)) {
491 0 : if (mPendingState == PendingState::PausePending) {
492 0 : mHoldTime.SetNull();
493 : }
494 0 : CancelPendingTasks();
495 0 : didChange = true;
496 0 : if (mReady) {
497 0 : mReady->MaybeResolve(this);
498 : }
499 : }
500 0 : UpdateTiming(SeekFlag::DidSeek, SyncNotifyFlag::Sync);
501 0 : if (didChange && IsRelevant()) {
502 0 : nsNodeUtils::AnimationChanged(this);
503 : }
504 0 : PostUpdate();
505 : }
506 :
507 : void
508 0 : Animation::Play(ErrorResult& aRv, LimitBehavior aLimitBehavior)
509 : {
510 0 : PlayNoUpdate(aRv, aLimitBehavior);
511 0 : PostUpdate();
512 0 : }
513 :
514 : void
515 0 : Animation::Pause(ErrorResult& aRv)
516 : {
517 0 : PauseNoUpdate(aRv);
518 0 : PostUpdate();
519 0 : }
520 :
521 : // https://w3c.github.io/web-animations/#reverse-an-animation
522 : void
523 0 : Animation::Reverse(ErrorResult& aRv)
524 : {
525 0 : if (!mTimeline || mTimeline->GetCurrentTime().IsNull()) {
526 0 : aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
527 0 : return;
528 : }
529 :
530 0 : if (mPlaybackRate == 0.0) {
531 0 : return;
532 : }
533 :
534 0 : AutoMutationBatchForAnimation mb(*this);
535 :
536 0 : SilentlySetPlaybackRate(-mPlaybackRate);
537 0 : Play(aRv, LimitBehavior::AutoRewind);
538 :
539 : // If Play() threw, restore state and don't report anything to mutation
540 : // observers.
541 0 : if (aRv.Failed()) {
542 0 : SilentlySetPlaybackRate(-mPlaybackRate);
543 0 : return;
544 : }
545 :
546 0 : if (IsRelevant()) {
547 0 : nsNodeUtils::AnimationChanged(this);
548 : }
549 : // Play(), above, unconditionally calls PostUpdate so we don't need to do
550 : // it here.
551 : }
552 :
553 : // ---------------------------------------------------------------------------
554 : //
555 : // JS wrappers for Animation interface:
556 : //
557 : // ---------------------------------------------------------------------------
558 :
559 : Nullable<double>
560 0 : Animation::GetStartTimeAsDouble() const
561 : {
562 0 : return AnimationUtils::TimeDurationToDouble(mStartTime);
563 : }
564 :
565 : void
566 0 : Animation::SetStartTimeAsDouble(const Nullable<double>& aStartTime)
567 : {
568 0 : return SetStartTime(AnimationUtils::DoubleToTimeDuration(aStartTime));
569 : }
570 :
571 : Nullable<double>
572 2 : Animation::GetCurrentTimeAsDouble() const
573 : {
574 2 : return AnimationUtils::TimeDurationToDouble(GetCurrentTime());
575 : }
576 :
577 : void
578 0 : Animation::SetCurrentTimeAsDouble(const Nullable<double>& aCurrentTime,
579 : ErrorResult& aRv)
580 : {
581 0 : if (aCurrentTime.IsNull()) {
582 0 : if (!GetCurrentTime().IsNull()) {
583 0 : aRv.Throw(NS_ERROR_DOM_TYPE_ERR);
584 : }
585 0 : return;
586 : }
587 :
588 0 : return SetCurrentTime(TimeDuration::FromMilliseconds(aCurrentTime.Value()));
589 : }
590 :
591 : // ---------------------------------------------------------------------------
592 :
593 : void
594 24 : Animation::Tick()
595 : {
596 : // Finish pending if we have a pending ready time, but only if we also
597 : // have an active timeline.
598 114 : if (mPendingState != PendingState::NotPending &&
599 22 : !mPendingReadyTime.IsNull() &&
600 54 : mTimeline &&
601 30 : !mTimeline->GetCurrentTime().IsNull()) {
602 : // Even though mPendingReadyTime is initialized using TimeStamp::Now()
603 : // during the *previous* tick of the refresh driver, it can still be
604 : // ahead of the *current* timeline time when we are using the
605 : // vsync timer so we need to clamp it to the timeline time.
606 4 : mPendingReadyTime.SetValue(std::min(mTimeline->GetCurrentTime().Value(),
607 4 : mPendingReadyTime.Value()));
608 2 : FinishPendingAt(mPendingReadyTime.Value());
609 2 : mPendingReadyTime.SetNull();
610 : }
611 :
612 24 : if (IsPossiblyOrphanedPendingAnimation()) {
613 0 : MOZ_ASSERT(mTimeline && !mTimeline->GetCurrentTime().IsNull(),
614 : "Orphaned pending animations should have an active timeline");
615 0 : FinishPendingAt(mTimeline->GetCurrentTime().Value());
616 : }
617 :
618 24 : UpdateTiming(SeekFlag::NoSeek, SyncNotifyFlag::Async);
619 :
620 24 : if (!mEffect) {
621 0 : return;
622 : }
623 :
624 : // Update layers if we are newly finished.
625 24 : KeyframeEffectReadOnly* keyframeEffect = mEffect->AsKeyframeEffect();
626 48 : if (keyframeEffect &&
627 48 : !keyframeEffect->Properties().IsEmpty() &&
628 72 : !mFinishedAtLastComposeStyle &&
629 24 : PlayState() == AnimationPlayState::Finished) {
630 2 : PostUpdate();
631 : }
632 : }
633 :
634 : void
635 2 : Animation::TriggerOnNextTick(const Nullable<TimeDuration>& aReadyTime)
636 : {
637 : // Normally we expect the play state to be pending but it's possible that,
638 : // due to the handling of possibly orphaned animations in Tick(), this
639 : // animation got started whilst still being in another document's pending
640 : // animation map.
641 2 : if (PlayState() != AnimationPlayState::Pending) {
642 0 : return;
643 : }
644 :
645 : // If aReadyTime.IsNull() we'll detect this in Tick() where we check for
646 : // orphaned animations and trigger this animation anyway
647 2 : mPendingReadyTime = aReadyTime;
648 : }
649 :
650 : void
651 0 : Animation::TriggerNow()
652 : {
653 : // Normally we expect the play state to be pending but when an animation
654 : // is cancelled and its rendered document can't be reached, we can end up
655 : // with the animation still in a pending player tracker even after it is
656 : // no longer pending.
657 0 : if (PlayState() != AnimationPlayState::Pending) {
658 0 : return;
659 : }
660 :
661 : // If we don't have an active timeline we can't trigger the animation.
662 : // However, this is a test-only method that we don't expect to be used in
663 : // conjunction with animations without an active timeline so generate
664 : // a warning if we do find ourselves in that situation.
665 0 : if (!mTimeline || mTimeline->GetCurrentTime().IsNull()) {
666 0 : NS_WARNING("Failed to trigger an animation with an active timeline");
667 0 : return;
668 : }
669 :
670 0 : FinishPendingAt(mTimeline->GetCurrentTime().Value());
671 : }
672 :
673 : Nullable<TimeDuration>
674 0 : Animation::GetCurrentOrPendingStartTime() const
675 : {
676 0 : Nullable<TimeDuration> result;
677 :
678 0 : if (!mStartTime.IsNull()) {
679 0 : result = mStartTime;
680 0 : return result;
681 : }
682 :
683 0 : if (mPendingReadyTime.IsNull() || mHoldTime.IsNull()) {
684 0 : return result;
685 : }
686 :
687 : // Calculate the equivalent start time from the pending ready time.
688 0 : result = StartTimeFromReadyTime(mPendingReadyTime.Value());
689 :
690 0 : return result;
691 : }
692 :
693 : TimeDuration
694 2 : Animation::StartTimeFromReadyTime(const TimeDuration& aReadyTime) const
695 : {
696 2 : MOZ_ASSERT(!mHoldTime.IsNull(), "Hold time should be set in order to"
697 : " convert a ready time to a start time");
698 2 : if (mPlaybackRate == 0) {
699 0 : return aReadyTime;
700 : }
701 2 : return aReadyTime - mHoldTime.Value().MultDouble(1 / mPlaybackRate);
702 : }
703 :
704 : TimeStamp
705 72 : Animation::AnimationTimeToTimeStamp(const StickyTimeDuration& aTime) const
706 : {
707 : // Initializes to null. Return the same object every time to benefit from
708 : // return-value-optimization.
709 72 : TimeStamp result;
710 :
711 : // We *don't* check for mTimeline->TracksWallclockTime() here because that
712 : // method only tells us if the timeline times can be converted to
713 : // TimeStamps that can be compared to TimeStamp::Now() or not, *not*
714 : // whether the timelines can be converted to TimeStamp values at all.
715 : //
716 : // Furthermore, we want to be able to use this method when the refresh driver
717 : // is under test control (in which case TracksWallclockTime() will return
718 : // false).
719 : //
720 : // Once we introduce timelines that are not time-based we will need to
721 : // differentiate between them here and determine how to sort their events.
722 72 : if (!mTimeline) {
723 0 : return result;
724 : }
725 :
726 : // Check the time is convertible to a timestamp
727 288 : if (aTime == TimeDuration::Forever() ||
728 288 : mPlaybackRate == 0.0 ||
729 72 : mStartTime.IsNull()) {
730 54 : return result;
731 : }
732 :
733 : // Invert the standard relation:
734 : // animation time = (timeline time - start time) * playback rate
735 : TimeDuration timelineTime =
736 18 : TimeDuration(aTime).MultDouble(1.0 / mPlaybackRate) + mStartTime.Value();
737 :
738 18 : result = mTimeline->ToTimeStamp(timelineTime);
739 18 : return result;
740 : }
741 :
742 : TimeStamp
743 48 : Animation::ElapsedTimeToTimeStamp(
744 : const StickyTimeDuration& aElapsedTime) const
745 : {
746 : TimeDuration delay = mEffect
747 48 : ? mEffect->SpecifiedTiming().Delay()
748 96 : : TimeDuration();
749 48 : return AnimationTimeToTimeStamp(aElapsedTime + delay);
750 : }
751 :
752 : // https://w3c.github.io/web-animations/#silently-set-the-current-time
753 : void
754 0 : Animation::SilentlySetCurrentTime(const TimeDuration& aSeekTime)
755 : {
756 0 : if (!mHoldTime.IsNull() ||
757 0 : mStartTime.IsNull() ||
758 0 : !mTimeline ||
759 0 : mTimeline->GetCurrentTime().IsNull() ||
760 0 : mPlaybackRate == 0.0) {
761 0 : mHoldTime.SetValue(aSeekTime);
762 0 : if (!mTimeline || mTimeline->GetCurrentTime().IsNull()) {
763 0 : mStartTime.SetNull();
764 : }
765 : } else {
766 0 : mStartTime.SetValue(mTimeline->GetCurrentTime().Value() -
767 0 : (aSeekTime.MultDouble(1 / mPlaybackRate)));
768 : }
769 :
770 0 : mPreviousCurrentTime.SetNull();
771 0 : }
772 :
773 : void
774 0 : Animation::SilentlySetPlaybackRate(double aPlaybackRate)
775 : {
776 0 : Nullable<TimeDuration> previousTime = GetCurrentTime();
777 0 : mPlaybackRate = aPlaybackRate;
778 0 : if (!previousTime.IsNull()) {
779 0 : SilentlySetCurrentTime(previousTime.Value());
780 : }
781 0 : }
782 :
783 : // https://w3c.github.io/web-animations/#cancel-an-animation
784 : void
785 0 : Animation::CancelNoUpdate()
786 : {
787 0 : ResetPendingTasks();
788 :
789 0 : if (mFinished) {
790 0 : mFinished->MaybeReject(NS_ERROR_DOM_ABORT_ERR);
791 : }
792 0 : ResetFinishedPromise();
793 :
794 0 : DispatchPlaybackEvent(NS_LITERAL_STRING("cancel"));
795 :
796 : StickyTimeDuration activeTime = mEffect
797 0 : ? mEffect->GetComputedTiming().mActiveTime
798 0 : : StickyTimeDuration();
799 :
800 0 : mHoldTime.SetNull();
801 0 : mStartTime.SetNull();
802 :
803 0 : if (mTimeline) {
804 0 : mTimeline->RemoveAnimation(this);
805 : }
806 0 : MaybeQueueCancelEvent(activeTime);
807 :
808 : // When an animation is cancelled it no longer needs further ticks from the
809 : // timeline. However, if we queued a cancel event and this was the last
810 : // animation attached to the timeline, the timeline will stop observing the
811 : // refresh driver and there may be no subsequent refresh driver tick for
812 : // dispatching the queued event.
813 : //
814 : // By calling UpdateTiming *after* removing ourselves from our timeline, we
815 : // ensure the timeline will register with the refresh driver for at least one
816 : // more tick.
817 0 : UpdateTiming(SeekFlag::NoSeek, SyncNotifyFlag::Async);
818 0 : }
819 :
820 : bool
821 0 : Animation::ShouldBeSynchronizedWithMainThread(
822 : nsCSSPropertyID aProperty,
823 : const nsIFrame* aFrame,
824 : AnimationPerformanceWarning::Type& aPerformanceWarning) const
825 : {
826 : // Only synchronize playing animations
827 0 : if (!IsPlaying()) {
828 0 : return false;
829 : }
830 :
831 : // Currently only transform animations need to be synchronized
832 0 : if (aProperty != eCSSProperty_transform) {
833 0 : return false;
834 : }
835 :
836 : KeyframeEffectReadOnly* keyframeEffect = mEffect
837 0 : ? mEffect->AsKeyframeEffect()
838 0 : : nullptr;
839 0 : if (!keyframeEffect) {
840 0 : return false;
841 : }
842 :
843 : // Are we starting at the same time as other geometric animations?
844 : // We check this before calling ShouldBlockAsyncTransformAnimations, partly
845 : // because it's cheaper, but also because it's often the most useful thing
846 : // to know when you're debugging performance.
847 0 : if (mSyncWithGeometricAnimations &&
848 0 : keyframeEffect->HasAnimationOfProperty(eCSSProperty_transform)) {
849 0 : aPerformanceWarning = AnimationPerformanceWarning::Type::
850 : TransformWithSyncGeometricAnimations;
851 0 : return true;
852 : }
853 :
854 : return keyframeEffect->
855 0 : ShouldBlockAsyncTransformAnimations(aFrame, aPerformanceWarning);
856 : }
857 :
858 : void
859 32 : Animation::UpdateRelevance()
860 : {
861 32 : bool wasRelevant = mIsRelevant;
862 32 : mIsRelevant = HasCurrentEffect() || IsInEffect();
863 :
864 : // Notify animation observers.
865 32 : if (wasRelevant && !mIsRelevant) {
866 2 : nsNodeUtils::AnimationRemoved(this);
867 30 : } else if (!wasRelevant && mIsRelevant) {
868 2 : nsNodeUtils::AnimationAdded(this);
869 : }
870 32 : }
871 :
872 : bool
873 3 : Animation::HasLowerCompositeOrderThan(const Animation& aOther) const
874 : {
875 : // 0. Object-equality case
876 3 : if (&aOther == this) {
877 0 : return false;
878 : }
879 :
880 : // 1. CSS Transitions sort lowest
881 : {
882 : auto asCSSTransitionForSorting =
883 6 : [] (const Animation& anim) -> const CSSTransition*
884 : {
885 6 : const CSSTransition* transition = anim.AsCSSTransition();
886 6 : return transition && transition->IsTiedToMarkup() ?
887 : transition :
888 6 : nullptr;
889 : };
890 3 : auto thisTransition = asCSSTransitionForSorting(*this);
891 3 : auto otherTransition = asCSSTransitionForSorting(aOther);
892 3 : if (thisTransition && otherTransition) {
893 6 : return thisTransition->HasLowerCompositeOrderThan(*otherTransition);
894 : }
895 0 : if (thisTransition || otherTransition) {
896 : // Cancelled transitions no longer have an owning element. To be strictly
897 : // correct we should store a strong reference to the owning element
898 : // so that if we arrive here while sorting cancel events, we can sort
899 : // them in the correct order.
900 : //
901 : // However, given that cancel events are almost always queued
902 : // synchronously in some deterministic manner, we can be fairly sure
903 : // that cancel events will be dispatched in a deterministic order
904 : // (which is our only hard requirement until specs say otherwise).
905 : // Furthermore, we only reach here when we have events with equal
906 : // timestamps so this is an edge case we can probably ignore for now.
907 0 : return thisTransition;
908 : }
909 : }
910 :
911 : // 2. CSS Animations sort next
912 : {
913 : auto asCSSAnimationForSorting =
914 0 : [] (const Animation& anim) -> const CSSAnimation*
915 : {
916 0 : const CSSAnimation* animation = anim.AsCSSAnimation();
917 0 : return animation && animation->IsTiedToMarkup() ? animation : nullptr;
918 : };
919 0 : auto thisAnimation = asCSSAnimationForSorting(*this);
920 0 : auto otherAnimation = asCSSAnimationForSorting(aOther);
921 0 : if (thisAnimation && otherAnimation) {
922 0 : return thisAnimation->HasLowerCompositeOrderThan(*otherAnimation);
923 : }
924 0 : if (thisAnimation || otherAnimation) {
925 0 : return thisAnimation;
926 : }
927 : }
928 :
929 : // Subclasses of Animation repurpose mAnimationIndex to implement their
930 : // own brand of composite ordering. However, by this point we should have
931 : // handled any such custom composite ordering so we should now have unique
932 : // animation indices.
933 0 : MOZ_ASSERT(mAnimationIndex != aOther.mAnimationIndex,
934 : "Animation indices should be unique");
935 :
936 : // 3. Finally, generic animations sort by their position in the global
937 : // animation array.
938 0 : return mAnimationIndex < aOther.mAnimationIndex;
939 : }
940 :
941 : void
942 4 : Animation::WillComposeStyle()
943 : {
944 4 : mFinishedAtLastComposeStyle = (PlayState() == AnimationPlayState::Finished);
945 :
946 4 : MOZ_ASSERT(mEffect);
947 :
948 4 : KeyframeEffectReadOnly* keyframeEffect = mEffect->AsKeyframeEffect();
949 4 : if (keyframeEffect) {
950 4 : keyframeEffect->WillComposeStyle();
951 : }
952 4 : }
953 :
954 : template<typename ComposeAnimationResult>
955 : void
956 4 : Animation::ComposeStyle(ComposeAnimationResult&& aComposeResult,
957 : const nsCSSPropertyIDSet& aPropertiesToSkip)
958 : {
959 4 : if (!mEffect) {
960 0 : return;
961 : }
962 :
963 : // In order to prevent flicker, there are a few cases where we want to use
964 : // a different time for rendering that would otherwise be returned by
965 : // GetCurrentTime. These are:
966 : //
967 : // (a) For animations that are pausing but which are still running on the
968 : // compositor. In this case we send a layer transaction that removes the
969 : // animation but which also contains the animation values calculated on
970 : // the main thread. To prevent flicker when this occurs we want to ensure
971 : // the timeline time used to calculate the main thread animation values
972 : // does not lag far behind the time used on the compositor. Ideally we
973 : // would like to use the "animation ready time" calculated at the end of
974 : // the layer transaction as the timeline time but it will be too late to
975 : // update the style rule at that point so instead we just use the current
976 : // wallclock time.
977 : //
978 : // (b) For animations that are pausing that we have already taken off the
979 : // compositor. In this case we record a pending ready time but we don't
980 : // apply it until the next tick. However, while waiting for the next tick,
981 : // we should still use the pending ready time as the timeline time. If we
982 : // use the regular timeline time the animation may appear jump backwards
983 : // if the main thread's timeline time lags behind the compositor.
984 : //
985 : // (c) For animations that are play-pending due to an aborted pause operation
986 : // (i.e. a pause operation that was interrupted before we entered the
987 : // paused state). When we cancel a pending pause we might momentarily take
988 : // the animation off the compositor, only to re-add it moments later. In
989 : // that case the compositor might have been ahead of the main thread so we
990 : // should use the current wallclock time to ensure the animation doesn't
991 : // temporarily jump backwards.
992 : //
993 : // To address each of these cases we temporarily tweak the hold time
994 : // immediately before updating the style rule and then restore it immediately
995 : // afterwards. This is purely to prevent visual flicker. Other behavior
996 : // such as dispatching events continues to rely on the regular timeline time.
997 4 : AnimationPlayState playState = PlayState();
998 : {
999 8 : AutoRestore<Nullable<TimeDuration>> restoreHoldTime(mHoldTime);
1000 :
1001 6 : if (playState == AnimationPlayState::Pending &&
1002 4 : mHoldTime.IsNull() &&
1003 0 : !mStartTime.IsNull()) {
1004 0 : Nullable<TimeDuration> timeToUse = mPendingReadyTime;
1005 0 : if (timeToUse.IsNull() &&
1006 0 : mTimeline &&
1007 0 : mTimeline->TracksWallclockTime()) {
1008 0 : timeToUse = mTimeline->ToTimelineTime(TimeStamp::Now());
1009 : }
1010 0 : if (!timeToUse.IsNull()) {
1011 0 : mHoldTime.SetValue((timeToUse.Value() - mStartTime.Value())
1012 0 : .MultDouble(mPlaybackRate));
1013 : }
1014 : }
1015 :
1016 4 : KeyframeEffectReadOnly* keyframeEffect = mEffect->AsKeyframeEffect();
1017 4 : if (keyframeEffect) {
1018 4 : keyframeEffect->ComposeStyle(Forward<ComposeAnimationResult>(aComposeResult),
1019 : aPropertiesToSkip);
1020 : }
1021 : }
1022 :
1023 4 : MOZ_ASSERT(playState == PlayState(),
1024 : "Play state should not change during the course of compositing");
1025 : }
1026 :
1027 : void
1028 0 : Animation::NotifyEffectTimingUpdated()
1029 : {
1030 0 : MOZ_ASSERT(mEffect,
1031 : "We should only update timing effect when we have a target "
1032 : "effect");
1033 : UpdateTiming(Animation::SeekFlag::NoSeek,
1034 0 : Animation::SyncNotifyFlag::Async);
1035 0 : }
1036 :
1037 : void
1038 0 : Animation::NotifyGeometricAnimationsStartingThisFrame()
1039 : {
1040 0 : if (!IsNewlyStarted() || !mEffect) {
1041 0 : return;
1042 : }
1043 :
1044 0 : mSyncWithGeometricAnimations = true;
1045 : }
1046 :
1047 : // https://w3c.github.io/web-animations/#play-an-animation
1048 : void
1049 2 : Animation::PlayNoUpdate(ErrorResult& aRv, LimitBehavior aLimitBehavior)
1050 : {
1051 4 : AutoMutationBatchForAnimation mb(*this);
1052 :
1053 2 : bool abortedPause = mPendingState == PendingState::PausePending;
1054 :
1055 4 : Nullable<TimeDuration> currentTime = GetCurrentTime();
1056 10 : if (mPlaybackRate > 0.0 &&
1057 2 : (currentTime.IsNull() ||
1058 0 : (aLimitBehavior == LimitBehavior::AutoRewind &&
1059 2 : (currentTime.Value() < TimeDuration() ||
1060 2 : currentTime.Value() >= EffectEnd())))) {
1061 2 : mHoldTime.SetValue(TimeDuration(0));
1062 0 : } else if (mPlaybackRate < 0.0 &&
1063 0 : (currentTime.IsNull() ||
1064 0 : (aLimitBehavior == LimitBehavior::AutoRewind &&
1065 0 : (currentTime.Value() <= TimeDuration() ||
1066 0 : currentTime.Value() > EffectEnd())))) {
1067 0 : if (EffectEnd() == TimeDuration::Forever()) {
1068 0 : aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
1069 0 : return;
1070 : }
1071 0 : mHoldTime.SetValue(TimeDuration(EffectEnd()));
1072 0 : } else if (mPlaybackRate == 0.0 && currentTime.IsNull()) {
1073 0 : mHoldTime.SetValue(TimeDuration(0));
1074 : }
1075 :
1076 2 : bool reuseReadyPromise = false;
1077 2 : if (mPendingState != PendingState::NotPending) {
1078 0 : CancelPendingTasks();
1079 0 : reuseReadyPromise = true;
1080 : }
1081 :
1082 : // If the hold time is null then we're either already playing normally (and
1083 : // we can ignore this call) or we aborted a pending pause operation (in which
1084 : // case, for consistency, we need to go through the motions of doing an
1085 : // asynchronous start even though we already have a resolved start time).
1086 2 : if (mHoldTime.IsNull() && !abortedPause) {
1087 0 : return;
1088 : }
1089 :
1090 : // Clear the start time until we resolve a new one. We do this except
1091 : // for the case where we are aborting a pause and don't have a hold time.
1092 : //
1093 : // If we're aborting a pause and *do* have a hold time (e.g. because
1094 : // the animation is finished or we just applied the auto-rewind behavior
1095 : // above) we should respect it by clearing the start time. If we *don't*
1096 : // have a hold time we should keep the current start time so that the
1097 : // the animation continues moving uninterrupted by the aborted pause.
1098 : //
1099 : // (If we're not aborting a pause, mHoldTime must be resolved by now
1100 : // or else we would have returned above.)
1101 2 : if (!mHoldTime.IsNull()) {
1102 2 : mStartTime.SetNull();
1103 : }
1104 :
1105 2 : if (!reuseReadyPromise) {
1106 : // Clear ready promise. We'll create a new one lazily.
1107 2 : mReady = nullptr;
1108 : }
1109 :
1110 2 : mPendingState = PendingState::PlayPending;
1111 :
1112 : // Clear flag that causes us to sync transform animations with the main
1113 : // thread for now. We'll set this when we go to set up compositor
1114 : // animations if it applies.
1115 2 : mSyncWithGeometricAnimations = false;
1116 :
1117 2 : nsIDocument* doc = GetRenderedDocument();
1118 2 : if (doc) {
1119 : PendingAnimationTracker* tracker =
1120 2 : doc->GetOrCreatePendingAnimationTracker();
1121 2 : tracker->AddPlayPending(*this);
1122 : } else {
1123 0 : TriggerOnNextTick(Nullable<TimeDuration>());
1124 : }
1125 :
1126 2 : UpdateTiming(SeekFlag::NoSeek, SyncNotifyFlag::Async);
1127 2 : if (IsRelevant()) {
1128 2 : nsNodeUtils::AnimationChanged(this);
1129 : }
1130 : }
1131 :
1132 : // https://w3c.github.io/web-animations/#pause-an-animation
1133 : void
1134 0 : Animation::PauseNoUpdate(ErrorResult& aRv)
1135 : {
1136 0 : if (IsPausedOrPausing()) {
1137 0 : return;
1138 : }
1139 :
1140 0 : AutoMutationBatchForAnimation mb(*this);
1141 :
1142 : // If we are transitioning from idle, fill in the current time
1143 0 : if (GetCurrentTime().IsNull()) {
1144 0 : if (mPlaybackRate >= 0.0) {
1145 0 : mHoldTime.SetValue(TimeDuration(0));
1146 : } else {
1147 0 : if (EffectEnd() == TimeDuration::Forever()) {
1148 0 : aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
1149 0 : return;
1150 : }
1151 0 : mHoldTime.SetValue(TimeDuration(EffectEnd()));
1152 : }
1153 : }
1154 :
1155 0 : bool reuseReadyPromise = false;
1156 0 : if (mPendingState == PendingState::PlayPending) {
1157 0 : CancelPendingTasks();
1158 0 : reuseReadyPromise = true;
1159 : }
1160 :
1161 0 : if (!reuseReadyPromise) {
1162 : // Clear ready promise. We'll create a new one lazily.
1163 0 : mReady = nullptr;
1164 : }
1165 :
1166 0 : mPendingState = PendingState::PausePending;
1167 :
1168 0 : nsIDocument* doc = GetRenderedDocument();
1169 0 : if (doc) {
1170 : PendingAnimationTracker* tracker =
1171 0 : doc->GetOrCreatePendingAnimationTracker();
1172 0 : tracker->AddPausePending(*this);
1173 : } else {
1174 0 : TriggerOnNextTick(Nullable<TimeDuration>());
1175 : }
1176 :
1177 0 : UpdateTiming(SeekFlag::NoSeek, SyncNotifyFlag::Async);
1178 0 : if (IsRelevant()) {
1179 0 : nsNodeUtils::AnimationChanged(this);
1180 : }
1181 : }
1182 :
1183 : void
1184 2 : Animation::ResumeAt(const TimeDuration& aReadyTime)
1185 : {
1186 : // This method is only expected to be called for an animation that is
1187 : // waiting to play. We can easily adapt it to handle other states
1188 : // but it's currently not necessary.
1189 2 : MOZ_ASSERT(mPendingState == PendingState::PlayPending,
1190 : "Expected to resume a play-pending animation");
1191 2 : MOZ_ASSERT(mHoldTime.IsNull() != mStartTime.IsNull(),
1192 : "An animation in the play-pending state should have either a"
1193 : " resolved hold time or resolved start time (but not both)");
1194 :
1195 : // If we aborted a pending pause operation we will already have a start time
1196 : // we should use. In all other cases, we resolve it from the ready time.
1197 2 : if (mStartTime.IsNull()) {
1198 2 : mStartTime = StartTimeFromReadyTime(aReadyTime);
1199 2 : if (mPlaybackRate != 0) {
1200 2 : mHoldTime.SetNull();
1201 : }
1202 : }
1203 2 : mPendingState = PendingState::NotPending;
1204 :
1205 2 : UpdateTiming(SeekFlag::NoSeek, SyncNotifyFlag::Async);
1206 :
1207 2 : if (mReady) {
1208 0 : mReady->MaybeResolve(this);
1209 : }
1210 2 : }
1211 :
1212 : void
1213 0 : Animation::PauseAt(const TimeDuration& aReadyTime)
1214 : {
1215 0 : MOZ_ASSERT(mPendingState == PendingState::PausePending,
1216 : "Expected to pause a pause-pending animation");
1217 :
1218 0 : if (!mStartTime.IsNull() && mHoldTime.IsNull()) {
1219 0 : mHoldTime.SetValue((aReadyTime - mStartTime.Value())
1220 0 : .MultDouble(mPlaybackRate));
1221 : }
1222 0 : mStartTime.SetNull();
1223 0 : mPendingState = PendingState::NotPending;
1224 :
1225 0 : UpdateTiming(SeekFlag::NoSeek, SyncNotifyFlag::Async);
1226 :
1227 0 : if (mReady) {
1228 0 : mReady->MaybeResolve(this);
1229 : }
1230 0 : }
1231 :
1232 : void
1233 32 : Animation::UpdateTiming(SeekFlag aSeekFlag, SyncNotifyFlag aSyncNotifyFlag)
1234 : {
1235 : // We call UpdateFinishedState before UpdateEffect because the former
1236 : // can change the current time, which is used by the latter.
1237 32 : UpdateFinishedState(aSeekFlag, aSyncNotifyFlag);
1238 32 : UpdateEffect();
1239 :
1240 32 : if (mTimeline) {
1241 32 : mTimeline->NotifyAnimationUpdated(*this);
1242 : }
1243 32 : }
1244 :
1245 : // https://w3c.github.io/web-animations/#update-an-animations-finished-state
1246 : void
1247 32 : Animation::UpdateFinishedState(SeekFlag aSeekFlag,
1248 : SyncNotifyFlag aSyncNotifyFlag)
1249 : {
1250 64 : Nullable<TimeDuration> currentTime = GetCurrentTime();
1251 32 : TimeDuration effectEnd = TimeDuration(EffectEnd());
1252 :
1253 40 : if (!mStartTime.IsNull() &&
1254 8 : mPendingState == PendingState::NotPending) {
1255 24 : if (mPlaybackRate > 0.0 &&
1256 16 : !currentTime.IsNull() &&
1257 8 : currentTime.Value() >= effectEnd) {
1258 2 : if (aSeekFlag == SeekFlag::DidSeek) {
1259 0 : mHoldTime = currentTime;
1260 2 : } else if (!mPreviousCurrentTime.IsNull()) {
1261 2 : mHoldTime.SetValue(std::max(mPreviousCurrentTime.Value(), effectEnd));
1262 : } else {
1263 0 : mHoldTime.SetValue(effectEnd);
1264 : }
1265 24 : } else if (mPlaybackRate < 0.0 &&
1266 12 : !currentTime.IsNull() &&
1267 6 : currentTime.Value() <= TimeDuration()) {
1268 0 : if (aSeekFlag == SeekFlag::DidSeek) {
1269 0 : mHoldTime = currentTime;
1270 0 : } else if (!mPreviousCurrentTime.IsNull()) {
1271 0 : mHoldTime.SetValue(std::min(mPreviousCurrentTime.Value(),
1272 0 : TimeDuration(0)));
1273 : } else {
1274 0 : mHoldTime.SetValue(0);
1275 : }
1276 24 : } else if (mPlaybackRate != 0.0 &&
1277 12 : !currentTime.IsNull() &&
1278 30 : mTimeline &&
1279 24 : !mTimeline->GetCurrentTime().IsNull()) {
1280 6 : if (aSeekFlag == SeekFlag::DidSeek && !mHoldTime.IsNull()) {
1281 0 : mStartTime.SetValue(mTimeline->GetCurrentTime().Value() -
1282 0 : (mHoldTime.Value().MultDouble(1 / mPlaybackRate)));
1283 : }
1284 6 : mHoldTime.SetNull();
1285 : }
1286 : }
1287 :
1288 32 : bool currentFinishedState = PlayState() == AnimationPlayState::Finished;
1289 32 : if (currentFinishedState && !mFinishedIsResolved) {
1290 2 : DoFinishNotification(aSyncNotifyFlag);
1291 30 : } else if (!currentFinishedState && mFinishedIsResolved) {
1292 0 : ResetFinishedPromise();
1293 : }
1294 : // We must recalculate the current time to take account of any mHoldTime
1295 : // changes the code above made.
1296 32 : mPreviousCurrentTime = GetCurrentTime();
1297 32 : }
1298 :
1299 : void
1300 32 : Animation::UpdateEffect()
1301 : {
1302 32 : if (mEffect) {
1303 30 : UpdateRelevance();
1304 :
1305 30 : KeyframeEffectReadOnly* keyframeEffect = mEffect->AsKeyframeEffect();
1306 30 : if (keyframeEffect) {
1307 30 : keyframeEffect->NotifyAnimationTimingUpdated();
1308 : }
1309 : }
1310 32 : }
1311 :
1312 : void
1313 0 : Animation::FlushStyle() const
1314 : {
1315 0 : nsIDocument* doc = GetRenderedDocument();
1316 0 : if (doc) {
1317 0 : doc->FlushPendingNotifications(FlushType::Style);
1318 : }
1319 0 : }
1320 :
1321 : void
1322 2 : Animation::PostUpdate()
1323 : {
1324 2 : if (!mEffect) {
1325 0 : return;
1326 : }
1327 :
1328 2 : KeyframeEffectReadOnly* keyframeEffect = mEffect->AsKeyframeEffect();
1329 2 : if (!keyframeEffect) {
1330 0 : return;
1331 : }
1332 2 : keyframeEffect->RequestRestyle(EffectCompositor::RestyleType::Layer);
1333 : }
1334 :
1335 : void
1336 0 : Animation::CancelPendingTasks()
1337 : {
1338 0 : if (mPendingState == PendingState::NotPending) {
1339 0 : return;
1340 : }
1341 :
1342 0 : nsIDocument* doc = GetRenderedDocument();
1343 0 : if (doc) {
1344 0 : PendingAnimationTracker* tracker = doc->GetPendingAnimationTracker();
1345 0 : if (tracker) {
1346 0 : if (mPendingState == PendingState::PlayPending) {
1347 0 : tracker->RemovePlayPending(*this);
1348 : } else {
1349 0 : tracker->RemovePausePending(*this);
1350 : }
1351 : }
1352 : }
1353 :
1354 0 : mPendingState = PendingState::NotPending;
1355 0 : mPendingReadyTime.SetNull();
1356 : }
1357 :
1358 : // https://w3c.github.io/web-animations/#reset-an-animations-pending-tasks
1359 : void
1360 0 : Animation::ResetPendingTasks()
1361 : {
1362 0 : if (mPendingState == PendingState::NotPending) {
1363 0 : return;
1364 : }
1365 :
1366 0 : CancelPendingTasks();
1367 0 : if (mReady) {
1368 0 : mReady->MaybeReject(NS_ERROR_DOM_ABORT_ERR);
1369 0 : mReady = nullptr;
1370 : }
1371 : }
1372 :
1373 : bool
1374 24 : Animation::IsPossiblyOrphanedPendingAnimation() const
1375 : {
1376 : // Check if we are pending but might never start because we are not being
1377 : // tracked.
1378 : //
1379 : // This covers the following cases:
1380 : //
1381 : // * We started playing but our effect's target element was orphaned
1382 : // or bound to a different document.
1383 : // (note that for the case of our effect changing we should handle
1384 : // that in SetEffect)
1385 : // * We started playing but our timeline became inactive.
1386 : // In this case the pending animation tracker will drop us from its hashmap
1387 : // when we have been painted.
1388 : // * When we started playing we couldn't find a PendingAnimationTracker to
1389 : // register with (perhaps the effect had no document) so we simply
1390 : // set mPendingState in PlayNoUpdate and relied on this method to catch us
1391 : // on the next tick.
1392 :
1393 : // If we're not pending we're ok.
1394 24 : if (mPendingState == PendingState::NotPending) {
1395 6 : return false;
1396 : }
1397 :
1398 : // If we have a pending ready time then we will be started on the next
1399 : // tick.
1400 18 : if (!mPendingReadyTime.IsNull()) {
1401 0 : return false;
1402 : }
1403 :
1404 : // If we don't have an active timeline then we shouldn't start until
1405 : // we do.
1406 18 : if (!mTimeline || mTimeline->GetCurrentTime().IsNull()) {
1407 0 : return false;
1408 : }
1409 :
1410 : // If we have no rendered document, or we're not in our rendered document's
1411 : // PendingAnimationTracker then there's a good chance no one is tracking us.
1412 : //
1413 : // If we're wrong and another document is tracking us then, at worst, we'll
1414 : // simply start/pause the animation one tick too soon. That's better than
1415 : // never starting/pausing the animation and is unlikely.
1416 18 : nsIDocument* doc = GetRenderedDocument();
1417 18 : if (!doc) {
1418 0 : return true;
1419 : }
1420 :
1421 18 : PendingAnimationTracker* tracker = doc->GetPendingAnimationTracker();
1422 36 : return !tracker ||
1423 18 : (!tracker->IsWaitingToPlay(*this) &&
1424 18 : !tracker->IsWaitingToPause(*this));
1425 : }
1426 :
1427 : StickyTimeDuration
1428 102 : Animation::EffectEnd() const
1429 : {
1430 102 : if (!mEffect) {
1431 2 : return StickyTimeDuration(0);
1432 : }
1433 :
1434 100 : return mEffect->SpecifiedTiming().EndTime();
1435 : }
1436 :
1437 : nsIDocument*
1438 20 : Animation::GetRenderedDocument() const
1439 : {
1440 20 : if (!mEffect || !mEffect->AsKeyframeEffect()) {
1441 0 : return nullptr;
1442 : }
1443 :
1444 20 : return mEffect->AsKeyframeEffect()->GetRenderedDocument();
1445 : }
1446 :
1447 : void
1448 2 : Animation::DoFinishNotification(SyncNotifyFlag aSyncNotifyFlag)
1449 : {
1450 2 : CycleCollectedJSContext* context = CycleCollectedJSContext::Get();
1451 :
1452 2 : if (aSyncNotifyFlag == SyncNotifyFlag::Sync) {
1453 0 : DoFinishNotificationImmediately();
1454 2 : } else if (!mFinishNotificationTask.IsPending()) {
1455 : RefPtr<nsRunnableMethod<Animation>> runnable =
1456 4 : NewRunnableMethod("dom::Animation::DoFinishNotificationImmediately",
1457 : this,
1458 4 : &Animation::DoFinishNotificationImmediately);
1459 2 : context->DispatchToMicroTask(do_AddRef(runnable));
1460 2 : mFinishNotificationTask = runnable.forget();
1461 : }
1462 2 : }
1463 :
1464 : void
1465 0 : Animation::ResetFinishedPromise()
1466 : {
1467 0 : mFinishedIsResolved = false;
1468 0 : mFinished = nullptr;
1469 0 : }
1470 :
1471 : void
1472 2 : Animation::MaybeResolveFinishedPromise()
1473 : {
1474 2 : if (mFinished) {
1475 0 : mFinished->MaybeResolve(this);
1476 : }
1477 2 : mFinishedIsResolved = true;
1478 2 : }
1479 :
1480 : void
1481 2 : Animation::DoFinishNotificationImmediately()
1482 : {
1483 2 : mFinishNotificationTask.Revoke();
1484 :
1485 2 : if (PlayState() != AnimationPlayState::Finished) {
1486 0 : return;
1487 : }
1488 :
1489 2 : MaybeResolveFinishedPromise();
1490 :
1491 2 : DispatchPlaybackEvent(NS_LITERAL_STRING("finish"));
1492 : }
1493 :
1494 : void
1495 2 : Animation::DispatchPlaybackEvent(const nsAString& aName)
1496 : {
1497 4 : AnimationPlaybackEventInit init;
1498 :
1499 2 : if (aName.EqualsLiteral("finish")) {
1500 2 : init.mCurrentTime = GetCurrentTimeAsDouble();
1501 : }
1502 2 : if (mTimeline) {
1503 2 : init.mTimelineTime = mTimeline->GetCurrentTimeAsDouble();
1504 : }
1505 :
1506 : RefPtr<AnimationPlaybackEvent> event =
1507 4 : AnimationPlaybackEvent::Constructor(this, aName, init);
1508 2 : event->SetTrusted(true);
1509 :
1510 : RefPtr<AsyncEventDispatcher> asyncDispatcher =
1511 6 : new AsyncEventDispatcher(this, event);
1512 2 : asyncDispatcher->PostDOMEvent();
1513 2 : }
1514 :
1515 : bool
1516 0 : Animation::IsRunningOnCompositor() const
1517 : {
1518 0 : return mEffect &&
1519 0 : mEffect->AsKeyframeEffect() &&
1520 0 : mEffect->AsKeyframeEffect()->IsRunningOnCompositor();
1521 : }
1522 :
1523 : template
1524 : void
1525 : Animation::ComposeStyle<RefPtr<AnimValuesStyleRule>&>(
1526 : RefPtr<AnimValuesStyleRule>& aAnimationRule,
1527 : const nsCSSPropertyIDSet& aPropertiesToSkip);
1528 :
1529 : template
1530 : void
1531 : Animation::ComposeStyle<RawServoAnimationValueMap&>(
1532 : RawServoAnimationValueMap& aAnimationValues,
1533 : const nsCSSPropertyIDSet& aPropertiesToSkip);
1534 :
1535 : } // namespace dom
1536 : } // namespace mozilla
|