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