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_KeyframeEffectReadOnly_h
8 #define mozilla_dom_KeyframeEffectReadOnly_h
9 
10 #include "nsChangeHint.h"
11 #include "nsCSSPropertyID.h"
12 #include "nsCSSPropertyIDSet.h"
13 #include "nsCSSValue.h"
14 #include "nsCycleCollectionParticipant.h"
15 #include "nsRefPtrHashtable.h"
16 #include "nsTArray.h"
17 #include "nsWrapperCache.h"
18 #include "mozilla/AnimationPerformanceWarning.h"
19 #include "mozilla/AnimationPropertySegment.h"
20 #include "mozilla/AnimationTarget.h"
21 #include "mozilla/Attributes.h"
22 #include "mozilla/ComputedTimingFunction.h"
23 #include "mozilla/EffectCompositor.h"
24 #include "mozilla/Keyframe.h"
25 #include "mozilla/KeyframeEffectParams.h"
26 // RawServoDeclarationBlock and associated RefPtrTraits
27 #include "mozilla/ServoBindingTypes.h"
28 #include "mozilla/StyleAnimationValue.h"
29 #include "mozilla/dom/AnimationEffectReadOnly.h"
30 #include "mozilla/dom/BindingDeclarations.h"
31 #include "mozilla/dom/Element.h"
32 
33 struct JSContext;
34 class JSObject;
35 class nsIContent;
36 class nsIDocument;
37 class nsIFrame;
38 class nsIPresShell;
39 
40 namespace mozilla {
41 
42 class AnimValuesStyleRule;
43 enum class CSSPseudoElementType : uint8_t;
44 class ErrorResult;
45 struct AnimationRule;
46 struct TimingParams;
47 class EffectSet;
48 class ServoStyleContext;
49 class GeckoStyleContext;
50 
51 namespace dom {
52 class ElementOrCSSPseudoElement;
53 class GlobalObject;
54 class OwningElementOrCSSPseudoElement;
55 class UnrestrictedDoubleOrKeyframeAnimationOptions;
56 class UnrestrictedDoubleOrKeyframeEffectOptions;
57 enum class IterationCompositeOperation : uint8_t;
58 enum class CompositeOperation : uint8_t;
59 struct AnimationPropertyDetails;
60 }  // namespace dom
61 
62 struct AnimationProperty {
63   nsCSSPropertyID mProperty = eCSSProperty_UNKNOWN;
64 
65   // If true, the propery is currently being animated on the compositor.
66   //
67   // Note that when the owning Animation requests a non-throttled restyle, in
68   // between calling RequestRestyle on its EffectCompositor and when the
69   // restyle is performed, this member may temporarily become false even if
70   // the animation remains on the layer after the restyle.
71   //
72   // **NOTE**: This member is not included when comparing AnimationProperty
73   // objects for equality.
74   bool mIsRunningOnCompositor = false;
75 
76   Maybe<AnimationPerformanceWarning> mPerformanceWarning;
77 
78   InfallibleTArray<AnimationPropertySegment> mSegments;
79 
80   // The copy constructor/assignment doesn't copy mIsRunningOnCompositor and
81   // mPerformanceWarning.
82   AnimationProperty() = default;
AnimationPropertyAnimationProperty83   AnimationProperty(const AnimationProperty& aOther)
84       : mProperty(aOther.mProperty), mSegments(aOther.mSegments) {}
85   AnimationProperty& operator=(const AnimationProperty& aOther) {
86     mProperty = aOther.mProperty;
87     mSegments = aOther.mSegments;
88     return *this;
89   }
90 
91   // NOTE: This operator does *not* compare the mIsRunningOnCompositor member.
92   // This is because AnimationProperty objects are compared when recreating
93   // CSS animations to determine if mutation observer change records need to
94   // be created or not. However, at the point when these objects are compared
95   // the mIsRunningOnCompositor will not have been set on the new objects so
96   // we ignore this member to avoid generating spurious change records.
97   bool operator==(const AnimationProperty& aOther) const {
98     return mProperty == aOther.mProperty && mSegments == aOther.mSegments;
99   }
100   bool operator!=(const AnimationProperty& aOther) const {
101     return !(*this == aOther);
102   }
103 };
104 
105 struct ElementPropertyTransition;
106 
107 namespace dom {
108 
109 class Animation;
110 
111 class KeyframeEffectReadOnly : public AnimationEffectReadOnly {
112  public:
113   KeyframeEffectReadOnly(nsIDocument* aDocument,
114                          const Maybe<OwningAnimationTarget>& aTarget,
115                          const TimingParams& aTiming,
116                          const KeyframeEffectParams& aOptions);
117 
118   NS_DECL_ISUPPORTS_INHERITED
119   NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_INHERITED(
120       KeyframeEffectReadOnly, AnimationEffectReadOnly)
121 
122   virtual JSObject* WrapObject(JSContext* aCx,
123                                JS::Handle<JSObject*> aGivenProto) override;
124 
AsKeyframeEffect()125   KeyframeEffectReadOnly* AsKeyframeEffect() override { return this; }
126 
127   // KeyframeEffectReadOnly interface
128   static already_AddRefed<KeyframeEffectReadOnly> Constructor(
129       const GlobalObject& aGlobal,
130       const Nullable<ElementOrCSSPseudoElement>& aTarget,
131       JS::Handle<JSObject*> aKeyframes,
132       const UnrestrictedDoubleOrKeyframeEffectOptions& aOptions,
133       ErrorResult& aRv);
134 
135   static already_AddRefed<KeyframeEffectReadOnly> Constructor(
136       const GlobalObject& aGlobal, KeyframeEffectReadOnly& aSource,
137       ErrorResult& aRv);
138 
139   void GetTarget(Nullable<OwningElementOrCSSPseudoElement>& aRv) const;
GetTarget()140   Maybe<NonOwningAnimationTarget> GetTarget() const {
141     Maybe<NonOwningAnimationTarget> result;
142     if (mTarget) {
143       result.emplace(*mTarget);
144     }
145     return result;
146   }
147   void GetKeyframes(JSContext*& aCx, nsTArray<JSObject*>& aResult,
148                     ErrorResult& aRv);
149   void GetProperties(nsTArray<AnimationPropertyDetails>& aProperties,
150                      ErrorResult& aRv) const;
151 
152   IterationCompositeOperation IterationComposite() const;
153   CompositeOperation Composite() const;
154   void NotifyAnimationTimingUpdated();
155   void RequestRestyle(EffectCompositor::RestyleType aRestyleType);
156   void SetAnimation(Animation* aAnimation) override;
157   void SetKeyframes(JSContext* aContext, JS::Handle<JSObject*> aKeyframes,
158                     ErrorResult& aRv);
159 #ifdef MOZ_OLD_STYLE
160   void SetKeyframes(nsTArray<Keyframe>&& aKeyframes,
161                     GeckoStyleContext* aStyleContext);
162 #endif
163   void SetKeyframes(nsTArray<Keyframe>&& aKeyframes,
164                     const ServoStyleContext* aComputedValues);
165 
166   // Returns true if the effect includes |aProperty| regardless of whether the
167   // property is overridden by !important rule.
168   bool HasAnimationOfProperty(nsCSSPropertyID aProperty) const;
169 
170   // GetEffectiveAnimationOfProperty returns AnimationProperty corresponding
171   // to a given CSS property if the effect includes the property and the
172   // property is not overridden by !important rules.
173   // Also EffectiveAnimationOfProperty returns true under the same condition.
174   //
175   // NOTE: We don't currently check for !important rules for properties that
176   // can't run on the compositor.
HasEffectiveAnimationOfProperty(nsCSSPropertyID aProperty)177   bool HasEffectiveAnimationOfProperty(nsCSSPropertyID aProperty) const {
178     return GetEffectiveAnimationOfProperty(aProperty) != nullptr;
179   }
180   const AnimationProperty* GetEffectiveAnimationOfProperty(
181       nsCSSPropertyID aProperty) const;
182 
Properties()183   const InfallibleTArray<AnimationProperty>& Properties() const {
184     return mProperties;
185   }
186 
187   // Update |mProperties| by recalculating from |mKeyframes| using
188   // |aStyleContext| to resolve specified values.
189   void UpdateProperties(nsStyleContext* aStyleContext);
190   // Servo version of the above function.
191   void UpdateProperties(const ServoStyleContext* aComputedValues);
192 
193   // Update various bits of state related to running ComposeStyle().
194   // We need to update this outside ComposeStyle() because we should avoid
195   // mutating any state in ComposeStyle() since it might be called during
196   // parallel traversal.
197   void WillComposeStyle();
198 
199   // Updates |aComposeResult| with the animation values produced by this
200   // AnimationEffect for the current time except any properties contained
201   // in |aPropertiesToSkip|.
202   template <typename ComposeAnimationResult>
203   void ComposeStyle(ComposeAnimationResult&& aRestultContainer,
204                     const nsCSSPropertyIDSet& aPropertiesToSkip);
205 
206 #ifdef MOZ_OLD_STYLE
207   // Composite |aValueToComposite| on |aUnderlyingValue| with
208   // |aCompositeOperation|.
209   // Returns |aValueToComposite| if |aCompositeOperation| is Replace.
210   static StyleAnimationValue CompositeValue(
211       nsCSSPropertyID aProperty, const StyleAnimationValue& aValueToComposite,
212       const StyleAnimationValue& aUnderlyingValue,
213       CompositeOperation aCompositeOperation);
214 #endif
215 
216   // Returns true if at least one property is being animated on compositor.
217   bool IsRunningOnCompositor() const;
218   void SetIsRunningOnCompositor(nsCSSPropertyID aProperty, bool aIsRunning);
219   void ResetIsRunningOnCompositor();
220 
221   // Returns true if this effect, applied to |aFrame|, contains properties
222   // that mean we shouldn't run transform compositor animations on this element.
223   //
224   // For example, if we have an animation of geometric properties like 'left'
225   // and 'top' on an element, we force all 'transform' animations running at
226   // the same time on the same element to run on the main thread.
227   //
228   // When returning true, |aPerformanceWarning| stores the reason why
229   // we shouldn't run the transform animations.
230   bool ShouldBlockAsyncTransformAnimations(
231       const nsIFrame* aFrame,
232       AnimationPerformanceWarning::Type& aPerformanceWarning) const;
233   bool HasGeometricProperties() const;
AffectsGeometry()234   bool AffectsGeometry() const override {
235     return GetTarget() && HasGeometricProperties();
236   }
237 
238   nsIDocument* GetRenderedDocument() const;
239   nsIPresShell* GetPresShell() const;
240 
241   // Associates a warning with the animated property on the specified frame
242   // indicating why, for example, the property could not be animated on the
243   // compositor. |aParams| and |aParamsLength| are optional parameters which
244   // will be used to generate a localized message for devtools.
245   void SetPerformanceWarning(nsCSSPropertyID aProperty,
246                              const AnimationPerformanceWarning& aWarning);
247 
248   // Cumulative change hint on each segment for each property.
249   // This is used for deciding the animation is paint-only.
250   template <typename StyleType>
251   void CalculateCumulativeChangeHint(StyleType* aStyleContext);
252 
253   // Returns true if all of animation properties' change hints
254   // can ignore painting if the animation is not visible.
255   // See nsChangeHint_Hints_CanIgnoreIfNotVisible in nsChangeHint.h
256   // in detail which change hint can be ignored.
257   bool CanIgnoreIfNotVisible() const;
258 
259   // Returns true if the effect is current state and has scale animation.
260   // |aFrame| is used for calculation of scale values.
261   bool ContainsAnimatedScale(const nsIFrame* aFrame) const;
262 
BaseStyle(nsCSSPropertyID aProperty)263   AnimationValue BaseStyle(nsCSSPropertyID aProperty) const {
264     AnimationValue result;
265     bool hasProperty = false;
266     if (mDocument->IsStyledByServo()) {
267       // We cannot use getters_AddRefs on RawServoAnimationValue because it is
268       // an incomplete type, so Get() doesn't work. Instead, use GetWeak, and
269       // then assign the raw pointer to a RefPtr.
270       result.mServo = mBaseStyleValuesForServo.GetWeak(aProperty, &hasProperty);
271     } else {
272 #ifdef MOZ_OLD_STYLE
273       hasProperty = mBaseStyleValues.Get(aProperty, &result.mGecko);
274 #else
275       MOZ_CRASH("old style system disabled");
276 #endif
277     }
278     MOZ_ASSERT(hasProperty || result.IsNull());
279     return result;
280   }
281 
282  protected:
283   KeyframeEffectReadOnly(nsIDocument* aDocument,
284                          const Maybe<OwningAnimationTarget>& aTarget,
285                          AnimationEffectTimingReadOnly* aTiming,
286                          const KeyframeEffectParams& aOptions);
287 
288   ~KeyframeEffectReadOnly() override = default;
289 
290   static Maybe<OwningAnimationTarget> ConvertTarget(
291       const Nullable<ElementOrCSSPseudoElement>& aTarget);
292 
293   template <class KeyframeEffectType, class OptionsType>
294   static already_AddRefed<KeyframeEffectType> ConstructKeyframeEffect(
295       const GlobalObject& aGlobal,
296       const Nullable<ElementOrCSSPseudoElement>& aTarget,
297       JS::Handle<JSObject*> aKeyframes, const OptionsType& aOptions,
298       ErrorResult& aRv);
299 
300   template <class KeyframeEffectType>
301   static already_AddRefed<KeyframeEffectType> ConstructKeyframeEffect(
302       const GlobalObject& aGlobal, KeyframeEffectReadOnly& aSource,
303       ErrorResult& aRv);
304 
305   // Build properties by recalculating from |mKeyframes| using |aStyleContext|
306   // to resolve specified values. This function also applies paced spacing if
307   // needed.
308   template <typename StyleType>
309   nsTArray<AnimationProperty> BuildProperties(StyleType* aStyle);
310 
311   // This effect is registered with its target element so long as:
312   //
313   // (a) It has a target element, and
314   // (b) It is "relevant" (i.e. yet to finish but not idle, or finished but
315   //     filling forwards)
316   //
317   // As a result, we need to make sure this gets called whenever anything
318   // changes with regards to this effects's timing including changes to the
319   // owning Animation's timing.
320   void UpdateTargetRegistration();
321 
322   // Remove the current effect target from its EffectSet.
323   void UnregisterTarget();
324 
325   // Update the associated frame state bits so that, if necessary, a stacking
326   // context will be created and the effect sent to the compositor.  We
327   // typically need to do this when the properties referenced by the keyframe
328   // have changed, or when the target frame might have changed.
329   void MaybeUpdateFrameForCompositor();
330 
331   // Looks up the style context associated with the target element, if any.
332   // We need to be careful to *not* call this when we are updating the style
333   // context. That's because calling GetStyleContext when we are in the process
334   // of building a style context may trigger various forms of infinite
335   // recursion.
336   already_AddRefed<nsStyleContext> GetTargetStyleContext();
337 
338   // A wrapper for marking cascade update according to the current
339   // target and its effectSet.
340   void MarkCascadeNeedsUpdate();
341 
342 #ifdef MOZ_OLD_STYLE
343   // Composites |aValueToComposite| using |aCompositeOperation| onto the value
344   // for |aProperty| in |aAnimationRule|, or, if there is no suitable value in
345   // |aAnimationRule|, uses the base value for the property recorded on the
346   // target element's EffectSet.
347   StyleAnimationValue CompositeValue(
348       nsCSSPropertyID aProperty,
349       const RefPtr<AnimValuesStyleRule>& aAnimationRule,
350       const StyleAnimationValue& aValueToComposite,
351       CompositeOperation aCompositeOperation);
352 
353   // Returns underlying style animation value for |aProperty|.
354   StyleAnimationValue GetUnderlyingStyle(
355       nsCSSPropertyID aProperty,
356       const RefPtr<AnimValuesStyleRule>& aAnimationRule);
357 
358   // Ensure the base styles is available for any properties in |aProperties|.
359   void EnsureBaseStyles(GeckoStyleContext* aStyleContext,
360                         const nsTArray<AnimationProperty>& aProperties);
361 #endif
362   void EnsureBaseStyles(const ServoStyleContext* aComputedValues,
363                         const nsTArray<AnimationProperty>& aProperties);
364 
365 #ifdef MOZ_OLD_STYLE
366   // If no base style is already stored for |aProperty|, resolves the base style
367   // for |aProperty| using |aStyleContext| and stores it in mBaseStyleValues.
368   // If |aCachedBaseStyleContext| is non-null, it will be used, otherwise the
369   // base style context will be resolved and stored in
370   // |aCachedBaseStyleContext|.
371   void EnsureBaseStyle(nsCSSPropertyID aProperty,
372                        GeckoStyleContext* aStyleContext,
373                        RefPtr<GeckoStyleContext>& aCachedBaseStyleContext);
374 #endif
375   // Stylo version of the above function that also first checks for an additive
376   // value in |aProperty|'s list of segments.
377   void EnsureBaseStyle(const AnimationProperty& aProperty,
378                        nsPresContext* aPresContext,
379                        const ServoStyleContext* aComputedValues,
380                        RefPtr<mozilla::ServoStyleContext>& aBaseComputedValues);
381 
382   Maybe<OwningAnimationTarget> mTarget;
383 
384   KeyframeEffectParams mEffectOptions;
385 
386   // The specified keyframes.
387   nsTArray<Keyframe> mKeyframes;
388 
389   // A set of per-property value arrays, derived from |mKeyframes|.
390   nsTArray<AnimationProperty> mProperties;
391 
392   // The computed progress last time we composed the style rule. This is
393   // used to detect when the progress is not changing (e.g. due to a step
394   // timing function) so we can avoid unnecessary style updates.
395   Nullable<double> mProgressOnLastCompose;
396 
397   // The purpose of this value is the same as mProgressOnLastCompose but
398   // this is used to detect when the current iteration is not changing
399   // in the case when iterationComposite is accumulate.
400   uint64_t mCurrentIterationOnLastCompose = 0;
401 
402   // We need to track when we go to or from being "in effect" since
403   // we need to re-evaluate the cascade of animations when that changes.
404   bool mInEffectOnLastAnimationTimingUpdate;
405 
406   // The non-animated values for properties in this effect that contain at
407   // least one animation value that is composited with the underlying value
408   // (i.e. it uses the additive or accumulate composite mode).
409 #ifdef MOZ_OLD_STYLE
410   nsDataHashtable<nsUint32HashKey, StyleAnimationValue> mBaseStyleValues;
411 #endif
412   nsRefPtrHashtable<nsUint32HashKey, RawServoAnimationValue>
413       mBaseStyleValuesForServo;
414 
415   // True if this effect is in the EffectSet for its target element. This is
416   // used as an optimization to avoid unnecessary hashmap lookups on the
417   // EffectSet.
418   bool mInEffectSet = false;
419 
420  private:
421   nsChangeHint mCumulativeChangeHint;
422 
423   template <typename StyleType>
424   void DoSetKeyframes(nsTArray<Keyframe>&& aKeyframes, StyleType* aStyle);
425 
426   template <typename StyleType>
427   void DoUpdateProperties(StyleType* aStyle);
428 
429 #ifdef MOZ_OLD_STYLE
430   void ComposeStyleRule(RefPtr<AnimValuesStyleRule>& aStyleRule,
431                         const AnimationProperty& aProperty,
432                         const AnimationPropertySegment& aSegment,
433                         const ComputedTiming& aComputedTiming);
434 #endif
435 
436   void ComposeStyleRule(RawServoAnimationValueMap& aAnimationValues,
437                         const AnimationProperty& aProperty,
438                         const AnimationPropertySegment& aSegment,
439                         const ComputedTiming& aComputedTiming);
440 
441 #ifdef MOZ_OLD_STYLE
442   already_AddRefed<nsStyleContext> CreateStyleContextForAnimationValue(
443       nsCSSPropertyID aProperty, const AnimationValue& aValue,
444       GeckoStyleContext* aBaseStyleContext);
445 #endif
446 
447   already_AddRefed<nsStyleContext> CreateStyleContextForAnimationValue(
448       nsCSSPropertyID aProperty, const AnimationValue& aValue,
449       const ServoStyleContext* aBaseStyleContext);
450 
451   nsIFrame* GetAnimationFrame() const;
452 
453   bool CanThrottle() const;
454   bool CanThrottleTransformChanges(const nsIFrame& aFrame) const;
455   bool CanThrottleTransformChangesInScrollable(nsIFrame& aFrame) const;
456 
457   // Returns true if the computedTiming has changed since the last
458   // composition.
459   bool HasComputedTimingChanged() const;
460 
461   // Returns true unless Gecko limitations prevent performing transform
462   // animations for |aFrame|. When returning true, the reason for the
463   // limitation is stored in |aOutPerformanceWarning|.
464   static bool CanAnimateTransformOnCompositor(
465       const nsIFrame* aFrame,
466       AnimationPerformanceWarning::Type& aPerformanceWarning);
467   static bool IsGeometricProperty(const nsCSSPropertyID aProperty);
468 
469   static const TimeDuration OverflowRegionRefreshInterval();
470 
471   void UpdateEffectSet(mozilla::EffectSet* aEffectSet = nullptr) const;
472 
473   // Returns true if this effect has transform and the transform might affect
474   // the overflow region.
475   // This function is used for updating scroll bars or notifying intersection
476   // observers reflected by the transform.
HasTransformThatMightAffectOverflow()477   bool HasTransformThatMightAffectOverflow() const {
478     return mCumulativeChangeHint & (nsChangeHint_UpdatePostTransformOverflow |
479                                     nsChangeHint_AddOrRemoveTransform |
480                                     nsChangeHint_UpdateTransformLayer);
481   }
482 
483   // FIXME: This flag will be removed in bug 1324966.
484   bool mIsComposingStyle = false;
485 
486   // Returns true if this effect causes visibility change.
487   // (i.e. 'visibility: hidden' -> 'visibility: visible' and vice versa.)
HasVisibilityChange()488   bool HasVisibilityChange() const {
489     return mCumulativeChangeHint & nsChangeHint_VisibilityChange;
490   }
491 };
492 
493 }  // namespace dom
494 }  // namespace mozilla
495 
496 #endif  // mozilla_dom_KeyframeEffectReadOnly_h
497