diff options
Diffstat (limited to 'dom/animation/EffectCompositor.cpp')
-rw-r--r-- | dom/animation/EffectCompositor.cpp | 920 |
1 files changed, 920 insertions, 0 deletions
diff --git a/dom/animation/EffectCompositor.cpp b/dom/animation/EffectCompositor.cpp new file mode 100644 index 000000000..c88cabe90 --- /dev/null +++ b/dom/animation/EffectCompositor.cpp @@ -0,0 +1,920 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "EffectCompositor.h" + +#include "mozilla/dom/Animation.h" +#include "mozilla/dom/Element.h" +#include "mozilla/dom/KeyframeEffectReadOnly.h" +#include "mozilla/AnimationComparator.h" +#include "mozilla/AnimationPerformanceWarning.h" +#include "mozilla/AnimationTarget.h" +#include "mozilla/AnimationUtils.h" +#include "mozilla/EffectSet.h" +#include "mozilla/LayerAnimationInfo.h" +#include "mozilla/RestyleManagerHandle.h" +#include "mozilla/RestyleManagerHandleInlines.h" +#include "nsComputedDOMStyle.h" // nsComputedDOMStyle::GetPresShellForContent +#include "nsCSSPropertyIDSet.h" +#include "nsCSSProps.h" +#include "nsIPresShell.h" +#include "nsLayoutUtils.h" +#include "nsRuleNode.h" // For nsRuleNode::ComputePropertiesOverridingAnimation +#include "nsRuleProcessorData.h" // For ElementRuleProcessorData etc. +#include "nsTArray.h" +#include <bitset> +#include <initializer_list> + +using mozilla::dom::Animation; +using mozilla::dom::Element; +using mozilla::dom::KeyframeEffectReadOnly; + +namespace mozilla { + +NS_IMPL_CYCLE_COLLECTION_CLASS(EffectCompositor) + +NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(EffectCompositor) + for (auto& elementSet : tmp->mElementsToRestyle) { + elementSet.Clear(); + } +NS_IMPL_CYCLE_COLLECTION_UNLINK_END + +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(EffectCompositor) + for (auto& elementSet : tmp->mElementsToRestyle) { + for (auto iter = elementSet.Iter(); !iter.Done(); iter.Next()) { + CycleCollectionNoteChild(cb, iter.Key().mElement, + "EffectCompositor::mElementsToRestyle[]", + cb.Flags()); + } + } +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END + +NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(EffectCompositor, AddRef) +NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(EffectCompositor, Release) + +// Helper function to factor out the common logic from +// GetAnimationsForCompositor and HasAnimationsForCompositor. +// +// Takes an optional array to fill with eligible animations. +// +// Returns true if there are eligible animations, false otherwise. +bool +FindAnimationsForCompositor(const nsIFrame* aFrame, + nsCSSPropertyID aProperty, + nsTArray<RefPtr<dom::Animation>>* aMatches /*out*/) +{ + MOZ_ASSERT(!aMatches || aMatches->IsEmpty(), + "Matches array, if provided, should be empty"); + + EffectSet* effects = EffectSet::GetEffectSet(aFrame); + if (!effects || effects->IsEmpty()) { + return false; + } + + // If the property will be added to the animations level of the cascade but + // there is an !important rule for that property in the cascade then the + // animation will not be applied since the !important rule overrides it. + if (effects->PropertiesWithImportantRules().HasProperty(aProperty) && + effects->PropertiesForAnimationsLevel().HasProperty(aProperty)) { + return false; + } + + if (aFrame->RefusedAsyncAnimation()) { + return false; + } + + // The animation cascade will almost always be up-to-date by this point + // but there are some cases such as when we are restoring the refresh driver + // from test control after seeking where it might not be the case. + // + // Those cases are probably not important but just to be safe, let's make + // sure the cascade is up to date since if it *is* up to date, this is + // basically a no-op. + Maybe<NonOwningAnimationTarget> pseudoElement = + EffectCompositor::GetAnimationElementAndPseudoForFrame(aFrame); + if (pseudoElement) { + EffectCompositor::MaybeUpdateCascadeResults(pseudoElement->mElement, + pseudoElement->mPseudoType, + aFrame->StyleContext()); + } + + if (!nsLayoutUtils::AreAsyncAnimationsEnabled()) { + if (nsLayoutUtils::IsAnimationLoggingEnabled()) { + nsCString message; + message.AppendLiteral("Performance warning: Async animations are " + "disabled"); + AnimationUtils::LogAsyncAnimationFailure(message); + } + return false; + } + + // Disable async animations if we have a rendering observer that + // depends on our content (svg masking, -moz-element etc) so that + // it gets updated correctly. + nsIContent* content = aFrame->GetContent(); + while (content) { + if (content->HasRenderingObservers()) { + EffectCompositor::SetPerformanceWarning( + aFrame, aProperty, + AnimationPerformanceWarning( + AnimationPerformanceWarning::Type::HasRenderingObserver)); + return false; + } + content = content->GetParent(); + } + + bool foundSome = false; + for (KeyframeEffectReadOnly* effect : *effects) { + MOZ_ASSERT(effect && effect->GetAnimation()); + Animation* animation = effect->GetAnimation(); + + if (!animation->IsPlayableOnCompositor()) { + continue; + } + + AnimationPerformanceWarning::Type warningType; + if (aProperty == eCSSProperty_transform && + effect->ShouldBlockAsyncTransformAnimations(aFrame, + warningType)) { + if (aMatches) { + aMatches->Clear(); + } + EffectCompositor::SetPerformanceWarning( + aFrame, aProperty, + AnimationPerformanceWarning(warningType)); + return false; + } + + if (!effect->HasEffectiveAnimationOfProperty(aProperty)) { + continue; + } + + if (aMatches) { + aMatches->AppendElement(animation); + } + foundSome = true; + } + + MOZ_ASSERT(!foundSome || !aMatches || !aMatches->IsEmpty(), + "If return value is true, matches array should be non-empty"); + + if (aMatches && foundSome) { + aMatches->Sort(AnimationPtrComparator<RefPtr<dom::Animation>>()); + } + return foundSome; +} + +void +EffectCompositor::RequestRestyle(dom::Element* aElement, + CSSPseudoElementType aPseudoType, + RestyleType aRestyleType, + CascadeLevel aCascadeLevel) +{ + if (!mPresContext) { + // Pres context will be null after the effect compositor is disconnected. + return; + } + + // Ignore animations on orphaned elements. + if (!aElement->IsInComposedDoc()) { + return; + } + + auto& elementsToRestyle = mElementsToRestyle[aCascadeLevel]; + PseudoElementHashEntry::KeyType key = { aElement, aPseudoType }; + + if (aRestyleType == RestyleType::Throttled) { + if (!elementsToRestyle.Contains(key)) { + elementsToRestyle.Put(key, false); + } + mPresContext->Document()->SetNeedStyleFlush(); + } else { + // Get() returns 0 if the element is not found. It will also return + // false if the element is found but does not have a pending restyle. + bool hasPendingRestyle = elementsToRestyle.Get(key); + if (!hasPendingRestyle) { + PostRestyleForAnimation(aElement, aPseudoType, aCascadeLevel); + } + elementsToRestyle.Put(key, true); + } + + if (aRestyleType == RestyleType::Layer) { + // Prompt layers to re-sync their animations. + MOZ_ASSERT(mPresContext->RestyleManager()->IsGecko(), + "stylo: Servo-backed style system should not be using " + "EffectCompositor"); + mPresContext->RestyleManager()->AsGecko()->IncrementAnimationGeneration(); + EffectSet* effectSet = + EffectSet::GetEffectSet(aElement, aPseudoType); + if (effectSet) { + effectSet->UpdateAnimationGeneration(mPresContext); + } + } +} + +void +EffectCompositor::PostRestyleForAnimation(dom::Element* aElement, + CSSPseudoElementType aPseudoType, + CascadeLevel aCascadeLevel) +{ + if (!mPresContext) { + return; + } + + dom::Element* element = GetElementToRestyle(aElement, aPseudoType); + if (!element) { + return; + } + + nsRestyleHint hint = aCascadeLevel == CascadeLevel::Transitions ? + eRestyle_CSSTransitions : + eRestyle_CSSAnimations; + mPresContext->PresShell()->RestyleForAnimation(element, hint); +} + +void +EffectCompositor::PostRestyleForThrottledAnimations() +{ + for (size_t i = 0; i < kCascadeLevelCount; i++) { + CascadeLevel cascadeLevel = CascadeLevel(i); + auto& elementSet = mElementsToRestyle[cascadeLevel]; + + for (auto iter = elementSet.Iter(); !iter.Done(); iter.Next()) { + bool& postedRestyle = iter.Data(); + if (postedRestyle) { + continue; + } + + PostRestyleForAnimation(iter.Key().mElement, + iter.Key().mPseudoType, + cascadeLevel); + postedRestyle = true; + } + } +} + +void +EffectCompositor::UpdateEffectProperties(nsStyleContext* aStyleContext, + dom::Element* aElement, + CSSPseudoElementType aPseudoType) +{ + EffectSet* effectSet = EffectSet::GetEffectSet(aElement, aPseudoType); + if (!effectSet) { + return; + } + + // Style context change might cause CSS cascade level, + // e.g removing !important, so we should update the cascading result. + effectSet->MarkCascadeNeedsUpdate(); + + for (KeyframeEffectReadOnly* effect : *effectSet) { + effect->UpdateProperties(aStyleContext); + } +} + +void +EffectCompositor::MaybeUpdateAnimationRule(dom::Element* aElement, + CSSPseudoElementType aPseudoType, + CascadeLevel aCascadeLevel, + nsStyleContext* aStyleContext) +{ + // First update cascade results since that may cause some elements to + // be marked as needing a restyle. + MaybeUpdateCascadeResults(aElement, aPseudoType, aStyleContext); + + auto& elementsToRestyle = mElementsToRestyle[aCascadeLevel]; + PseudoElementHashEntry::KeyType key = { aElement, aPseudoType }; + + if (!mPresContext || !elementsToRestyle.Contains(key)) { + return; + } + + ComposeAnimationRule(aElement, aPseudoType, aCascadeLevel, + mPresContext->RefreshDriver()->MostRecentRefresh()); + + elementsToRestyle.Remove(key); +} + +nsIStyleRule* +EffectCompositor::GetAnimationRule(dom::Element* aElement, + CSSPseudoElementType aPseudoType, + CascadeLevel aCascadeLevel, + nsStyleContext* aStyleContext) +{ + // NOTE: We need to be careful about early returns in this method where + // we *don't* update mElementsToRestyle. When we get a call to + // RequestRestyle that results in a call to PostRestyleForAnimation, we + // will set a bool flag in mElementsToRestyle indicating that we've + // called PostRestyleForAnimation so we don't need to call it again + // until that restyle happens. During that restyle, if we arrive here + // and *don't* update mElementsToRestyle we'll continue to skip calling + // PostRestyleForAnimation from RequestRestyle. + + if (!mPresContext || !mPresContext->IsDynamic()) { + // For print or print preview, ignore animations. + return nullptr; + } + + MOZ_ASSERT(mPresContext->RestyleManager()->IsGecko(), + "stylo: Servo-backed style system should not be using " + "EffectCompositor"); + if (mPresContext->RestyleManager()->AsGecko()->SkipAnimationRules()) { + // We don't need to worry about updating mElementsToRestyle in this case + // since this is not the animation restyle we requested when we called + // PostRestyleForAnimation (see comment at start of this method). + return nullptr; + } + + MaybeUpdateAnimationRule(aElement, aPseudoType, aCascadeLevel, aStyleContext); + +#ifdef DEBUG + { + auto& elementsToRestyle = mElementsToRestyle[aCascadeLevel]; + PseudoElementHashEntry::KeyType key = { aElement, aPseudoType }; + MOZ_ASSERT(!elementsToRestyle.Contains(key), + "Element should no longer require a restyle after its " + "animation rule has been updated"); + } +#endif + + EffectSet* effectSet = EffectSet::GetEffectSet(aElement, aPseudoType); + if (!effectSet) { + return nullptr; + } + + return effectSet->AnimationRule(aCascadeLevel); +} + +/* static */ dom::Element* +EffectCompositor::GetElementToRestyle(dom::Element* aElement, + CSSPseudoElementType aPseudoType) +{ + if (aPseudoType == CSSPseudoElementType::NotPseudo) { + return aElement; + } + + nsIFrame* primaryFrame = aElement->GetPrimaryFrame(); + if (!primaryFrame) { + return nullptr; + } + nsIFrame* pseudoFrame; + if (aPseudoType == CSSPseudoElementType::before) { + pseudoFrame = nsLayoutUtils::GetBeforeFrame(primaryFrame); + } else if (aPseudoType == CSSPseudoElementType::after) { + pseudoFrame = nsLayoutUtils::GetAfterFrame(primaryFrame); + } else { + NS_NOTREACHED("Should not try to get the element to restyle for a pseudo " + "other that :before or :after"); + return nullptr; + } + if (!pseudoFrame) { + return nullptr; + } + return pseudoFrame->GetContent()->AsElement(); +} + +bool +EffectCompositor::HasPendingStyleUpdates() const +{ + for (auto& elementSet : mElementsToRestyle) { + if (elementSet.Count()) { + return true; + } + } + + return false; +} + +bool +EffectCompositor::HasThrottledStyleUpdates() const +{ + for (auto& elementSet : mElementsToRestyle) { + for (auto iter = elementSet.ConstIter(); !iter.Done(); iter.Next()) { + if (!iter.Data()) { + return true; + } + } + } + + return false; +} + +void +EffectCompositor::AddStyleUpdatesTo(RestyleTracker& aTracker) +{ + if (!mPresContext) { + return; + } + + for (size_t i = 0; i < kCascadeLevelCount; i++) { + CascadeLevel cascadeLevel = CascadeLevel(i); + auto& elementSet = mElementsToRestyle[cascadeLevel]; + + // Copy the list of elements to restyle to a separate array that we can + // iterate over. This is because we need to call MaybeUpdateCascadeResults + // on each element, but doing that can mutate elementSet. In this case + // it will only mutate the bool value associated with each element in the + // set but even doing that will cause assertions in PLDHashTable to fail + // if we are iterating over the hashtable at the same time. + nsTArray<PseudoElementHashEntry::KeyType> elementsToRestyle( + elementSet.Count()); + for (auto iter = elementSet.Iter(); !iter.Done(); iter.Next()) { + // Skip animations on elements that have been orphaned since they + // requested a restyle. + if (iter.Key().mElement->IsInComposedDoc()) { + elementsToRestyle.AppendElement(iter.Key()); + } + } + + for (auto& pseudoElem : elementsToRestyle) { + MaybeUpdateCascadeResults(pseudoElem.mElement, + pseudoElem.mPseudoType, + nullptr); + + ComposeAnimationRule(pseudoElem.mElement, + pseudoElem.mPseudoType, + cascadeLevel, + mPresContext->RefreshDriver()->MostRecentRefresh()); + + dom::Element* elementToRestyle = + GetElementToRestyle(pseudoElem.mElement, pseudoElem.mPseudoType); + if (elementToRestyle) { + nsRestyleHint rshint = cascadeLevel == CascadeLevel::Transitions ? + eRestyle_CSSTransitions : + eRestyle_CSSAnimations; + aTracker.AddPendingRestyle(elementToRestyle, rshint, nsChangeHint(0)); + } + } + + elementSet.Clear(); + // Note: mElement pointers in elementsToRestyle might now dangle + } +} + +/* static */ bool +EffectCompositor::HasAnimationsForCompositor(const nsIFrame* aFrame, + nsCSSPropertyID aProperty) +{ + return FindAnimationsForCompositor(aFrame, aProperty, nullptr); +} + +/* static */ nsTArray<RefPtr<dom::Animation>> +EffectCompositor::GetAnimationsForCompositor(const nsIFrame* aFrame, + nsCSSPropertyID aProperty) +{ + nsTArray<RefPtr<dom::Animation>> result; + +#ifdef DEBUG + bool foundSome = +#endif + FindAnimationsForCompositor(aFrame, aProperty, &result); + MOZ_ASSERT(!foundSome || !result.IsEmpty(), + "If return value is true, matches array should be non-empty"); + + return result; +} + +/* static */ void +EffectCompositor::ClearIsRunningOnCompositor(const nsIFrame *aFrame, + nsCSSPropertyID aProperty) +{ + EffectSet* effects = EffectSet::GetEffectSet(aFrame); + if (!effects) { + return; + } + + for (KeyframeEffectReadOnly* effect : *effects) { + effect->SetIsRunningOnCompositor(aProperty, false); + } +} + +/* static */ void +EffectCompositor::MaybeUpdateCascadeResults(Element* aElement, + CSSPseudoElementType aPseudoType, + nsStyleContext* aStyleContext) +{ + EffectSet* effects = EffectSet::GetEffectSet(aElement, aPseudoType); + if (!effects || !effects->CascadeNeedsUpdate()) { + return; + } + + nsStyleContext* styleContext = aStyleContext; + if (!styleContext) { + dom::Element* elementToRestyle = GetElementToRestyle(aElement, aPseudoType); + if (elementToRestyle) { + nsIFrame* frame = elementToRestyle->GetPrimaryFrame(); + if (frame) { + styleContext = frame->StyleContext(); + } + } + } + UpdateCascadeResults(*effects, aElement, aPseudoType, styleContext); + + MOZ_ASSERT(!effects->CascadeNeedsUpdate(), "Failed to update cascade state"); +} + +namespace { + class EffectCompositeOrderComparator { + public: + bool Equals(const KeyframeEffectReadOnly* a, + const KeyframeEffectReadOnly* b) const + { + return a == b; + } + + bool LessThan(const KeyframeEffectReadOnly* a, + const KeyframeEffectReadOnly* b) const + { + MOZ_ASSERT(a->GetAnimation() && b->GetAnimation()); + MOZ_ASSERT( + Equals(a, b) || + a->GetAnimation()->HasLowerCompositeOrderThan(*b->GetAnimation()) != + b->GetAnimation()->HasLowerCompositeOrderThan(*a->GetAnimation())); + return a->GetAnimation()->HasLowerCompositeOrderThan(*b->GetAnimation()); + } + }; +} + +/* static */ void +EffectCompositor::UpdateCascadeResults(Element* aElement, + CSSPseudoElementType aPseudoType, + nsStyleContext* aStyleContext) +{ + EffectSet* effects = EffectSet::GetEffectSet(aElement, aPseudoType); + if (!effects) { + return; + } + + UpdateCascadeResults(*effects, aElement, aPseudoType, aStyleContext); +} + +/* static */ Maybe<NonOwningAnimationTarget> +EffectCompositor::GetAnimationElementAndPseudoForFrame(const nsIFrame* aFrame) +{ + // Always return the same object to benefit from return-value optimization. + Maybe<NonOwningAnimationTarget> result; + + CSSPseudoElementType pseudoType = + aFrame->StyleContext()->GetPseudoType(); + + if (pseudoType != CSSPseudoElementType::NotPseudo && + pseudoType != CSSPseudoElementType::before && + pseudoType != CSSPseudoElementType::after) { + return result; + } + + nsIContent* content = aFrame->GetContent(); + if (!content) { + return result; + } + + if (pseudoType == CSSPseudoElementType::before || + pseudoType == CSSPseudoElementType::after) { + content = content->GetParent(); + if (!content) { + return result; + } + } + + if (!content->IsElement()) { + return result; + } + + result.emplace(content->AsElement(), pseudoType); + + return result; +} + +/* static */ void +EffectCompositor::ComposeAnimationRule(dom::Element* aElement, + CSSPseudoElementType aPseudoType, + CascadeLevel aCascadeLevel, + TimeStamp aRefreshTime) +{ + EffectSet* effects = EffectSet::GetEffectSet(aElement, aPseudoType); + if (!effects) { + return; + } + + // The caller is responsible for calling MaybeUpdateCascadeResults first. + MOZ_ASSERT(!effects->CascadeNeedsUpdate(), + "Animation cascade out of date when composing animation rule"); + + // Get a list of effects sorted by composite order. + nsTArray<KeyframeEffectReadOnly*> sortedEffectList(effects->Count()); + for (KeyframeEffectReadOnly* effect : *effects) { + sortedEffectList.AppendElement(effect); + } + sortedEffectList.Sort(EffectCompositeOrderComparator()); + + RefPtr<AnimValuesStyleRule>& animationRule = + effects->AnimationRule(aCascadeLevel); + animationRule = nullptr; + + // If multiple animations affect the same property, animations with higher + // composite order (priority) override or add or animations with lower + // priority except properties in propertiesToSkip. + const nsCSSPropertyIDSet& propertiesToSkip = + aCascadeLevel == CascadeLevel::Animations + ? effects->PropertiesForAnimationsLevel().Invert() + : effects->PropertiesForAnimationsLevel(); + for (KeyframeEffectReadOnly* effect : sortedEffectList) { + effect->GetAnimation()->ComposeStyle(animationRule, propertiesToSkip); + } + + MOZ_ASSERT(effects == EffectSet::GetEffectSet(aElement, aPseudoType), + "EffectSet should not change while composing style"); + + effects->UpdateAnimationRuleRefreshTime(aCascadeLevel, aRefreshTime); +} + +/* static */ void +EffectCompositor::GetOverriddenProperties(nsStyleContext* aStyleContext, + EffectSet& aEffectSet, + nsCSSPropertyIDSet& + aPropertiesOverridden) +{ + AutoTArray<nsCSSPropertyID, LayerAnimationInfo::kRecords> propertiesToTrack; + { + nsCSSPropertyIDSet propertiesToTrackAsSet; + for (KeyframeEffectReadOnly* effect : aEffectSet) { + for (const AnimationProperty& property : effect->Properties()) { + if (nsCSSProps::PropHasFlags(property.mProperty, + CSS_PROPERTY_CAN_ANIMATE_ON_COMPOSITOR) && + !propertiesToTrackAsSet.HasProperty(property.mProperty)) { + propertiesToTrackAsSet.AddProperty(property.mProperty); + propertiesToTrack.AppendElement(property.mProperty); + } + } + // Skip iterating over the rest of the effects if we've already + // found all the compositor-animatable properties. + if (propertiesToTrack.Length() == LayerAnimationInfo::kRecords) { + break; + } + } + } + + if (propertiesToTrack.IsEmpty()) { + return; + } + + nsRuleNode::ComputePropertiesOverridingAnimation(propertiesToTrack, + aStyleContext, + aPropertiesOverridden); +} + +/* static */ void +EffectCompositor::UpdateCascadeResults(EffectSet& aEffectSet, + Element* aElement, + CSSPseudoElementType aPseudoType, + nsStyleContext* aStyleContext) +{ + MOZ_ASSERT(EffectSet::GetEffectSet(aElement, aPseudoType) == &aEffectSet, + "Effect set should correspond to the specified (pseudo-)element"); + if (aEffectSet.IsEmpty()) { + aEffectSet.MarkCascadeUpdated(); + return; + } + + // Get a list of effects sorted by composite order. + nsTArray<KeyframeEffectReadOnly*> sortedEffectList(aEffectSet.Count()); + for (KeyframeEffectReadOnly* effect : aEffectSet) { + sortedEffectList.AppendElement(effect); + } + sortedEffectList.Sort(EffectCompositeOrderComparator()); + + // Get properties that override the *animations* level of the cascade. + // + // We only do this for properties that we can animate on the compositor + // since we will apply other properties on the main thread where the usual + // cascade applies. + nsCSSPropertyIDSet overriddenProperties; + if (aStyleContext) { + GetOverriddenProperties(aStyleContext, aEffectSet, overriddenProperties); + } + + // Returns a bitset the represents which properties from + // LayerAnimationInfo::sRecords are present in |aPropertySet|. + auto compositorPropertiesInSet = + [](nsCSSPropertyIDSet& aPropertySet) -> + std::bitset<LayerAnimationInfo::kRecords> { + std::bitset<LayerAnimationInfo::kRecords> result; + for (size_t i = 0; i < LayerAnimationInfo::kRecords; i++) { + if (aPropertySet.HasProperty( + LayerAnimationInfo::sRecords[i].mProperty)) { + result.set(i); + } + } + return result; + }; + + nsCSSPropertyIDSet& propertiesWithImportantRules = + aEffectSet.PropertiesWithImportantRules(); + nsCSSPropertyIDSet& propertiesForAnimationsLevel = + aEffectSet.PropertiesForAnimationsLevel(); + + // Record which compositor-animatable properties were originally set so we can + // compare for changes later. + std::bitset<LayerAnimationInfo::kRecords> + prevCompositorPropertiesWithImportantRules = + compositorPropertiesInSet(propertiesWithImportantRules); + std::bitset<LayerAnimationInfo::kRecords> + prevCompositorPropertiesForAnimationsLevel = + compositorPropertiesInSet(propertiesForAnimationsLevel); + + propertiesWithImportantRules.Empty(); + propertiesForAnimationsLevel.Empty(); + + bool hasCompositorPropertiesForTransition = false; + + for (const KeyframeEffectReadOnly* effect : sortedEffectList) { + MOZ_ASSERT(effect->GetAnimation(), + "Effects on a target element should have an Animation"); + CascadeLevel cascadeLevel = effect->GetAnimation()->CascadeLevel(); + + for (const AnimationProperty& prop : effect->Properties()) { + if (overriddenProperties.HasProperty(prop.mProperty)) { + propertiesWithImportantRules.AddProperty(prop.mProperty); + } + if (cascadeLevel == EffectCompositor::CascadeLevel::Animations) { + propertiesForAnimationsLevel.AddProperty(prop.mProperty); + } + + if (nsCSSProps::PropHasFlags(prop.mProperty, + CSS_PROPERTY_CAN_ANIMATE_ON_COMPOSITOR) && + cascadeLevel == EffectCompositor::CascadeLevel::Transitions) { + hasCompositorPropertiesForTransition = true; + } + } + } + + aEffectSet.MarkCascadeUpdated(); + + nsPresContext* presContext = GetPresContext(aElement); + if (!presContext) { + return; + } + + // If properties for compositor are newly overridden by !important rules, or + // released from being overridden by !important rules, we need to update + // layers for animations level because it's a trigger to send animations to + // the compositor or pull animations back from the compositor. + if (prevCompositorPropertiesWithImportantRules != + compositorPropertiesInSet(propertiesWithImportantRules)) { + presContext->EffectCompositor()-> + RequestRestyle(aElement, aPseudoType, + EffectCompositor::RestyleType::Layer, + EffectCompositor::CascadeLevel::Animations); + } + // If we have transition properties for compositor and if the same propery + // for animations level is newly added or removed, we need to update layers + // for transitions level because composite order has been changed now. + if (hasCompositorPropertiesForTransition && + prevCompositorPropertiesForAnimationsLevel != + compositorPropertiesInSet(propertiesForAnimationsLevel)) { + presContext->EffectCompositor()-> + RequestRestyle(aElement, aPseudoType, + EffectCompositor::RestyleType::Layer, + EffectCompositor::CascadeLevel::Transitions); + } +} + +/* static */ nsPresContext* +EffectCompositor::GetPresContext(Element* aElement) +{ + MOZ_ASSERT(aElement); + nsIPresShell* shell = nsComputedDOMStyle::GetPresShellForContent(aElement); + if (!shell) { + return nullptr; + } + return shell->GetPresContext(); +} + +/* static */ void +EffectCompositor::SetPerformanceWarning( + const nsIFrame *aFrame, + nsCSSPropertyID aProperty, + const AnimationPerformanceWarning& aWarning) +{ + EffectSet* effects = EffectSet::GetEffectSet(aFrame); + if (!effects) { + return; + } + + for (KeyframeEffectReadOnly* effect : *effects) { + effect->SetPerformanceWarning(aProperty, aWarning); + } +} + +// --------------------------------------------------------- +// +// Nested class: AnimationStyleRuleProcessor +// +// --------------------------------------------------------- + +NS_IMPL_ISUPPORTS(EffectCompositor::AnimationStyleRuleProcessor, + nsIStyleRuleProcessor) + +nsRestyleHint +EffectCompositor::AnimationStyleRuleProcessor::HasStateDependentStyle( + StateRuleProcessorData* aData) +{ + return nsRestyleHint(0); +} + +nsRestyleHint +EffectCompositor::AnimationStyleRuleProcessor::HasStateDependentStyle( + PseudoElementStateRuleProcessorData* aData) +{ + return nsRestyleHint(0); +} + +bool +EffectCompositor::AnimationStyleRuleProcessor::HasDocumentStateDependentStyle( + StateRuleProcessorData* aData) +{ + return false; +} + +nsRestyleHint +EffectCompositor::AnimationStyleRuleProcessor::HasAttributeDependentStyle( + AttributeRuleProcessorData* aData, + RestyleHintData& aRestyleHintDataResult) +{ + return nsRestyleHint(0); +} + +bool +EffectCompositor::AnimationStyleRuleProcessor::MediumFeaturesChanged( + nsPresContext* aPresContext) +{ + return false; +} + +void +EffectCompositor::AnimationStyleRuleProcessor::RulesMatching( + ElementRuleProcessorData* aData) +{ + nsIStyleRule *rule = + mCompositor->GetAnimationRule(aData->mElement, + CSSPseudoElementType::NotPseudo, + mCascadeLevel, + nullptr); + if (rule) { + aData->mRuleWalker->Forward(rule); + aData->mRuleWalker->CurrentNode()->SetIsAnimationRule(); + } +} + +void +EffectCompositor::AnimationStyleRuleProcessor::RulesMatching( + PseudoElementRuleProcessorData* aData) +{ + if (aData->mPseudoType != CSSPseudoElementType::before && + aData->mPseudoType != CSSPseudoElementType::after) { + return; + } + + nsIStyleRule *rule = + mCompositor->GetAnimationRule(aData->mElement, + aData->mPseudoType, + mCascadeLevel, + nullptr); + if (rule) { + aData->mRuleWalker->Forward(rule); + aData->mRuleWalker->CurrentNode()->SetIsAnimationRule(); + } +} + +void +EffectCompositor::AnimationStyleRuleProcessor::RulesMatching( + AnonBoxRuleProcessorData* aData) +{ +} + +#ifdef MOZ_XUL +void +EffectCompositor::AnimationStyleRuleProcessor::RulesMatching( + XULTreeRuleProcessorData* aData) +{ +} +#endif + +size_t +EffectCompositor::AnimationStyleRuleProcessor::SizeOfExcludingThis( + MallocSizeOf aMallocSizeOf) const +{ + return 0; +} + +size_t +EffectCompositor::AnimationStyleRuleProcessor::SizeOfIncludingThis( + MallocSizeOf aMallocSizeOf) const +{ + return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf); +} + +} // namespace mozilla |