Bug 1776444 [wpt PR 34582] - Revert "Add TimedHTMLParserBudget to fieldtrial_testing_...
[gecko.git] / dom / animation / EffectCompositor.cpp
blobb946be864fd23dcbf9751b620eeb6e48f41c2245
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"
9 #include <bitset>
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"
35 #include "nsAtom.h"
36 #include "nsLayoutUtils.h"
37 #include "nsTArray.h"
38 #include "PendingAnimationTracker.h"
40 using mozilla::dom::Animation;
41 using mozilla::dom::Element;
42 using mozilla::dom::KeyframeEffect;
44 namespace mozilla {
46 NS_IMPL_CYCLE_COLLECTION_CLASS(EffectCompositor)
48 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(EffectCompositor)
49 for (auto& elementSet : tmp->mElementsToRestyle) {
50 elementSet.Clear();
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[]",
59 cb.Flags());
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)
67 /* static */
68 bool EffectCompositor::AllowCompositorAnimationsOnFrame(
69 const nsIFrame* aFrame,
70 AnimationPerformanceWarning::Type& aWarning /* out */) {
71 if (aFrame->RefusedAsyncAnimation()) {
72 return false;
75 if (!nsLayoutUtils::AreAsyncAnimationsEnabled()) {
76 if (StaticPrefs::layers_offmainthreadcomposition_log_animations()) {
77 nsCString message;
78 message.AppendLiteral(
79 "Performance warning: Async animations are "
80 "disabled");
81 AnimationUtils::LogAsyncAnimationFailure(message);
83 return false;
86 // Disable async animations if we have a rendering observer that
87 // depends on our content (svg masking, -moz-element etc) so that
88 // it gets updated correctly.
89 nsIContent* content = aFrame->GetContent();
90 while (content) {
91 if (content->HasRenderingObservers()) {
92 aWarning = AnimationPerformanceWarning::Type::HasRenderingObserver;
93 return false;
95 content = content->GetParent();
98 return true;
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
111 // preview.
112 if (aFrame->PresContext()->IsPrintingOrPrintPreview()) {
113 return false;
116 MOZ_ASSERT(
117 aPropertySet.IsSubsetOf(LayerAnimationInfo::GetCSSPropertiesFor(
118 DisplayItemType::TYPE_TRANSFORM)) ||
119 aPropertySet.IsSubsetOf(LayerAnimationInfo::GetCSSPropertiesFor(
120 DisplayItemType::TYPE_OPACITY)) ||
121 aPropertySet.IsSubsetOf(LayerAnimationInfo::GetCSSPropertiesFor(
122 DisplayItemType::TYPE_BACKGROUND_COLOR)),
123 "Should be the subset of transform-like properties, or opacity, "
124 "or background color");
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()) {
131 return false;
134 // First check for newly-started transform animations that should be
135 // synchronized with geometric animations. We need to do this before any
136 // other early returns (the one above is ok) since we can only check this
137 // state when the animation is newly-started.
138 if (aPropertySet.Intersects(LayerAnimationInfo::GetCSSPropertiesFor(
139 DisplayItemType::TYPE_TRANSFORM))) {
140 PendingAnimationTracker* tracker =
141 aFrame->PresContext()->Document()->GetPendingAnimationTracker();
142 if (tracker) {
143 tracker->MarkAnimationsThatMightNeedSynchronization();
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));
154 return false;
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,
179 effectWarning);
180 if (effectWarning != AnimationPerformanceWarning::Type::None) {
181 EffectCompositor::SetPerformanceWarning(
182 aFrame, aPropertySet, AnimationPerformanceWarning(effectWarning));
185 if (matchResult ==
186 KeyframeEffect::MatchForCompositor::NoAndBlockThisProperty) {
187 // For a given |aFrame|, we don't want some animations of |aPropertySet|
188 // to run on the compositor and others to run on the main thread, so if
189 // any need to be synchronized with the main thread, run them all there.
190 if (aMatches) {
191 aMatches->Clear();
193 return false;
196 if (matchResult == KeyframeEffect::MatchForCompositor::No) {
197 continue;
200 if (aMatches) {
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) {
212 aMatches->Clear();
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) {
229 if (!mPresContext) {
230 // Pres context will be null after the effect compositor is disconnected.
231 return;
234 // Ignore animations on orphaned elements and elements in documents without
235 // a pres shell (e.g. XMLHttpRequest responseXML documents).
236 if (!nsContentUtils::GetPresShellForContent(aElement)) {
237 return;
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();
246 } else {
247 // Update hashtable first in case PostRestyleForAnimation mutates it
248 // and invalidates the restyleEntry reference.
249 // (It shouldn't, but just to be sure.)
250 bool skipRestyle = std::exchange(restyleEntry, true);
251 if (!skipRestyle) {
252 PostRestyleForAnimation(aElement, aPseudoType, aCascadeLevel);
256 if (aRestyleType == RestyleType::Layer) {
257 mPresContext->RestyleManager()->IncrementAnimationGeneration();
258 EffectSet* effectSet = EffectSet::GetEffectSet(aElement, aPseudoType);
259 if (effectSet) {
260 effectSet->UpdateAnimationGeneration(mPresContext);
265 void EffectCompositor::PostRestyleForAnimation(dom::Element* aElement,
266 PseudoStyleType aPseudoType,
267 CascadeLevel aCascadeLevel) {
268 if (!mPresContext) {
269 return;
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);
279 if (!element) {
280 return;
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
301 // PresShell.
302 return;
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();
317 if (postedRestyle) {
318 continue;
321 PostRestyleForAnimation(iter.Key().mElement, iter.Key().mPseudoType,
322 cascadeLevel);
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,
357 Element* aElement,
358 PseudoStyleType aPseudoType) {
359 EffectSet* effectSet = EffectSet::GetEffectSet(aElement, aPseudoType);
360 if (!effectSet) {
361 return;
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
366 // result.
367 effectSet->MarkCascadeNeedsUpdate();
369 for (KeyframeEffect* effect : *effectSet) {
370 effect->UpdateProperties(aStyle);
374 namespace {
375 class EffectCompositeOrderComparator {
376 public:
377 bool Equals(const KeyframeEffect* a, const KeyframeEffect* b) const {
378 return a == b;
381 bool LessThan(const KeyframeEffect* a, const KeyframeEffect* b) const {
382 MOZ_ASSERT(a->GetAnimation() && b->GetAnimation());
383 MOZ_ASSERT(
384 Equals(a, b) ||
385 a->GetAnimation()->HasLowerCompositeOrderThan(*b->GetAnimation()) !=
386 b->GetAnimation()->HasLowerCompositeOrderThan(*a->GetAnimation()));
387 return a->GetAnimation()->HasLowerCompositeOrderThan(*b->GetAnimation());
390 } // namespace
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());
411 if (aEffectSet) {
412 propertiesToSkip =
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);
434 if (!effectSet) {
435 return false;
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) {
443 if (isTransition &&
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.
450 continue;
452 sortedEffectList.AppendElement(effect);
455 if (sortedEffectList.IsEmpty()) {
456 return false;
459 sortedEffectList.Sort(EffectCompositeOrderComparator());
461 ComposeSortedEffects(sortedEffectList, effectSet, aCascadeLevel,
462 aAnimationValues);
464 MOZ_ASSERT(effectSet == EffectSet::GetEffectSet(aElement, aPseudoType),
465 "EffectSet should not change while composing style");
467 return true;
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();
478 if (!target) {
479 return false;
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)) {
485 return false;
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
501 : 1);
502 if (effectSet) {
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,
513 aAnimationValues);
515 MOZ_ASSERT(
516 effectSet == EffectSet::GetEffectSet(target.mElement, target.mPseudoType),
517 "EffectSet should not change while composing style");
519 return true;
522 /* static */ dom::Element* EffectCompositor::GetElementToRestyle(
523 dom::Element* aElement, PseudoStyleType aPseudoType) {
524 if (aPseudoType == PseudoStyleType::NotPseudo) {
525 return aElement;
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");
543 return nullptr;
546 bool EffectCompositor::HasPendingStyleUpdates() const {
547 for (auto& elementSet : mElementsToRestyle) {
548 if (elementSet.Count()) {
549 return true;
553 return false;
556 /* static */
557 bool EffectCompositor::HasAnimationsForCompositor(const nsIFrame* aFrame,
558 DisplayItemType aType) {
559 return FindAnimationsForCompositor(
560 aFrame, LayerAnimationInfo::GetCSSPropertiesFor(aType), nullptr);
563 /* static */
564 nsTArray<RefPtr<dom::Animation>> EffectCompositor::GetAnimationsForCompositor(
565 const nsIFrame* aFrame, const nsCSSPropertyIDSet& aPropertySet) {
566 nsTArray<RefPtr<dom::Animation>> result;
568 #ifdef DEBUG
569 bool foundSome =
570 #endif
571 FindAnimationsForCompositor(aFrame, aPropertySet, &result);
572 MOZ_ASSERT(!foundSome || !result.IsEmpty(),
573 "If return value is true, matches array should be non-empty");
575 return result;
578 /* static */
579 void EffectCompositor::ClearIsRunningOnCompositor(const nsIFrame* aFrame,
580 DisplayItemType aType) {
581 EffectSet* effects = EffectSet::GetEffectSetForFrame(aFrame, aType);
582 if (!effects) {
583 return;
586 const nsCSSPropertyIDSet& propertySet =
587 LayerAnimationInfo::GetCSSPropertiesFor(aType);
588 for (KeyframeEffect* effect : *effects) {
589 effect->SetIsRunningOnCompositor(propertySet, false);
593 /* static */
594 void EffectCompositor::MaybeUpdateCascadeResults(Element* aElement,
595 PseudoStyleType aPseudoType) {
596 EffectSet* effects = EffectSet::GetEffectSet(aElement, aPseudoType);
597 if (!effects || !effects->CascadeNeedsUpdate()) {
598 return;
601 UpdateCascadeResults(*effects, aElement, aPseudoType);
603 MOZ_ASSERT(!effects->CascadeNeedsUpdate(), "Failed to update cascade state");
606 /* static */
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)) {
616 return result;
619 nsIContent* content = aFrame->GetContent();
620 if (!content) {
621 return result;
624 if (AnimationUtils::IsSupportedPseudoForAnimations(pseudoType)) {
625 content = content->GetParent();
626 if (!content) {
627 return result;
631 if (!content->IsElement()) {
632 return result;
635 result.emplace(content->AsElement(), pseudoType);
637 return result;
640 /* static */
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) {
649 return result;
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) {
669 break;
674 if (propertiesToTrack.IsEmpty()) {
675 return result;
678 Servo_GetProperties_Overriding_Animation(elementToRestyle, &propertiesToTrack,
679 &result);
680 return result;
683 /* static */
684 void EffectCompositor::UpdateCascadeResults(EffectSet& aEffectSet,
685 Element* aElement,
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();
691 return;
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
705 // cascade applies.
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);
742 break;
743 case EffectCompositor::CascadeLevel::Transitions:
744 propertiesForTransitionsLevel.AddProperty(prop.mProperty);
745 break;
750 aEffectSet.MarkCascadeUpdated();
752 nsPresContext* presContext = nsContentUtils::GetContextForContent(aElement);
753 if (!presContext) {
754 return;
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);
786 /* static */
787 void EffectCompositor::SetPerformanceWarning(
788 const nsIFrame* aFrame, const nsCSSPropertyIDSet& aPropertySet,
789 const AnimationPerformanceWarning& aWarning) {
790 EffectSet* effects = EffectSet::GetEffectSetForFrame(aFrame, aPropertySet);
791 if (!effects) {
792 return;
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,
805 Element* aRoot) {
806 MOZ_ASSERT(NS_IsMainThread());
807 MOZ_ASSERT(!aRoot || nsContentUtils::GetPresShellForContent(aRoot),
808 "Traversal root, if provided, should be bound to a display "
809 "document");
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
840 // true.
841 if (!flushThrottledRestyles && !aIter.Data()) {
842 return returnTarget;
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)) {
855 return returnTarget;
858 // Ignore restyles that aren't in the flattened tree subtree rooted at
859 // aRoot.
860 if (aRoot && !nsContentUtils::ContentIsFlattenedTreeDescendantOfForStyle(
861 target.mElement, aRoot)) {
862 return returnTarget;
865 returnTarget = target;
866 return returnTarget;
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) {
878 continue;
881 EffectSet* effects =
882 EffectSet::GetEffectSet(target.mElement, target.mPseudoType);
883 if (!effects || !effects->CascadeNeedsUpdate()) {
884 continue;
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) {
902 continue;
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;
917 EffectSet* effects =
918 EffectSet::GetEffectSet(target.mElement, target.mPseudoType);
919 if (!effects) {
920 // Drop EffectSets that have been destroyed.
921 iter.Remove();
922 continue;
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.
931 iter.Remove();
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) {
939 elementSet.Clear();
943 return foundElementsNeedingRestyle;
946 void EffectCompositor::NoteElementForReducing(
947 const NonOwningAnimationTarget& aTarget) {
948 if (!StaticPrefs::dom_animations_api_autoremove_enabled()) {
949 return;
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();
968 ++iter) {
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)) {
975 animation.Remove();
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);
987 if (effectSet) {
988 ReduceEffectSet(*effectSet);
992 mElementsToReduce.clear();
995 } // namespace mozilla