Coverage Report

Created: 2018-09-25 14:53

/src/mozilla-central/dom/animation/EffectCompositor.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 "EffectCompositor.h"
8
9
#include <bitset>
10
#include <initializer_list>
11
12
#include "mozilla/dom/Animation.h"
13
#include "mozilla/dom/Element.h"
14
#include "mozilla/dom/KeyframeEffect.h"
15
#include "mozilla/AnimationComparator.h"
16
#include "mozilla/AnimationPerformanceWarning.h"
17
#include "mozilla/AnimationTarget.h"
18
#include "mozilla/AnimationUtils.h"
19
#include "mozilla/AutoRestore.h"
20
#include "mozilla/ComputedStyleInlines.h"
21
#include "mozilla/EffectSet.h"
22
#include "mozilla/LayerAnimationInfo.h"
23
#include "mozilla/RestyleManager.h"
24
#include "mozilla/ServoBindings.h" // Servo_GetProperties_Overriding_Animation
25
#include "mozilla/ServoStyleSet.h"
26
#include "mozilla/StyleAnimationValue.h"
27
#include "mozilla/TypeTraits.h" // For std::forward<>
28
#include "nsContentUtils.h"
29
#include "nsCSSPseudoElements.h"
30
#include "nsCSSPropertyIDSet.h"
31
#include "nsCSSProps.h"
32
#include "nsAtom.h"
33
#include "nsIPresShell.h"
34
#include "nsIPresShellInlines.h"
35
#include "nsLayoutUtils.h"
36
#include "nsTArray.h"
37
#include "PendingAnimationTracker.h"
38
39
using mozilla::dom::Animation;
40
using mozilla::dom::Element;
41
using mozilla::dom::KeyframeEffect;
42
43
namespace mozilla {
44
45
NS_IMPL_CYCLE_COLLECTION_CLASS(EffectCompositor)
46
47
0
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(EffectCompositor)
48
0
  for (auto& elementSet : tmp->mElementsToRestyle) {
49
0
    elementSet.Clear();
50
0
  }
51
0
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
52
53
0
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(EffectCompositor)
54
0
  for (auto& elementSet : tmp->mElementsToRestyle) {
55
0
    for (auto iter = elementSet.Iter(); !iter.Done(); iter.Next()) {
56
0
      CycleCollectionNoteChild(cb, iter.Key().mElement,
57
0
                               "EffectCompositor::mElementsToRestyle[]",
58
0
                               cb.Flags());
59
0
    }
60
0
  }
61
0
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
62
63
NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(EffectCompositor, AddRef)
64
NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(EffectCompositor, Release)
65
66
namespace {
67
enum class MatchForCompositor {
68
  // This animation matches and should run on the compositor if possible.
69
  Yes,
70
  // This (not currently playing) animation matches and can be run on the
71
  // compositor if there are other animations for this property that return
72
  // 'Yes'.
73
  IfNeeded,
74
  // This animation does not match or can't be run on the compositor.
75
  No,
76
  // This animation does not match or can't be run on the compositor and,
77
  // furthermore, its presence means we should not run any animations for this
78
  // property on the compositor.
79
  NoAndBlockThisProperty
80
};
81
}
82
83
static MatchForCompositor
84
IsMatchForCompositor(const KeyframeEffect& aEffect,
85
                     nsCSSPropertyID aProperty,
86
                     const nsIFrame* aFrame)
87
0
{
88
0
  const Animation* animation = aEffect.GetAnimation();
89
0
  MOZ_ASSERT(animation);
90
0
91
0
  if (!animation->IsRelevant()) {
92
0
    return MatchForCompositor::No;
93
0
  }
94
0
95
0
  AnimationPerformanceWarning::Type warningType;
96
0
  if (animation->ShouldBeSynchronizedWithMainThread(aProperty, aFrame,
97
0
                                                    warningType)) {
98
0
    EffectCompositor::SetPerformanceWarning(
99
0
      aFrame, aProperty,
100
0
      AnimationPerformanceWarning(warningType));
101
0
    // For a given |aFrame|, we don't want some animations of |aProperty| to
102
0
    // run on the compositor and others to run on the main thread, so if any
103
0
    // need to be synchronized with the main thread, run them all there.
104
0
    return MatchForCompositor::NoAndBlockThisProperty;
105
0
  }
106
0
107
0
  if (!aEffect.HasEffectiveAnimationOfProperty(aProperty)) {
108
0
    return MatchForCompositor::No;
109
0
  }
110
0
111
0
  // If we know that the animation is not visible, we don't need to send the
112
0
  // animation to the compositor.
113
0
  if (!aFrame->IsVisibleOrMayHaveVisibleDescendants() ||
114
0
      aFrame->IsScrolledOutOfView()) {
115
0
    return MatchForCompositor::NoAndBlockThisProperty;
116
0
  }
117
0
118
0
  return animation->IsPlaying()
119
0
         ? MatchForCompositor::Yes
120
0
         : MatchForCompositor::IfNeeded;
121
0
}
122
123
// Helper function to factor out the common logic from
124
// GetAnimationsForCompositor and HasAnimationsForCompositor.
125
//
126
// Takes an optional array to fill with eligible animations.
127
//
128
// Returns true if there are eligible animations, false otherwise.
129
bool
130
FindAnimationsForCompositor(const nsIFrame* aFrame,
131
                            nsCSSPropertyID aProperty,
132
                            nsTArray<RefPtr<dom::Animation>>* aMatches /*out*/)
133
0
{
134
0
  MOZ_ASSERT(!aMatches || aMatches->IsEmpty(),
135
0
             "Matches array, if provided, should be empty");
136
0
137
0
  EffectSet* effects = EffectSet::GetEffectSet(aFrame);
138
0
  if (!effects || effects->IsEmpty()) {
139
0
    return false;
140
0
  }
141
0
142
0
  // First check for newly-started transform animations that should be
143
0
  // synchronized with geometric animations. We need to do this before any
144
0
  // other early returns (the one above is ok) since we can only check this
145
0
  // state when the animation is newly-started.
146
0
  if (aProperty == eCSSProperty_transform) {
147
0
    PendingAnimationTracker* tracker =
148
0
      aFrame->PresContext()->Document()->GetPendingAnimationTracker();
149
0
    if (tracker) {
150
0
      tracker->MarkAnimationsThatMightNeedSynchronization();
151
0
    }
152
0
  }
153
0
154
0
  // If the property will be added to the animations level of the cascade but
155
0
  // there is an !important rule for that property in the cascade then the
156
0
  // animation will not be applied since the !important rule overrides it.
157
0
  if (effects->PropertiesWithImportantRules().HasProperty(aProperty) &&
158
0
      effects->PropertiesForAnimationsLevel().HasProperty(aProperty)) {
159
0
    return false;
160
0
  }
161
0
162
0
  if (aFrame->RefusedAsyncAnimation()) {
163
0
    return false;
164
0
  }
165
0
166
0
  // The animation cascade will almost always be up-to-date by this point
167
0
  // but there are some cases such as when we are restoring the refresh driver
168
0
  // from test control after seeking where it might not be the case.
169
0
  //
170
0
  // Those cases are probably not important but just to be safe, let's make
171
0
  // sure the cascade is up to date since if it *is* up to date, this is
172
0
  // basically a no-op.
173
0
  Maybe<NonOwningAnimationTarget> pseudoElement =
174
0
    EffectCompositor::GetAnimationElementAndPseudoForFrame(aFrame);
175
0
  MOZ_ASSERT(pseudoElement,
176
0
             "We have a valid element for the frame, if we don't we should "
177
0
             "have bailed out at above the call to EffectSet::GetEffectSet");
178
0
  EffectCompositor::MaybeUpdateCascadeResults(pseudoElement->mElement,
179
0
                                              pseudoElement->mPseudoType);
180
0
181
0
  if (!nsLayoutUtils::AreAsyncAnimationsEnabled()) {
182
0
    if (nsLayoutUtils::IsAnimationLoggingEnabled()) {
183
0
      nsCString message;
184
0
      message.AppendLiteral("Performance warning: Async animations are "
185
0
                            "disabled");
186
0
      AnimationUtils::LogAsyncAnimationFailure(message);
187
0
    }
188
0
    return false;
189
0
  }
190
0
191
0
  // Disable async animations if we have a rendering observer that
192
0
  // depends on our content (svg masking, -moz-element etc) so that
193
0
  // it gets updated correctly.
194
0
  nsIContent* content = aFrame->GetContent();
195
0
  while (content) {
196
0
    if (content->HasRenderingObservers()) {
197
0
      EffectCompositor::SetPerformanceWarning(
198
0
        aFrame, aProperty,
199
0
        AnimationPerformanceWarning(
200
0
          AnimationPerformanceWarning::Type::HasRenderingObserver));
201
0
      return false;
202
0
    }
203
0
    content = content->GetParent();
204
0
  }
205
0
206
0
  bool foundRunningAnimations = false;
207
0
  for (KeyframeEffect* effect : *effects) {
208
0
    MatchForCompositor matchResult =
209
0
      IsMatchForCompositor(*effect, aProperty, aFrame);
210
0
211
0
    if (matchResult == MatchForCompositor::NoAndBlockThisProperty) {
212
0
      // For a given |aFrame|, we don't want some animations of |aProperty| to
213
0
      // run on the compositor and others to run on the main thread, so if any
214
0
      // need to be synchronized with the main thread, run them all there.
215
0
      if (aMatches) {
216
0
        aMatches->Clear();
217
0
      }
218
0
      return false;
219
0
    }
220
0
221
0
    if (matchResult == MatchForCompositor::No) {
222
0
      continue;
223
0
    }
224
0
225
0
    if (aMatches) {
226
0
      aMatches->AppendElement(effect->GetAnimation());
227
0
    }
228
0
229
0
    if (matchResult == MatchForCompositor::Yes) {
230
0
      foundRunningAnimations = true;
231
0
    }
232
0
  }
233
0
234
0
  // If all animations we added were not currently playing animations, don't
235
0
  // send them to the compositor.
236
0
  if (aMatches && !foundRunningAnimations) {
237
0
    aMatches->Clear();
238
0
  }
239
0
240
0
  MOZ_ASSERT(!foundRunningAnimations || !aMatches || !aMatches->IsEmpty(),
241
0
             "If return value is true, matches array should be non-empty");
242
0
243
0
  if (aMatches && foundRunningAnimations) {
244
0
    aMatches->Sort(AnimationPtrComparator<RefPtr<dom::Animation>>());
245
0
  }
246
0
247
0
  return foundRunningAnimations;
248
0
}
249
250
void
251
EffectCompositor::RequestRestyle(dom::Element* aElement,
252
                                 CSSPseudoElementType aPseudoType,
253
                                 RestyleType aRestyleType,
254
                                 CascadeLevel aCascadeLevel)
255
0
{
256
0
  if (!mPresContext) {
257
0
    // Pres context will be null after the effect compositor is disconnected.
258
0
    return;
259
0
  }
260
0
261
0
  // Ignore animations on orphaned elements and elements in documents without
262
0
  // a pres shell (e.g. XMLHttpRequest responseXML documents).
263
0
  if (!nsContentUtils::GetPresShellForContent(aElement)) {
264
0
    return;
265
0
  }
266
0
267
0
  auto& elementsToRestyle = mElementsToRestyle[aCascadeLevel];
268
0
  PseudoElementHashEntry::KeyType key = { aElement, aPseudoType };
269
0
270
0
  if (aRestyleType == RestyleType::Throttled) {
271
0
    elementsToRestyle.LookupForAdd(key).OrInsert([]() { return false; });
272
0
    mPresContext->PresShell()->SetNeedThrottledAnimationFlush();
273
0
  } else {
274
0
    bool skipRestyle;
275
0
    // Update hashtable first in case PostRestyleForAnimation mutates it.
276
0
    // (It shouldn't, but just to be sure.)
277
0
    if (auto p = elementsToRestyle.LookupForAdd(key)) {
278
0
      skipRestyle = p.Data();
279
0
      p.Data() = true;
280
0
    } else {
281
0
      skipRestyle = false;
282
0
      p.OrInsert([]() { return true; });
283
0
    }
284
0
285
0
    if (!skipRestyle) {
286
0
      PostRestyleForAnimation(aElement, aPseudoType, aCascadeLevel);
287
0
    }
288
0
  }
289
0
290
0
  if (aRestyleType == RestyleType::Layer) {
291
0
    mPresContext->RestyleManager()->IncrementAnimationGeneration();
292
0
    EffectSet* effectSet =
293
0
      EffectSet::GetEffectSet(aElement, aPseudoType);
294
0
    if (effectSet) {
295
0
      effectSet->UpdateAnimationGeneration(mPresContext);
296
0
    }
297
0
  }
298
0
}
299
300
void
301
EffectCompositor::PostRestyleForAnimation(dom::Element* aElement,
302
                                          CSSPseudoElementType aPseudoType,
303
                                          CascadeLevel aCascadeLevel)
304
0
{
305
0
  if (!mPresContext) {
306
0
    return;
307
0
  }
308
0
309
0
  dom::Element* element = GetElementToRestyle(aElement, aPseudoType);
310
0
  if (!element) {
311
0
    return;
312
0
  }
313
0
314
0
  nsRestyleHint hint = aCascadeLevel == CascadeLevel::Transitions ?
315
0
                                        eRestyle_CSSTransitions :
316
0
                                        eRestyle_CSSAnimations;
317
0
318
0
  MOZ_ASSERT(NS_IsMainThread(),
319
0
             "Restyle request during restyling should be requested only on "
320
0
             "the main-thread. e.g. after the parallel traversal");
321
0
  if (ServoStyleSet::IsInServoTraversal() || mIsInPreTraverse) {
322
0
    MOZ_ASSERT(hint == eRestyle_CSSAnimations ||
323
0
               hint == eRestyle_CSSTransitions);
324
0
325
0
    // We can't call Servo_NoteExplicitHints here since AtomicRefCell does not
326
0
    // allow us mutate ElementData of the |aElement| in SequentialTask.
327
0
    // Instead we call Servo_NoteExplicitHints for the element in PreTraverse()
328
0
    // which will be called right before the second traversal that we do for
329
0
    // updating CSS animations.
330
0
    // In that case PreTraverse() will return true so that we know to do the
331
0
    // second traversal so we don't need to post any restyle requests to the
332
0
    // PresShell.
333
0
    return;
334
0
  }
335
0
336
0
  MOZ_ASSERT(!mPresContext->RestyleManager()->IsInStyleRefresh());
337
0
338
0
  mPresContext->PresShell()->RestyleForAnimation(element, hint);
339
0
}
340
341
void
342
EffectCompositor::PostRestyleForThrottledAnimations()
343
0
{
344
0
  for (size_t i = 0; i < kCascadeLevelCount; i++) {
345
0
    CascadeLevel cascadeLevel = CascadeLevel(i);
346
0
    auto& elementSet = mElementsToRestyle[cascadeLevel];
347
0
348
0
    for (auto iter = elementSet.Iter(); !iter.Done(); iter.Next()) {
349
0
      bool& postedRestyle = iter.Data();
350
0
      if (postedRestyle) {
351
0
        continue;
352
0
      }
353
0
354
0
      PostRestyleForAnimation(iter.Key().mElement,
355
0
                              iter.Key().mPseudoType,
356
0
                              cascadeLevel);
357
0
      postedRestyle = true;
358
0
    }
359
0
  }
360
0
}
361
362
void
363
EffectCompositor::ClearRestyleRequestsFor(Element* aElement)
364
0
{
365
0
  MOZ_ASSERT(aElement);
366
0
367
0
  auto& elementsToRestyle = mElementsToRestyle[CascadeLevel::Animations];
368
0
369
0
  CSSPseudoElementType pseudoType = aElement->GetPseudoElementType();
370
0
  if (pseudoType == CSSPseudoElementType::NotPseudo) {
371
0
    PseudoElementHashEntry::KeyType notPseudoKey =
372
0
      { aElement, CSSPseudoElementType::NotPseudo };
373
0
    PseudoElementHashEntry::KeyType beforePseudoKey =
374
0
      { aElement, CSSPseudoElementType::before };
375
0
    PseudoElementHashEntry::KeyType afterPseudoKey =
376
0
      { aElement, CSSPseudoElementType::after };
377
0
378
0
    elementsToRestyle.Remove(notPseudoKey);
379
0
    elementsToRestyle.Remove(beforePseudoKey);
380
0
    elementsToRestyle.Remove(afterPseudoKey);
381
0
  } else if (pseudoType == CSSPseudoElementType::before ||
382
0
             pseudoType == CSSPseudoElementType::after) {
383
0
    Element* parentElement = aElement->GetParentElement();
384
0
    MOZ_ASSERT(parentElement);
385
0
    PseudoElementHashEntry::KeyType key = { parentElement, pseudoType };
386
0
    elementsToRestyle.Remove(key);
387
0
  }
388
0
}
389
390
void
391
EffectCompositor::UpdateEffectProperties(const ComputedStyle* aStyle,
392
                                         Element* aElement,
393
                                         CSSPseudoElementType aPseudoType)
394
0
{
395
0
  EffectSet* effectSet = EffectSet::GetEffectSet(aElement, aPseudoType);
396
0
  if (!effectSet) {
397
0
    return;
398
0
  }
399
0
400
0
  // Style context (Gecko) or computed values (Stylo) change might cause CSS
401
0
  // cascade level, e.g removing !important, so we should update the cascading
402
0
  // result.
403
0
  effectSet->MarkCascadeNeedsUpdate();
404
0
405
0
  for (KeyframeEffect* effect : *effectSet) {
406
0
    effect->UpdateProperties(aStyle);
407
0
  }
408
0
}
409
410
411
namespace {
412
  class EffectCompositeOrderComparator {
413
  public:
414
    bool Equals(const KeyframeEffect* a, const KeyframeEffect* b) const
415
0
    {
416
0
      return a == b;
417
0
    }
418
419
    bool LessThan(const KeyframeEffect* a, const KeyframeEffect* b) const
420
0
    {
421
0
      MOZ_ASSERT(a->GetAnimation() && b->GetAnimation());
422
0
      MOZ_ASSERT(
423
0
        Equals(a, b) ||
424
0
        a->GetAnimation()->HasLowerCompositeOrderThan(*b->GetAnimation()) !=
425
0
          b->GetAnimation()->HasLowerCompositeOrderThan(*a->GetAnimation()));
426
0
      return a->GetAnimation()->HasLowerCompositeOrderThan(*b->GetAnimation());
427
0
    }
428
  };
429
}
430
431
bool
432
EffectCompositor::GetServoAnimationRule(
433
  const dom::Element* aElement,
434
  CSSPseudoElementType aPseudoType,
435
  CascadeLevel aCascadeLevel,
436
  RawServoAnimationValueMapBorrowedMut aAnimationValues)
437
0
{
438
0
  MOZ_ASSERT(aAnimationValues);
439
0
  MOZ_ASSERT(mPresContext && mPresContext->IsDynamic(),
440
0
             "Should not be in print preview");
441
0
  // Gecko_GetAnimationRule should have already checked this
442
0
  MOZ_ASSERT(nsContentUtils::GetPresShellForContent(aElement),
443
0
             "Should not be trying to run animations on elements in documents"
444
0
             " without a pres shell (e.g. XMLHttpRequest documents)");
445
0
446
0
  EffectSet* effectSet = EffectSet::GetEffectSet(aElement, aPseudoType);
447
0
  if (!effectSet) {
448
0
    return false;
449
0
  }
450
0
451
0
  // Get a list of effects sorted by composite order.
452
0
  nsTArray<KeyframeEffect*> sortedEffectList(effectSet->Count());
453
0
  for (KeyframeEffect* effect : *effectSet) {
454
0
    sortedEffectList.AppendElement(effect);
455
0
  }
456
0
  sortedEffectList.Sort(EffectCompositeOrderComparator());
457
0
458
0
  // If multiple animations affect the same property, animations with higher
459
0
  // composite order (priority) override or add or animations with lower
460
0
  // priority.
461
0
  const nsCSSPropertyIDSet propertiesToSkip =
462
0
    aCascadeLevel == CascadeLevel::Animations
463
0
      ? effectSet->PropertiesForAnimationsLevel().Inverse()
464
0
      : effectSet->PropertiesForAnimationsLevel();
465
0
  for (KeyframeEffect* effect : sortedEffectList) {
466
0
    effect->GetAnimation()->ComposeStyle(*aAnimationValues, propertiesToSkip);
467
0
  }
468
0
469
0
  MOZ_ASSERT(effectSet == EffectSet::GetEffectSet(aElement, aPseudoType),
470
0
             "EffectSet should not change while composing style");
471
0
472
0
  return true;
473
0
}
474
475
/* static */ dom::Element*
476
EffectCompositor::GetElementToRestyle(dom::Element* aElement,
477
                                      CSSPseudoElementType aPseudoType)
478
0
{
479
0
  if (aPseudoType == CSSPseudoElementType::NotPseudo) {
480
0
    return aElement;
481
0
  }
482
0
483
0
  if (aPseudoType == CSSPseudoElementType::before) {
484
0
    return nsLayoutUtils::GetBeforePseudo(aElement);
485
0
  }
486
0
487
0
  if (aPseudoType == CSSPseudoElementType::after) {
488
0
    return nsLayoutUtils::GetAfterPseudo(aElement);
489
0
  }
490
0
491
0
  MOZ_ASSERT_UNREACHABLE("Should not try to get the element to restyle for "
492
0
                         "a pseudo other that :before or :after");
493
0
  return nullptr;
494
0
}
495
496
bool
497
EffectCompositor::HasPendingStyleUpdates() const
498
0
{
499
0
  for (auto& elementSet : mElementsToRestyle) {
500
0
    if (elementSet.Count()) {
501
0
      return true;
502
0
    }
503
0
  }
504
0
505
0
  return false;
506
0
}
507
508
509
/* static */ bool
510
EffectCompositor::HasAnimationsForCompositor(const nsIFrame* aFrame,
511
                                             nsCSSPropertyID aProperty)
512
0
{
513
0
  return FindAnimationsForCompositor(aFrame, aProperty, nullptr);
514
0
}
515
516
/* static */ nsTArray<RefPtr<dom::Animation>>
517
EffectCompositor::GetAnimationsForCompositor(const nsIFrame* aFrame,
518
                                             nsCSSPropertyID aProperty)
519
0
{
520
0
  nsTArray<RefPtr<dom::Animation>> result;
521
0
522
#ifdef DEBUG
523
  bool foundSome =
524
#endif
525
    FindAnimationsForCompositor(aFrame, aProperty, &result);
526
0
  MOZ_ASSERT(!foundSome || !result.IsEmpty(),
527
0
             "If return value is true, matches array should be non-empty");
528
0
529
0
  return result;
530
0
}
531
532
/* static */ void
533
EffectCompositor::ClearIsRunningOnCompositor(const nsIFrame *aFrame,
534
                                             nsCSSPropertyID aProperty)
535
0
{
536
0
  EffectSet* effects = EffectSet::GetEffectSet(aFrame);
537
0
  if (!effects) {
538
0
    return;
539
0
  }
540
0
541
0
  for (KeyframeEffect* effect : *effects) {
542
0
    effect->SetIsRunningOnCompositor(aProperty, false);
543
0
  }
544
0
}
545
546
/* static */ void
547
EffectCompositor::MaybeUpdateCascadeResults(Element* aElement,
548
                                            CSSPseudoElementType aPseudoType)
549
0
{
550
0
  EffectSet* effects = EffectSet::GetEffectSet(aElement, aPseudoType);
551
0
  if (!effects || !effects->CascadeNeedsUpdate()) {
552
0
    return;
553
0
  }
554
0
555
0
  UpdateCascadeResults(*effects, aElement, aPseudoType);
556
0
557
0
  MOZ_ASSERT(!effects->CascadeNeedsUpdate(), "Failed to update cascade state");
558
0
}
559
560
/* static */ Maybe<NonOwningAnimationTarget>
561
EffectCompositor::GetAnimationElementAndPseudoForFrame(const nsIFrame* aFrame)
562
0
{
563
0
  // Always return the same object to benefit from return-value optimization.
564
0
  Maybe<NonOwningAnimationTarget> result;
565
0
566
0
  CSSPseudoElementType pseudoType =
567
0
    aFrame->Style()->GetPseudoType();
568
0
569
0
  if (pseudoType != CSSPseudoElementType::NotPseudo &&
570
0
      pseudoType != CSSPseudoElementType::before &&
571
0
      pseudoType != CSSPseudoElementType::after) {
572
0
    return result;
573
0
  }
574
0
575
0
  nsIContent* content = aFrame->GetContent();
576
0
  if (!content) {
577
0
    return result;
578
0
  }
579
0
580
0
  if (pseudoType == CSSPseudoElementType::before ||
581
0
      pseudoType == CSSPseudoElementType::after) {
582
0
    content = content->GetParent();
583
0
    if (!content) {
584
0
      return result;
585
0
    }
586
0
  }
587
0
588
0
  if (!content->IsElement()) {
589
0
    return result;
590
0
  }
591
0
592
0
  result.emplace(content->AsElement(), pseudoType);
593
0
594
0
  return result;
595
0
}
596
597
598
/* static */ nsCSSPropertyIDSet
599
EffectCompositor::GetOverriddenProperties(EffectSet& aEffectSet,
600
                                          Element* aElement,
601
                                          CSSPseudoElementType aPseudoType)
602
0
{
603
0
  MOZ_ASSERT(aElement, "Should have an element to get style data from");
604
0
605
0
  nsCSSPropertyIDSet result;
606
0
607
0
  Element* elementToRestyle = GetElementToRestyle(aElement, aPseudoType);
608
0
  if (!elementToRestyle) {
609
0
    return result;
610
0
  }
611
0
612
0
  AutoTArray<nsCSSPropertyID, LayerAnimationInfo::kRecords> propertiesToTrack;
613
0
  {
614
0
    nsCSSPropertyIDSet propertiesToTrackAsSet;
615
0
    for (KeyframeEffect* effect : aEffectSet) {
616
0
      for (const AnimationProperty& property : effect->Properties()) {
617
0
        if (nsCSSProps::PropHasFlags(property.mProperty,
618
0
                                     CSSPropFlags::CanAnimateOnCompositor) &&
619
0
            !propertiesToTrackAsSet.HasProperty(property.mProperty)) {
620
0
          propertiesToTrackAsSet.AddProperty(property.mProperty);
621
0
          propertiesToTrack.AppendElement(property.mProperty);
622
0
        }
623
0
      }
624
0
      // Skip iterating over the rest of the effects if we've already
625
0
      // found all the compositor-animatable properties.
626
0
      if (propertiesToTrack.Length() == LayerAnimationInfo::kRecords) {
627
0
        break;
628
0
      }
629
0
    }
630
0
  }
631
0
632
0
  if (propertiesToTrack.IsEmpty()) {
633
0
    return result;
634
0
  }
635
0
636
0
  Servo_GetProperties_Overriding_Animation(elementToRestyle,
637
0
                                           &propertiesToTrack,
638
0
                                           &result);
639
0
  return result;
640
0
}
641
642
/* static */ void
643
EffectCompositor::UpdateCascadeResults(EffectSet& aEffectSet,
644
                                       Element* aElement,
645
                                       CSSPseudoElementType aPseudoType)
646
0
{
647
0
  MOZ_ASSERT(EffectSet::GetEffectSet(aElement, aPseudoType) == &aEffectSet,
648
0
             "Effect set should correspond to the specified (pseudo-)element");
649
0
  if (aEffectSet.IsEmpty()) {
650
0
    aEffectSet.MarkCascadeUpdated();
651
0
    return;
652
0
  }
653
0
654
0
  // Get a list of effects sorted by composite order.
655
0
  nsTArray<KeyframeEffect*> sortedEffectList(aEffectSet.Count());
656
0
  for (KeyframeEffect* effect : aEffectSet) {
657
0
    sortedEffectList.AppendElement(effect);
658
0
  }
659
0
  sortedEffectList.Sort(EffectCompositeOrderComparator());
660
0
661
0
  // Get properties that override the *animations* level of the cascade.
662
0
  //
663
0
  // We only do this for properties that we can animate on the compositor
664
0
  // since we will apply other properties on the main thread where the usual
665
0
  // cascade applies.
666
0
  nsCSSPropertyIDSet overriddenProperties =
667
0
    GetOverriddenProperties(aEffectSet, aElement, aPseudoType);
668
0
669
0
  // Returns a bitset the represents which properties from
670
0
  // LayerAnimationInfo::sRecords are present in |aPropertySet|.
671
0
  auto compositorPropertiesInSet =
672
0
    [](nsCSSPropertyIDSet& aPropertySet) ->
673
0
      std::bitset<LayerAnimationInfo::kRecords> {
674
0
        std::bitset<LayerAnimationInfo::kRecords> result;
675
0
        for (size_t i = 0; i < LayerAnimationInfo::kRecords; i++) {
676
0
          if (aPropertySet.HasProperty(
677
0
                LayerAnimationInfo::sRecords[i].mProperty)) {
678
0
            result.set(i);
679
0
          }
680
0
        }
681
0
      return result;
682
0
    };
683
0
684
0
  nsCSSPropertyIDSet& propertiesWithImportantRules =
685
0
    aEffectSet.PropertiesWithImportantRules();
686
0
  nsCSSPropertyIDSet& propertiesForAnimationsLevel =
687
0
    aEffectSet.PropertiesForAnimationsLevel();
688
0
689
0
  // Record which compositor-animatable properties were originally set so we can
690
0
  // compare for changes later.
691
0
  std::bitset<LayerAnimationInfo::kRecords>
692
0
    prevCompositorPropertiesWithImportantRules =
693
0
      compositorPropertiesInSet(propertiesWithImportantRules);
694
0
695
0
  nsCSSPropertyIDSet prevPropertiesForAnimationsLevel =
696
0
    propertiesForAnimationsLevel;
697
0
698
0
  propertiesWithImportantRules.Empty();
699
0
  propertiesForAnimationsLevel.Empty();
700
0
701
0
  nsCSSPropertyIDSet propertiesForTransitionsLevel;
702
0
703
0
  for (const KeyframeEffect* effect : sortedEffectList) {
704
0
    MOZ_ASSERT(effect->GetAnimation(),
705
0
               "Effects on a target element should have an Animation");
706
0
    CascadeLevel cascadeLevel = effect->GetAnimation()->CascadeLevel();
707
0
708
0
    for (const AnimationProperty& prop : effect->Properties()) {
709
0
      if (overriddenProperties.HasProperty(prop.mProperty)) {
710
0
        propertiesWithImportantRules.AddProperty(prop.mProperty);
711
0
      }
712
0
713
0
      switch (cascadeLevel) {
714
0
        case EffectCompositor::CascadeLevel::Animations:
715
0
          propertiesForAnimationsLevel.AddProperty(prop.mProperty);
716
0
          break;
717
0
        case EffectCompositor::CascadeLevel::Transitions:
718
0
          propertiesForTransitionsLevel.AddProperty(prop.mProperty);
719
0
          break;
720
0
      }
721
0
    }
722
0
  }
723
0
724
0
  aEffectSet.MarkCascadeUpdated();
725
0
726
0
  nsPresContext* presContext = nsContentUtils::GetContextForContent(aElement);
727
0
  if (!presContext) {
728
0
    return;
729
0
  }
730
0
731
0
  // If properties for compositor are newly overridden by !important rules, or
732
0
  // released from being overridden by !important rules, we need to update
733
0
  // layers for animations level because it's a trigger to send animations to
734
0
  // the compositor or pull animations back from the compositor.
735
0
  if (prevCompositorPropertiesWithImportantRules !=
736
0
        compositorPropertiesInSet(propertiesWithImportantRules)) {
737
0
    presContext->EffectCompositor()->
738
0
      RequestRestyle(aElement, aPseudoType,
739
0
                     EffectCompositor::RestyleType::Layer,
740
0
                     EffectCompositor::CascadeLevel::Animations);
741
0
  }
742
0
743
0
  // If we have transition properties and if the same propery for animations
744
0
  // level is newly added or removed, we need to update the transition level
745
0
  // rule since the it will be added/removed from the rule tree.
746
0
  nsCSSPropertyIDSet changedPropertiesForAnimationLevel =
747
0
    prevPropertiesForAnimationsLevel.Xor(propertiesForAnimationsLevel);
748
0
  nsCSSPropertyIDSet commonProperties =
749
0
    propertiesForTransitionsLevel.Intersect(
750
0
      changedPropertiesForAnimationLevel);
751
0
  if (!commonProperties.IsEmpty()) {
752
0
    EffectCompositor::RestyleType restyleType =
753
0
      compositorPropertiesInSet(changedPropertiesForAnimationLevel).none()
754
0
      ? EffectCompositor::RestyleType::Standard
755
0
      : EffectCompositor::RestyleType::Layer;
756
0
    presContext->EffectCompositor()->
757
0
      RequestRestyle(aElement, aPseudoType,
758
0
                     restyleType,
759
0
                     EffectCompositor::CascadeLevel::Transitions);
760
0
  }
761
0
}
762
763
/* static */ void
764
EffectCompositor::SetPerformanceWarning(
765
  const nsIFrame *aFrame,
766
  nsCSSPropertyID aProperty,
767
  const AnimationPerformanceWarning& aWarning)
768
0
{
769
0
  EffectSet* effects = EffectSet::GetEffectSet(aFrame);
770
0
  if (!effects) {
771
0
    return;
772
0
  }
773
0
774
0
  for (KeyframeEffect* effect : *effects) {
775
0
    effect->SetPerformanceWarning(aProperty, aWarning);
776
0
  }
777
0
}
778
779
bool
780
EffectCompositor::PreTraverse(ServoTraversalFlags aFlags)
781
0
{
782
0
  return PreTraverseInSubtree(aFlags, nullptr);
783
0
}
784
785
bool
786
EffectCompositor::PreTraverseInSubtree(ServoTraversalFlags aFlags,
787
                                       Element* aRoot)
788
0
{
789
0
  MOZ_ASSERT(NS_IsMainThread());
790
0
  MOZ_ASSERT(!aRoot || nsContentUtils::GetPresShellForContent(aRoot),
791
0
             "Traversal root, if provided, should be bound to a display "
792
0
             "document");
793
0
794
0
  // Convert the root element to the parent element if the root element is
795
0
  // pseudo since we check each element in mElementsToRestyle is in the subtree
796
0
  // of the root element later in this function, but for pseudo elements the
797
0
  // element in mElementsToRestyle is the parent of the pseudo.
798
0
  if (aRoot &&
799
0
      (aRoot->IsGeneratedContentContainerForBefore() ||
800
0
       aRoot->IsGeneratedContentContainerForAfter())) {
801
0
    aRoot = aRoot->GetParentElement();
802
0
  }
803
0
804
0
  AutoRestore<bool> guard(mIsInPreTraverse);
805
0
  mIsInPreTraverse = true;
806
0
807
0
  // We need to force flush all throttled animations if we also have
808
0
  // non-animation restyles (since we'll want the up-to-date animation style
809
0
  // when we go to process them so we can trigger transitions correctly), and
810
0
  // if we are currently flushing all throttled animation restyles.
811
0
  bool flushThrottledRestyles =
812
0
    (aRoot && aRoot->HasDirtyDescendantsForServo()) ||
813
0
    (aFlags & ServoTraversalFlags::FlushThrottledAnimations);
814
0
815
0
  using ElementsToRestyleIterType =
816
0
    nsDataHashtable<PseudoElementHashEntry, bool>::Iterator;
817
0
  auto getNeededRestyleTarget = [&](const ElementsToRestyleIterType& aIter)
818
0
                                -> NonOwningAnimationTarget {
819
0
    NonOwningAnimationTarget returnTarget;
820
0
821
0
    // If aIter.Data() is false, the element only requested a throttled
822
0
    // (skippable) restyle, so we can skip it if flushThrottledRestyles is not
823
0
    // true.
824
0
    if (!flushThrottledRestyles && !aIter.Data()) {
825
0
      return returnTarget;
826
0
    }
827
0
828
0
    const NonOwningAnimationTarget& target = aIter.Key();
829
0
830
0
    // Skip elements in documents without a pres shell. Normally we filter out
831
0
    // such elements in RequestRestyle but it can happen that, after adding
832
0
    // them to mElementsToRestyle, they are transferred to a different document.
833
0
    //
834
0
    // We will drop them from mElementsToRestyle at the end of the next full
835
0
    // document restyle (at the end of this function) but for consistency with
836
0
    // how we treat such elements in RequestRestyle, we just ignore them here.
837
0
    if (!nsContentUtils::GetPresShellForContent(target.mElement)) {
838
0
      return returnTarget;
839
0
    }
840
0
841
0
    // Ignore restyles that aren't in the flattened tree subtree rooted at
842
0
    // aRoot.
843
0
    if (aRoot && !nsContentUtils::ContentIsFlattenedTreeDescendantOfForStyle(
844
0
          target.mElement, aRoot)) {
845
0
      return returnTarget;
846
0
    }
847
0
848
0
    returnTarget = target;
849
0
    return returnTarget;
850
0
  };
851
0
852
0
  bool foundElementsNeedingRestyle = false;
853
0
854
0
  nsTArray<NonOwningAnimationTarget> elementsWithCascadeUpdates;
855
0
  for (size_t i = 0; i < kCascadeLevelCount; ++i) {
856
0
    CascadeLevel cascadeLevel = CascadeLevel(i);
857
0
    auto& elementSet = mElementsToRestyle[cascadeLevel];
858
0
    for (auto iter = elementSet.Iter(); !iter.Done(); iter.Next()) {
859
0
      const NonOwningAnimationTarget& target = getNeededRestyleTarget(iter);
860
0
      if (!target.mElement) {
861
0
        continue;
862
0
      }
863
0
864
0
      EffectSet* effects = EffectSet::GetEffectSet(target.mElement,
865
0
                                                   target.mPseudoType);
866
0
      if (!effects || !effects->CascadeNeedsUpdate()) {
867
0
        continue;
868
0
      }
869
0
870
0
      elementsWithCascadeUpdates.AppendElement(target);
871
0
    }
872
0
  }
873
0
874
0
  for (const NonOwningAnimationTarget& target: elementsWithCascadeUpdates) {
875
0
      MaybeUpdateCascadeResults(target.mElement, target.mPseudoType);
876
0
  }
877
0
  elementsWithCascadeUpdates.Clear();
878
0
879
0
  for (size_t i = 0; i < kCascadeLevelCount; ++i) {
880
0
    CascadeLevel cascadeLevel = CascadeLevel(i);
881
0
    auto& elementSet = mElementsToRestyle[cascadeLevel];
882
0
    for (auto iter = elementSet.Iter(); !iter.Done(); iter.Next()) {
883
0
      const NonOwningAnimationTarget& target = getNeededRestyleTarget(iter);
884
0
      if (!target.mElement) {
885
0
        continue;
886
0
      }
887
0
888
0
      // We need to post restyle hints even if the target is not in EffectSet to
889
0
      // ensure the final restyling for removed animations.
890
0
      // We can't call PostRestyleEvent directly here since we are still in the
891
0
      // middle of the servo traversal.
892
0
      mPresContext->RestyleManager()->
893
0
        PostRestyleEventForAnimations(target.mElement,
894
0
                                      target.mPseudoType,
895
0
                                      cascadeLevel == CascadeLevel::Transitions
896
0
                                        ? eRestyle_CSSTransitions
897
0
                                        : eRestyle_CSSAnimations);
898
0
899
0
      foundElementsNeedingRestyle = true;
900
0
901
0
      EffectSet* effects = EffectSet::GetEffectSet(target.mElement,
902
0
                                                   target.mPseudoType);
903
0
      if (!effects) {
904
0
        // Drop EffectSets that have been destroyed.
905
0
        iter.Remove();
906
0
        continue;
907
0
      }
908
0
909
0
      for (KeyframeEffect* effect : *effects) {
910
0
        effect->GetAnimation()->WillComposeStyle();
911
0
      }
912
0
913
0
      // Remove the element from the list of elements to restyle since we are
914
0
      // about to restyle it.
915
0
      iter.Remove();
916
0
    }
917
0
918
0
    // If this is a full document restyle, then unconditionally clear
919
0
    // elementSet in case there are any elements that didn't match above
920
0
    // because they were moved to a document without a pres shell after
921
0
    // posting an animation restyle.
922
0
    if (!aRoot && flushThrottledRestyles) {
923
0
      elementSet.Clear();
924
0
    }
925
0
  }
926
0
927
0
  return foundElementsNeedingRestyle;
928
0
}
929
930
bool
931
EffectCompositor::PreTraverse(dom::Element* aElement,
932
                              CSSPseudoElementType aPseudoType)
933
0
{
934
0
  MOZ_ASSERT(NS_IsMainThread());
935
0
936
0
  // If |aElement|'s document does not have a pres shell, e.g. it is document
937
0
  // without a browsing context such as we might get from an XMLHttpRequest, we
938
0
  // should not run animations on it.
939
0
  if (!nsContentUtils::GetPresShellForContent(aElement)) {
940
0
    return false;
941
0
  }
942
0
943
0
  bool found = false;
944
0
  if (aPseudoType != CSSPseudoElementType::NotPseudo &&
945
0
      aPseudoType != CSSPseudoElementType::before &&
946
0
      aPseudoType != CSSPseudoElementType::after) {
947
0
    return found;
948
0
  }
949
0
950
0
  AutoRestore<bool> guard(mIsInPreTraverse);
951
0
  mIsInPreTraverse = true;
952
0
953
0
  PseudoElementHashEntry::KeyType key = { aElement, aPseudoType };
954
0
955
0
  // We need to flush all throttled animation restyles too if we also have
956
0
  // non-animation restyles (since we'll want the up-to-date animation style
957
0
  // when we go to process them so we can trigger transitions correctly).
958
0
  Element* elementToRestyle = GetElementToRestyle(aElement, aPseudoType);
959
0
  bool flushThrottledRestyles = elementToRestyle &&
960
0
                                elementToRestyle->HasDirtyDescendantsForServo();
961
0
962
0
  for (size_t i = 0; i < kCascadeLevelCount; ++i) {
963
0
    CascadeLevel cascadeLevel = CascadeLevel(i);
964
0
    auto& elementSet = mElementsToRestyle[cascadeLevel];
965
0
966
0
    // Skip if we don't have a restyle, or if we only have a throttled
967
0
    // (skippable) restyle and we're not required to flush throttled restyles.
968
0
    bool hasUnthrottledRestyle = false;
969
0
    if (!elementSet.Get(key, &hasUnthrottledRestyle) ||
970
0
        (!flushThrottledRestyles && !hasUnthrottledRestyle)) {
971
0
      continue;
972
0
    }
973
0
974
0
    mPresContext->RestyleManager()->
975
0
      PostRestyleEventForAnimations(aElement,
976
0
                                    aPseudoType,
977
0
                                    cascadeLevel == CascadeLevel::Transitions
978
0
                                      ? eRestyle_CSSTransitions
979
0
                                      : eRestyle_CSSAnimations);
980
0
981
0
    EffectSet* effects = EffectSet::GetEffectSet(aElement, aPseudoType);
982
0
    if (effects) {
983
0
      MaybeUpdateCascadeResults(aElement, aPseudoType);
984
0
985
0
      for (KeyframeEffect* effect : *effects) {
986
0
        effect->GetAnimation()->WillComposeStyle();
987
0
      }
988
0
    }
989
0
990
0
    elementSet.Remove(key);
991
0
    found = true;
992
0
  }
993
0
  return found;
994
0
}
995
996
997
} // namespace mozilla