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 (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
);
358 void EffectCompositor::UpdateEffectProperties(const ComputedStyle
* aStyle
,
360 PseudoStyleType aPseudoType
) {
361 EffectSet
* effectSet
= EffectSet::GetEffectSet(aElement
, aPseudoType
);
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
369 effectSet
->MarkCascadeNeedsUpdate();
371 for (KeyframeEffect
* effect
: *effectSet
) {
372 effect
->UpdateProperties(aStyle
);
377 class EffectCompositeOrderComparator
{
379 bool Equals(const KeyframeEffect
* a
, const KeyframeEffect
* b
) const {
383 bool LessThan(const KeyframeEffect
* a
, const KeyframeEffect
* b
) const {
384 MOZ_ASSERT(a
->GetAnimation() && b
->GetAnimation());
387 a
->GetAnimation()->HasLowerCompositeOrderThan(*b
->GetAnimation()) !=
388 b
->GetAnimation()->HasLowerCompositeOrderThan(*a
->GetAnimation()));
389 return a
->GetAnimation()->HasLowerCompositeOrderThan(*b
->GetAnimation());
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:
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.
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.
412 // MOZ_ASSERT_IF(aEffectSet, !aEffectSet->CascadeNeedsUpdate());
415 isTransition
? aEffectSet
->PropertiesForAnimationsLevel()
416 : aEffectSet
->PropertiesForAnimationsLevel().Inverse();
419 for (KeyframeEffect
* effect
: aSortedEffects
) {
420 auto* animation
= effect
->GetAnimation();
421 MOZ_ASSERT(!isTransition
|| animation
->CascadeLevel() == aCascadeLevel
);
422 animation
->ComposeStyle(*aAnimationValues
, propertiesToSkip
);
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)");
435 EffectSet
* effectSet
= EffectSet::GetEffectSet(aElement
, aPseudoType
);
440 const bool isTransition
= aCascadeLevel
== CascadeLevel::Transitions
;
442 // Get a list of effects sorted by composite order.
443 nsTArray
<KeyframeEffect
*> sortedEffectList(effectSet
->Count());
444 for (KeyframeEffect
* effect
: *effectSet
) {
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.
454 sortedEffectList
.AppendElement(effect
);
457 if (sortedEffectList
.IsEmpty()) {
461 sortedEffectList
.Sort(EffectCompositeOrderComparator());
463 ComposeSortedEffects(sortedEffectList
, effectSet
, aCascadeLevel
,
466 MOZ_ASSERT(effectSet
== EffectSet::GetEffectSet(aElement
, aPseudoType
),
467 "EffectSet should not change while composing style");
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");
479 NonOwningAnimationTarget target
= aEffect
.GetAnimationTarget();
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
)) {
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
);
496 EffectSet
* effectSet
=
497 EffectSet::GetEffectSet(target
.mElement
, target
.mPseudoType
);
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
505 for (KeyframeEffect
* effect
: *effectSet
) {
506 if (comparator
.LessThan(effect
, &aEffect
)) {
507 sortedEffectList
.AppendElement(effect
);
510 sortedEffectList
.Sort(comparator
);
512 sortedEffectList
.AppendElement(&aEffect
);
514 ComposeSortedEffects(sortedEffectList
, effectSet
, aCascadeLevel
,
518 effectSet
== EffectSet::GetEffectSet(target
.mElement
, target
.mPseudoType
),
519 "EffectSet should not change while composing style");
524 /* static */ dom::Element
* EffectCompositor::GetElementToRestyle(
525 dom::Element
* aElement
, PseudoStyleType aPseudoType
) {
526 if (aPseudoType
== PseudoStyleType::NotPseudo
) {
530 if (aPseudoType
== PseudoStyleType::before
) {
531 return nsLayoutUtils::GetBeforePseudo(aElement
);
534 if (aPseudoType
== PseudoStyleType::after
) {
535 return nsLayoutUtils::GetAfterPseudo(aElement
);
538 if (aPseudoType
== PseudoStyleType::marker
) {
539 return nsLayoutUtils::GetMarkerPseudo(aElement
);
542 MOZ_ASSERT_UNREACHABLE(
543 "Should not try to get the element to restyle for "
544 "a pseudo other that :before, :after or ::marker");
548 bool EffectCompositor::HasPendingStyleUpdates() const {
549 for (auto& elementSet
: mElementsToRestyle
) {
550 if (elementSet
.Count()) {
559 bool EffectCompositor::HasAnimationsForCompositor(const nsIFrame
* aFrame
,
560 DisplayItemType aType
) {
561 return FindAnimationsForCompositor(
562 aFrame
, LayerAnimationInfo::GetCSSPropertiesFor(aType
), nullptr);
566 nsTArray
<RefPtr
<dom::Animation
>> EffectCompositor::GetAnimationsForCompositor(
567 const nsIFrame
* aFrame
, const nsCSSPropertyIDSet
& aPropertySet
) {
568 nsTArray
<RefPtr
<dom::Animation
>> result
;
573 FindAnimationsForCompositor(aFrame
, aPropertySet
, &result
);
574 MOZ_ASSERT(!foundSome
|| !result
.IsEmpty(),
575 "If return value is true, matches array should be non-empty");
581 void EffectCompositor::ClearIsRunningOnCompositor(const nsIFrame
* aFrame
,
582 DisplayItemType aType
) {
583 EffectSet
* effects
= EffectSet::GetEffectSetForFrame(aFrame
, aType
);
588 const nsCSSPropertyIDSet
& propertySet
=
589 LayerAnimationInfo::GetCSSPropertiesFor(aType
);
590 for (KeyframeEffect
* effect
: *effects
) {
591 effect
->SetIsRunningOnCompositor(propertySet
, false);
596 void EffectCompositor::MaybeUpdateCascadeResults(Element
* aElement
,
597 PseudoStyleType aPseudoType
) {
598 EffectSet
* effects
= EffectSet::GetEffectSet(aElement
, aPseudoType
);
599 if (!effects
|| !effects
->CascadeNeedsUpdate()) {
603 UpdateCascadeResults(*effects
, aElement
, aPseudoType
);
605 MOZ_ASSERT(!effects
->CascadeNeedsUpdate(), "Failed to update cascade state");
609 Maybe
<NonOwningAnimationTarget
>
610 EffectCompositor::GetAnimationElementAndPseudoForFrame(const nsIFrame
* aFrame
) {
611 // Always return the same object to benefit from return-value optimization.
612 Maybe
<NonOwningAnimationTarget
> result
;
614 PseudoStyleType pseudoType
= aFrame
->Style()->GetPseudoType();
616 if (pseudoType
!= PseudoStyleType::NotPseudo
&&
617 pseudoType
!= PseudoStyleType::before
&&
618 pseudoType
!= PseudoStyleType::after
&&
619 pseudoType
!= PseudoStyleType::marker
) {
623 nsIContent
* content
= aFrame
->GetContent();
628 if (pseudoType
== PseudoStyleType::before
||
629 pseudoType
== PseudoStyleType::after
||
630 pseudoType
== PseudoStyleType::marker
) {
631 content
= content
->GetParent();
637 if (!content
->IsElement()) {
641 result
.emplace(content
->AsElement(), pseudoType
);
647 nsCSSPropertyIDSet
EffectCompositor::GetOverriddenProperties(
648 EffectSet
& aEffectSet
, Element
* aElement
, PseudoStyleType aPseudoType
) {
649 MOZ_ASSERT(aElement
, "Should have an element to get style data from");
651 nsCSSPropertyIDSet result
;
653 Element
* elementToRestyle
= GetElementToRestyle(aElement
, aPseudoType
);
654 if (!elementToRestyle
) {
658 static constexpr size_t compositorAnimatableCount
=
659 nsCSSPropertyIDSet::CompositorAnimatableCount();
660 AutoTArray
<nsCSSPropertyID
, compositorAnimatableCount
> propertiesToTrack
;
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
);
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
) {
680 if (propertiesToTrack
.IsEmpty()) {
684 Servo_GetProperties_Overriding_Animation(elementToRestyle
, &propertiesToTrack
,
690 void EffectCompositor::UpdateCascadeResults(EffectSet
& aEffectSet
,
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();
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
);
705 sortedEffectList
.Sort(EffectCompositeOrderComparator());
707 // Get properties that override the *animations* level of the cascade.
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
712 nsCSSPropertyIDSet overriddenProperties
=
713 GetOverriddenProperties(aEffectSet
, aElement
, aPseudoType
);
715 nsCSSPropertyIDSet
& propertiesWithImportantRules
=
716 aEffectSet
.PropertiesWithImportantRules();
717 nsCSSPropertyIDSet
& propertiesForAnimationsLevel
=
718 aEffectSet
.PropertiesForAnimationsLevel();
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
);
727 nsCSSPropertyIDSet prevPropertiesForAnimationsLevel
=
728 propertiesForAnimationsLevel
;
730 propertiesWithImportantRules
.Empty();
731 propertiesForAnimationsLevel
.Empty();
733 nsCSSPropertyIDSet propertiesForTransitionsLevel
;
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();
740 for (const AnimationProperty
& prop
: effect
->Properties()) {
741 if (overriddenProperties
.HasProperty(prop
.mProperty
)) {
742 propertiesWithImportantRules
.AddProperty(prop
.mProperty
);
745 switch (cascadeLevel
) {
746 case EffectCompositor::CascadeLevel::Animations
:
747 propertiesForAnimationsLevel
.AddProperty(prop
.mProperty
);
749 case EffectCompositor::CascadeLevel::Transitions
:
750 propertiesForTransitionsLevel
.AddProperty(prop
.mProperty
);
756 aEffectSet
.MarkCascadeUpdated();
758 nsPresContext
* presContext
= nsContentUtils::GetContextForContent(aElement
);
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
);
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
);
793 void EffectCompositor::SetPerformanceWarning(
794 const nsIFrame
* aFrame
, const nsCSSPropertyIDSet
& aPropertySet
,
795 const AnimationPerformanceWarning
& aWarning
) {
796 EffectSet
* effects
= EffectSet::GetEffectSetForFrame(aFrame
, aPropertySet
);
801 for (KeyframeEffect
* effect
: *effects
) {
802 effect
->SetPerformanceWarning(aPropertySet
, aWarning
);
806 bool EffectCompositor::PreTraverse(ServoTraversalFlags aFlags
) {
807 return PreTraverseInSubtree(aFlags
, nullptr);
810 bool EffectCompositor::PreTraverseInSubtree(ServoTraversalFlags aFlags
,
812 MOZ_ASSERT(NS_IsMainThread());
813 MOZ_ASSERT(!aRoot
|| nsContentUtils::GetPresShellForContent(aRoot
),
814 "Traversal root, if provided, should be bound to a display "
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();
827 AutoRestore
<bool> guard(mIsInPreTraverse
);
828 mIsInPreTraverse
= true;
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
);
838 using ElementsToRestyleIterType
=
839 nsTHashMap
<PseudoElementHashEntry
, bool>::ConstIterator
;
840 auto getNeededRestyleTarget
=
841 [&](const ElementsToRestyleIterType
& aIter
) -> NonOwningAnimationTarget
{
842 NonOwningAnimationTarget returnTarget
;
844 // If aIter.Data() is false, the element only requested a throttled
845 // (skippable) restyle, so we can skip it if flushThrottledRestyles is not
847 if (!flushThrottledRestyles
&& !aIter
.Data()) {
851 const NonOwningAnimationTarget
& target
= aIter
.Key();
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.
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
)) {
864 // Ignore restyles that aren't in the flattened tree subtree rooted at
866 if (aRoot
&& !nsContentUtils::ContentIsFlattenedTreeDescendantOfForStyle(
867 target
.mElement
, aRoot
)) {
871 returnTarget
= target
;
875 bool foundElementsNeedingRestyle
= false;
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
) {
888 EffectSet::GetEffectSet(target
.mElement
, target
.mPseudoType
);
889 if (!effects
|| !effects
->CascadeNeedsUpdate()) {
893 elementsWithCascadeUpdates
.AppendElement(target
);
897 for (const NonOwningAnimationTarget
& target
: elementsWithCascadeUpdates
) {
898 MaybeUpdateCascadeResults(target
.mElement
, target
.mPseudoType
);
900 elementsWithCascadeUpdates
.Clear();
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
) {
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
);
921 foundElementsNeedingRestyle
= true;
924 EffectSet::GetEffectSet(target
.mElement
, target
.mPseudoType
);
926 // Drop EffectSets that have been destroyed.
931 for (KeyframeEffect
* effect
: *effects
) {
932 effect
->GetAnimation()->WillComposeStyle();
935 // Remove the element from the list of elements to restyle since we are
936 // about to restyle it.
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
) {
949 return foundElementsNeedingRestyle
;
952 void EffectCompositor::NoteElementForReducing(
953 const NonOwningAnimationTarget
& aTarget
) {
954 if (!StaticPrefs::dom_animations_api_autoremove_enabled()) {
958 Unused
<< mElementsToReduce
.put(
959 OwningAnimationTarget
{aTarget
.mElement
, aTarget
.mPseudoType
});
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
);
968 sortedEffectList
.Sort(EffectCompositeOrderComparator());
970 nsCSSPropertyIDSet setProperties
;
972 // Iterate in reverse
973 for (auto iter
= sortedEffectList
.rbegin(); iter
!= sortedEffectList
.rend();
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
)) {
982 } else if (animation
.IsReplaceable()) {
983 setProperties
|= effect
.GetPropertySet();
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
);
994 ReduceEffectSet(*effectSet
);
998 mElementsToReduce
.clear();
1001 } // namespace mozilla