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
64 NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(EffectCompositor
, AddRef
)
65 NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(EffectCompositor
, Release
)
68 bool EffectCompositor::AllowCompositorAnimationsOnFrame(
69 const nsIFrame
* aFrame
,
70 AnimationPerformanceWarning::Type
& aWarning
/* out */) {
71 if (aFrame
->RefusedAsyncAnimation()) {
75 if (!nsLayoutUtils::AreAsyncAnimationsEnabled()) {
76 if (StaticPrefs::layers_offmainthreadcomposition_log_animations()) {
78 message
.AppendLiteral(
79 "Performance warning: Async animations are "
81 AnimationUtils::LogAsyncAnimationFailure(message
);
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();
91 if (content
->HasRenderingObservers()) {
92 aWarning
= AnimationPerformanceWarning::Type::HasRenderingObserver
;
95 content
= content
->GetParent();
101 // Helper function to factor out the common logic from
102 // GetAnimationsForCompositor and HasAnimationsForCompositor.
104 // Takes an optional array to fill with eligible animations.
106 // Returns true if there are eligible animations, false otherwise.
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
112 if (aFrame
->PresContext()->IsPrintingOrPrintPreview()) {
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");
126 MOZ_ASSERT(!aMatches
|| aMatches
->IsEmpty(),
127 "Matches array, if provided, should be empty");
129 EffectSet
* effects
= EffectSet::GetEffectSetForFrame(aFrame
, aPropertySet
);
130 if (!effects
|| effects
->IsEmpty()) {
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();
143 tracker
->MarkAnimationsThatMightNeedSynchronization();
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
));
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.
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
);
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
,
180 if (effectWarning
!= AnimationPerformanceWarning::Type::None
) {
181 EffectCompositor::SetPerformanceWarning(
182 aFrame
, aPropertySet
, AnimationPerformanceWarning(effectWarning
));
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.
196 if (matchResult
== KeyframeEffect::MatchForCompositor::No
) {
201 aMatches
->AppendElement(effect
->GetAnimation());
204 if (matchResult
== KeyframeEffect::MatchForCompositor::Yes
) {
205 foundRunningAnimations
= true;
209 // If all animations we added were not currently playing animations, don't
210 // send them to the compositor.
211 if (aMatches
&& !foundRunningAnimations
) {
215 MOZ_ASSERT(!foundRunningAnimations
|| !aMatches
|| !aMatches
->IsEmpty(),
216 "If return value is true, matches array should be non-empty");
218 if (aMatches
&& foundRunningAnimations
) {
219 aMatches
->Sort(AnimationPtrComparator
<RefPtr
<dom::Animation
>>());
222 return foundRunningAnimations
;
225 void EffectCompositor::RequestRestyle(dom::Element
* aElement
,
226 PseudoStyleType aPseudoType
,
227 RestyleType aRestyleType
,
228 CascadeLevel aCascadeLevel
) {
230 // Pres context will be null after the effect compositor is disconnected.
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
)) {
240 auto& elementsToRestyle
= mElementsToRestyle
[aCascadeLevel
];
241 PseudoElementHashEntry::KeyType key
= {aElement
, aPseudoType
};
243 bool& restyleEntry
= elementsToRestyle
.LookupOrInsert(key
, false);
244 if (aRestyleType
== RestyleType::Throttled
) {
245 mPresContext
->PresShell()->SetNeedThrottledAnimationFlush();
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);
252 PostRestyleForAnimation(aElement
, aPseudoType
, aCascadeLevel
);
256 if (aRestyleType
== RestyleType::Layer
) {
257 mPresContext
->RestyleManager()->IncrementAnimationGeneration();
258 EffectSet
* effectSet
= EffectSet::GetEffectSet(aElement
, aPseudoType
);
260 effectSet
->UpdateAnimationGeneration(mPresContext
);
265 void EffectCompositor::PostRestyleForAnimation(dom::Element
* aElement
,
266 PseudoStyleType aPseudoType
,
267 CascadeLevel aCascadeLevel
) {
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.
277 // See wpt for more info: web-animations/interfaces/KeyframeEffect/target.html
278 dom::Element
* element
= GetElementToRestyle(aElement
, aPseudoType
);
283 RestyleHint hint
= aCascadeLevel
== CascadeLevel::Transitions
284 ? RestyleHint::RESTYLE_CSS_TRANSITIONS
285 : RestyleHint::RESTYLE_CSS_ANIMATIONS
;
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
);
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
305 MOZ_ASSERT(!mPresContext
->RestyleManager()->IsInStyleRefresh());
307 mPresContext
->PresShell()->RestyleForAnimation(element
, hint
);
310 void EffectCompositor::PostRestyleForThrottledAnimations() {
311 for (size_t i
= 0; i
< kCascadeLevelCount
; i
++) {
312 CascadeLevel cascadeLevel
= CascadeLevel(i
);
313 auto& elementSet
= mElementsToRestyle
[cascadeLevel
];
315 for (auto iter
= elementSet
.Iter(); !iter
.Done(); iter
.Next()) {
316 bool& postedRestyle
= iter
.Data();
321 PostRestyleForAnimation(iter
.Key().mElement
, iter
.Key().mPseudoType
,
323 postedRestyle
= true;
328 void EffectCompositor::ClearRestyleRequestsFor(Element
* aElement
) {
329 MOZ_ASSERT(aElement
);
331 auto& elementsToRestyle
= mElementsToRestyle
[CascadeLevel::Animations
];
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
};
344 elementsToRestyle
.Remove(notPseudoKey
);
345 elementsToRestyle
.Remove(beforePseudoKey
);
346 elementsToRestyle
.Remove(afterPseudoKey
);
347 elementsToRestyle
.Remove(markerPseudoKey
);
348 } else if (AnimationUtils::IsSupportedPseudoForAnimations(pseudoType
)) {
349 Element
* parentElement
= aElement
->GetParentElement();
350 MOZ_ASSERT(parentElement
);
351 PseudoElementHashEntry::KeyType key
= {parentElement
, pseudoType
};
352 elementsToRestyle
.Remove(key
);
356 void EffectCompositor::UpdateEffectProperties(const ComputedStyle
* aStyle
,
358 PseudoStyleType aPseudoType
) {
359 EffectSet
* effectSet
= EffectSet::GetEffectSet(aElement
, aPseudoType
);
364 // Style context (Gecko) or computed values (Stylo) change might cause CSS
365 // cascade level, e.g removing !important, so we should update the cascading
367 effectSet
->MarkCascadeNeedsUpdate();
369 for (KeyframeEffect
* effect
: *effectSet
) {
370 effect
->UpdateProperties(aStyle
);
375 class EffectCompositeOrderComparator
{
377 bool Equals(const KeyframeEffect
* a
, const KeyframeEffect
* b
) const {
381 bool LessThan(const KeyframeEffect
* a
, const KeyframeEffect
* b
) const {
382 MOZ_ASSERT(a
->GetAnimation() && b
->GetAnimation());
385 a
->GetAnimation()->HasLowerCompositeOrderThan(*b
->GetAnimation()) !=
386 b
->GetAnimation()->HasLowerCompositeOrderThan(*a
->GetAnimation()));
387 return a
->GetAnimation()->HasLowerCompositeOrderThan(*b
->GetAnimation());
392 static void ComposeSortedEffects(
393 const nsTArray
<KeyframeEffect
*>& aSortedEffects
,
394 const EffectSet
* aEffectSet
, EffectCompositor::CascadeLevel aCascadeLevel
,
395 RawServoAnimationValueMap
* aAnimationValues
) {
396 const bool isTransition
=
397 aCascadeLevel
== EffectCompositor::CascadeLevel::Transitions
;
398 nsCSSPropertyIDSet propertiesToSkip
;
399 // Transitions should be overridden by running animations of the same
400 // property per https://drafts.csswg.org/css-transitions/#application:
402 // > Implementations must add this value to the cascade if and only if that
403 // > property is not currently undergoing a CSS Animation on the same element.
405 // FIXME(emilio, bug 1606176): This should assert that
406 // aEffectSet->PropertiesForAnimationsLevel() is up-to-date, and it may not
407 // follow the spec in those cases. There are various places where we get style
408 // without flushing that would trigger the below assertion.
410 // MOZ_ASSERT_IF(aEffectSet, !aEffectSet->CascadeNeedsUpdate());
413 isTransition
? aEffectSet
->PropertiesForAnimationsLevel()
414 : aEffectSet
->PropertiesForAnimationsLevel().Inverse();
417 for (KeyframeEffect
* effect
: aSortedEffects
) {
418 auto* animation
= effect
->GetAnimation();
419 MOZ_ASSERT(!isTransition
|| animation
->CascadeLevel() == aCascadeLevel
);
420 animation
->ComposeStyle(*aAnimationValues
, propertiesToSkip
);
424 bool EffectCompositor::GetServoAnimationRule(
425 const dom::Element
* aElement
, PseudoStyleType aPseudoType
,
426 CascadeLevel aCascadeLevel
, RawServoAnimationValueMap
* aAnimationValues
) {
427 MOZ_ASSERT(aAnimationValues
);
428 // Gecko_GetAnimationRule should have already checked this
429 MOZ_ASSERT(nsContentUtils::GetPresShellForContent(aElement
),
430 "Should not be trying to run animations on elements in documents"
431 " without a pres shell (e.g. XMLHttpRequest documents)");
433 EffectSet
* effectSet
= EffectSet::GetEffectSet(aElement
, aPseudoType
);
438 const bool isTransition
= aCascadeLevel
== CascadeLevel::Transitions
;
440 // Get a list of effects sorted by composite order.
441 nsTArray
<KeyframeEffect
*> sortedEffectList(effectSet
->Count());
442 for (KeyframeEffect
* effect
: *effectSet
) {
444 effect
->GetAnimation()->CascadeLevel() != aCascadeLevel
) {
445 // We may need to use transition rules for the animations level for the
446 // case of missing keyframes in animations, but we don't ever need to look
447 // at non-transition levels to build a transition rule. When the effect
448 // set information is out of date (see above), this avoids creating bogus
449 // transition rules, see bug 1605610.
452 sortedEffectList
.AppendElement(effect
);
455 if (sortedEffectList
.IsEmpty()) {
459 sortedEffectList
.Sort(EffectCompositeOrderComparator());
461 ComposeSortedEffects(sortedEffectList
, effectSet
, aCascadeLevel
,
464 MOZ_ASSERT(effectSet
== EffectSet::GetEffectSet(aElement
, aPseudoType
),
465 "EffectSet should not change while composing style");
470 bool EffectCompositor::ComposeServoAnimationRuleForEffect(
471 KeyframeEffect
& aEffect
, CascadeLevel aCascadeLevel
,
472 RawServoAnimationValueMap
* aAnimationValues
) {
473 MOZ_ASSERT(aAnimationValues
);
474 MOZ_ASSERT(mPresContext
&& mPresContext
->IsDynamic(),
475 "Should not be in print preview");
477 NonOwningAnimationTarget target
= aEffect
.GetAnimationTarget();
482 // Don't try to compose animations for elements in documents without a pres
483 // shell (e.g. XMLHttpRequest documents).
484 if (!nsContentUtils::GetPresShellForContent(target
.mElement
)) {
488 // GetServoAnimationRule is called as part of the regular style resolution
489 // where the cascade results are updated in the pre-traversal as needed.
490 // This function, however, is only called when committing styles so we
491 // need to ensure the cascade results are up-to-date manually.
492 MaybeUpdateCascadeResults(target
.mElement
, target
.mPseudoType
);
494 EffectSet
* effectSet
=
495 EffectSet::GetEffectSet(target
.mElement
, target
.mPseudoType
);
497 // Get a list of effects sorted by composite order up to and including
498 // |aEffect|, even if it is not in the EffectSet.
499 auto comparator
= EffectCompositeOrderComparator();
500 nsTArray
<KeyframeEffect
*> sortedEffectList(effectSet
? effectSet
->Count() + 1
503 for (KeyframeEffect
* effect
: *effectSet
) {
504 if (comparator
.LessThan(effect
, &aEffect
)) {
505 sortedEffectList
.AppendElement(effect
);
508 sortedEffectList
.Sort(comparator
);
510 sortedEffectList
.AppendElement(&aEffect
);
512 ComposeSortedEffects(sortedEffectList
, effectSet
, aCascadeLevel
,
516 effectSet
== EffectSet::GetEffectSet(target
.mElement
, target
.mPseudoType
),
517 "EffectSet should not change while composing style");
522 /* static */ dom::Element
* EffectCompositor::GetElementToRestyle(
523 dom::Element
* aElement
, PseudoStyleType aPseudoType
) {
524 if (aPseudoType
== PseudoStyleType::NotPseudo
) {
528 if (aPseudoType
== PseudoStyleType::before
) {
529 return nsLayoutUtils::GetBeforePseudo(aElement
);
532 if (aPseudoType
== PseudoStyleType::after
) {
533 return nsLayoutUtils::GetAfterPseudo(aElement
);
536 if (aPseudoType
== PseudoStyleType::marker
) {
537 return nsLayoutUtils::GetMarkerPseudo(aElement
);
540 MOZ_ASSERT_UNREACHABLE(
541 "Should not try to get the element to restyle for "
542 "a pseudo other that :before, :after or ::marker");
546 bool EffectCompositor::HasPendingStyleUpdates() const {
547 for (auto& elementSet
: mElementsToRestyle
) {
548 if (elementSet
.Count()) {
557 bool EffectCompositor::HasAnimationsForCompositor(const nsIFrame
* aFrame
,
558 DisplayItemType aType
) {
559 return FindAnimationsForCompositor(
560 aFrame
, LayerAnimationInfo::GetCSSPropertiesFor(aType
), nullptr);
564 nsTArray
<RefPtr
<dom::Animation
>> EffectCompositor::GetAnimationsForCompositor(
565 const nsIFrame
* aFrame
, const nsCSSPropertyIDSet
& aPropertySet
) {
566 nsTArray
<RefPtr
<dom::Animation
>> result
;
571 FindAnimationsForCompositor(aFrame
, aPropertySet
, &result
);
572 MOZ_ASSERT(!foundSome
|| !result
.IsEmpty(),
573 "If return value is true, matches array should be non-empty");
579 void EffectCompositor::ClearIsRunningOnCompositor(const nsIFrame
* aFrame
,
580 DisplayItemType aType
) {
581 EffectSet
* effects
= EffectSet::GetEffectSetForFrame(aFrame
, aType
);
586 const nsCSSPropertyIDSet
& propertySet
=
587 LayerAnimationInfo::GetCSSPropertiesFor(aType
);
588 for (KeyframeEffect
* effect
: *effects
) {
589 effect
->SetIsRunningOnCompositor(propertySet
, false);
594 void EffectCompositor::MaybeUpdateCascadeResults(Element
* aElement
,
595 PseudoStyleType aPseudoType
) {
596 EffectSet
* effects
= EffectSet::GetEffectSet(aElement
, aPseudoType
);
597 if (!effects
|| !effects
->CascadeNeedsUpdate()) {
601 UpdateCascadeResults(*effects
, aElement
, aPseudoType
);
603 MOZ_ASSERT(!effects
->CascadeNeedsUpdate(), "Failed to update cascade state");
607 Maybe
<NonOwningAnimationTarget
>
608 EffectCompositor::GetAnimationElementAndPseudoForFrame(const nsIFrame
* aFrame
) {
609 // Always return the same object to benefit from return-value optimization.
610 Maybe
<NonOwningAnimationTarget
> result
;
612 PseudoStyleType pseudoType
= aFrame
->Style()->GetPseudoType();
614 if (pseudoType
!= PseudoStyleType::NotPseudo
&&
615 !AnimationUtils::IsSupportedPseudoForAnimations(pseudoType
)) {
619 nsIContent
* content
= aFrame
->GetContent();
624 if (AnimationUtils::IsSupportedPseudoForAnimations(pseudoType
)) {
625 content
= content
->GetParent();
631 if (!content
->IsElement()) {
635 result
.emplace(content
->AsElement(), pseudoType
);
641 nsCSSPropertyIDSet
EffectCompositor::GetOverriddenProperties(
642 EffectSet
& aEffectSet
, Element
* aElement
, PseudoStyleType aPseudoType
) {
643 MOZ_ASSERT(aElement
, "Should have an element to get style data from");
645 nsCSSPropertyIDSet result
;
647 Element
* elementToRestyle
= GetElementToRestyle(aElement
, aPseudoType
);
648 if (!elementToRestyle
) {
652 static constexpr size_t compositorAnimatableCount
=
653 nsCSSPropertyIDSet::CompositorAnimatableCount();
654 AutoTArray
<nsCSSPropertyID
, compositorAnimatableCount
> propertiesToTrack
;
656 nsCSSPropertyIDSet propertiesToTrackAsSet
;
657 for (KeyframeEffect
* effect
: aEffectSet
) {
658 for (const AnimationProperty
& property
: effect
->Properties()) {
659 if (nsCSSProps::PropHasFlags(property
.mProperty
,
660 CSSPropFlags::CanAnimateOnCompositor
) &&
661 !propertiesToTrackAsSet
.HasProperty(property
.mProperty
)) {
662 propertiesToTrackAsSet
.AddProperty(property
.mProperty
);
663 propertiesToTrack
.AppendElement(property
.mProperty
);
666 // Skip iterating over the rest of the effects if we've already
667 // found all the compositor-animatable properties.
668 if (propertiesToTrack
.Length() == compositorAnimatableCount
) {
674 if (propertiesToTrack
.IsEmpty()) {
678 Servo_GetProperties_Overriding_Animation(elementToRestyle
, &propertiesToTrack
,
684 void EffectCompositor::UpdateCascadeResults(EffectSet
& aEffectSet
,
686 PseudoStyleType aPseudoType
) {
687 MOZ_ASSERT(EffectSet::GetEffectSet(aElement
, aPseudoType
) == &aEffectSet
,
688 "Effect set should correspond to the specified (pseudo-)element");
689 if (aEffectSet
.IsEmpty()) {
690 aEffectSet
.MarkCascadeUpdated();
694 // Get a list of effects sorted by composite order.
695 nsTArray
<KeyframeEffect
*> sortedEffectList(aEffectSet
.Count());
696 for (KeyframeEffect
* effect
: aEffectSet
) {
697 sortedEffectList
.AppendElement(effect
);
699 sortedEffectList
.Sort(EffectCompositeOrderComparator());
701 // Get properties that override the *animations* level of the cascade.
703 // We only do this for properties that we can animate on the compositor
704 // since we will apply other properties on the main thread where the usual
706 nsCSSPropertyIDSet overriddenProperties
=
707 GetOverriddenProperties(aEffectSet
, aElement
, aPseudoType
);
709 nsCSSPropertyIDSet
& propertiesWithImportantRules
=
710 aEffectSet
.PropertiesWithImportantRules();
711 nsCSSPropertyIDSet
& propertiesForAnimationsLevel
=
712 aEffectSet
.PropertiesForAnimationsLevel();
714 static constexpr nsCSSPropertyIDSet compositorAnimatables
=
715 nsCSSPropertyIDSet::CompositorAnimatables();
716 // Record which compositor-animatable properties were originally set so we can
717 // compare for changes later.
718 nsCSSPropertyIDSet prevCompositorPropertiesWithImportantRules
=
719 propertiesWithImportantRules
.Intersect(compositorAnimatables
);
721 nsCSSPropertyIDSet prevPropertiesForAnimationsLevel
=
722 propertiesForAnimationsLevel
;
724 propertiesWithImportantRules
.Empty();
725 propertiesForAnimationsLevel
.Empty();
727 nsCSSPropertyIDSet propertiesForTransitionsLevel
;
729 for (const KeyframeEffect
* effect
: sortedEffectList
) {
730 MOZ_ASSERT(effect
->GetAnimation(),
731 "Effects on a target element should have an Animation");
732 CascadeLevel cascadeLevel
= effect
->GetAnimation()->CascadeLevel();
734 for (const AnimationProperty
& prop
: effect
->Properties()) {
735 if (overriddenProperties
.HasProperty(prop
.mProperty
)) {
736 propertiesWithImportantRules
.AddProperty(prop
.mProperty
);
739 switch (cascadeLevel
) {
740 case EffectCompositor::CascadeLevel::Animations
:
741 propertiesForAnimationsLevel
.AddProperty(prop
.mProperty
);
743 case EffectCompositor::CascadeLevel::Transitions
:
744 propertiesForTransitionsLevel
.AddProperty(prop
.mProperty
);
750 aEffectSet
.MarkCascadeUpdated();
752 nsPresContext
* presContext
= nsContentUtils::GetContextForContent(aElement
);
757 // If properties for compositor are newly overridden by !important rules, or
758 // released from being overridden by !important rules, we need to update
759 // layers for animations level because it's a trigger to send animations to
760 // the compositor or pull animations back from the compositor.
761 if (!prevCompositorPropertiesWithImportantRules
.Equals(
762 propertiesWithImportantRules
.Intersect(compositorAnimatables
))) {
763 presContext
->EffectCompositor()->RequestRestyle(
764 aElement
, aPseudoType
, EffectCompositor::RestyleType::Layer
,
765 EffectCompositor::CascadeLevel::Animations
);
768 // If we have transition properties and if the same propery for animations
769 // level is newly added or removed, we need to update the transition level
770 // rule since the it will be added/removed from the rule tree.
771 nsCSSPropertyIDSet changedPropertiesForAnimationLevel
=
772 prevPropertiesForAnimationsLevel
.Xor(propertiesForAnimationsLevel
);
773 nsCSSPropertyIDSet commonProperties
= propertiesForTransitionsLevel
.Intersect(
774 changedPropertiesForAnimationLevel
);
775 if (!commonProperties
.IsEmpty()) {
776 EffectCompositor::RestyleType restyleType
=
777 changedPropertiesForAnimationLevel
.Intersects(compositorAnimatables
)
778 ? EffectCompositor::RestyleType::Standard
779 : EffectCompositor::RestyleType::Layer
;
780 presContext
->EffectCompositor()->RequestRestyle(
781 aElement
, aPseudoType
, restyleType
,
782 EffectCompositor::CascadeLevel::Transitions
);
787 void EffectCompositor::SetPerformanceWarning(
788 const nsIFrame
* aFrame
, const nsCSSPropertyIDSet
& aPropertySet
,
789 const AnimationPerformanceWarning
& aWarning
) {
790 EffectSet
* effects
= EffectSet::GetEffectSetForFrame(aFrame
, aPropertySet
);
795 for (KeyframeEffect
* effect
: *effects
) {
796 effect
->SetPerformanceWarning(aPropertySet
, aWarning
);
800 bool EffectCompositor::PreTraverse(ServoTraversalFlags aFlags
) {
801 return PreTraverseInSubtree(aFlags
, nullptr);
804 bool EffectCompositor::PreTraverseInSubtree(ServoTraversalFlags aFlags
,
806 MOZ_ASSERT(NS_IsMainThread());
807 MOZ_ASSERT(!aRoot
|| nsContentUtils::GetPresShellForContent(aRoot
),
808 "Traversal root, if provided, should be bound to a display "
811 // Convert the root element to the parent element if the root element is
812 // pseudo since we check each element in mElementsToRestyle is in the subtree
813 // of the root element later in this function, but for pseudo elements the
814 // element in mElementsToRestyle is the parent of the pseudo.
815 if (aRoot
&& (aRoot
->IsGeneratedContentContainerForBefore() ||
816 aRoot
->IsGeneratedContentContainerForAfter() ||
817 aRoot
->IsGeneratedContentContainerForMarker())) {
818 aRoot
= aRoot
->GetParentElement();
821 AutoRestore
<bool> guard(mIsInPreTraverse
);
822 mIsInPreTraverse
= true;
824 // We need to force flush all throttled animations if we also have
825 // non-animation restyles (since we'll want the up-to-date animation style
826 // when we go to process them so we can trigger transitions correctly), and
827 // if we are currently flushing all throttled animation restyles.
828 bool flushThrottledRestyles
=
829 (aRoot
&& aRoot
->HasDirtyDescendantsForServo()) ||
830 (aFlags
& ServoTraversalFlags::FlushThrottledAnimations
);
832 using ElementsToRestyleIterType
=
833 nsTHashMap
<PseudoElementHashEntry
, bool>::ConstIterator
;
834 auto getNeededRestyleTarget
=
835 [&](const ElementsToRestyleIterType
& aIter
) -> NonOwningAnimationTarget
{
836 NonOwningAnimationTarget returnTarget
;
838 // If aIter.Data() is false, the element only requested a throttled
839 // (skippable) restyle, so we can skip it if flushThrottledRestyles is not
841 if (!flushThrottledRestyles
&& !aIter
.Data()) {
845 const NonOwningAnimationTarget
& target
= aIter
.Key();
847 // Skip elements in documents without a pres shell. Normally we filter out
848 // such elements in RequestRestyle but it can happen that, after adding
849 // them to mElementsToRestyle, they are transferred to a different document.
851 // We will drop them from mElementsToRestyle at the end of the next full
852 // document restyle (at the end of this function) but for consistency with
853 // how we treat such elements in RequestRestyle, we just ignore them here.
854 if (!nsContentUtils::GetPresShellForContent(target
.mElement
)) {
858 // Ignore restyles that aren't in the flattened tree subtree rooted at
860 if (aRoot
&& !nsContentUtils::ContentIsFlattenedTreeDescendantOfForStyle(
861 target
.mElement
, aRoot
)) {
865 returnTarget
= target
;
869 bool foundElementsNeedingRestyle
= false;
871 nsTArray
<NonOwningAnimationTarget
> elementsWithCascadeUpdates
;
872 for (size_t i
= 0; i
< kCascadeLevelCount
; ++i
) {
873 CascadeLevel cascadeLevel
= CascadeLevel(i
);
874 auto& elementSet
= mElementsToRestyle
[cascadeLevel
];
875 for (auto iter
= elementSet
.ConstIter(); !iter
.Done(); iter
.Next()) {
876 const NonOwningAnimationTarget
& target
= getNeededRestyleTarget(iter
);
877 if (!target
.mElement
) {
882 EffectSet::GetEffectSet(target
.mElement
, target
.mPseudoType
);
883 if (!effects
|| !effects
->CascadeNeedsUpdate()) {
887 elementsWithCascadeUpdates
.AppendElement(target
);
891 for (const NonOwningAnimationTarget
& target
: elementsWithCascadeUpdates
) {
892 MaybeUpdateCascadeResults(target
.mElement
, target
.mPseudoType
);
894 elementsWithCascadeUpdates
.Clear();
896 for (size_t i
= 0; i
< kCascadeLevelCount
; ++i
) {
897 CascadeLevel cascadeLevel
= CascadeLevel(i
);
898 auto& elementSet
= mElementsToRestyle
[cascadeLevel
];
899 for (auto iter
= elementSet
.Iter(); !iter
.Done(); iter
.Next()) {
900 const NonOwningAnimationTarget
& target
= getNeededRestyleTarget(iter
);
901 if (!target
.mElement
) {
905 // We need to post restyle hints even if the target is not in EffectSet to
906 // ensure the final restyling for removed animations.
907 // We can't call PostRestyleEvent directly here since we are still in the
908 // middle of the servo traversal.
909 mPresContext
->RestyleManager()->PostRestyleEventForAnimations(
910 target
.mElement
, target
.mPseudoType
,
911 cascadeLevel
== CascadeLevel::Transitions
912 ? RestyleHint::RESTYLE_CSS_TRANSITIONS
913 : RestyleHint::RESTYLE_CSS_ANIMATIONS
);
915 foundElementsNeedingRestyle
= true;
918 EffectSet::GetEffectSet(target
.mElement
, target
.mPseudoType
);
920 // Drop EffectSets that have been destroyed.
925 for (KeyframeEffect
* effect
: *effects
) {
926 effect
->GetAnimation()->WillComposeStyle();
929 // Remove the element from the list of elements to restyle since we are
930 // about to restyle it.
934 // If this is a full document restyle, then unconditionally clear
935 // elementSet in case there are any elements that didn't match above
936 // because they were moved to a document without a pres shell after
937 // posting an animation restyle.
938 if (!aRoot
&& flushThrottledRestyles
) {
943 return foundElementsNeedingRestyle
;
946 void EffectCompositor::NoteElementForReducing(
947 const NonOwningAnimationTarget
& aTarget
) {
948 if (!StaticPrefs::dom_animations_api_autoremove_enabled()) {
952 Unused
<< mElementsToReduce
.put(
953 OwningAnimationTarget
{aTarget
.mElement
, aTarget
.mPseudoType
});
956 static void ReduceEffectSet(EffectSet
& aEffectSet
) {
957 // Get a list of effects sorted by composite order.
958 nsTArray
<KeyframeEffect
*> sortedEffectList(aEffectSet
.Count());
959 for (KeyframeEffect
* effect
: aEffectSet
) {
960 sortedEffectList
.AppendElement(effect
);
962 sortedEffectList
.Sort(EffectCompositeOrderComparator());
964 nsCSSPropertyIDSet setProperties
;
966 // Iterate in reverse
967 for (auto iter
= sortedEffectList
.rbegin(); iter
!= sortedEffectList
.rend();
969 MOZ_ASSERT(*iter
&& (*iter
)->GetAnimation(),
970 "Effect in an EffectSet should have an animation");
971 KeyframeEffect
& effect
= **iter
;
972 Animation
& animation
= *effect
.GetAnimation();
973 if (animation
.IsRemovable() &&
974 effect
.GetPropertySet().IsSubsetOf(setProperties
)) {
976 } else if (animation
.IsReplaceable()) {
977 setProperties
|= effect
.GetPropertySet();
982 void EffectCompositor::ReduceAnimations() {
983 for (auto iter
= mElementsToReduce
.iter(); !iter
.done(); iter
.next()) {
984 const OwningAnimationTarget
& target
= iter
.get();
985 EffectSet
* effectSet
=
986 EffectSet::GetEffectSet(target
.mElement
, target
.mPseudoType
);
988 ReduceEffectSet(*effectSet
);
992 mElementsToReduce
.clear();
995 } // namespace mozilla