Coverage Report

Created: 2018-09-25 14:53

/src/mozilla-central/layout/style/nsAnimationManager.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 "nsAnimationManager.h"
8
#include "nsTransitionManager.h"
9
#include "mozilla/dom/CSSAnimationBinding.h"
10
11
#include "mozilla/AnimationEventDispatcher.h"
12
#include "mozilla/AnimationTarget.h"
13
#include "mozilla/EffectCompositor.h"
14
#include "mozilla/EffectSet.h"
15
#include "mozilla/MemoryReporting.h"
16
#include "mozilla/ServoStyleSet.h"
17
#include "mozilla/StyleAnimationValue.h"
18
#include "mozilla/dom/AnimationEffect.h"
19
#include "mozilla/dom/DocumentTimeline.h"
20
#include "mozilla/dom/KeyframeEffect.h"
21
22
#include "nsPresContext.h"
23
#include "nsStyleChangeList.h"
24
#include "nsLayoutUtils.h"
25
#include "nsIFrame.h"
26
#include "nsIDocument.h"
27
#include "nsDOMMutationObserver.h"
28
#include "nsIPresShell.h"
29
#include "nsIPresShellInlines.h"
30
#include "nsRFPService.h"
31
#include <algorithm> // std::stable_sort
32
#include <math.h>
33
34
using namespace mozilla;
35
using namespace mozilla::css;
36
using mozilla::dom::Animation;
37
using mozilla::dom::AnimationEffect;
38
using mozilla::dom::AnimationPlayState;
39
using mozilla::dom::KeyframeEffect;
40
using mozilla::dom::CSSAnimation;
41
42
typedef mozilla::ComputedTiming::AnimationPhase AnimationPhase;
43
44
////////////////////////// CSSAnimation ////////////////////////////
45
46
JSObject*
47
CSSAnimation::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
48
0
{
49
0
  return dom::CSSAnimation_Binding::Wrap(aCx, this, aGivenProto);
50
0
}
51
52
mozilla::dom::Promise*
53
CSSAnimation::GetReady(ErrorResult& aRv)
54
0
{
55
0
  FlushUnanimatedStyle();
56
0
  return Animation::GetReady(aRv);
57
0
}
58
59
void
60
CSSAnimation::Play(ErrorResult &aRv, LimitBehavior aLimitBehavior)
61
0
{
62
0
  mPauseShouldStick = false;
63
0
  Animation::Play(aRv, aLimitBehavior);
64
0
}
65
66
void
67
CSSAnimation::Pause(ErrorResult& aRv)
68
0
{
69
0
  mPauseShouldStick = true;
70
0
  Animation::Pause(aRv);
71
0
}
72
73
AnimationPlayState
74
CSSAnimation::PlayStateFromJS() const
75
0
{
76
0
  // Flush style to ensure that any properties controlling animation state
77
0
  // (e.g. animation-play-state) are fully updated.
78
0
  FlushUnanimatedStyle();
79
0
  return Animation::PlayStateFromJS();
80
0
}
81
82
bool
83
CSSAnimation::PendingFromJS() const
84
0
{
85
0
  // Flush style since, for example, if the animation-play-state was just
86
0
  // changed its possible we should now be pending.
87
0
  FlushUnanimatedStyle();
88
0
  return Animation::PendingFromJS();
89
0
}
90
91
void
92
CSSAnimation::PlayFromJS(ErrorResult& aRv)
93
0
{
94
0
  // Note that flushing style below might trigger calls to
95
0
  // PlayFromStyle()/PauseFromStyle() on this object.
96
0
  FlushUnanimatedStyle();
97
0
  Animation::PlayFromJS(aRv);
98
0
}
99
100
void
101
CSSAnimation::PlayFromStyle()
102
0
{
103
0
  mIsStylePaused = false;
104
0
  if (!mPauseShouldStick) {
105
0
    ErrorResult rv;
106
0
    Animation::Play(rv, Animation::LimitBehavior::Continue);
107
0
    // play() should not throw when LimitBehavior is Continue
108
0
    MOZ_ASSERT(!rv.Failed(), "Unexpected exception playing animation");
109
0
  }
110
0
}
111
112
void
113
CSSAnimation::PauseFromStyle()
114
0
{
115
0
  // Check if the pause state is being overridden
116
0
  if (mIsStylePaused) {
117
0
    return;
118
0
  }
119
0
120
0
  mIsStylePaused = true;
121
0
  ErrorResult rv;
122
0
  Animation::Pause(rv);
123
0
  // pause() should only throw when *all* of the following conditions are true:
124
0
  // - we are in the idle state, and
125
0
  // - we have a negative playback rate, and
126
0
  // - we have an infinitely repeating animation
127
0
  // The first two conditions will never happen under regular style processing
128
0
  // but could happen if an author made modifications to the Animation object
129
0
  // and then updated animation-play-state. It's an unusual case and there's
130
0
  // no obvious way to pass on the exception information so we just silently
131
0
  // fail for now.
132
0
  if (rv.Failed()) {
133
0
    NS_WARNING("Unexpected exception pausing animation - silently failing");
134
0
  }
135
0
}
136
137
void
138
CSSAnimation::Tick()
139
0
{
140
0
  Animation::Tick();
141
0
  QueueEvents();
142
0
}
143
144
bool
145
CSSAnimation::HasLowerCompositeOrderThan(const CSSAnimation& aOther) const
146
0
{
147
0
  MOZ_ASSERT(IsTiedToMarkup() && aOther.IsTiedToMarkup(),
148
0
             "Should only be called for CSS animations that are sorted "
149
0
             "as CSS animations (i.e. tied to CSS markup)");
150
0
151
0
  // 0. Object-equality case
152
0
  if (&aOther == this) {
153
0
    return false;
154
0
  }
155
0
156
0
  // 1. Sort by document order
157
0
  if (!mOwningElement.Equals(aOther.mOwningElement)) {
158
0
    return mOwningElement.LessThan(
159
0
      const_cast<CSSAnimation*>(this)->CachedChildIndexRef(),
160
0
      aOther.mOwningElement,
161
0
      const_cast<CSSAnimation*>(&aOther)->CachedChildIndexRef());
162
0
  }
163
0
164
0
  // 2. (Same element and pseudo): Sort by position in animation-name
165
0
  return mAnimationIndex < aOther.mAnimationIndex;
166
0
}
167
168
void
169
CSSAnimation::QueueEvents(const StickyTimeDuration& aActiveTime)
170
0
{
171
0
  // If the animation is pending, we ignore animation events until we finish
172
0
  // pending.
173
0
  if (mPendingState != PendingState::NotPending) {
174
0
    return;
175
0
  }
176
0
177
0
  // CSS animations dispatch events at their owning element. This allows
178
0
  // script to repurpose a CSS animation to target a different element,
179
0
  // to use a group effect (which has no obvious "target element"), or
180
0
  // to remove the animation effect altogether whilst still getting
181
0
  // animation events.
182
0
  //
183
0
  // It does mean, however, that for a CSS animation that has no owning
184
0
  // element (e.g. it was created using the CSSAnimation constructor or
185
0
  // disassociated from CSS) no events are fired. If it becomes desirable
186
0
  // for these animations to still fire events we should spec the concept
187
0
  // of the "original owning element" or "event target" and allow script
188
0
  // to set it when creating a CSSAnimation object.
189
0
  if (!mOwningElement.IsSet()) {
190
0
    return;
191
0
  }
192
0
193
0
  nsPresContext* presContext = mOwningElement.GetPresContext();
194
0
  if (!presContext) {
195
0
    return;
196
0
  }
197
0
198
0
  uint64_t currentIteration = 0;
199
0
  ComputedTiming::AnimationPhase currentPhase;
200
0
  StickyTimeDuration intervalStartTime;
201
0
  StickyTimeDuration intervalEndTime;
202
0
  StickyTimeDuration iterationStartTime;
203
0
204
0
  if (!mEffect) {
205
0
    currentPhase = GetAnimationPhaseWithoutEffect
206
0
      <ComputedTiming::AnimationPhase>(*this);
207
0
    if (currentPhase == mPreviousPhase) {
208
0
      return;
209
0
    }
210
0
  } else {
211
0
    ComputedTiming computedTiming = mEffect->GetComputedTiming();
212
0
    currentPhase = computedTiming.mPhase;
213
0
    currentIteration = computedTiming.mCurrentIteration;
214
0
    if (currentPhase == mPreviousPhase &&
215
0
        currentIteration == mPreviousIteration) {
216
0
      return;
217
0
    }
218
0
    intervalStartTime = IntervalStartTime(computedTiming.mActiveDuration);
219
0
    intervalEndTime = IntervalEndTime(computedTiming.mActiveDuration);
220
0
221
0
    uint64_t iterationBoundary = mPreviousIteration > currentIteration
222
0
                                 ? currentIteration + 1
223
0
                                 : currentIteration;
224
0
    iterationStartTime  =
225
0
      computedTiming.mDuration.MultDouble(
226
0
        (iterationBoundary - computedTiming.mIterationStart));
227
0
  }
228
0
229
0
  TimeStamp startTimeStamp     = ElapsedTimeToTimeStamp(intervalStartTime);
230
0
  TimeStamp endTimeStamp       = ElapsedTimeToTimeStamp(intervalEndTime);
231
0
  TimeStamp iterationTimeStamp = ElapsedTimeToTimeStamp(iterationStartTime);
232
0
233
0
  AutoTArray<AnimationEventInfo, 2> events;
234
0
235
0
  auto appendAnimationEvent = [&](EventMessage aMessage,
236
0
                                  const StickyTimeDuration& aElapsedTime,
237
0
                                  const TimeStamp& aScheduledEventTimeStamp) {
238
0
    double elapsedTime = aElapsedTime.ToSeconds();
239
0
    if (aMessage == eAnimationCancel) {
240
0
      // 0 is an inappropriate value for this callsite. What we need to do is
241
0
      // use a single random value for all increasing times reportable.
242
0
      // That is to say, whenever elapsedTime goes negative (because an
243
0
      // animation restarts, something rewinds the animation, or otherwise)
244
0
      // a new random value for the mix-in must be generated.
245
0
      elapsedTime = nsRFPService::ReduceTimePrecisionAsSecs(elapsedTime, 0, TimerPrecisionType::RFPOnly);
246
0
    }
247
0
    events.AppendElement(AnimationEventInfo(mAnimationName,
248
0
                                            mOwningElement.Target(),
249
0
                                            aMessage,
250
0
                                            elapsedTime,
251
0
                                            aScheduledEventTimeStamp,
252
0
                                            this));
253
0
  };
254
0
255
0
  // Handle cancel event first
256
0
  if ((mPreviousPhase != AnimationPhase::Idle &&
257
0
       mPreviousPhase != AnimationPhase::After) &&
258
0
      currentPhase == AnimationPhase::Idle) {
259
0
    appendAnimationEvent(eAnimationCancel,
260
0
                         aActiveTime,
261
0
                         GetTimelineCurrentTimeAsTimeStamp());
262
0
  }
263
0
264
0
  switch (mPreviousPhase) {
265
0
    case AnimationPhase::Idle:
266
0
    case AnimationPhase::Before:
267
0
      if (currentPhase == AnimationPhase::Active) {
268
0
        appendAnimationEvent(eAnimationStart,
269
0
                             intervalStartTime,
270
0
                             startTimeStamp);
271
0
      } else if (currentPhase == AnimationPhase::After) {
272
0
        appendAnimationEvent(eAnimationStart,
273
0
                             intervalStartTime,
274
0
                             startTimeStamp);
275
0
        appendAnimationEvent(eAnimationEnd, intervalEndTime, endTimeStamp);
276
0
      }
277
0
      break;
278
0
    case AnimationPhase::Active:
279
0
      if (currentPhase == AnimationPhase::Before) {
280
0
        appendAnimationEvent(eAnimationEnd, intervalStartTime, startTimeStamp);
281
0
      } else if (currentPhase == AnimationPhase::Active) {
282
0
        // The currentIteration must have changed or element we would have
283
0
        // returned early above.
284
0
        MOZ_ASSERT(currentIteration != mPreviousIteration);
285
0
        appendAnimationEvent(eAnimationIteration,
286
0
                             iterationStartTime,
287
0
                             iterationTimeStamp);
288
0
      } else if (currentPhase == AnimationPhase::After) {
289
0
        appendAnimationEvent(eAnimationEnd, intervalEndTime, endTimeStamp);
290
0
      }
291
0
      break;
292
0
    case AnimationPhase::After:
293
0
      if (currentPhase == AnimationPhase::Before) {
294
0
        appendAnimationEvent(eAnimationStart, intervalEndTime, startTimeStamp);
295
0
        appendAnimationEvent(eAnimationEnd, intervalStartTime, endTimeStamp);
296
0
      } else if (currentPhase == AnimationPhase::Active) {
297
0
        appendAnimationEvent(eAnimationStart, intervalEndTime, endTimeStamp);
298
0
      }
299
0
      break;
300
0
  }
301
0
  mPreviousPhase = currentPhase;
302
0
  mPreviousIteration = currentIteration;
303
0
304
0
  if (!events.IsEmpty()) {
305
0
    presContext->AnimationEventDispatcher()->QueueEvents(std::move(events));
306
0
  }
307
0
}
308
309
void
310
CSSAnimation::UpdateTiming(SeekFlag aSeekFlag, SyncNotifyFlag aSyncNotifyFlag)
311
0
{
312
0
  if (mNeedsNewAnimationIndexWhenRun &&
313
0
      PlayState() != AnimationPlayState::Idle) {
314
0
    mAnimationIndex = sNextAnimationIndex++;
315
0
    mNeedsNewAnimationIndexWhenRun = false;
316
0
  }
317
0
318
0
  Animation::UpdateTiming(aSeekFlag, aSyncNotifyFlag);
319
0
}
320
321
////////////////////////// nsAnimationManager ////////////////////////////
322
323
// Find the matching animation by |aName| in the old list
324
// of animations and remove the matched animation from the list.
325
static already_AddRefed<CSSAnimation>
326
PopExistingAnimation(const nsAtom* aName,
327
                     nsAnimationManager::CSSAnimationCollection* aCollection)
328
0
{
329
0
  if (!aCollection) {
330
0
    return nullptr;
331
0
  }
332
0
333
0
  // Animations are stored in reverse order to how they appear in the
334
0
  // animation-name property. However, we want to match animations beginning
335
0
  // from the end of the animation-name list, so we iterate *forwards*
336
0
  // through the collection.
337
0
  for (size_t idx = 0, length = aCollection->mAnimations.Length();
338
0
       idx != length; ++ idx) {
339
0
    CSSAnimation* cssAnim = aCollection->mAnimations[idx];
340
0
    if (cssAnim->AnimationName() == aName) {
341
0
      RefPtr<CSSAnimation> match = cssAnim;
342
0
      aCollection->mAnimations.RemoveElementAt(idx);
343
0
      return match.forget();
344
0
    }
345
0
  }
346
0
347
0
  return nullptr;
348
0
}
349
350
351
class MOZ_STACK_CLASS ServoCSSAnimationBuilder final {
352
public:
353
  explicit ServoCSSAnimationBuilder(const ComputedStyle* aComputedStyle)
354
    : mComputedStyle(aComputedStyle)
355
0
  {
356
0
    MOZ_ASSERT(aComputedStyle);
357
0
  }
358
359
  bool BuildKeyframes(const Element& aElement,
360
                      nsPresContext* aPresContext,
361
                      nsAtom* aName,
362
                      const nsTimingFunction& aTimingFunction,
363
                      nsTArray<Keyframe>& aKeyframes)
364
0
  {
365
0
    return aPresContext->StyleSet()->GetKeyframesForName(
366
0
        aElement,
367
0
        *mComputedStyle,
368
0
        aName,
369
0
        aTimingFunction,
370
0
        aKeyframes);
371
0
  }
372
  void SetKeyframes(KeyframeEffect& aEffect, nsTArray<Keyframe>&& aKeyframes)
373
0
  {
374
0
    aEffect.SetKeyframes(std::move(aKeyframes), mComputedStyle);
375
0
  }
376
377
  // Currently all the animation building code in this file is based on
378
  // assumption that creating and removing animations should *not* trigger
379
  // additional restyles since those changes will be handled within the same
380
  // restyle.
381
  //
382
  // While that is true for the Gecko style backend, it is not true for the
383
  // Servo style backend where we want restyles to be triggered so that we
384
  // perform a second animation restyle where we will incorporate the changes
385
  // arising from creating and removing animations.
386
  //
387
  // Fortunately, our attempts to avoid posting extra restyles as part of the
388
  // processing here are imperfect and most of the time we happen to post
389
  // them anyway. Occasionally, however, we don't. For example, we don't post
390
  // a restyle when we create a new animation whose an animation index matches
391
  // the default value it was given already (which is typically only true when
392
  // the CSSAnimation we create is the first Animation created in a particular
393
  // content process).
394
  //
395
  // As a result, when we are using the Servo backend, whenever we have an added
396
  // or removed animation we need to explicitly trigger a restyle.
397
  //
398
  // This code should eventually disappear along with the Gecko style backend
399
  // and we should simply call Play() / Pause() / Cancel() etc. which will
400
  // post the required restyles.
401
  void NotifyNewOrRemovedAnimation(const Animation& aAnimation)
402
0
  {
403
0
    dom::AnimationEffect* effect = aAnimation.GetEffect();
404
0
    if (!effect) {
405
0
      return;
406
0
    }
407
0
408
0
    KeyframeEffect* keyframeEffect = effect->AsKeyframeEffect();
409
0
    if (!keyframeEffect) {
410
0
      return;
411
0
    }
412
0
413
0
    keyframeEffect->RequestRestyle(EffectCompositor::RestyleType::Standard);
414
0
  }
415
416
private:
417
  const ComputedStyle* mComputedStyle;
418
};
419
420
421
static void
422
UpdateOldAnimationPropertiesWithNew(
423
    CSSAnimation& aOld,
424
    TimingParams& aNewTiming,
425
    nsTArray<Keyframe>&& aNewKeyframes,
426
    bool aNewIsStylePaused,
427
    ServoCSSAnimationBuilder& aBuilder)
428
0
{
429
0
  bool animationChanged = false;
430
0
431
0
  // Update the old from the new so we can keep the original object
432
0
  // identity (and any expando properties attached to it).
433
0
  if (aOld.GetEffect()) {
434
0
    dom::AnimationEffect* oldEffect = aOld.GetEffect();
435
0
    animationChanged = oldEffect->SpecifiedTiming() != aNewTiming;
436
0
    oldEffect->SetSpecifiedTiming(aNewTiming);
437
0
438
0
    KeyframeEffect* oldKeyframeEffect = oldEffect->AsKeyframeEffect();
439
0
    if (oldKeyframeEffect) {
440
0
      aBuilder.SetKeyframes(*oldKeyframeEffect, std::move(aNewKeyframes));
441
0
    }
442
0
  }
443
0
444
0
  // Handle changes in play state. If the animation is idle, however,
445
0
  // changes to animation-play-state should *not* restart it.
446
0
  if (aOld.PlayState() != AnimationPlayState::Idle) {
447
0
    // CSSAnimation takes care of override behavior so that,
448
0
    // for example, if the author has called pause(), that will
449
0
    // override the animation-play-state.
450
0
    // (We should check aNew->IsStylePaused() but that requires
451
0
    //  downcasting to CSSAnimation and we happen to know that
452
0
    //  aNew will only ever be paused by calling PauseFromStyle
453
0
    //  making IsPausedOrPausing synonymous in this case.)
454
0
    if (!aOld.IsStylePaused() && aNewIsStylePaused) {
455
0
      aOld.PauseFromStyle();
456
0
      animationChanged = true;
457
0
    } else if (aOld.IsStylePaused() && !aNewIsStylePaused) {
458
0
      aOld.PlayFromStyle();
459
0
      animationChanged = true;
460
0
    }
461
0
  }
462
0
463
0
  // Updating the effect timing above might already have caused the
464
0
  // animation to become irrelevant so only add a changed record if
465
0
  // the animation is still relevant.
466
0
  if (animationChanged && aOld.IsRelevant()) {
467
0
    nsNodeUtils::AnimationChanged(&aOld);
468
0
  }
469
0
}
470
471
// Returns a new animation set up with given StyleAnimation.
472
// Or returns an existing animation matching StyleAnimation's name updated
473
// with the new StyleAnimation.
474
static already_AddRefed<CSSAnimation>
475
BuildAnimation(nsPresContext* aPresContext,
476
               const NonOwningAnimationTarget& aTarget,
477
               const nsStyleDisplay& aStyleDisplay,
478
               uint32_t animIdx,
479
               ServoCSSAnimationBuilder& aBuilder,
480
               nsAnimationManager::CSSAnimationCollection* aCollection)
481
0
{
482
0
  MOZ_ASSERT(aPresContext);
483
0
484
0
  nsAtom* animationName = aStyleDisplay.GetAnimationName(animIdx);
485
0
  nsTArray<Keyframe> keyframes;
486
0
  if (!aBuilder.BuildKeyframes(*aTarget.mElement,
487
0
                               aPresContext,
488
0
                               animationName,
489
0
                               aStyleDisplay.GetAnimationTimingFunction(animIdx),
490
0
                               keyframes)) {
491
0
    return nullptr;
492
0
  }
493
0
494
0
  TimingParams timing =
495
0
    TimingParamsFromCSSParams(aStyleDisplay.GetAnimationDuration(animIdx),
496
0
                              aStyleDisplay.GetAnimationDelay(animIdx),
497
0
                              aStyleDisplay.GetAnimationIterationCount(animIdx),
498
0
                              aStyleDisplay.GetAnimationDirection(animIdx),
499
0
                              aStyleDisplay.GetAnimationFillMode(animIdx));
500
0
501
0
  bool isStylePaused =
502
0
    aStyleDisplay.GetAnimationPlayState(animIdx) ==
503
0
      NS_STYLE_ANIMATION_PLAY_STATE_PAUSED;
504
0
505
0
  // Find the matching animation with animation name in the old list
506
0
  // of animations and remove the matched animation from the list.
507
0
  RefPtr<CSSAnimation> oldAnim =
508
0
    PopExistingAnimation(animationName, aCollection);
509
0
510
0
  if (oldAnim) {
511
0
    // Copy over the start times and (if still paused) pause starts
512
0
    // for each animation (matching on name only) that was also in the
513
0
    // old list of animations.
514
0
    // This means that we honor dynamic changes, which isn't what the
515
0
    // spec says to do, but WebKit seems to honor at least some of
516
0
    // them.  See
517
0
    // http://lists.w3.org/Archives/Public/www-style/2011Apr/0079.html
518
0
    // In order to honor what the spec said, we'd copy more data over.
519
0
    UpdateOldAnimationPropertiesWithNew(*oldAnim,
520
0
                                        timing,
521
0
                                        std::move(keyframes),
522
0
                                        isStylePaused,
523
0
                                        aBuilder);
524
0
    return oldAnim.forget();
525
0
  }
526
0
527
0
  // mTarget is non-null here, so we emplace it directly.
528
0
  Maybe<OwningAnimationTarget> target;
529
0
  target.emplace(aTarget.mElement, aTarget.mPseudoType);
530
0
  KeyframeEffectParams effectOptions;
531
0
  RefPtr<KeyframeEffect> effect =
532
0
    new KeyframeEffect(aPresContext->Document(), target, timing, effectOptions);
533
0
534
0
  aBuilder.SetKeyframes(*effect, std::move(keyframes));
535
0
536
0
  RefPtr<CSSAnimation> animation =
537
0
    new CSSAnimation(aPresContext->Document()->GetScopeObject(), animationName);
538
0
  animation->SetOwningElement(
539
0
    OwningElementRef(*aTarget.mElement, aTarget.mPseudoType));
540
0
541
0
  animation->SetTimelineNoUpdate(aTarget.mElement->OwnerDoc()->Timeline());
542
0
  animation->SetEffectNoUpdate(effect);
543
0
544
0
  if (isStylePaused) {
545
0
    animation->PauseFromStyle();
546
0
  } else {
547
0
    animation->PlayFromStyle();
548
0
  }
549
0
550
0
  aBuilder.NotifyNewOrRemovedAnimation(*animation);
551
0
552
0
  return animation.forget();
553
0
}
554
555
556
static nsAnimationManager::OwningCSSAnimationPtrArray
557
BuildAnimations(nsPresContext* aPresContext,
558
                const NonOwningAnimationTarget& aTarget,
559
                const nsStyleDisplay& aStyleDisplay,
560
                ServoCSSAnimationBuilder& aBuilder,
561
                nsAnimationManager::CSSAnimationCollection* aCollection,
562
                nsTHashtable<nsRefPtrHashKey<nsAtom>>& aReferencedAnimations)
563
0
{
564
0
  nsAnimationManager::OwningCSSAnimationPtrArray result;
565
0
566
0
  for (size_t animIdx = aStyleDisplay.mAnimationNameCount; animIdx-- != 0;) {
567
0
    nsAtom* name = aStyleDisplay.GetAnimationName(animIdx);
568
0
    // CSS Animations whose animation-name does not match a @keyframes rule do
569
0
    // not generate animation events. This includes when the animation-name is
570
0
    // "none" which is represented by an empty name in the StyleAnimation.
571
0
    // Since such animations neither affect style nor dispatch events, we do
572
0
    // not generate a corresponding CSSAnimation for them.
573
0
    if (name == nsGkAtoms::_empty) {
574
0
      continue;
575
0
    }
576
0
577
0
    aReferencedAnimations.PutEntry(name);
578
0
    RefPtr<CSSAnimation> dest = BuildAnimation(aPresContext,
579
0
                                               aTarget,
580
0
                                               aStyleDisplay,
581
0
                                               animIdx,
582
0
                                               aBuilder,
583
0
                                               aCollection);
584
0
    if (!dest) {
585
0
      continue;
586
0
    }
587
0
588
0
    dest->SetAnimationIndex(static_cast<uint64_t>(animIdx));
589
0
    result.AppendElement(dest);
590
0
  }
591
0
  return result;
592
0
}
593
594
595
void
596
nsAnimationManager::UpdateAnimations(
597
  dom::Element* aElement,
598
  CSSPseudoElementType aPseudoType,
599
  const ComputedStyle* aComputedStyle)
600
0
{
601
0
  MOZ_ASSERT(mPresContext->IsDynamic(),
602
0
             "Should not update animations for print or print preview");
603
0
  MOZ_ASSERT(aElement->IsInComposedDoc(),
604
0
             "Should not update animations that are not attached to the "
605
0
             "document tree");
606
0
607
0
  const nsStyleDisplay* disp = aComputedStyle
608
0
    ? aComputedStyle->ComputedData()->GetStyleDisplay()
609
0
    : nullptr;
610
0
611
0
  if (!disp || disp->mDisplay == StyleDisplay::None) {
612
0
    // If we are in a display:none subtree we will have no computed values.
613
0
    // However, if we are on the root of display:none subtree, the computed
614
0
    // values might not have been cleared yet.
615
0
    // In either case, since CSS animations should not run in display:none
616
0
    // subtrees we should stop (actually, destroy) any animations on this
617
0
    // element here.
618
0
    StopAnimationsForElement(aElement, aPseudoType);
619
0
    return;
620
0
  }
621
0
622
0
  NonOwningAnimationTarget target(aElement, aPseudoType);
623
0
  ServoCSSAnimationBuilder builder(aComputedStyle);
624
0
625
0
  DoUpdateAnimations(target, *disp, builder);
626
0
}
627
628
void
629
nsAnimationManager::DoUpdateAnimations(
630
  const NonOwningAnimationTarget& aTarget,
631
  const nsStyleDisplay& aStyleDisplay,
632
  ServoCSSAnimationBuilder& aBuilder)
633
0
{
634
0
  // Everything that causes our animation data to change triggers a
635
0
  // style change, which in turn triggers a non-animation restyle.
636
0
  // Likewise, when we initially construct frames, we're not in a
637
0
  // style change, but also not in an animation restyle.
638
0
639
0
  CSSAnimationCollection* collection =
640
0
    CSSAnimationCollection::GetAnimationCollection(aTarget.mElement,
641
0
                                                   aTarget.mPseudoType);
642
0
  if (!collection &&
643
0
      aStyleDisplay.mAnimationNameCount == 1 &&
644
0
      aStyleDisplay.mAnimations[0].GetName() == nsGkAtoms::_empty) {
645
0
    return;
646
0
  }
647
0
648
0
  nsAutoAnimationMutationBatch mb(aTarget.mElement->OwnerDoc());
649
0
650
0
  // Build the updated animations list, extracting matching animations from
651
0
  // the existing collection as we go.
652
0
  OwningCSSAnimationPtrArray newAnimations =
653
0
    BuildAnimations(mPresContext,
654
0
                    aTarget,
655
0
                    aStyleDisplay,
656
0
                    aBuilder,
657
0
                    collection,
658
0
                    mMaybeReferencedAnimations);
659
0
660
0
  if (newAnimations.IsEmpty()) {
661
0
    if (collection) {
662
0
      collection->Destroy();
663
0
    }
664
0
    return;
665
0
  }
666
0
667
0
  if (!collection) {
668
0
    bool createdCollection = false;
669
0
    collection =
670
0
      CSSAnimationCollection::GetOrCreateAnimationCollection(
671
0
        aTarget.mElement, aTarget.mPseudoType, &createdCollection);
672
0
    if (!collection) {
673
0
      MOZ_ASSERT(!createdCollection, "outparam should agree with return value");
674
0
      NS_WARNING("allocating collection failed");
675
0
      return;
676
0
    }
677
0
678
0
    if (createdCollection) {
679
0
      AddElementCollection(collection);
680
0
    }
681
0
  }
682
0
  collection->mAnimations.SwapElements(newAnimations);
683
0
684
0
  // Cancel removed animations
685
0
  for (size_t newAnimIdx = newAnimations.Length(); newAnimIdx-- != 0; ) {
686
0
    aBuilder.NotifyNewOrRemovedAnimation(*newAnimations[newAnimIdx]);
687
0
    newAnimations[newAnimIdx]->CancelFromStyle();
688
0
  }
689
0
}