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