Coverage Report

Created: 2018-09-25 14:53

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