/* -*- 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/. */ #ifndef mozilla_dom_KeyframeEffect_h #define mozilla_dom_KeyframeEffect_h #include "nsChangeHint.h" #include "nsCSSPropertyID.h" #include "nsCSSPropertyIDSet.h" #include "nsCSSValue.h" #include "nsCycleCollectionParticipant.h" #include "nsRefPtrHashtable.h" #include "nsTArray.h" #include "nsWrapperCache.h" #include "mozilla/AnimationPerformanceWarning.h" #include "mozilla/AnimationPropertySegment.h" #include "mozilla/AnimationTarget.h" #include "mozilla/Attributes.h" #include "mozilla/EffectCompositor.h" #include "mozilla/Keyframe.h" #include "mozilla/KeyframeEffectParams.h" #include "mozilla/PostRestyleMode.h" // StyleLockedDeclarationBlock and associated RefPtrTraits #include "mozilla/ServoBindingTypes.h" #include "mozilla/StyleAnimationValue.h" #include "mozilla/dom/AnimationEffect.h" #include "mozilla/dom/BindingDeclarations.h" struct JSContext; class JSObject; class nsIContent; class nsIFrame; namespace mozilla { class AnimValuesStyleRule; class ErrorResult; struct AnimationRule; struct TimingParams; class EffectSet; class ComputedStyle; class PresShell; namespace dom { class Element; class GlobalObject; class UnrestrictedDoubleOrKeyframeAnimationOptions; class UnrestrictedDoubleOrKeyframeEffectOptions; enum class IterationCompositeOperation : uint8_t; enum class CompositeOperation : uint8_t; struct AnimationPropertyDetails; } // namespace dom struct AnimationProperty { nsCSSPropertyID mProperty = eCSSProperty_UNKNOWN; // If true, the propery is currently being animated on the compositor. // // Note that when the owning Animation requests a non-throttled restyle, in // between calling RequestRestyle on its EffectCompositor and when the // restyle is performed, this member may temporarily become false even if // the animation remains on the layer after the restyle. // // **NOTE**: This member is not included when comparing AnimationProperty // objects for equality. bool mIsRunningOnCompositor = false; Maybe mPerformanceWarning; nsTArray mSegments; // The copy constructor/assignment doesn't copy mIsRunningOnCompositor and // mPerformanceWarning. AnimationProperty() = default; AnimationProperty(const AnimationProperty& aOther) : mProperty(aOther.mProperty), mSegments(aOther.mSegments.Clone()) {} AnimationProperty& operator=(const AnimationProperty& aOther) { mProperty = aOther.mProperty; mSegments = aOther.mSegments.Clone(); return *this; } // NOTE: This operator does *not* compare the mIsRunningOnCompositor member. // This is because AnimationProperty objects are compared when recreating // CSS animations to determine if mutation observer change records need to // be created or not. However, at the point when these objects are compared // the mIsRunningOnCompositor will not have been set on the new objects so // we ignore this member to avoid generating spurious change records. bool operator==(const AnimationProperty& aOther) const { return mProperty == aOther.mProperty && mSegments == aOther.mSegments; } bool operator!=(const AnimationProperty& aOther) const { return !(*this == aOther); } void SetPerformanceWarning(const AnimationPerformanceWarning& aWarning, const dom::Element* aElement); }; namespace dom { class Animation; class Document; class KeyframeEffect : public AnimationEffect { public: KeyframeEffect(Document* aDocument, OwningAnimationTarget&& aTarget, TimingParams&& aTiming, const KeyframeEffectParams& aOptions); KeyframeEffect(Document* aDocument, OwningAnimationTarget&& aTarget, const KeyframeEffect& aOther); NS_DECL_ISUPPORTS_INHERITED NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_INHERITED(KeyframeEffect, AnimationEffect) virtual JSObject* WrapObject(JSContext* aCx, JS::Handle aGivenProto) override; KeyframeEffect* AsKeyframeEffect() override { return this; } bool IsValidTransition() const { return Properties().Length() == 1 && Properties()[0].mSegments.Length() == 1; } // KeyframeEffect interface static already_AddRefed Constructor( const GlobalObject& aGlobal, Element* aTarget, JS::Handle aKeyframes, const UnrestrictedDoubleOrKeyframeEffectOptions& aOptions, ErrorResult& aRv); static already_AddRefed Constructor( const GlobalObject& aGlobal, KeyframeEffect& aSource, ErrorResult& aRv); // Variant of Constructor that accepts a KeyframeAnimationOptions object // for use with for Animatable.animate. // Not exposed to content. static already_AddRefed Constructor( const GlobalObject& aGlobal, Element* aTarget, JS::Handle aKeyframes, const UnrestrictedDoubleOrKeyframeAnimationOptions& aOptions, ErrorResult& aRv); Element* GetTarget() const { return mTarget.mElement.get(); } NonOwningAnimationTarget GetAnimationTarget() const { return NonOwningAnimationTarget(mTarget.mElement, mTarget.mPseudoType); } void GetPseudoElement(nsAString& aRetVal) const { if (mTarget.mPseudoType == PseudoStyleType::NotPseudo) { SetDOMStringToNull(aRetVal); return; } aRetVal = nsCSSPseudoElements::PseudoTypeAsString(mTarget.mPseudoType); } // These two setters call GetTargetComputedStyle which is not safe to use when // we are in the middle of updating style. If we need to use this when // updating style, we should pass the ComputedStyle into this method and use // that to update the properties rather than calling // GetComputedStyle. void SetTarget(Element* aTarget) { UpdateTarget(aTarget, mTarget.mPseudoType); } void SetPseudoElement(const nsAString& aPseudoElement, ErrorResult& aRv); void GetKeyframes(JSContext* aCx, nsTArray& aResult, ErrorResult& aRv) const; void GetProperties(nsTArray& aProperties, ErrorResult& aRv) const; IterationCompositeOperation IterationComposite() const; void SetIterationComposite( const IterationCompositeOperation& aIterationComposite); CompositeOperation Composite() const; virtual void SetComposite(const CompositeOperation& aComposite); void SetCompositeFromStyle(const CompositeOperation& aComposite) { KeyframeEffect::SetComposite(aComposite); } void NotifySpecifiedTimingUpdated(); void NotifyAnimationTimingUpdated(PostRestyleMode aPostRestyle); void RequestRestyle(EffectCompositor::RestyleType aRestyleType); void SetAnimation(Animation* aAnimation) override; virtual void SetKeyframes(JSContext* aContext, JS::Handle aKeyframes, ErrorResult& aRv); void SetKeyframes(nsTArray&& aKeyframes, const ComputedStyle* aStyle, const AnimationTimeline* aTimeline); // Replace the start value of the transition. This is used for updating // transitions running on the compositor. void ReplaceTransitionStartValue(AnimationValue&& aStartValue); // Returns the set of properties affected by this effect regardless of // whether any of these properties is overridden by an !important rule. nsCSSPropertyIDSet GetPropertySet() const; // Returns true if the effect includes a property in |aPropertySet| regardless // of whether any property in the set is overridden by an !important rule. bool HasAnimationOfPropertySet(const nsCSSPropertyIDSet& aPropertySet) const { return GetPropertySet().Intersects(aPropertySet); } // GetEffectiveAnimationOfProperty returns AnimationProperty corresponding // to a given CSS property if the effect includes the property and the // property is not overridden by !important rules. // Also EffectiveAnimationOfProperty returns true under the same condition. // // |aEffect| should be the EffectSet containing this KeyframeEffect since // this function is typically called for all KeyframeEffects on an element // so that we can avoid multiple calls of EffectSet::GetEffect(). // // Note that does not consider the interaction between related transform // properties where an !important rule on another transform property may // cause all transform properties to be run on the main thread. That check is // performed by GetPropertiesForCompositor. bool HasEffectiveAnimationOfProperty(nsCSSPropertyID aProperty, const EffectSet& aEffect) const { return GetEffectiveAnimationOfProperty(aProperty, aEffect) != nullptr; } const AnimationProperty* GetEffectiveAnimationOfProperty( nsCSSPropertyID aProperty, const EffectSet& aEffect) const; // Similar to HasEffectiveAnimationOfProperty, above, but for // an nsCSSPropertyIDSet. Returns true if this keyframe effect has at least // one property in |aPropertySet| that is not overridden by an !important // rule. // // Note that does not consider the interaction between related transform // properties where an !important rule on another transform property may // cause all transform properties to be run on the main thread. That check is // performed by GetPropertiesForCompositor. bool HasEffectiveAnimationOfPropertySet( const nsCSSPropertyIDSet& aPropertySet, const EffectSet& aEffectSet) const; // Returns all the effective animated CSS properties that can be animated on // the compositor and are not overridden by a higher cascade level. // // NOTE: This function is basically called for all KeyframeEffects on an // element thus it takes |aEffects| to avoid multiple calls of // EffectSet::GetEffect(). // // NOTE(2): This function does NOT check that animations are permitted on // |aFrame|. It is the responsibility of the caller to first call // EffectCompositor::AllowCompositorAnimationsOnFrame for |aFrame|, or use // nsLayoutUtils::GetAnimationPropertiesForCompositor instead. nsCSSPropertyIDSet GetPropertiesForCompositor(EffectSet& aEffects, const nsIFrame* aFrame) const; const nsTArray& Properties() const { return mProperties; } // Update |mProperties| by recalculating from |mKeyframes| using // |aComputedStyle| to resolve specified values. // Note: we use |aTimeline| to check if we need to ensure the base styles. // If it is nullptr, we use the timeline from |mAnimation|. void UpdateProperties(const ComputedStyle* aStyle, const AnimationTimeline* aTimeline = nullptr); // Update various bits of state related to running ComposeStyle(). // We need to update this outside ComposeStyle() because we should avoid // mutating any state in ComposeStyle() since it might be called during // parallel traversal. void WillComposeStyle(); // Updates |aComposeResult| with the animation values produced by this // AnimationEffect for the current time except any properties contained // in |aPropertiesToSkip|. void ComposeStyle(StyleAnimationValueMap& aComposeResult, const nsCSSPropertyIDSet& aPropertiesToSkip); // Returns true if at least one property is being animated on compositor. bool IsRunningOnCompositor() const; void SetIsRunningOnCompositor(nsCSSPropertyID aProperty, bool aIsRunning); void SetIsRunningOnCompositor(const nsCSSPropertyIDSet& aPropertySet, bool aIsRunning); void ResetIsRunningOnCompositor(); void ResetPartialPrerendered(); // Returns true if this effect, applied to |aFrame|, contains properties // that mean we shouldn't run transform compositor animations on this element. // // For example, if we have an animation of geometric properties like 'left' // and 'top' on an element, we force all 'transform' animations running at // the same time on the same element to run on the main thread. // // When returning true, |aPerformanceWarning| stores the reason why // we shouldn't run the transform animations. bool ShouldBlockAsyncTransformAnimations( const nsIFrame* aFrame, const nsCSSPropertyIDSet& aPropertySet, AnimationPerformanceWarning::Type& aPerformanceWarning /* out */) const; bool HasGeometricProperties() const; bool AffectsGeometry() const override { return mTarget && HasGeometricProperties(); } Document* GetRenderedDocument() const; PresShell* GetPresShell() const; // Associates a warning with the animated property set on the specified frame // indicating why, for example, the property could not be animated on the // compositor. |aParams| and |aParamsLength| are optional parameters which // will be used to generate a localized message for devtools. void SetPerformanceWarning(const nsCSSPropertyIDSet& aPropertySet, const AnimationPerformanceWarning& aWarning); // Cumulative change hint on each segment for each property. // This is used for deciding the animation is paint-only. void CalculateCumulativeChangeHint(const ComputedStyle* aStyle); // Returns true if all of animation properties' change hints // can ignore painting if the animation is not visible. // See nsChangeHint_Hints_CanIgnoreIfNotVisible in nsChangeHint.h // in detail which change hint can be ignored. bool CanIgnoreIfNotVisible() const; // Returns true if the effect is current state and has scale animation. // |aFrame| is used for calculation of scale values. bool ContainsAnimatedScale(const nsIFrame* aFrame) const; AnimationValue BaseStyle(nsCSSPropertyID aProperty) const { AnimationValue result; bool hasProperty = false; // We cannot use getters_AddRefs on StyleAnimationValue because it is // an incomplete type, so Get() doesn't work. Instead, use GetWeak, and // then assign the raw pointer to a RefPtr. result.mServo = mBaseValues.GetWeak(aProperty, &hasProperty); MOZ_ASSERT(hasProperty || result.IsNull()); return result; } enum class MatchForCompositor { // This animation matches and should run on the compositor if possible. Yes, // This (not currently playing) animation matches and can be run on the // compositor if there are other animations for this property that return // 'Yes'. IfNeeded, // This animation does not match or can't be run on the compositor. No, // This animation does not match or can't be run on the compositor and, // furthermore, its presence means we should not run any animations for this // property on the compositor. NoAndBlockThisProperty }; MatchForCompositor IsMatchForCompositor( const nsCSSPropertyIDSet& aPropertySet, const nsIFrame* aFrame, const EffectSet& aEffects, AnimationPerformanceWarning::Type& aPerformanceWarning /* out */) const; static bool HasComputedTimingChanged( const ComputedTiming& aComputedTiming, IterationCompositeOperation aIterationComposite, const Nullable& aProgressOnLastCompose, uint64_t aCurrentIterationOnLastCompose); bool HasOpacityChange() const { return mCumulativeChangeHint & nsChangeHint_UpdateOpacityLayer; } protected: ~KeyframeEffect() override = default; template static already_AddRefed ConstructKeyframeEffect( const GlobalObject& aGlobal, Element* aTarget, JS::Handle aKeyframes, const OptionsType& aOptions, ErrorResult& aRv); // Build properties by recalculating from |mKeyframes| using |aComputedStyle| // to resolve specified values. This function also applies paced spacing if // needed. nsTArray BuildProperties(const ComputedStyle* aStyle); // Helper for SetTarget() and SetPseudoElement(). void UpdateTarget(Element* aElement, PseudoStyleType aPseudoType); // This effect is registered with its target element so long as: // // (a) It has a target element, and // (b) It is "relevant" (i.e. yet to finish but not idle, or finished but // filling forwards) // // As a result, we need to make sure this gets called whenever anything // changes with regards to this effects's timing including changes to the // owning Animation's timing. void UpdateTargetRegistration(); // Remove the current effect target from its EffectSet. void UnregisterTarget(); // Looks up the ComputedStyle associated with the target element, if any. // We need to be careful to *not* call this when we are updating the style // context. That's because calling GetComputedStyle when we are in the process // of building a ComputedStyle may trigger various forms of infinite // recursion. enum class Flush { Style, None, }; already_AddRefed GetTargetComputedStyle(Flush) const; // A wrapper for marking cascade update according to the current // target and its effectSet. void MarkCascadeNeedsUpdate(); void EnsureBaseStyles(const ComputedStyle* aComputedValues, const nsTArray& aProperties, const AnimationTimeline* aTimeline, bool* aBaseStylesChanged); void EnsureBaseStyle(const AnimationProperty& aProperty, nsPresContext* aPresContext, const ComputedStyle* aComputedValues, const AnimationTimeline* aTimeline, RefPtr& aBaseComputedValues); OwningAnimationTarget mTarget; KeyframeEffectParams mEffectOptions; // The specified keyframes. nsTArray mKeyframes; // A set of per-property value arrays, derived from |mKeyframes|. nsTArray mProperties; // The computed progress last time we composed the style rule. This is // used to detect when the progress is not changing (e.g. due to a step // timing function) so we can avoid unnecessary style updates. Nullable mProgressOnLastCompose; // The purpose of this value is the same as mProgressOnLastCompose but // this is used to detect when the current iteration is not changing // in the case when iterationComposite is accumulate. uint64_t mCurrentIterationOnLastCompose = 0; // We need to track when we go to or from being "in effect" since // we need to re-evaluate the cascade of animations when that changes. bool mInEffectOnLastAnimationTimingUpdate = false; // True if this effect is in the EffectSet for its target element. This is // used as an optimization to avoid unnecessary hashmap lookups on the // EffectSet. bool mInEffectSet = false; // True if the last time we tried to update the cumulative change hint we // didn't have style data to do so. We set this flag so that the next time // our style context changes we know to update the cumulative change hint even // if our properties haven't changed. bool mNeedsStyleData = false; // True if there is any current-color for background color in this keyframes. bool mHasCurrentColor = false; // The non-animated values for properties in this effect that contain at // least one animation value that is composited with the underlying value // (i.e. it uses the additive or accumulate composite mode). using BaseValuesHashmap = nsRefPtrHashtable; BaseValuesHashmap mBaseValues; private: nsChangeHint mCumulativeChangeHint = nsChangeHint{0}; void ComposeStyleRule(StyleAnimationValueMap& aAnimationValues, const AnimationProperty& aProperty, const AnimationPropertySegment& aSegment, const ComputedTiming& aComputedTiming); already_AddRefed CreateComputedStyleForAnimationValue( nsCSSPropertyID aProperty, const AnimationValue& aValue, nsPresContext* aPresContext, const ComputedStyle* aBaseComputedStyle); // Return the primary frame for the target (pseudo-)element. nsIFrame* GetPrimaryFrame() const; // Returns the frame which is used for styling. nsIFrame* GetStyleFrame() const; bool CanThrottle() const; bool CanThrottleOverflowChanges(const nsIFrame& aFrame) const; bool CanThrottleOverflowChangesInScrollable(nsIFrame& aFrame) const; bool CanThrottleIfNotVisible(nsIFrame& aFrame) const; // Returns true if the computedTiming has changed since the last // composition. bool HasComputedTimingChanged() const; // Returns true unless Gecko limitations prevent performing transform // animations for |aFrame|. When returning true, the reason for the // limitation is stored in |aOutPerformanceWarning|. static bool CanAnimateTransformOnCompositor( const nsIFrame* aFrame, AnimationPerformanceWarning::Type& aPerformanceWarning /* out */); static bool IsGeometricProperty(const nsCSSPropertyID aProperty); static const TimeDuration OverflowRegionRefreshInterval(); void UpdateEffectSet(mozilla::EffectSet* aEffectSet = nullptr) const; // Returns true if this effect has properties that might affect the overflow // region. // This function is used for updating scroll bars or notifying intersection // observers reflected by the transform. bool HasPropertiesThatMightAffectOverflow() const { return mCumulativeChangeHint & (nsChangeHint_AddOrRemoveTransform | nsChangeHint_UpdateOverflow | nsChangeHint_UpdatePostTransformOverflow | nsChangeHint_UpdateTransformLayer); } // Returns true if this effect causes visibility change. // (i.e. 'visibility: hidden' -> 'visibility: visible' and vice versa.) bool HasVisibilityChange() const { return mCumulativeChangeHint & nsChangeHint_VisibilityChange; } }; } // namespace dom } // namespace mozilla #endif // mozilla_dom_KeyframeEffect_h