Bug 1769952 - Fix running raptor on a Win10-64 VM r=sparky
[gecko.git] / dom / animation / EffectCompositor.cpp
blobb960c53ab4a7ed244018b02e972a354d0e510900
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 (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,
359 Element* aElement,
360 PseudoStyleType aPseudoType) {
361 EffectSet* effectSet = EffectSet::GetEffectSet(aElement, aPseudoType);
362 if (!effectSet) {
363 return;
366 // Style context (Gecko) or computed values (Stylo) change might cause CSS
367 // cascade level, e.g removing !important, so we should update the cascading
368 // result.
369 effectSet->MarkCascadeNeedsUpdate();
371 for (KeyframeEffect* effect : *effectSet) {
372 effect->UpdateProperties(aStyle);
376 namespace {
377 class EffectCompositeOrderComparator {
378 public:
379 bool Equals(const KeyframeEffect* a, const KeyframeEffect* b) const {
380 return a == b;
383 bool LessThan(const KeyframeEffect* a, const KeyframeEffect* b) const {
384 MOZ_ASSERT(a->GetAnimation() && b->GetAnimation());
385 MOZ_ASSERT(
386 Equals(a, b) ||
387 a->GetAnimation()->HasLowerCompositeOrderThan(*b->GetAnimation()) !=
388 b->GetAnimation()->HasLowerCompositeOrderThan(*a->GetAnimation()));
389 return a->GetAnimation()->HasLowerCompositeOrderThan(*b->GetAnimation());
392 } // namespace
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());
413 if (aEffectSet) {
414 propertiesToSkip =
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);
436 if (!effectSet) {
437 return false;
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) {
445 if (isTransition &&
446 effect->GetAnimation()->CascadeLevel() != aCascadeLevel) {
447 // We may need to use transition rules for the animations level for the
448 // case of missing keyframes in animations, but we don't ever need to look
449 // at non-transition levels to build a transition rule. When the effect
450 // set information is out of date (see above), this avoids creating bogus
451 // transition rules, see bug 1605610.
452 continue;
454 sortedEffectList.AppendElement(effect);
457 if (sortedEffectList.IsEmpty()) {
458 return false;
461 sortedEffectList.Sort(EffectCompositeOrderComparator());
463 ComposeSortedEffects(sortedEffectList, effectSet, aCascadeLevel,
464 aAnimationValues);
466 MOZ_ASSERT(effectSet == EffectSet::GetEffectSet(aElement, aPseudoType),
467 "EffectSet should not change while composing style");
469 return true;
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();
480 if (!target) {
481 return false;
484 // Don't try to compose animations for elements in documents without a pres
485 // shell (e.g. XMLHttpRequest documents).
486 if (!nsContentUtils::GetPresShellForContent(target.mElement)) {
487 return false;
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
503 : 1);
504 if (effectSet) {
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,
515 aAnimationValues);
517 MOZ_ASSERT(
518 effectSet == EffectSet::GetEffectSet(target.mElement, target.mPseudoType),
519 "EffectSet should not change while composing style");
521 return true;
524 /* static */ dom::Element* EffectCompositor::GetElementToRestyle(
525 dom::Element* aElement, PseudoStyleType aPseudoType) {
526 if (aPseudoType == PseudoStyleType::NotPseudo) {
527 return aElement;
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");
545 return nullptr;
548 bool EffectCompositor::HasPendingStyleUpdates() const {
549 for (auto& elementSet : mElementsToRestyle) {
550 if (elementSet.Count()) {
551 return true;
555 return false;
558 /* static */
559 bool EffectCompositor::HasAnimationsForCompositor(const nsIFrame* aFrame,
560 DisplayItemType aType) {
561 return FindAnimationsForCompositor(
562 aFrame, LayerAnimationInfo::GetCSSPropertiesFor(aType), nullptr);
565 /* static */
566 nsTArray<RefPtr<dom::Animation>> EffectCompositor::GetAnimationsForCompositor(
567 const nsIFrame* aFrame, const nsCSSPropertyIDSet& aPropertySet) {
568 nsTArray<RefPtr<dom::Animation>> result;
570 #ifdef DEBUG
571 bool foundSome =
572 #endif
573 FindAnimationsForCompositor(aFrame, aPropertySet, &result);
574 MOZ_ASSERT(!foundSome || !result.IsEmpty(),
575 "If return value is true, matches array should be non-empty");
577 return result;
580 /* static */
581 void EffectCompositor::ClearIsRunningOnCompositor(const nsIFrame* aFrame,
582 DisplayItemType aType) {
583 EffectSet* effects = EffectSet::GetEffectSetForFrame(aFrame, aType);
584 if (!effects) {
585 return;
588 const nsCSSPropertyIDSet& propertySet =
589 LayerAnimationInfo::GetCSSPropertiesFor(aType);
590 for (KeyframeEffect* effect : *effects) {
591 effect->SetIsRunningOnCompositor(propertySet, false);
595 /* static */
596 void EffectCompositor::MaybeUpdateCascadeResults(Element* aElement,
597 PseudoStyleType aPseudoType) {
598 EffectSet* effects = EffectSet::GetEffectSet(aElement, aPseudoType);
599 if (!effects || !effects->CascadeNeedsUpdate()) {
600 return;
603 UpdateCascadeResults(*effects, aElement, aPseudoType);
605 MOZ_ASSERT(!effects->CascadeNeedsUpdate(), "Failed to update cascade state");
608 /* static */
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) {
620 return result;
623 nsIContent* content = aFrame->GetContent();
624 if (!content) {
625 return result;
628 if (pseudoType == PseudoStyleType::before ||
629 pseudoType == PseudoStyleType::after ||
630 pseudoType == PseudoStyleType::marker) {
631 content = content->GetParent();
632 if (!content) {
633 return result;
637 if (!content->IsElement()) {
638 return result;
641 result.emplace(content->AsElement(), pseudoType);
643 return result;
646 /* static */
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) {
655 return result;
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) {
675 break;
680 if (propertiesToTrack.IsEmpty()) {
681 return result;
684 Servo_GetProperties_Overriding_Animation(elementToRestyle, &propertiesToTrack,
685 &result);
686 return result;
689 /* static */
690 void EffectCompositor::UpdateCascadeResults(EffectSet& aEffectSet,
691 Element* aElement,
692 PseudoStyleType aPseudoType) {
693 MOZ_ASSERT(EffectSet::GetEffectSet(aElement, aPseudoType) == &aEffectSet,
694 "Effect set should correspond to the specified (pseudo-)element");
695 if (aEffectSet.IsEmpty()) {
696 aEffectSet.MarkCascadeUpdated();
697 return;
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
711 // cascade applies.
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);
748 break;
749 case EffectCompositor::CascadeLevel::Transitions:
750 propertiesForTransitionsLevel.AddProperty(prop.mProperty);
751 break;
756 aEffectSet.MarkCascadeUpdated();
758 nsPresContext* presContext = nsContentUtils::GetContextForContent(aElement);
759 if (!presContext) {
760 return;
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);
792 /* static */
793 void EffectCompositor::SetPerformanceWarning(
794 const nsIFrame* aFrame, const nsCSSPropertyIDSet& aPropertySet,
795 const AnimationPerformanceWarning& aWarning) {
796 EffectSet* effects = EffectSet::GetEffectSetForFrame(aFrame, aPropertySet);
797 if (!effects) {
798 return;
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,
811 Element* aRoot) {
812 MOZ_ASSERT(NS_IsMainThread());
813 MOZ_ASSERT(!aRoot || nsContentUtils::GetPresShellForContent(aRoot),
814 "Traversal root, if provided, should be bound to a display "
815 "document");
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
846 // true.
847 if (!flushThrottledRestyles && !aIter.Data()) {
848 return returnTarget;
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)) {
861 return returnTarget;
864 // Ignore restyles that aren't in the flattened tree subtree rooted at
865 // aRoot.
866 if (aRoot && !nsContentUtils::ContentIsFlattenedTreeDescendantOfForStyle(
867 target.mElement, aRoot)) {
868 return returnTarget;
871 returnTarget = target;
872 return returnTarget;
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) {
884 continue;
887 EffectSet* effects =
888 EffectSet::GetEffectSet(target.mElement, target.mPseudoType);
889 if (!effects || !effects->CascadeNeedsUpdate()) {
890 continue;
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) {
908 continue;
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;
923 EffectSet* effects =
924 EffectSet::GetEffectSet(target.mElement, target.mPseudoType);
925 if (!effects) {
926 // Drop EffectSets that have been destroyed.
927 iter.Remove();
928 continue;
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.
937 iter.Remove();
940 // If this is a full document restyle, then unconditionally clear
941 // elementSet in case there are any elements that didn't match above
942 // because they were moved to a document without a pres shell after
943 // posting an animation restyle.
944 if (!aRoot && flushThrottledRestyles) {
945 elementSet.Clear();
949 return foundElementsNeedingRestyle;
952 void EffectCompositor::NoteElementForReducing(
953 const NonOwningAnimationTarget& aTarget) {
954 if (!StaticPrefs::dom_animations_api_autoremove_enabled()) {
955 return;
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();
974 ++iter) {
975 MOZ_ASSERT(*iter && (*iter)->GetAnimation(),
976 "Effect in an EffectSet should have an animation");
977 KeyframeEffect& effect = **iter;
978 Animation& animation = *effect.GetAnimation();
979 if (animation.IsRemovable() &&
980 effect.GetPropertySet().IsSubsetOf(setProperties)) {
981 animation.Remove();
982 } else if (animation.IsReplaceable()) {
983 setProperties |= effect.GetPropertySet();
988 void EffectCompositor::ReduceAnimations() {
989 for (auto iter = mElementsToReduce.iter(); !iter.done(); iter.next()) {
990 const OwningAnimationTarget& target = iter.get();
991 EffectSet* effectSet =
992 EffectSet::GetEffectSet(target.mElement, target.mPseudoType);
993 if (effectSet) {
994 ReduceEffectSet(*effectSet);
998 mElementsToReduce.clear();
1001 } // namespace mozilla