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/. */
7 #include "EffectCompositor.h"
10 #include <initializer_list>
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"
36 #include "nsLayoutUtils.h"
38 #include "PendingAnimationTracker.h"
40 using mozilla::dom::Animation
;
41 using mozilla::dom::Element
;
42 using mozilla::dom::KeyframeEffect
;
46 NS_IMPL_CYCLE_COLLECTION_CLASS(EffectCompositor
)
48 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(EffectCompositor
)
49 for (auto& elementSet
: tmp
->mElementsToRestyle
) {
52 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
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[]",
62 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
65 bool EffectCompositor::AllowCompositorAnimationsOnFrame(
66 const nsIFrame
* aFrame
,
67 AnimationPerformanceWarning::Type
& aWarning
/* out */) {
68 if (aFrame
->RefusedAsyncAnimation()) {
72 if (!nsLayoutUtils::AreAsyncAnimationsEnabled()) {
73 if (StaticPrefs::layers_offmainthreadcomposition_log_animations()) {
75 message
.AppendLiteral(
76 "Performance warning: Async animations are "
78 AnimationUtils::LogAsyncAnimationFailure(message
);
83 // Disable async animations if we have a rendering observer that
84 // depends on our content (svg masking, -moz-element etc) so that
85 // it gets updated correctly.
86 nsIContent
* content
= aFrame
->GetContent();
88 if (content
->HasRenderingObservers()) {
89 aWarning
= AnimationPerformanceWarning::Type::HasRenderingObserver
;
92 content
= content
->GetParent();
98 // Helper function to factor out the common logic from
99 // GetAnimationsForCompositor and HasAnimationsForCompositor.
101 // Takes an optional array to fill with eligible animations.
103 // Returns true if there are eligible animations, false otherwise.
104 bool FindAnimationsForCompositor(
105 const nsIFrame
* aFrame
, const nsCSSPropertyIDSet
& aPropertySet
,
106 nsTArray
<RefPtr
<dom::Animation
>>* aMatches
/*out*/) {
107 // Do not process any animations on the compositor when in print or print
109 if (aFrame
->PresContext()->IsPrintingOrPrintPreview()) {
114 aPropertySet
.IsSubsetOf(LayerAnimationInfo::GetCSSPropertiesFor(
115 DisplayItemType::TYPE_TRANSFORM
)) ||
116 aPropertySet
.IsSubsetOf(LayerAnimationInfo::GetCSSPropertiesFor(
117 DisplayItemType::TYPE_OPACITY
)) ||
118 aPropertySet
.IsSubsetOf(LayerAnimationInfo::GetCSSPropertiesFor(
119 DisplayItemType::TYPE_BACKGROUND_COLOR
)),
120 "Should be the subset of transform-like properties, or opacity, "
121 "or background color");
123 MOZ_ASSERT(!aMatches
|| aMatches
->IsEmpty(),
124 "Matches array, if provided, should be empty");
126 EffectSet
* effects
= EffectSet::GetForFrame(aFrame
, aPropertySet
);
127 if (!effects
|| effects
->IsEmpty()) {
131 // First check for newly-started transform animations that should be
132 // synchronized with geometric animations. We need to do this before any
133 // other early returns (the one above is ok) since we can only check this
134 // state when the animation is newly-started.
135 if (aPropertySet
.Intersects(LayerAnimationInfo::GetCSSPropertiesFor(
136 DisplayItemType::TYPE_TRANSFORM
))) {
137 PendingAnimationTracker
* tracker
=
138 aFrame
->PresContext()->Document()->GetPendingAnimationTracker();
140 tracker
->MarkAnimationsThatMightNeedSynchronization();
144 AnimationPerformanceWarning::Type warning
=
145 AnimationPerformanceWarning::Type::None
;
146 if (!EffectCompositor::AllowCompositorAnimationsOnFrame(aFrame
, warning
)) {
147 if (warning
!= AnimationPerformanceWarning::Type::None
) {
148 EffectCompositor::SetPerformanceWarning(
149 aFrame
, aPropertySet
, AnimationPerformanceWarning(warning
));
154 // The animation cascade will almost always be up-to-date by this point
155 // but there are some cases such as when we are restoring the refresh driver
156 // from test control after seeking where it might not be the case.
158 // Those cases are probably not important but just to be safe, let's make
159 // sure the cascade is up to date since if it *is* up to date, this is
160 // basically a no-op.
161 Maybe
<NonOwningAnimationTarget
> pseudoElement
=
162 EffectCompositor::GetAnimationElementAndPseudoForFrame(
163 nsLayoutUtils::GetStyleFrame(aFrame
));
164 MOZ_ASSERT(pseudoElement
,
165 "We have a valid element for the frame, if we don't we should "
166 "have bailed out at above the call to EffectSet::Get");
167 EffectCompositor::MaybeUpdateCascadeResults(pseudoElement
->mElement
,
168 pseudoElement
->mPseudoType
);
170 bool foundRunningAnimations
= false;
171 for (KeyframeEffect
* effect
: *effects
) {
172 AnimationPerformanceWarning::Type effectWarning
=
173 AnimationPerformanceWarning::Type::None
;
174 KeyframeEffect::MatchForCompositor matchResult
=
175 effect
->IsMatchForCompositor(aPropertySet
, aFrame
, *effects
,
177 if (effectWarning
!= AnimationPerformanceWarning::Type::None
) {
178 EffectCompositor::SetPerformanceWarning(
179 aFrame
, aPropertySet
, AnimationPerformanceWarning(effectWarning
));
183 KeyframeEffect::MatchForCompositor::NoAndBlockThisProperty
) {
184 // For a given |aFrame|, we don't want some animations of |aPropertySet|
185 // to run on the compositor and others to run on the main thread, so if
186 // any need to be synchronized with the main thread, run them all there.
193 if (matchResult
== KeyframeEffect::MatchForCompositor::No
) {
198 aMatches
->AppendElement(effect
->GetAnimation());
201 if (matchResult
== KeyframeEffect::MatchForCompositor::Yes
) {
202 foundRunningAnimations
= true;
206 // If all animations we added were not currently playing animations, don't
207 // send them to the compositor.
208 if (aMatches
&& !foundRunningAnimations
) {
212 MOZ_ASSERT(!foundRunningAnimations
|| !aMatches
|| !aMatches
->IsEmpty(),
213 "If return value is true, matches array should be non-empty");
215 if (aMatches
&& foundRunningAnimations
) {
216 aMatches
->Sort(AnimationPtrComparator
<RefPtr
<dom::Animation
>>());
219 return foundRunningAnimations
;
222 void EffectCompositor::RequestRestyle(dom::Element
* aElement
,
223 PseudoStyleType aPseudoType
,
224 RestyleType aRestyleType
,
225 CascadeLevel aCascadeLevel
) {
227 // Pres context will be null after the effect compositor is disconnected.
231 // Ignore animations on orphaned elements and elements in documents without
232 // a pres shell (e.g. XMLHttpRequest responseXML documents).
233 if (!nsContentUtils::GetPresShellForContent(aElement
)) {
237 auto& elementsToRestyle
= mElementsToRestyle
[aCascadeLevel
];
238 PseudoElementHashEntry::KeyType key
= {aElement
, aPseudoType
};
240 bool& restyleEntry
= elementsToRestyle
.LookupOrInsert(key
, false);
241 if (aRestyleType
== RestyleType::Throttled
) {
242 mPresContext
->PresShell()->SetNeedThrottledAnimationFlush();
244 // Update hashtable first in case PostRestyleForAnimation mutates it
245 // and invalidates the restyleEntry reference.
246 // (It shouldn't, but just to be sure.)
247 bool skipRestyle
= std::exchange(restyleEntry
, true);
249 PostRestyleForAnimation(aElement
, aPseudoType
, aCascadeLevel
);
253 if (aRestyleType
== RestyleType::Layer
) {
254 mPresContext
->RestyleManager()->IncrementAnimationGeneration();
255 if (auto* effectSet
= EffectSet::Get(aElement
, aPseudoType
)) {
256 effectSet
->UpdateAnimationGeneration(mPresContext
);
261 void EffectCompositor::PostRestyleForAnimation(dom::Element
* aElement
,
262 PseudoStyleType aPseudoType
,
263 CascadeLevel aCascadeLevel
) {
268 // FIXME: Bug 1615083 KeyframeEffect::SetTarget() and
269 // KeyframeEffect::SetPseudoElement() may set a non-existing pseudo element,
270 // and we still have to update its style, based on the wpt. However, we don't
271 // have the generated element here, so we failed the wpt.
273 // See wpt for more info: web-animations/interfaces/KeyframeEffect/target.html
275 AnimationUtils::GetElementForRestyle(aElement
, aPseudoType
);
280 RestyleHint hint
= aCascadeLevel
== CascadeLevel::Transitions
281 ? RestyleHint::RESTYLE_CSS_TRANSITIONS
282 : RestyleHint::RESTYLE_CSS_ANIMATIONS
;
284 MOZ_ASSERT(NS_IsMainThread(),
285 "Restyle request during restyling should be requested only on "
286 "the main-thread. e.g. after the parallel traversal");
287 if (ServoStyleSet::IsInServoTraversal() || mIsInPreTraverse
) {
288 MOZ_ASSERT(hint
== RestyleHint::RESTYLE_CSS_ANIMATIONS
||
289 hint
== RestyleHint::RESTYLE_CSS_TRANSITIONS
);
291 // We can't call Servo_NoteExplicitHints here since AtomicRefCell does not
292 // allow us mutate ElementData of the |aElement| in SequentialTask.
293 // Instead we call Servo_NoteExplicitHints for the element in PreTraverse()
294 // which will be called right before the second traversal that we do for
295 // updating CSS animations.
296 // In that case PreTraverse() will return true so that we know to do the
297 // second traversal so we don't need to post any restyle requests to the
302 MOZ_ASSERT(!mPresContext
->RestyleManager()->IsInStyleRefresh());
304 mPresContext
->PresShell()->RestyleForAnimation(element
, hint
);
307 void EffectCompositor::PostRestyleForThrottledAnimations() {
308 for (size_t i
= 0; i
< kCascadeLevelCount
; i
++) {
309 CascadeLevel cascadeLevel
= CascadeLevel(i
);
310 auto& elementSet
= mElementsToRestyle
[cascadeLevel
];
312 for (auto iter
= elementSet
.Iter(); !iter
.Done(); iter
.Next()) {
313 bool& postedRestyle
= iter
.Data();
318 PostRestyleForAnimation(iter
.Key().mElement
, iter
.Key().mPseudoType
,
320 postedRestyle
= true;
325 void EffectCompositor::ClearRestyleRequestsFor(Element
* aElement
) {
326 MOZ_ASSERT(aElement
);
328 auto& elementsToRestyle
= mElementsToRestyle
[CascadeLevel::Animations
];
330 PseudoStyleType pseudoType
= aElement
->GetPseudoElementType();
331 if (pseudoType
== PseudoStyleType::NotPseudo
) {
332 PseudoElementHashEntry::KeyType notPseudoKey
= {aElement
,
333 PseudoStyleType::NotPseudo
};
334 PseudoElementHashEntry::KeyType beforePseudoKey
= {aElement
,
335 PseudoStyleType::before
};
336 PseudoElementHashEntry::KeyType afterPseudoKey
= {aElement
,
337 PseudoStyleType::after
};
338 PseudoElementHashEntry::KeyType markerPseudoKey
= {aElement
,
339 PseudoStyleType::marker
};
341 elementsToRestyle
.Remove(notPseudoKey
);
342 elementsToRestyle
.Remove(beforePseudoKey
);
343 elementsToRestyle
.Remove(afterPseudoKey
);
344 elementsToRestyle
.Remove(markerPseudoKey
);
345 } else if (AnimationUtils::IsSupportedPseudoForAnimations(pseudoType
)) {
346 Element
* parentElement
= aElement
->GetParentElement();
347 MOZ_ASSERT(parentElement
);
348 PseudoElementHashEntry::KeyType key
= {parentElement
, pseudoType
};
349 elementsToRestyle
.Remove(key
);
353 void EffectCompositor::UpdateEffectProperties(const ComputedStyle
* aStyle
,
355 PseudoStyleType aPseudoType
) {
356 EffectSet
* effectSet
= EffectSet::Get(aElement
, aPseudoType
);
361 // Style context (Gecko) or computed values (Stylo) change might cause CSS
362 // cascade level, e.g removing !important, so we should update the cascading
364 effectSet
->MarkCascadeNeedsUpdate();
366 for (KeyframeEffect
* effect
: *effectSet
) {
367 effect
->UpdateProperties(aStyle
);
372 class EffectCompositeOrderComparator
{
374 bool Equals(const KeyframeEffect
* a
, const KeyframeEffect
* b
) const {
378 bool LessThan(const KeyframeEffect
* a
, const KeyframeEffect
* b
) const {
379 MOZ_ASSERT(a
->GetAnimation() && b
->GetAnimation());
382 a
->GetAnimation()->HasLowerCompositeOrderThan(*b
->GetAnimation()) !=
383 b
->GetAnimation()->HasLowerCompositeOrderThan(*a
->GetAnimation()));
384 return a
->GetAnimation()->HasLowerCompositeOrderThan(*b
->GetAnimation());
389 static void ComposeSortedEffects(
390 const nsTArray
<KeyframeEffect
*>& aSortedEffects
,
391 const EffectSet
* aEffectSet
, EffectCompositor::CascadeLevel aCascadeLevel
,
392 StyleAnimationValueMap
* aAnimationValues
) {
393 const bool isTransition
=
394 aCascadeLevel
== EffectCompositor::CascadeLevel::Transitions
;
395 nsCSSPropertyIDSet propertiesToSkip
;
396 // Transitions should be overridden by running animations of the same
397 // property per https://drafts.csswg.org/css-transitions/#application:
399 // > Implementations must add this value to the cascade if and only if that
400 // > property is not currently undergoing a CSS Animation on the same element.
402 // FIXME(emilio, bug 1606176): This should assert that
403 // aEffectSet->PropertiesForAnimationsLevel() is up-to-date, and it may not
404 // follow the spec in those cases. There are various places where we get style
405 // without flushing that would trigger the below assertion.
407 // MOZ_ASSERT_IF(aEffectSet, !aEffectSet->CascadeNeedsUpdate());
410 isTransition
? aEffectSet
->PropertiesForAnimationsLevel()
411 : aEffectSet
->PropertiesForAnimationsLevel().Inverse();
414 for (KeyframeEffect
* effect
: aSortedEffects
) {
415 auto* animation
= effect
->GetAnimation();
416 MOZ_ASSERT(!isTransition
|| animation
->CascadeLevel() == aCascadeLevel
);
417 animation
->ComposeStyle(*aAnimationValues
, propertiesToSkip
);
421 bool EffectCompositor::GetServoAnimationRule(
422 const dom::Element
* aElement
, PseudoStyleType aPseudoType
,
423 CascadeLevel aCascadeLevel
, StyleAnimationValueMap
* aAnimationValues
) {
424 MOZ_ASSERT(aAnimationValues
);
425 // Gecko_GetAnimationRule should have already checked this
426 MOZ_ASSERT(nsContentUtils::GetPresShellForContent(aElement
),
427 "Should not be trying to run animations on elements in documents"
428 " without a pres shell (e.g. XMLHttpRequest documents)");
430 EffectSet
* effectSet
= EffectSet::Get(aElement
, aPseudoType
);
435 const bool isTransition
= aCascadeLevel
== CascadeLevel::Transitions
;
437 // Get a list of effects sorted by composite order.
438 nsTArray
<KeyframeEffect
*> sortedEffectList(effectSet
->Count());
439 for (KeyframeEffect
* effect
: *effectSet
) {
441 effect
->GetAnimation()->CascadeLevel() != aCascadeLevel
) {
442 // We may need to use transition rules for the animations level for the
443 // case of missing keyframes in animations, but we don't ever need to look
444 // at non-transition levels to build a transition rule. When the effect
445 // set information is out of date (see above), this avoids creating bogus
446 // transition rules, see bug 1605610.
449 sortedEffectList
.AppendElement(effect
);
452 if (sortedEffectList
.IsEmpty()) {
456 sortedEffectList
.Sort(EffectCompositeOrderComparator());
458 ComposeSortedEffects(sortedEffectList
, effectSet
, aCascadeLevel
,
461 MOZ_ASSERT(effectSet
== EffectSet::Get(aElement
, aPseudoType
),
462 "EffectSet should not change while composing style");
467 bool EffectCompositor::ComposeServoAnimationRuleForEffect(
468 KeyframeEffect
& aEffect
, CascadeLevel aCascadeLevel
,
469 StyleAnimationValueMap
* aAnimationValues
) {
470 MOZ_ASSERT(aAnimationValues
);
471 MOZ_ASSERT(mPresContext
&& mPresContext
->IsDynamic(),
472 "Should not be in print preview");
474 NonOwningAnimationTarget target
= aEffect
.GetAnimationTarget();
479 // Don't try to compose animations for elements in documents without a pres
480 // shell (e.g. XMLHttpRequest documents).
481 if (!nsContentUtils::GetPresShellForContent(target
.mElement
)) {
485 // GetServoAnimationRule is called as part of the regular style resolution
486 // where the cascade results are updated in the pre-traversal as needed.
487 // This function, however, is only called when committing styles so we
488 // need to ensure the cascade results are up-to-date manually.
489 MaybeUpdateCascadeResults(target
.mElement
, target
.mPseudoType
);
491 EffectSet
* effectSet
= EffectSet::Get(target
.mElement
, target
.mPseudoType
);
493 // Get a list of effects sorted by composite order up to and including
494 // |aEffect|, even if it is not in the EffectSet.
495 auto comparator
= EffectCompositeOrderComparator();
496 nsTArray
<KeyframeEffect
*> sortedEffectList(effectSet
? effectSet
->Count() + 1
499 for (KeyframeEffect
* effect
: *effectSet
) {
500 if (comparator
.LessThan(effect
, &aEffect
)) {
501 sortedEffectList
.AppendElement(effect
);
504 sortedEffectList
.Sort(comparator
);
506 sortedEffectList
.AppendElement(&aEffect
);
508 ComposeSortedEffects(sortedEffectList
, effectSet
, aCascadeLevel
,
511 MOZ_ASSERT(effectSet
== EffectSet::Get(target
.mElement
, target
.mPseudoType
),
512 "EffectSet should not change while composing style");
517 bool EffectCompositor::HasPendingStyleUpdates() const {
518 for (auto& elementSet
: mElementsToRestyle
) {
519 if (elementSet
.Count()) {
528 bool EffectCompositor::HasAnimationsForCompositor(const nsIFrame
* aFrame
,
529 DisplayItemType aType
) {
530 return FindAnimationsForCompositor(
531 aFrame
, LayerAnimationInfo::GetCSSPropertiesFor(aType
), nullptr);
535 nsTArray
<RefPtr
<dom::Animation
>> EffectCompositor::GetAnimationsForCompositor(
536 const nsIFrame
* aFrame
, const nsCSSPropertyIDSet
& aPropertySet
) {
537 nsTArray
<RefPtr
<dom::Animation
>> result
;
542 FindAnimationsForCompositor(aFrame
, aPropertySet
, &result
);
543 MOZ_ASSERT(!foundSome
|| !result
.IsEmpty(),
544 "If return value is true, matches array should be non-empty");
550 void EffectCompositor::ClearIsRunningOnCompositor(const nsIFrame
* aFrame
,
551 DisplayItemType aType
) {
552 EffectSet
* effects
= EffectSet::GetForFrame(aFrame
, aType
);
557 const nsCSSPropertyIDSet
& propertySet
=
558 LayerAnimationInfo::GetCSSPropertiesFor(aType
);
559 for (KeyframeEffect
* effect
: *effects
) {
560 effect
->SetIsRunningOnCompositor(propertySet
, false);
565 void EffectCompositor::MaybeUpdateCascadeResults(Element
* aElement
,
566 PseudoStyleType aPseudoType
) {
567 EffectSet
* effects
= EffectSet::Get(aElement
, aPseudoType
);
568 if (!effects
|| !effects
->CascadeNeedsUpdate()) {
572 UpdateCascadeResults(*effects
, aElement
, aPseudoType
);
574 MOZ_ASSERT(!effects
->CascadeNeedsUpdate(), "Failed to update cascade state");
578 Maybe
<NonOwningAnimationTarget
>
579 EffectCompositor::GetAnimationElementAndPseudoForFrame(const nsIFrame
* aFrame
) {
580 // Always return the same object to benefit from return-value optimization.
581 Maybe
<NonOwningAnimationTarget
> result
;
583 PseudoStyleType pseudoType
= aFrame
->Style()->GetPseudoType();
585 if (pseudoType
!= PseudoStyleType::NotPseudo
&&
586 !AnimationUtils::IsSupportedPseudoForAnimations(pseudoType
)) {
590 nsIContent
* content
= aFrame
->GetContent();
595 if (AnimationUtils::IsSupportedPseudoForAnimations(pseudoType
)) {
596 content
= content
->GetParent();
602 if (!content
->IsElement()) {
606 result
.emplace(content
->AsElement(), pseudoType
);
612 nsCSSPropertyIDSet
EffectCompositor::GetOverriddenProperties(
613 EffectSet
& aEffectSet
, Element
* aElement
, PseudoStyleType aPseudoType
) {
614 MOZ_ASSERT(aElement
, "Should have an element to get style data from");
616 nsCSSPropertyIDSet result
;
618 Element
* elementForRestyle
=
619 AnimationUtils::GetElementForRestyle(aElement
, aPseudoType
);
620 if (!elementForRestyle
) {
624 static constexpr size_t compositorAnimatableCount
=
625 nsCSSPropertyIDSet::CompositorAnimatableCount();
626 AutoTArray
<nsCSSPropertyID
, compositorAnimatableCount
> propertiesToTrack
;
628 nsCSSPropertyIDSet propertiesToTrackAsSet
;
629 for (KeyframeEffect
* effect
: aEffectSet
) {
630 for (const AnimationProperty
& property
: effect
->Properties()) {
631 if (nsCSSProps::PropHasFlags(property
.mProperty
,
632 CSSPropFlags::CanAnimateOnCompositor
) &&
633 !propertiesToTrackAsSet
.HasProperty(property
.mProperty
)) {
634 propertiesToTrackAsSet
.AddProperty(property
.mProperty
);
635 propertiesToTrack
.AppendElement(property
.mProperty
);
638 // Skip iterating over the rest of the effects if we've already
639 // found all the compositor-animatable properties.
640 if (propertiesToTrack
.Length() == compositorAnimatableCount
) {
646 if (propertiesToTrack
.IsEmpty()) {
650 Servo_GetProperties_Overriding_Animation(elementForRestyle
,
651 &propertiesToTrack
, &result
);
656 void EffectCompositor::UpdateCascadeResults(EffectSet
& aEffectSet
,
658 PseudoStyleType aPseudoType
) {
659 MOZ_ASSERT(EffectSet::Get(aElement
, aPseudoType
) == &aEffectSet
,
660 "Effect set should correspond to the specified (pseudo-)element");
661 if (aEffectSet
.IsEmpty()) {
662 aEffectSet
.MarkCascadeUpdated();
666 // Get a list of effects sorted by composite order.
667 nsTArray
<KeyframeEffect
*> sortedEffectList(aEffectSet
.Count());
668 for (KeyframeEffect
* effect
: aEffectSet
) {
669 sortedEffectList
.AppendElement(effect
);
671 sortedEffectList
.Sort(EffectCompositeOrderComparator());
673 // Get properties that override the *animations* level of the cascade.
675 // We only do this for properties that we can animate on the compositor
676 // since we will apply other properties on the main thread where the usual
678 nsCSSPropertyIDSet overriddenProperties
=
679 GetOverriddenProperties(aEffectSet
, aElement
, aPseudoType
);
681 nsCSSPropertyIDSet
& propertiesWithImportantRules
=
682 aEffectSet
.PropertiesWithImportantRules();
683 nsCSSPropertyIDSet
& propertiesForAnimationsLevel
=
684 aEffectSet
.PropertiesForAnimationsLevel();
686 static constexpr nsCSSPropertyIDSet compositorAnimatables
=
687 nsCSSPropertyIDSet::CompositorAnimatables();
688 // Record which compositor-animatable properties were originally set so we can
689 // compare for changes later.
690 nsCSSPropertyIDSet prevCompositorPropertiesWithImportantRules
=
691 propertiesWithImportantRules
.Intersect(compositorAnimatables
);
693 nsCSSPropertyIDSet prevPropertiesForAnimationsLevel
=
694 propertiesForAnimationsLevel
;
696 propertiesWithImportantRules
.Empty();
697 propertiesForAnimationsLevel
.Empty();
699 nsCSSPropertyIDSet propertiesForTransitionsLevel
;
701 for (const KeyframeEffect
* effect
: sortedEffectList
) {
702 MOZ_ASSERT(effect
->GetAnimation(),
703 "Effects on a target element should have an Animation");
704 CascadeLevel cascadeLevel
= effect
->GetAnimation()->CascadeLevel();
706 for (const AnimationProperty
& prop
: effect
->Properties()) {
707 if (overriddenProperties
.HasProperty(prop
.mProperty
)) {
708 propertiesWithImportantRules
.AddProperty(prop
.mProperty
);
711 switch (cascadeLevel
) {
712 case EffectCompositor::CascadeLevel::Animations
:
713 propertiesForAnimationsLevel
.AddProperty(prop
.mProperty
);
715 case EffectCompositor::CascadeLevel::Transitions
:
716 propertiesForTransitionsLevel
.AddProperty(prop
.mProperty
);
722 aEffectSet
.MarkCascadeUpdated();
724 nsPresContext
* presContext
= nsContentUtils::GetContextForContent(aElement
);
729 // If properties for compositor are newly overridden by !important rules, or
730 // released from being overridden by !important rules, we need to update
731 // layers for animations level because it's a trigger to send animations to
732 // the compositor or pull animations back from the compositor.
733 if (!prevCompositorPropertiesWithImportantRules
.Equals(
734 propertiesWithImportantRules
.Intersect(compositorAnimatables
))) {
735 presContext
->EffectCompositor()->RequestRestyle(
736 aElement
, aPseudoType
, EffectCompositor::RestyleType::Layer
,
737 EffectCompositor::CascadeLevel::Animations
);
740 // If we have transition properties and if the same propery for animations
741 // level is newly added or removed, we need to update the transition level
742 // rule since the it will be added/removed from the rule tree.
743 nsCSSPropertyIDSet changedPropertiesForAnimationLevel
=
744 prevPropertiesForAnimationsLevel
.Xor(propertiesForAnimationsLevel
);
745 nsCSSPropertyIDSet commonProperties
= propertiesForTransitionsLevel
.Intersect(
746 changedPropertiesForAnimationLevel
);
747 if (!commonProperties
.IsEmpty()) {
748 EffectCompositor::RestyleType restyleType
=
749 changedPropertiesForAnimationLevel
.Intersects(compositorAnimatables
)
750 ? EffectCompositor::RestyleType::Standard
751 : EffectCompositor::RestyleType::Layer
;
752 presContext
->EffectCompositor()->RequestRestyle(
753 aElement
, aPseudoType
, restyleType
,
754 EffectCompositor::CascadeLevel::Transitions
);
759 void EffectCompositor::SetPerformanceWarning(
760 const nsIFrame
* aFrame
, const nsCSSPropertyIDSet
& aPropertySet
,
761 const AnimationPerformanceWarning
& aWarning
) {
762 EffectSet
* effects
= EffectSet::GetForFrame(aFrame
, aPropertySet
);
767 for (KeyframeEffect
* effect
: *effects
) {
768 effect
->SetPerformanceWarning(aPropertySet
, aWarning
);
772 bool EffectCompositor::PreTraverse(ServoTraversalFlags aFlags
) {
773 return PreTraverseInSubtree(aFlags
, nullptr);
776 bool EffectCompositor::PreTraverseInSubtree(ServoTraversalFlags aFlags
,
778 MOZ_ASSERT(NS_IsMainThread());
779 MOZ_ASSERT(!aRoot
|| nsContentUtils::GetPresShellForContent(aRoot
),
780 "Traversal root, if provided, should be bound to a display "
783 // Convert the root element to the parent element if the root element is
784 // pseudo since we check each element in mElementsToRestyle is in the subtree
785 // of the root element later in this function, but for pseudo elements the
786 // element in mElementsToRestyle is the parent of the pseudo.
787 if (aRoot
&& (aRoot
->IsGeneratedContentContainerForBefore() ||
788 aRoot
->IsGeneratedContentContainerForAfter() ||
789 aRoot
->IsGeneratedContentContainerForMarker())) {
790 aRoot
= aRoot
->GetParentElement();
793 AutoRestore
<bool> guard(mIsInPreTraverse
);
794 mIsInPreTraverse
= true;
796 // We need to force flush all throttled animations if we also have
797 // non-animation restyles (since we'll want the up-to-date animation style
798 // when we go to process them so we can trigger transitions correctly), and
799 // if we are currently flushing all throttled animation restyles.
800 bool flushThrottledRestyles
=
801 (aRoot
&& aRoot
->HasDirtyDescendantsForServo()) ||
802 (aFlags
& ServoTraversalFlags::FlushThrottledAnimations
);
804 using ElementsToRestyleIterType
=
805 nsTHashMap
<PseudoElementHashEntry
, bool>::ConstIterator
;
806 auto getNeededRestyleTarget
=
807 [&](const ElementsToRestyleIterType
& aIter
) -> NonOwningAnimationTarget
{
808 NonOwningAnimationTarget returnTarget
;
810 // If aIter.Data() is false, the element only requested a throttled
811 // (skippable) restyle, so we can skip it if flushThrottledRestyles is not
813 if (!flushThrottledRestyles
&& !aIter
.Data()) {
817 const NonOwningAnimationTarget
& target
= aIter
.Key();
819 // Skip elements in documents without a pres shell. Normally we filter out
820 // such elements in RequestRestyle but it can happen that, after adding
821 // them to mElementsToRestyle, they are transferred to a different document.
823 // We will drop them from mElementsToRestyle at the end of the next full
824 // document restyle (at the end of this function) but for consistency with
825 // how we treat such elements in RequestRestyle, we just ignore them here.
826 if (!nsContentUtils::GetPresShellForContent(target
.mElement
)) {
830 // Ignore restyles that aren't in the flattened tree subtree rooted at
832 if (aRoot
&& !nsContentUtils::ContentIsFlattenedTreeDescendantOfForStyle(
833 target
.mElement
, aRoot
)) {
837 returnTarget
= target
;
841 bool foundElementsNeedingRestyle
= false;
843 nsTArray
<NonOwningAnimationTarget
> elementsWithCascadeUpdates
;
844 for (size_t i
= 0; i
< kCascadeLevelCount
; ++i
) {
845 CascadeLevel cascadeLevel
= CascadeLevel(i
);
846 auto& elementSet
= mElementsToRestyle
[cascadeLevel
];
847 for (auto iter
= elementSet
.ConstIter(); !iter
.Done(); iter
.Next()) {
848 const NonOwningAnimationTarget
& target
= getNeededRestyleTarget(iter
);
849 if (!target
.mElement
) {
853 EffectSet
* effects
= EffectSet::Get(target
.mElement
, target
.mPseudoType
);
854 if (!effects
|| !effects
->CascadeNeedsUpdate()) {
858 elementsWithCascadeUpdates
.AppendElement(target
);
862 for (const NonOwningAnimationTarget
& target
: elementsWithCascadeUpdates
) {
863 MaybeUpdateCascadeResults(target
.mElement
, target
.mPseudoType
);
865 elementsWithCascadeUpdates
.Clear();
867 for (size_t i
= 0; i
< kCascadeLevelCount
; ++i
) {
868 CascadeLevel cascadeLevel
= CascadeLevel(i
);
869 auto& elementSet
= mElementsToRestyle
[cascadeLevel
];
870 for (auto iter
= elementSet
.Iter(); !iter
.Done(); iter
.Next()) {
871 const NonOwningAnimationTarget
& target
= getNeededRestyleTarget(iter
);
872 if (!target
.mElement
) {
876 if (target
.mElement
->GetComposedDoc() != mPresContext
->Document()) {
881 // We need to post restyle hints even if the target is not in EffectSet to
882 // ensure the final restyling for removed animations.
883 // We can't call PostRestyleEvent directly here since we are still in the
884 // middle of the servo traversal.
885 mPresContext
->RestyleManager()->PostRestyleEventForAnimations(
886 target
.mElement
, target
.mPseudoType
,
887 cascadeLevel
== CascadeLevel::Transitions
888 ? RestyleHint::RESTYLE_CSS_TRANSITIONS
889 : RestyleHint::RESTYLE_CSS_ANIMATIONS
);
891 foundElementsNeedingRestyle
= true;
893 auto* effects
= EffectSet::Get(target
.mElement
, target
.mPseudoType
);
895 // Drop EffectSets that have been destroyed.
900 for (KeyframeEffect
* effect
: *effects
) {
901 effect
->GetAnimation()->WillComposeStyle();
904 // Remove the element from the list of elements to restyle since we are
905 // about to restyle it.
909 // If this is a full document restyle, then unconditionally clear
910 // elementSet in case there are any elements that didn't match above
911 // because they were moved to a document without a pres shell after
912 // posting an animation restyle.
913 if (!aRoot
&& flushThrottledRestyles
) {
918 return foundElementsNeedingRestyle
;
921 void EffectCompositor::NoteElementForReducing(
922 const NonOwningAnimationTarget
& aTarget
) {
923 if (!StaticPrefs::dom_animations_api_autoremove_enabled()) {
927 Unused
<< mElementsToReduce
.put(
928 OwningAnimationTarget
{aTarget
.mElement
, aTarget
.mPseudoType
});
931 static void ReduceEffectSet(EffectSet
& aEffectSet
) {
932 // Get a list of effects sorted by composite order.
933 nsTArray
<KeyframeEffect
*> sortedEffectList(aEffectSet
.Count());
934 for (KeyframeEffect
* effect
: aEffectSet
) {
935 sortedEffectList
.AppendElement(effect
);
937 sortedEffectList
.Sort(EffectCompositeOrderComparator());
939 nsCSSPropertyIDSet setProperties
;
941 // Iterate in reverse
942 for (auto iter
= sortedEffectList
.rbegin(); iter
!= sortedEffectList
.rend();
944 MOZ_ASSERT(*iter
&& (*iter
)->GetAnimation(),
945 "Effect in an EffectSet should have an animation");
946 KeyframeEffect
& effect
= **iter
;
947 Animation
& animation
= *effect
.GetAnimation();
948 if (animation
.IsRemovable() &&
949 effect
.GetPropertySet().IsSubsetOf(setProperties
)) {
951 } else if (animation
.IsReplaceable()) {
952 setProperties
|= effect
.GetPropertySet();
957 void EffectCompositor::ReduceAnimations() {
958 for (auto iter
= mElementsToReduce
.iter(); !iter
.done(); iter
.next()) {
959 const OwningAnimationTarget
& target
= iter
.get();
960 auto* effectSet
= EffectSet::Get(target
.mElement
, target
.mPseudoType
);
962 ReduceEffectSet(*effectSet
);
966 mElementsToReduce
.clear();
969 } // namespace mozilla