KeyframeEffect.h (22623B)
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 file, 5 * You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 #ifndef mozilla_dom_KeyframeEffect_h 8 #define mozilla_dom_KeyframeEffect_h 9 10 #include "NonCustomCSSPropertyId.h" 11 #include "mozilla/AnimatedPropertyIDSet.h" 12 #include "mozilla/AnimationPerformanceWarning.h" 13 #include "mozilla/AnimationPropertySegment.h" 14 #include "mozilla/AnimationTarget.h" 15 #include "mozilla/CSSPropertyId.h" 16 #include "mozilla/EffectCompositor.h" 17 #include "mozilla/Keyframe.h" 18 #include "mozilla/KeyframeEffectParams.h" 19 #include "mozilla/PostRestyleMode.h" 20 #include "nsCSSPropertyIDSet.h" 21 #include "nsCSSValue.h" 22 #include "nsChangeHint.h" 23 #include "nsCycleCollectionParticipant.h" 24 #include "nsRefPtrHashtable.h" 25 #include "nsTArray.h" 26 #include "nsWrapperCache.h" 27 // StyleLockedDeclarationBlock and associated RefPtrTraits 28 #include "mozilla/ServoBindingTypes.h" 29 #include "mozilla/StyleAnimationValue.h" 30 #include "mozilla/dom/AnimationEffect.h" 31 #include "mozilla/dom/BindingDeclarations.h" 32 33 struct JSContext; 34 class JSObject; 35 class nsIContent; 36 class nsIFrame; 37 38 namespace mozilla { 39 40 class AnimValuesStyleRule; 41 class ErrorResult; 42 struct AnimationRule; 43 struct TimingParams; 44 class EffectSet; 45 class ComputedStyle; 46 class PresShell; 47 48 namespace dom { 49 class Element; 50 class GlobalObject; 51 class UnrestrictedDoubleOrKeyframeAnimationOptions; 52 class UnrestrictedDoubleOrKeyframeEffectOptions; 53 enum class IterationCompositeOperation : uint8_t; 54 enum class CompositeOperation : uint8_t; 55 struct AnimationPropertyDetails; 56 } // namespace dom 57 58 struct AnimationProperty { 59 CSSPropertyId mProperty; 60 61 // If true, the propery is currently being animated on the compositor. 62 // 63 // Note that when the owning Animation requests a non-throttled restyle, in 64 // between calling RequestRestyle on its EffectCompositor and when the 65 // restyle is performed, this member may temporarily become false even if 66 // the animation remains on the layer after the restyle. 67 // 68 // **NOTE**: This member is not included when comparing AnimationProperty 69 // objects for equality. 70 bool mIsRunningOnCompositor = false; 71 72 Maybe<AnimationPerformanceWarning> mPerformanceWarning; 73 74 nsTArray<AnimationPropertySegment> mSegments; 75 76 // The copy constructor/assignment doesn't copy mIsRunningOnCompositor and 77 // mPerformanceWarning. 78 AnimationProperty() : mProperty(eCSSProperty_UNKNOWN) {}; 79 AnimationProperty(const AnimationProperty& aOther) 80 : mProperty(aOther.mProperty), mSegments(aOther.mSegments.Clone()) {} 81 AnimationProperty& operator=(const AnimationProperty& aOther) { 82 mProperty = aOther.mProperty; 83 mSegments = aOther.mSegments.Clone(); 84 return *this; 85 } 86 87 // NOTE: This operator does *not* compare the mIsRunningOnCompositor member. 88 // This is because AnimationProperty objects are compared when recreating 89 // CSS animations to determine if mutation observer change records need to 90 // be created or not. However, at the point when these objects are compared 91 // the mIsRunningOnCompositor will not have been set on the new objects so 92 // we ignore this member to avoid generating spurious change records. 93 bool operator==(const AnimationProperty& aOther) const { 94 return mProperty == aOther.mProperty && mSegments == aOther.mSegments; 95 } 96 bool operator!=(const AnimationProperty& aOther) const { 97 return !(*this == aOther); 98 } 99 100 void SetPerformanceWarning(const AnimationPerformanceWarning& aWarning, 101 const dom::Element* aElement); 102 }; 103 104 namespace dom { 105 106 class Animation; 107 class Document; 108 109 class KeyframeEffect : public AnimationEffect { 110 public: 111 KeyframeEffect(Document* aDocument, OwningAnimationTarget&& aTarget, 112 TimingParams&& aTiming, const KeyframeEffectParams& aOptions); 113 114 KeyframeEffect(Document* aDocument, OwningAnimationTarget&& aTarget, 115 const KeyframeEffect& aOther); 116 117 NS_DECL_ISUPPORTS_INHERITED 118 NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_INHERITED(KeyframeEffect, 119 AnimationEffect) 120 121 virtual JSObject* WrapObject(JSContext* aCx, 122 JS::Handle<JSObject*> aGivenProto) override; 123 124 KeyframeEffect* AsKeyframeEffect() override { return this; } 125 126 bool IsValidTransition() const { 127 return Properties().Length() == 1 && 128 Properties()[0].mSegments.Length() == 1; 129 } 130 131 // KeyframeEffect interface 132 static already_AddRefed<KeyframeEffect> Constructor( 133 const GlobalObject& aGlobal, Element* aTarget, 134 JS::Handle<JSObject*> aKeyframes, 135 const UnrestrictedDoubleOrKeyframeEffectOptions& aOptions, 136 ErrorResult& aRv); 137 138 static already_AddRefed<KeyframeEffect> Constructor( 139 const GlobalObject& aGlobal, KeyframeEffect& aSource, ErrorResult& aRv); 140 141 // Variant of Constructor that accepts a KeyframeAnimationOptions object 142 // for use with for Animatable.animate. 143 // Not exposed to content. 144 static already_AddRefed<KeyframeEffect> Constructor( 145 const GlobalObject& aGlobal, Element* aTarget, 146 JS::Handle<JSObject*> aKeyframes, 147 const UnrestrictedDoubleOrKeyframeAnimationOptions& aOptions, 148 ErrorResult& aRv); 149 150 Element* GetTarget() const { return mTarget.mElement.get(); } 151 NonOwningAnimationTarget GetAnimationTarget() const { 152 return NonOwningAnimationTarget(mTarget.mElement, mTarget.mPseudoRequest); 153 } 154 void GetPseudoElement(nsAString& aRetVal) const { 155 if (mTarget.mPseudoRequest.IsNotPseudo()) { 156 SetDOMStringToNull(aRetVal); 157 return; 158 } 159 aRetVal = 160 nsCSSPseudoElements::PseudoRequestAsString(mTarget.mPseudoRequest); 161 } 162 163 // These two setters call GetTargetComputedStyle which is not safe to use when 164 // we are in the middle of updating style. If we need to use this when 165 // updating style, we should pass the ComputedStyle into this method and use 166 // that to update the properties rather than calling 167 // GetComputedStyle. 168 void SetTarget(Element* aTarget) { 169 UpdateTarget(aTarget, mTarget.mPseudoRequest); 170 } 171 void SetPseudoElement(const nsAString& aPseudoElement, ErrorResult& aRv); 172 173 void GetKeyframes(JSContext* aCx, nsTArray<JSObject*>& aResult, 174 ErrorResult& aRv) const; 175 void GetProperties(nsTArray<AnimationPropertyDetails>& aProperties, 176 ErrorResult& aRv) const; 177 178 IterationCompositeOperation IterationComposite() const; 179 void SetIterationComposite( 180 const IterationCompositeOperation& aIterationComposite); 181 182 CompositeOperation Composite() const; 183 virtual void SetComposite(const CompositeOperation& aComposite); 184 void SetCompositeFromStyle(const CompositeOperation& aComposite) { 185 KeyframeEffect::SetComposite(aComposite); 186 } 187 188 void NotifySpecifiedTimingUpdated(); 189 void NotifyAnimationTimingUpdated(PostRestyleMode aPostRestyle); 190 void RequestRestyle(EffectCompositor::RestyleType aRestyleType); 191 void SetAnimation(Animation* aAnimation) override; 192 virtual void SetKeyframes(JSContext* aContext, 193 JS::Handle<JSObject*> aKeyframes, ErrorResult& aRv); 194 void SetKeyframes(nsTArray<Keyframe>&& aKeyframes, 195 const ComputedStyle* aStyle, 196 const AnimationTimeline* aTimeline); 197 198 // Replace the start value of the transition. This is used for updating 199 // transitions running on the compositor. 200 void ReplaceTransitionStartValue(AnimationValue&& aStartValue); 201 202 // Returns the set of properties affected by this effect regardless of 203 // whether any of these properties is overridden by an !important rule. 204 AnimatedPropertyIDSet GetPropertySet() const; 205 206 // Returns true if the effect includes a property in |aPropertySet| regardless 207 // of whether any property in the set is overridden by an !important rule. 208 bool HasAnimationOfPropertySet(const nsCSSPropertyIDSet& aPropertySet) const { 209 return GetPropertySet().Intersects(aPropertySet); 210 } 211 212 // GetEffectiveAnimationOfProperty returns AnimationProperty corresponding 213 // to a given CSS property if the effect includes the property and the 214 // property is not overridden by !important rules. 215 // Also EffectiveAnimationOfProperty returns true under the same condition. 216 // 217 // |aEffect| should be the EffectSet containing this KeyframeEffect since 218 // this function is typically called for all KeyframeEffects on an element 219 // so that we can avoid multiple calls of EffectSet::GetEffect(). 220 // 221 // Note that does not consider the interaction between related transform 222 // properties where an !important rule on another transform property may 223 // cause all transform properties to be run on the main thread. That check is 224 // performed by GetPropertiesForCompositor. 225 bool HasEffectiveAnimationOfProperty(const CSSPropertyId& aProperty, 226 const EffectSet& aEffect) const { 227 return GetEffectiveAnimationOfProperty(aProperty, aEffect) != nullptr; 228 } 229 const AnimationProperty* GetEffectiveAnimationOfProperty( 230 const CSSPropertyId&, const EffectSet&) const; 231 232 // Similar to HasEffectiveAnimationOfProperty, above, but for 233 // an nsCSSPropertyIDSet. Returns true if this keyframe effect has at least 234 // one property in |aPropertySet| that is not overridden by an !important 235 // rule. 236 // 237 // Note that does not consider the interaction between related transform 238 // properties where an !important rule on another transform property may 239 // cause all transform properties to be run on the main thread. That check is 240 // performed by GetPropertiesForCompositor. 241 bool HasEffectiveAnimationOfPropertySet( 242 const nsCSSPropertyIDSet& aPropertySet, 243 const EffectSet& aEffectSet) const; 244 245 // Returns all the effective animated CSS properties that can be animated on 246 // the compositor and are not overridden by a higher cascade level. 247 // 248 // NOTE: This function is basically called for all KeyframeEffects on an 249 // element thus it takes |aEffects| to avoid multiple calls of 250 // EffectSet::GetEffect(). 251 // 252 // NOTE(2): This function does NOT check that animations are permitted on 253 // |aFrame|. It is the responsibility of the caller to first call 254 // EffectCompositor::AllowCompositorAnimationsOnFrame for |aFrame|, or use 255 // nsLayoutUtils::GetAnimationPropertiesForCompositor instead. 256 nsCSSPropertyIDSet GetPropertiesForCompositor(EffectSet& aEffects, 257 const nsIFrame* aFrame) const; 258 259 const nsTArray<AnimationProperty>& Properties() const { return mProperties; } 260 261 // Update |mProperties| by recalculating from |mKeyframes| using 262 // |aComputedStyle| to resolve specified values. 263 // Note: we use |aTimeline| to check if we need to ensure the base styles. 264 // If it is nullptr, we use the timeline from |mAnimation|. 265 void UpdateProperties(const ComputedStyle* aStyle, 266 const AnimationTimeline* aTimeline = nullptr); 267 268 // Update various bits of state related to running ComposeStyle(). 269 // We need to update this outside ComposeStyle() because we should avoid 270 // mutating any state in ComposeStyle() since it might be called during 271 // parallel traversal. 272 void WillComposeStyle(); 273 274 // Updates |aComposeResult| with the animation values produced by this 275 // AnimationEffect for the current time except any properties contained 276 // in |aPropertiesToSkip|. 277 void ComposeStyle( 278 StyleAnimationValueMap& aComposeResult, 279 const InvertibleAnimatedPropertyIDSet& aPropertiesToSkip, 280 EndpointBehavior aEndpointBehavior = EndpointBehavior::Exclusive); 281 282 // Returns true if at least one property is being animated on compositor. 283 bool IsRunningOnCompositor() const; 284 void SetIsRunningOnCompositor(NonCustomCSSPropertyId aProperty, 285 bool aIsRunning); 286 void SetIsRunningOnCompositor(const nsCSSPropertyIDSet& aPropertySet, 287 bool aIsRunning); 288 void ResetIsRunningOnCompositor(); 289 290 void ResetPartialPrerendered(); 291 292 // Returns true if this effect, applied to |aFrame|, contains properties 293 // that mean we shouldn't run transform compositor animations on this element. 294 // 295 // For example, if we have an animation of geometric properties like 'left' 296 // and 'top' on an element, we force all 'transform' animations running at 297 // the same time on the same element to run on the main thread. 298 // 299 // When returning true, |aPerformanceWarning| stores the reason why 300 // we shouldn't run the transform animations. 301 bool ShouldBlockAsyncTransformAnimations( 302 const nsIFrame* aFrame, const nsCSSPropertyIDSet& aPropertySet, 303 AnimationPerformanceWarning::Type& aPerformanceWarning /* out */) const; 304 bool HasGeometricProperties() const; 305 bool AffectsGeometry() const override { 306 return mTarget && HasGeometricProperties(); 307 } 308 309 Document* GetRenderedDocument() const; 310 PresShell* GetPresShell() const; 311 312 // Associates a warning with the animated property set on the specified frame 313 // indicating why, for example, the property could not be animated on the 314 // compositor. |aParams| and |aParamsLength| are optional parameters which 315 // will be used to generate a localized message for devtools. 316 void SetPerformanceWarning(const nsCSSPropertyIDSet& aPropertySet, 317 const AnimationPerformanceWarning& aWarning); 318 319 // Cumulative change hint on each segment for each property. 320 // This is used for deciding the animation is paint-only. 321 void CalculateCumulativeChangesForProperty(const AnimationProperty&); 322 323 // Returns true if all of animation properties' change hints 324 // can ignore painting if the animation is not visible. 325 // See nsChangeHint_Hints_CanIgnoreIfNotVisible in nsChangeHint.h 326 // in detail which change hint can be ignored. 327 bool CanIgnoreIfNotVisible() const; 328 329 // Returns true if the effect is current state and has scale animation. 330 // |aFrame| is used for calculation of scale values. 331 bool ContainsAnimatedScale(const nsIFrame* aFrame) const; 332 333 AnimationValue BaseStyle(const CSSPropertyId& aProperty) const { 334 AnimationValue result; 335 bool hasProperty = false; 336 // We cannot use getters_AddRefs on StyleAnimationValue because it is 337 // an incomplete type, so Get() doesn't work. Instead, use GetWeak, and 338 // then assign the raw pointer to a RefPtr. 339 result.mServo = mBaseValues.GetWeak(aProperty, &hasProperty); 340 MOZ_ASSERT(hasProperty || result.IsNull()); 341 return result; 342 } 343 344 void UpdateBaseStyle(const ComputedStyle* aStyle); 345 346 enum class MatchForCompositor { 347 // This animation matches and should run on the compositor if possible. 348 Yes, 349 // This (not currently playing) animation matches and can be run on the 350 // compositor if there are other animations for this property that return 351 // 'Yes'. 352 IfNeeded, 353 // This animation does not match or can't be run on the compositor. 354 No, 355 // This animation does not match or can't be run on the compositor and, 356 // furthermore, its presence means we should not run any animations for this 357 // property on the compositor. 358 NoAndBlockThisProperty 359 }; 360 361 MatchForCompositor IsMatchForCompositor( 362 const nsCSSPropertyIDSet& aPropertySet, const nsIFrame* aFrame, 363 const EffectSet& aEffects, 364 AnimationPerformanceWarning::Type& aPerformanceWarning /* out */) const; 365 366 static bool HasComputedTimingChanged( 367 const ComputedTiming& aComputedTiming, 368 IterationCompositeOperation aIterationComposite, 369 const Nullable<double>& aProgressOnLastCompose, 370 uint64_t aCurrentIterationOnLastCompose); 371 372 bool HasOpacityChange() const { return mCumulativeChanges.mOpacity; } 373 374 protected: 375 ~KeyframeEffect() override = default; 376 377 template <class OptionsType> 378 static already_AddRefed<KeyframeEffect> ConstructKeyframeEffect( 379 const GlobalObject& aGlobal, Element* aTarget, 380 JS::Handle<JSObject*> aKeyframes, const OptionsType& aOptions, 381 ErrorResult& aRv); 382 383 // Build properties by recalculating from |mKeyframes| using |aComputedStyle| 384 // to resolve specified values. This function also applies paced spacing if 385 // needed. 386 nsTArray<AnimationProperty> BuildProperties(const ComputedStyle* aStyle); 387 388 // Helper for SetTarget() and SetPseudoElement(). 389 void UpdateTarget(Element* aElement, 390 const PseudoStyleRequest& aPseudoRequest); 391 392 // This effect is registered with its target element so long as: 393 // 394 // (a) It has a target element, and 395 // (b) It is "relevant" (i.e. yet to finish but not idle, or finished but 396 // filling forwards) 397 // 398 // As a result, we need to make sure this gets called whenever anything 399 // changes with regards to this effects's timing including changes to the 400 // owning Animation's timing. 401 void UpdateTargetRegistration(); 402 403 // Remove the current effect target from its EffectSet. 404 void UnregisterTarget(); 405 406 // Looks up the ComputedStyle associated with the target element, if any. 407 // We need to be careful to *not* call this when we are updating the style 408 // context. That's because calling GetComputedStyle when we are in the process 409 // of building a ComputedStyle may trigger various forms of infinite 410 // recursion. 411 enum class Flush { 412 Style, 413 None, 414 }; 415 already_AddRefed<const ComputedStyle> GetTargetComputedStyle(Flush) const; 416 417 // A wrapper for marking cascade update according to the current 418 // target and its effectSet. 419 void MarkCascadeNeedsUpdate(); 420 421 void EnsureBaseStyles(const ComputedStyle* aComputedValues, 422 const nsTArray<AnimationProperty>& aProperties, 423 const AnimationTimeline* aTimeline, 424 bool* aBaseStylesChanged); 425 void EnsureBaseStyle(const AnimationProperty& aProperty, 426 nsPresContext* aPresContext, 427 const ComputedStyle* aComputedValues, 428 const AnimationTimeline* aTimeline, 429 RefPtr<const ComputedStyle>& aBaseComputedValues); 430 431 OwningAnimationTarget mTarget; 432 433 KeyframeEffectParams mEffectOptions; 434 435 // The specified keyframes. 436 nsTArray<Keyframe> mKeyframes; 437 438 // A set of per-property value arrays, derived from |mKeyframes|. 439 nsTArray<AnimationProperty> mProperties; 440 441 // The computed progress last time we composed the style rule. This is 442 // used to detect when the progress is not changing (e.g. due to a step 443 // timing function) so we can avoid unnecessary style updates. 444 Nullable<double> mProgressOnLastCompose; 445 446 // The purpose of this value is the same as mProgressOnLastCompose but 447 // this is used to detect when the current iteration is not changing 448 // in the case when iterationComposite is accumulate. 449 uint64_t mCurrentIterationOnLastCompose = 0; 450 451 // We need to track when we go to or from being "in effect" since 452 // we need to re-evaluate the cascade of animations when that changes. 453 bool mInEffectOnLastAnimationTimingUpdate = false; 454 455 // True if this effect is in the EffectSet for its target element. This is 456 // used as an optimization to avoid unnecessary hashmap lookups on the 457 // EffectSet. 458 bool mInEffectSet = false; 459 460 // The non-animated values for properties in this effect that contain at 461 // least one animation value that is composited with the underlying value 462 // (i.e. it uses the additive or accumulate composite mode). 463 using BaseValuesHashmap = 464 nsRefPtrHashtable<nsGenericHashKey<CSSPropertyId>, StyleAnimationValue>; 465 BaseValuesHashmap mBaseValues; 466 467 private: 468 // The cumulative changes of all the animation segments. 469 struct CumulativeChanges { 470 // Whether the opacity property is changing. 471 bool mOpacity : 1; 472 // Whether the visibility property is changing. 473 bool mVisibility : 1; 474 // Whether layout is changing. 475 bool mLayout : 1; 476 // Whether overflow is changing. 477 bool mOverflow : 1; 478 // True if there is any current-color for background color. 479 bool mHasBackgroundColorCurrentColor : 1; 480 481 CumulativeChanges() 482 : mOpacity(false), 483 mVisibility(false), 484 mLayout(false), 485 mOverflow(false), 486 mHasBackgroundColorCurrentColor(false) {} 487 }; 488 CumulativeChanges mCumulativeChanges; 489 490 void ComposeStyleRule(StyleAnimationValueMap& aAnimationValues, 491 const AnimationProperty& aProperty, 492 const AnimationPropertySegment& aSegment, 493 const ComputedTiming& aComputedTiming); 494 495 already_AddRefed<const ComputedStyle> CreateComputedStyleForAnimationValue( 496 NonCustomCSSPropertyId aProperty, const AnimationValue& aValue, 497 nsPresContext* aPresContext, const ComputedStyle* aBaseComputedStyle); 498 499 // Return the primary frame for the target (pseudo-)element. 500 nsIFrame* GetPrimaryFrame() const; 501 // Returns the frame which is used for styling. 502 nsIFrame* GetStyleFrame() const; 503 504 bool CanThrottle() const; 505 bool CanThrottleOverflowChanges(const nsIFrame& aFrame) const; 506 bool CanThrottleOverflowChangesInScrollable(nsIFrame& aFrame) const; 507 bool CanThrottleIfNotVisible(nsIFrame& aFrame) const; 508 509 // Returns true if the computedTiming has changed since the last 510 // composition. 511 bool HasComputedTimingChanged() const; 512 513 // Returns true unless Gecko limitations prevent performing transform 514 // animations for |aFrame|. When returning true, the reason for the 515 // limitation is stored in |aOutPerformanceWarning|. 516 static bool CanAnimateTransformOnCompositor( 517 const nsIFrame* aFrame, 518 AnimationPerformanceWarning::Type& aPerformanceWarning /* out */); 519 static bool IsGeometricProperty(const NonCustomCSSPropertyId aProperty); 520 521 static const TimeDuration OverflowRegionRefreshInterval(); 522 523 void UpdateEffectSet(mozilla::EffectSet* aEffectSet = nullptr) const; 524 525 // Returns true if this effect has properties that might affect the overflow 526 // region. 527 // This function is used for updating scroll bars or notifying intersection 528 // observers reflected by the transform. 529 bool HasPropertiesThatMightAffectOverflow() const { 530 return mCumulativeChanges.mOverflow; 531 } 532 533 // Returns true if this effect causes visibility change. 534 // (i.e. 'visibility: hidden' -> 'visibility: visible' and vice versa.) 535 bool HasVisibilityChange() const { return mCumulativeChanges.mVisibility; } 536 }; 537 538 } // namespace dom 539 } // namespace mozilla 540 541 #endif // mozilla_dom_KeyframeEffect_h