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 #include "mozilla/dom/KeyframeEffectReadOnly.h"
8 
9 #include "FrameLayerBuilder.h"
10 #include "mozilla/dom/Animation.h"
11 #include "mozilla/dom/KeyframeAnimationOptionsBinding.h"
12 // For UnrestrictedDoubleOrKeyframeAnimationOptions;
13 #include "mozilla/dom/CSSPseudoElement.h"
14 #include "mozilla/dom/KeyframeEffectBinding.h"
15 #ifdef MOZ_OLD_STYLE
16 #include "mozilla/AnimValuesStyleRule.h"
17 #endif
18 #include "mozilla/AnimationUtils.h"
19 #include "mozilla/AutoRestore.h"
20 #include "mozilla/EffectSet.h"
21 #include "mozilla/FloatingPoint.h"  // For IsFinite
22 #ifdef MOZ_OLD_STYLE
23 #include "mozilla/GeckoStyleContext.h"
24 #endif
25 #include "mozilla/LayerAnimationInfo.h"
26 #include "mozilla/LookAndFeel.h"  // For LookAndFeel::GetInt
27 #include "mozilla/KeyframeUtils.h"
28 #include "mozilla/ServoBindings.h"
29 #include "mozilla/TypeTraits.h"
30 #include "Layers.h"              // For Layer
31 #include "nsComputedDOMStyle.h"  // nsComputedDOMStyle::GetStyleContext
32 #include "nsContentUtils.h"
33 #include "nsCSSPropertyIDSet.h"
34 #include "nsCSSProps.h"           // For nsCSSProps::PropHasFlags
35 #include "nsCSSPseudoElements.h"  // For CSSPseudoElementType
36 #include "nsDocument.h"           // For nsDocument::IsWebAnimationsEnabled
37 #include "nsIFrame.h"
38 #include "nsIPresShell.h"
39 #include "nsIScriptError.h"
40 #include "nsRefreshDriver.h"
41 #include "nsStyleContextInlines.h"
42 
43 namespace mozilla {
44 
operator ==(const PropertyValuePair & aOther) const45 bool PropertyValuePair::operator==(const PropertyValuePair& aOther) const {
46   if (mProperty != aOther.mProperty || mValue != aOther.mValue) {
47     return false;
48   }
49   if (mServoDeclarationBlock == aOther.mServoDeclarationBlock) {
50     return true;
51   }
52   if (!mServoDeclarationBlock || !aOther.mServoDeclarationBlock) {
53     return false;
54   }
55   return Servo_DeclarationBlock_Equals(mServoDeclarationBlock,
56                                        aOther.mServoDeclarationBlock);
57 }
58 
59 namespace dom {
60 
NS_IMPL_CYCLE_COLLECTION_INHERITED(KeyframeEffectReadOnly,AnimationEffectReadOnly,mTarget)61 NS_IMPL_CYCLE_COLLECTION_INHERITED(KeyframeEffectReadOnly,
62                                    AnimationEffectReadOnly, mTarget)
63 
64 NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(KeyframeEffectReadOnly,
65                                                AnimationEffectReadOnly)
66 NS_IMPL_CYCLE_COLLECTION_TRACE_END
67 
68 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(KeyframeEffectReadOnly)
69 NS_INTERFACE_MAP_END_INHERITING(AnimationEffectReadOnly)
70 
71 NS_IMPL_ADDREF_INHERITED(KeyframeEffectReadOnly, AnimationEffectReadOnly)
72 NS_IMPL_RELEASE_INHERITED(KeyframeEffectReadOnly, AnimationEffectReadOnly)
73 
74 KeyframeEffectReadOnly::KeyframeEffectReadOnly(
75     nsIDocument* aDocument, const Maybe<OwningAnimationTarget>& aTarget,
76     const TimingParams& aTiming, const KeyframeEffectParams& aOptions)
77     : KeyframeEffectReadOnly(
78           aDocument, aTarget,
79           new AnimationEffectTimingReadOnly(aDocument, aTiming), aOptions) {}
80 
KeyframeEffectReadOnly(nsIDocument * aDocument,const Maybe<OwningAnimationTarget> & aTarget,AnimationEffectTimingReadOnly * aTiming,const KeyframeEffectParams & aOptions)81 KeyframeEffectReadOnly::KeyframeEffectReadOnly(
82     nsIDocument* aDocument, const Maybe<OwningAnimationTarget>& aTarget,
83     AnimationEffectTimingReadOnly* aTiming,
84     const KeyframeEffectParams& aOptions)
85     : AnimationEffectReadOnly(aDocument, aTiming),
86       mTarget(aTarget),
87       mEffectOptions(aOptions),
88       mInEffectOnLastAnimationTimingUpdate(false),
89       mCumulativeChangeHint(nsChangeHint(0)) {}
90 
WrapObject(JSContext * aCx,JS::Handle<JSObject * > aGivenProto)91 JSObject* KeyframeEffectReadOnly::WrapObject(
92     JSContext* aCx, JS::Handle<JSObject*> aGivenProto) {
93   return KeyframeEffectReadOnlyBinding::Wrap(aCx, this, aGivenProto);
94 }
95 
IterationComposite() const96 IterationCompositeOperation KeyframeEffectReadOnly::IterationComposite() const {
97   return mEffectOptions.mIterationComposite;
98 }
99 
Composite() const100 CompositeOperation KeyframeEffectReadOnly::Composite() const {
101   return mEffectOptions.mComposite;
102 }
103 
NotifyAnimationTimingUpdated()104 void KeyframeEffectReadOnly::NotifyAnimationTimingUpdated() {
105   UpdateTargetRegistration();
106 
107   // If the effect is not relevant it will be removed from the target
108   // element's effect set. However, effects not in the effect set
109   // will not be included in the set of candidate effects for running on
110   // the compositor and hence they won't have their compositor status
111   // updated. As a result, we need to make sure we clear their compositor
112   // status here.
113   bool isRelevant = mAnimation && mAnimation->IsRelevant();
114   if (!isRelevant) {
115     ResetIsRunningOnCompositor();
116   }
117 
118   // Request restyle if necessary.
119   if (mAnimation && !mProperties.IsEmpty() && HasComputedTimingChanged()) {
120     EffectCompositor::RestyleType restyleType =
121         CanThrottle() ? EffectCompositor::RestyleType::Throttled
122                       : EffectCompositor::RestyleType::Standard;
123     RequestRestyle(restyleType);
124   }
125 
126   // Detect changes to "in effect" status since we need to recalculate the
127   // animation cascade for this element whenever that changes.
128   // Note that updating mInEffectOnLastAnimationTimingUpdate has to be done
129   // after above CanThrottle() call since the function uses the flag inside it.
130   bool inEffect = IsInEffect();
131   if (inEffect != mInEffectOnLastAnimationTimingUpdate) {
132     MarkCascadeNeedsUpdate();
133     mInEffectOnLastAnimationTimingUpdate = inEffect;
134   }
135 
136   // If we're no longer "in effect", our ComposeStyle method will never be
137   // called and we will never have a chance to update mProgressOnLastCompose
138   // and mCurrentIterationOnLastCompose.
139   // We clear them here to ensure that if we later become "in effect" we will
140   // request a restyle (above).
141   if (!inEffect) {
142     mProgressOnLastCompose.SetNull();
143     mCurrentIterationOnLastCompose = 0;
144   }
145 }
146 
KeyframesEqualIgnoringComputedOffsets(const nsTArray<Keyframe> & aLhs,const nsTArray<Keyframe> & aRhs)147 static bool KeyframesEqualIgnoringComputedOffsets(
148     const nsTArray<Keyframe>& aLhs, const nsTArray<Keyframe>& aRhs) {
149   if (aLhs.Length() != aRhs.Length()) {
150     return false;
151   }
152 
153   for (size_t i = 0, len = aLhs.Length(); i < len; ++i) {
154     const Keyframe& a = aLhs[i];
155     const Keyframe& b = aRhs[i];
156     if (a.mOffset != b.mOffset || a.mTimingFunction != b.mTimingFunction ||
157         a.mPropertyValues != b.mPropertyValues) {
158       return false;
159     }
160   }
161   return true;
162 }
163 
164 // https://drafts.csswg.org/web-animations/#dom-keyframeeffect-setkeyframes
SetKeyframes(JSContext * aContext,JS::Handle<JSObject * > aKeyframes,ErrorResult & aRv)165 void KeyframeEffectReadOnly::SetKeyframes(JSContext* aContext,
166                                           JS::Handle<JSObject*> aKeyframes,
167                                           ErrorResult& aRv) {
168   nsTArray<Keyframe> keyframes = KeyframeUtils::GetKeyframesFromObject(
169       aContext, mDocument, aKeyframes, aRv);
170   if (aRv.Failed()) {
171     return;
172   }
173 
174   RefPtr<nsStyleContext> styleContext = GetTargetStyleContext();
175   if (styleContext) {
176     if (styleContext->IsGecko()) {
177 #ifdef MOZ_OLD_STYLE
178       auto gecko = styleContext->AsGecko();
179       SetKeyframes(Move(keyframes), gecko);
180 #else
181       MOZ_CRASH("old style system disabled");
182 #endif
183     } else {
184       SetKeyframes(Move(keyframes), styleContext->AsServo());
185     }
186   } else {
187     // SetKeyframes has the same behavior for null StyleType* for
188     // both backends, just pick one and use it.
189     SetKeyframes(Move(keyframes), (ServoStyleContext*)nullptr);
190   }
191 }
192 
193 #ifdef MOZ_OLD_STYLE
SetKeyframes(nsTArray<Keyframe> && aKeyframes,GeckoStyleContext * aStyleContext)194 void KeyframeEffectReadOnly::SetKeyframes(nsTArray<Keyframe>&& aKeyframes,
195                                           GeckoStyleContext* aStyleContext) {
196   DoSetKeyframes(Move(aKeyframes), Move(aStyleContext));
197 }
198 #endif
199 
SetKeyframes(nsTArray<Keyframe> && aKeyframes,const ServoStyleContext * aComputedValues)200 void KeyframeEffectReadOnly::SetKeyframes(
201     nsTArray<Keyframe>&& aKeyframes, const ServoStyleContext* aComputedValues) {
202   DoSetKeyframes(Move(aKeyframes), aComputedValues);
203 }
204 
205 template <typename StyleType>
DoSetKeyframes(nsTArray<Keyframe> && aKeyframes,StyleType * aStyle)206 void KeyframeEffectReadOnly::DoSetKeyframes(nsTArray<Keyframe>&& aKeyframes,
207                                             StyleType* aStyle) {
208 #ifdef MOZ_OLD_STYLE
209   static_assert(IsSame<StyleType, GeckoStyleContext>::value ||
210                     IsSame<StyleType, const ServoStyleContext>::value,
211                 "StyleType should be GeckoStyleContext* or "
212                 "const ServoStyleContext*");
213 #endif
214 
215   if (KeyframesEqualIgnoringComputedOffsets(aKeyframes, mKeyframes)) {
216     return;
217   }
218 
219   mKeyframes = Move(aKeyframes);
220   KeyframeUtils::DistributeKeyframes(mKeyframes);
221 
222   if (mAnimation && mAnimation->IsRelevant()) {
223     nsNodeUtils::AnimationChanged(mAnimation);
224   }
225 
226   // We need to call UpdateProperties() if the StyleType is not nullptr.
227   if (aStyle) {
228     UpdateProperties(aStyle);
229     MaybeUpdateFrameForCompositor();
230   }
231 }
232 
233 const AnimationProperty*
GetEffectiveAnimationOfProperty(nsCSSPropertyID aProperty) const234 KeyframeEffectReadOnly::GetEffectiveAnimationOfProperty(
235     nsCSSPropertyID aProperty) const {
236   EffectSet* effectSet =
237       EffectSet::GetEffectSet(mTarget->mElement, mTarget->mPseudoType);
238   for (size_t propIdx = 0, propEnd = mProperties.Length(); propIdx != propEnd;
239        ++propIdx) {
240     if (aProperty == mProperties[propIdx].mProperty) {
241       const AnimationProperty* result = &mProperties[propIdx];
242       // Skip if there is a property of animation level that is overridden
243       // by !important rules.
244       if (effectSet &&
245           effectSet->PropertiesWithImportantRules().HasProperty(
246               result->mProperty) &&
247           effectSet->PropertiesForAnimationsLevel().HasProperty(
248               result->mProperty)) {
249         result = nullptr;
250       }
251       return result;
252     }
253   }
254   return nullptr;
255 }
256 
HasAnimationOfProperty(nsCSSPropertyID aProperty) const257 bool KeyframeEffectReadOnly::HasAnimationOfProperty(
258     nsCSSPropertyID aProperty) const {
259   for (const AnimationProperty& property : mProperties) {
260     if (property.mProperty == aProperty) {
261       return true;
262     }
263   }
264   return false;
265 }
266 
267 #ifdef DEBUG
SpecifiedKeyframeArraysAreEqual(const nsTArray<Keyframe> & aA,const nsTArray<Keyframe> & aB)268 bool SpecifiedKeyframeArraysAreEqual(const nsTArray<Keyframe>& aA,
269                                      const nsTArray<Keyframe>& aB) {
270   if (aA.Length() != aB.Length()) {
271     return false;
272   }
273 
274   for (size_t i = 0; i < aA.Length(); i++) {
275     const Keyframe& a = aA[i];
276     const Keyframe& b = aB[i];
277     if (a.mOffset != b.mOffset || a.mTimingFunction != b.mTimingFunction ||
278         a.mPropertyValues != b.mPropertyValues) {
279       return false;
280     }
281   }
282 
283   return true;
284 }
285 #endif
286 
UpdateProperties(nsStyleContext * aStyleContext)287 void KeyframeEffectReadOnly::UpdateProperties(nsStyleContext* aStyleContext) {
288   MOZ_ASSERT(aStyleContext);
289 
290   if (aStyleContext->IsGecko()) {
291 #ifdef MOZ_OLD_STYLE
292     auto gecko = aStyleContext->AsGecko();
293     DoUpdateProperties(Move(gecko));
294     return;
295 #else
296     MOZ_CRASH("old style system disabled");
297 #endif
298   }
299 
300   UpdateProperties(aStyleContext->AsServo());
301 }
302 
UpdateProperties(const ServoStyleContext * aStyleContext)303 void KeyframeEffectReadOnly::UpdateProperties(
304     const ServoStyleContext* aStyleContext) {
305   DoUpdateProperties(aStyleContext);
306 }
307 
308 template <typename StyleType>
DoUpdateProperties(StyleType * aStyle)309 void KeyframeEffectReadOnly::DoUpdateProperties(StyleType* aStyle) {
310   MOZ_ASSERT(aStyle);
311 
312   // Skip updating properties when we are composing style.
313   // FIXME: Bug 1324966. Drop this check once we have a function to get
314   // nsStyleContext without resolving animating style.
315   MOZ_DIAGNOSTIC_ASSERT(!mIsComposingStyle,
316                         "Should not be called while processing ComposeStyle()");
317   if (mIsComposingStyle) {
318     return;
319   }
320 
321   nsTArray<AnimationProperty> properties = BuildProperties(aStyle);
322 
323   // We need to update base styles even if any properties are not changed at all
324   // since base styles might have been changed due to parent style changes, etc.
325   EnsureBaseStyles(aStyle, properties);
326 
327   if (mProperties == properties) {
328     return;
329   }
330 
331   // Preserve the state of the mIsRunningOnCompositor flag.
332   nsCSSPropertyIDSet runningOnCompositorProperties;
333 
334   for (const AnimationProperty& property : mProperties) {
335     if (property.mIsRunningOnCompositor) {
336       runningOnCompositorProperties.AddProperty(property.mProperty);
337     }
338   }
339 
340   mProperties = Move(properties);
341   UpdateEffectSet();
342 
343   for (AnimationProperty& property : mProperties) {
344     property.mIsRunningOnCompositor =
345         runningOnCompositorProperties.HasProperty(property.mProperty);
346   }
347 
348   CalculateCumulativeChangeHint(aStyle);
349 
350   MarkCascadeNeedsUpdate();
351 
352   RequestRestyle(EffectCompositor::RestyleType::Layer);
353 }
354 
355 #ifdef MOZ_OLD_STYLE
CompositeValue(nsCSSPropertyID aProperty,const StyleAnimationValue & aValueToComposite,const StyleAnimationValue & aUnderlyingValue,CompositeOperation aCompositeOperation)356 /* static */ StyleAnimationValue KeyframeEffectReadOnly::CompositeValue(
357     nsCSSPropertyID aProperty, const StyleAnimationValue& aValueToComposite,
358     const StyleAnimationValue& aUnderlyingValue,
359     CompositeOperation aCompositeOperation) {
360   // Just return the underlying value if |aValueToComposite| is null
361   // (i.e. missing keyframe).
362   if (aValueToComposite.IsNull()) {
363     return aUnderlyingValue;
364   }
365 
366   switch (aCompositeOperation) {
367     case dom::CompositeOperation::Replace:
368       return aValueToComposite;
369     case dom::CompositeOperation::Add: {
370       StyleAnimationValue result(aValueToComposite);
371       return StyleAnimationValue::Add(aProperty, aUnderlyingValue,
372                                       Move(result));
373     }
374     case dom::CompositeOperation::Accumulate: {
375       StyleAnimationValue result(aValueToComposite);
376       return StyleAnimationValue::Accumulate(aProperty, aUnderlyingValue,
377                                              Move(result));
378     }
379     default:
380       MOZ_ASSERT_UNREACHABLE("Unknown compisite operation type");
381       break;
382   }
383   return StyleAnimationValue();
384 }
385 
GetUnderlyingStyle(nsCSSPropertyID aProperty,const RefPtr<AnimValuesStyleRule> & aAnimationRule)386 StyleAnimationValue KeyframeEffectReadOnly::GetUnderlyingStyle(
387     nsCSSPropertyID aProperty,
388     const RefPtr<AnimValuesStyleRule>& aAnimationRule) {
389   StyleAnimationValue result;
390 
391   if (aAnimationRule && aAnimationRule->HasValue(aProperty)) {
392     // If we have already composed style for the property, we use the style
393     // as the underlying style.
394     DebugOnly<bool> success = aAnimationRule->GetValue(aProperty, result);
395     MOZ_ASSERT(success, "AnimValuesStyleRule::GetValue should not fail");
396   } else {
397     // If we are composing with composite operation that is not 'replace'
398     // and we have not composed style for the property yet, we have to get
399     // the base style for the property.
400     result = BaseStyle(aProperty).mGecko;
401   }
402 
403   return result;
404 }
405 
CompositeValue(nsCSSPropertyID aProperty,const RefPtr<AnimValuesStyleRule> & aAnimationRule,const StyleAnimationValue & aValueToComposite,CompositeOperation aCompositeOperation)406 StyleAnimationValue KeyframeEffectReadOnly::CompositeValue(
407     nsCSSPropertyID aProperty,
408     const RefPtr<AnimValuesStyleRule>& aAnimationRule,
409     const StyleAnimationValue& aValueToComposite,
410     CompositeOperation aCompositeOperation) {
411   MOZ_ASSERT(mTarget, "CompositeValue should be called with target element");
412   MOZ_ASSERT(!mDocument->IsStyledByServo());
413 
414   StyleAnimationValue underlyingValue =
415       GetUnderlyingStyle(aProperty, aAnimationRule);
416 
417   return CompositeValue(aProperty, aValueToComposite, underlyingValue,
418                         aCompositeOperation);
419 }
420 
EnsureBaseStyles(GeckoStyleContext * aStyleContext,const nsTArray<AnimationProperty> & aProperties)421 void KeyframeEffectReadOnly::EnsureBaseStyles(
422     GeckoStyleContext* aStyleContext,
423     const nsTArray<AnimationProperty>& aProperties) {
424   if (!mTarget) {
425     return;
426   }
427 
428   mBaseStyleValues.Clear();
429 
430   RefPtr<GeckoStyleContext> cachedBaseStyleContext;
431 
432   for (const AnimationProperty& property : aProperties) {
433     for (const AnimationPropertySegment& segment : property.mSegments) {
434       if (segment.HasReplaceableValues()) {
435         continue;
436       }
437 
438       EnsureBaseStyle(property.mProperty, aStyleContext,
439                       cachedBaseStyleContext);
440       break;
441     }
442   }
443 }
444 
EnsureBaseStyle(nsCSSPropertyID aProperty,GeckoStyleContext * aStyleContext,RefPtr<GeckoStyleContext> & aCachedBaseStyleContext)445 void KeyframeEffectReadOnly::EnsureBaseStyle(
446     nsCSSPropertyID aProperty, GeckoStyleContext* aStyleContext,
447     RefPtr<GeckoStyleContext>& aCachedBaseStyleContext) {
448   if (mBaseStyleValues.Contains(aProperty)) {
449     return;
450   }
451 
452   if (!aCachedBaseStyleContext) {
453     aCachedBaseStyleContext =
454         aStyleContext->PresContext()
455             ->StyleSet()
456             ->AsGecko()
457             ->ResolveStyleByRemovingAnimation(mTarget->mElement, aStyleContext,
458                                               eRestyle_AllHintsWithAnimations);
459   }
460 
461   StyleAnimationValue result;
462   DebugOnly<bool> success = StyleAnimationValue::ExtractComputedValue(
463       aProperty, aCachedBaseStyleContext, result);
464 
465   MOZ_ASSERT(success, "Should be able to extract computed animation value");
466   MOZ_ASSERT(!result.IsNull(), "Should have a valid StyleAnimationValue");
467 
468   mBaseStyleValues.Put(aProperty, result);
469 }
470 #endif
471 
EnsureBaseStyles(const ServoStyleContext * aComputedValues,const nsTArray<AnimationProperty> & aProperties)472 void KeyframeEffectReadOnly::EnsureBaseStyles(
473     const ServoStyleContext* aComputedValues,
474     const nsTArray<AnimationProperty>& aProperties) {
475   if (!mTarget) {
476     return;
477   }
478 
479   mBaseStyleValuesForServo.Clear();
480 
481   nsPresContext* presContext =
482       nsContentUtils::GetContextForContent(mTarget->mElement);
483   // If |aProperties| is empty we're not going to dereference |presContext| so
484   // we don't care if it is nullptr.
485   //
486   // We could just return early when |aProperties| is empty and save looking up
487   // the pres context, but that won't save any effort normally since we don't
488   // call this function if we have no keyframes to begin with. Furthermore, the
489   // case where |presContext| is nullptr is so rare (we've only ever seen in
490   // fuzzing, and even then we've never been able to reproduce it reliably)
491   // it's not worth the runtime cost of an extra branch.
492   MOZ_ASSERT(presContext || aProperties.IsEmpty(),
493              "Typically presContext should not be nullptr but if it is"
494              " we should have also failed to calculate the computed values"
495              " passed-in as aProperties");
496 
497   RefPtr<ServoStyleContext> baseStyleContext;
498   for (const AnimationProperty& property : aProperties) {
499     EnsureBaseStyle(property, presContext, aComputedValues, baseStyleContext);
500   }
501 }
502 
EnsureBaseStyle(const AnimationProperty & aProperty,nsPresContext * aPresContext,const ServoStyleContext * aComputedStyle,RefPtr<ServoStyleContext> & aBaseStyleContext)503 void KeyframeEffectReadOnly::EnsureBaseStyle(
504     const AnimationProperty& aProperty, nsPresContext* aPresContext,
505     const ServoStyleContext* aComputedStyle,
506     RefPtr<ServoStyleContext>& aBaseStyleContext) {
507   bool hasAdditiveValues = false;
508 
509   for (const AnimationPropertySegment& segment : aProperty.mSegments) {
510     if (!segment.HasReplaceableValues()) {
511       hasAdditiveValues = true;
512       break;
513     }
514   }
515 
516   if (!hasAdditiveValues) {
517     return;
518   }
519 
520   if (!aBaseStyleContext) {
521     Element* animatingElement = EffectCompositor::GetElementToRestyle(
522         mTarget->mElement, mTarget->mPseudoType);
523     aBaseStyleContext =
524         aPresContext->StyleSet()->AsServo()->GetBaseContextForElement(
525             animatingElement, aPresContext, aComputedStyle);
526   }
527   RefPtr<RawServoAnimationValue> baseValue =
528       Servo_ComputedValues_ExtractAnimationValue(aBaseStyleContext,
529                                                  aProperty.mProperty)
530           .Consume();
531   mBaseStyleValuesForServo.Put(aProperty.mProperty, baseValue);
532 }
533 
WillComposeStyle()534 void KeyframeEffectReadOnly::WillComposeStyle() {
535   ComputedTiming computedTiming = GetComputedTiming();
536   mProgressOnLastCompose = computedTiming.mProgress;
537   mCurrentIterationOnLastCompose = computedTiming.mCurrentIteration;
538 }
539 
540 #ifdef MOZ_OLD_STYLE
ComposeStyleRule(RefPtr<AnimValuesStyleRule> & aStyleRule,const AnimationProperty & aProperty,const AnimationPropertySegment & aSegment,const ComputedTiming & aComputedTiming)541 void KeyframeEffectReadOnly::ComposeStyleRule(
542     RefPtr<AnimValuesStyleRule>& aStyleRule, const AnimationProperty& aProperty,
543     const AnimationPropertySegment& aSegment,
544     const ComputedTiming& aComputedTiming) {
545   StyleAnimationValue fromValue =
546       CompositeValue(aProperty.mProperty, aStyleRule,
547                      aSegment.mFromValue.mGecko, aSegment.mFromComposite);
548   StyleAnimationValue toValue =
549       CompositeValue(aProperty.mProperty, aStyleRule, aSegment.mToValue.mGecko,
550                      aSegment.mToComposite);
551   if (fromValue.IsNull() || toValue.IsNull()) {
552     return;
553   }
554 
555   if (!aStyleRule) {
556     // Allocate the style rule now that we know we have animation data.
557     aStyleRule = new AnimValuesStyleRule();
558   }
559 
560   // Iteration composition for accumulate
561   if (mEffectOptions.mIterationComposite ==
562           IterationCompositeOperation::Accumulate &&
563       aComputedTiming.mCurrentIteration > 0) {
564     const AnimationPropertySegment& lastSegment =
565         aProperty.mSegments.LastElement();
566     // FIXME: Bug 1293492: Add a utility function to calculate both of
567     // below StyleAnimationValues.
568     StyleAnimationValue lastValue =
569         lastSegment.mToValue.mGecko.IsNull()
570             ? GetUnderlyingStyle(aProperty.mProperty, aStyleRule)
571             : lastSegment.mToValue.mGecko;
572     fromValue = StyleAnimationValue::Accumulate(
573         aProperty.mProperty, lastValue, Move(fromValue),
574         aComputedTiming.mCurrentIteration);
575     toValue = StyleAnimationValue::Accumulate(
576         aProperty.mProperty, lastValue, Move(toValue),
577         aComputedTiming.mCurrentIteration);
578   }
579 
580   // Special handling for zero-length segments
581   if (aSegment.mToKey == aSegment.mFromKey) {
582     if (aComputedTiming.mProgress.Value() < 0) {
583       aStyleRule->AddValue(aProperty.mProperty, Move(fromValue));
584     } else {
585       aStyleRule->AddValue(aProperty.mProperty, Move(toValue));
586     }
587     return;
588   }
589 
590   double positionInSegment =
591       (aComputedTiming.mProgress.Value() - aSegment.mFromKey) /
592       (aSegment.mToKey - aSegment.mFromKey);
593   double valuePosition = ComputedTimingFunction::GetPortion(
594       aSegment.mTimingFunction, positionInSegment, aComputedTiming.mBeforeFlag);
595 
596   MOZ_ASSERT(IsFinite(valuePosition), "Position value should be finite");
597 
598   StyleAnimationValue val;
599   if (StyleAnimationValue::Interpolate(aProperty.mProperty, fromValue, toValue,
600                                        valuePosition, val)) {
601     aStyleRule->AddValue(aProperty.mProperty, Move(val));
602   } else if (valuePosition < 0.5) {
603     aStyleRule->AddValue(aProperty.mProperty, Move(fromValue));
604   } else {
605     aStyleRule->AddValue(aProperty.mProperty, Move(toValue));
606   }
607 }
608 #endif
609 
ComposeStyleRule(RawServoAnimationValueMap & aAnimationValues,const AnimationProperty & aProperty,const AnimationPropertySegment & aSegment,const ComputedTiming & aComputedTiming)610 void KeyframeEffectReadOnly::ComposeStyleRule(
611     RawServoAnimationValueMap& aAnimationValues,
612     const AnimationProperty& aProperty,
613     const AnimationPropertySegment& aSegment,
614     const ComputedTiming& aComputedTiming) {
615   Servo_AnimationCompose(&aAnimationValues, &mBaseStyleValuesForServo,
616                          aProperty.mProperty, &aSegment,
617                          &aProperty.mSegments.LastElement(), &aComputedTiming,
618                          mEffectOptions.mIterationComposite);
619 }
620 
621 template <typename ComposeAnimationResult>
ComposeStyle(ComposeAnimationResult && aComposeResult,const nsCSSPropertyIDSet & aPropertiesToSkip)622 void KeyframeEffectReadOnly::ComposeStyle(
623     ComposeAnimationResult&& aComposeResult,
624     const nsCSSPropertyIDSet& aPropertiesToSkip) {
625   MOZ_DIAGNOSTIC_ASSERT(!mIsComposingStyle, "Should not be called recursively");
626   if (mIsComposingStyle) {
627     return;
628   }
629 
630   AutoRestore<bool> isComposingStyle(mIsComposingStyle);
631   mIsComposingStyle = true;
632 
633   ComputedTiming computedTiming = GetComputedTiming();
634 
635   // If the progress is null, we don't have fill data for the current
636   // time so we shouldn't animate.
637   if (computedTiming.mProgress.IsNull()) {
638     return;
639   }
640 
641   for (size_t propIdx = 0, propEnd = mProperties.Length(); propIdx != propEnd;
642        ++propIdx) {
643     const AnimationProperty& prop = mProperties[propIdx];
644 
645     MOZ_ASSERT(prop.mSegments[0].mFromKey == 0.0, "incorrect first from key");
646     MOZ_ASSERT(prop.mSegments[prop.mSegments.Length() - 1].mToKey == 1.0,
647                "incorrect last to key");
648 
649     if (aPropertiesToSkip.HasProperty(prop.mProperty)) {
650       continue;
651     }
652 
653     MOZ_ASSERT(prop.mSegments.Length() > 0,
654                "property should not be in animations if it has no segments");
655 
656     // FIXME: Maybe cache the current segment?
657     const AnimationPropertySegment *segment = prop.mSegments.Elements(),
658                                    *segmentEnd =
659                                        segment + prop.mSegments.Length();
660     while (segment->mToKey <= computedTiming.mProgress.Value()) {
661       MOZ_ASSERT(segment->mFromKey <= segment->mToKey, "incorrect keys");
662       if ((segment + 1) == segmentEnd) {
663         break;
664       }
665       ++segment;
666       MOZ_ASSERT(segment->mFromKey == (segment - 1)->mToKey, "incorrect keys");
667     }
668     MOZ_ASSERT(segment->mFromKey <= segment->mToKey, "incorrect keys");
669     MOZ_ASSERT(segment >= prop.mSegments.Elements() &&
670                    size_t(segment - prop.mSegments.Elements()) <
671                        prop.mSegments.Length(),
672                "out of array bounds");
673 
674     ComposeStyleRule(Forward<ComposeAnimationResult>(aComposeResult), prop,
675                      *segment, computedTiming);
676   }
677 
678   // If the animation produces a transform change hint that affects the overflow
679   // region, we need to record the current time to unthrottle the animation
680   // periodically when the animation is being throttled because it's scrolled
681   // out of view.
682   if (HasTransformThatMightAffectOverflow()) {
683     nsPresContext* presContext =
684         nsContentUtils::GetContextForContent(mTarget->mElement);
685     if (presContext) {
686       TimeStamp now = presContext->RefreshDriver()->MostRecentRefresh();
687       EffectSet* effectSet =
688           EffectSet::GetEffectSet(mTarget->mElement, mTarget->mPseudoType);
689       MOZ_ASSERT(effectSet,
690                  "ComposeStyle should only be called on an effect "
691                  "that is part of an effect set");
692       effectSet->UpdateLastTransformSyncTime(now);
693     }
694   }
695 }
696 
IsRunningOnCompositor() const697 bool KeyframeEffectReadOnly::IsRunningOnCompositor() const {
698   // We consider animation is running on compositor if there is at least
699   // one property running on compositor.
700   // Animation.IsRunningOnCompotitor will return more fine grained
701   // information in bug 1196114.
702   for (const AnimationProperty& property : mProperties) {
703     if (property.mIsRunningOnCompositor) {
704       return true;
705     }
706   }
707   return false;
708 }
709 
SetIsRunningOnCompositor(nsCSSPropertyID aProperty,bool aIsRunning)710 void KeyframeEffectReadOnly::SetIsRunningOnCompositor(nsCSSPropertyID aProperty,
711                                                       bool aIsRunning) {
712   MOZ_ASSERT(nsCSSProps::PropHasFlags(aProperty,
713                                       CSS_PROPERTY_CAN_ANIMATE_ON_COMPOSITOR),
714              "Property being animated on compositor is a recognized "
715              "compositor-animatable property");
716   for (AnimationProperty& property : mProperties) {
717     if (property.mProperty == aProperty) {
718       property.mIsRunningOnCompositor = aIsRunning;
719       // We currently only set a performance warning message when animations
720       // cannot be run on the compositor, so if this animation is running
721       // on the compositor we don't need a message.
722       if (aIsRunning) {
723         property.mPerformanceWarning.reset();
724       }
725       return;
726     }
727   }
728 }
729 
ResetIsRunningOnCompositor()730 void KeyframeEffectReadOnly::ResetIsRunningOnCompositor() {
731   for (AnimationProperty& property : mProperties) {
732     property.mIsRunningOnCompositor = false;
733   }
734 }
735 
KeyframeEffectOptionsFromUnion(const UnrestrictedDoubleOrKeyframeEffectOptions & aOptions)736 static const KeyframeEffectOptions& KeyframeEffectOptionsFromUnion(
737     const UnrestrictedDoubleOrKeyframeEffectOptions& aOptions) {
738   MOZ_ASSERT(aOptions.IsKeyframeEffectOptions());
739   return aOptions.GetAsKeyframeEffectOptions();
740 }
741 
KeyframeEffectOptionsFromUnion(const UnrestrictedDoubleOrKeyframeAnimationOptions & aOptions)742 static const KeyframeEffectOptions& KeyframeEffectOptionsFromUnion(
743     const UnrestrictedDoubleOrKeyframeAnimationOptions& aOptions) {
744   MOZ_ASSERT(aOptions.IsKeyframeAnimationOptions());
745   return aOptions.GetAsKeyframeAnimationOptions();
746 }
747 
748 template <class OptionsType>
KeyframeEffectParamsFromUnion(const OptionsType & aOptions,CallerType aCallerType)749 static KeyframeEffectParams KeyframeEffectParamsFromUnion(
750     const OptionsType& aOptions, CallerType aCallerType) {
751   KeyframeEffectParams result;
752   if (aOptions.IsUnrestrictedDouble() ||
753       // Ignore iterationComposite if the Web Animations API is not enabled,
754       // then the default value 'Replace' will be used.
755       !nsDocument::IsWebAnimationsEnabled(aCallerType)) {
756     return result;
757   }
758 
759   const KeyframeEffectOptions& options =
760       KeyframeEffectOptionsFromUnion(aOptions);
761   result.mIterationComposite = options.mIterationComposite;
762   result.mComposite = options.mComposite;
763   return result;
764 }
765 
ConvertTarget(const Nullable<ElementOrCSSPseudoElement> & aTarget)766 /* static */ Maybe<OwningAnimationTarget> KeyframeEffectReadOnly::ConvertTarget(
767     const Nullable<ElementOrCSSPseudoElement>& aTarget) {
768   // Return value optimization.
769   Maybe<OwningAnimationTarget> result;
770 
771   if (aTarget.IsNull()) {
772     return result;
773   }
774 
775   const ElementOrCSSPseudoElement& target = aTarget.Value();
776   MOZ_ASSERT(target.IsElement() || target.IsCSSPseudoElement(),
777              "Uninitialized target");
778 
779   if (target.IsElement()) {
780     result.emplace(&target.GetAsElement());
781   } else {
782     RefPtr<Element> elem = target.GetAsCSSPseudoElement().ParentElement();
783     result.emplace(elem, target.GetAsCSSPseudoElement().GetType());
784   }
785   return result;
786 }
787 
788 template <class KeyframeEffectType, class OptionsType>
789 /* static */ already_AddRefed<KeyframeEffectType>
ConstructKeyframeEffect(const GlobalObject & aGlobal,const Nullable<ElementOrCSSPseudoElement> & aTarget,JS::Handle<JSObject * > aKeyframes,const OptionsType & aOptions,ErrorResult & aRv)790 KeyframeEffectReadOnly::ConstructKeyframeEffect(
791     const GlobalObject& aGlobal,
792     const Nullable<ElementOrCSSPseudoElement>& aTarget,
793     JS::Handle<JSObject*> aKeyframes, const OptionsType& aOptions,
794     ErrorResult& aRv) {
795   // We should get the document from `aGlobal` instead of the current Realm
796   // to make this works in Xray case.
797   //
798   // In all non-Xray cases, `aGlobal` matches the current Realm, so this
799   // matches the spec behavior.
800   //
801   // In Xray case, the new objects should be created using the document of
802   // the target global, but KeyframeEffect and KeyframeEffectReadOnly
803   // constructors are called in the caller's compartment to access
804   // `aKeyframes` object.
805   nsIDocument* doc = AnimationUtils::GetDocumentFromGlobal(aGlobal.Get());
806   if (!doc) {
807     aRv.Throw(NS_ERROR_FAILURE);
808     return nullptr;
809   }
810 
811   TimingParams timingParams =
812       TimingParams::FromOptionsUnion(aOptions, doc, aRv);
813   if (aRv.Failed()) {
814     return nullptr;
815   }
816 
817   KeyframeEffectParams effectOptions =
818       KeyframeEffectParamsFromUnion(aOptions, aGlobal.CallerType());
819 
820   Maybe<OwningAnimationTarget> target = ConvertTarget(aTarget);
821   RefPtr<KeyframeEffectType> effect =
822       new KeyframeEffectType(doc, target, timingParams, effectOptions);
823 
824   effect->SetKeyframes(aGlobal.Context(), aKeyframes, aRv);
825   if (aRv.Failed()) {
826     return nullptr;
827   }
828 
829   return effect.forget();
830 }
831 
832 template <class KeyframeEffectType>
833 /* static */ already_AddRefed<KeyframeEffectType>
ConstructKeyframeEffect(const GlobalObject & aGlobal,KeyframeEffectReadOnly & aSource,ErrorResult & aRv)834 KeyframeEffectReadOnly::ConstructKeyframeEffect(const GlobalObject& aGlobal,
835                                                 KeyframeEffectReadOnly& aSource,
836                                                 ErrorResult& aRv) {
837   nsIDocument* doc = AnimationUtils::GetCurrentRealmDocument(aGlobal.Context());
838   if (!doc) {
839     aRv.Throw(NS_ERROR_FAILURE);
840     return nullptr;
841   }
842 
843   // Create a new KeyframeEffectReadOnly object with aSource's target,
844   // iteration composite operation, composite operation, and spacing mode.
845   // The constructor creates a new AnimationEffect(ReadOnly) object by
846   // aSource's TimingParams.
847   // Note: we don't need to re-throw exceptions since the value specified on
848   //       aSource's timing object can be assumed valid.
849   RefPtr<KeyframeEffectType> effect = new KeyframeEffectType(
850       doc, aSource.mTarget, aSource.SpecifiedTiming(), aSource.mEffectOptions);
851   // Copy cumulative change hint. mCumulativeChangeHint should be the same as
852   // the source one because both of targets are the same.
853   effect->mCumulativeChangeHint = aSource.mCumulativeChangeHint;
854 
855   // Copy aSource's keyframes and animation properties.
856   // Note: We don't call SetKeyframes directly, which might revise the
857   //       computed offsets and rebuild the animation properties.
858   effect->mKeyframes = aSource.mKeyframes;
859   effect->mProperties = aSource.mProperties;
860   return effect.forget();
861 }
862 
863 template <typename StyleType>
BuildProperties(StyleType * aStyle)864 nsTArray<AnimationProperty> KeyframeEffectReadOnly::BuildProperties(
865     StyleType* aStyle) {
866 #ifdef MOZ_OLD_STYLE
867   static_assert(IsSame<StyleType, GeckoStyleContext>::value ||
868                     IsSame<StyleType, const ServoStyleContext>::value,
869                 "StyleType should be GeckoStyleContext* or "
870                 "const ServoStyleContext*");
871 #endif
872 
873   MOZ_ASSERT(aStyle);
874 
875   nsTArray<AnimationProperty> result;
876   // If mTarget is null, return an empty property array.
877   if (!mTarget) {
878     return result;
879   }
880 
881   // When GetComputedKeyframeValues or GetAnimationPropertiesFromKeyframes
882   // calculate computed values from |mKeyframes|, they could possibly
883   // trigger a subsequent restyle in which we rebuild animations. If that
884   // happens we could find that |mKeyframes| is overwritten while it is
885   // being iterated over. Normally that shouldn't happen but just in case we
886   // make a copy of |mKeyframes| first and iterate over that instead.
887   auto keyframesCopy(mKeyframes);
888 
889   result = KeyframeUtils::GetAnimationPropertiesFromKeyframes(
890       keyframesCopy, mTarget->mElement, aStyle, mEffectOptions.mComposite);
891 
892 #ifdef DEBUG
893   MOZ_ASSERT(SpecifiedKeyframeArraysAreEqual(mKeyframes, keyframesCopy),
894              "Apart from the computed offset members, the keyframes array"
895              " should not be modified");
896 #endif
897 
898   mKeyframes.SwapElements(keyframesCopy);
899   return result;
900 }
901 
UpdateTargetRegistration()902 void KeyframeEffectReadOnly::UpdateTargetRegistration() {
903   if (!mTarget) {
904     return;
905   }
906 
907   bool isRelevant = mAnimation && mAnimation->IsRelevant();
908 
909   // Animation::IsRelevant() returns a cached value. It only updates when
910   // something calls Animation::UpdateRelevance. Whenever our timing changes,
911   // we should be notifying our Animation before calling this, so
912   // Animation::IsRelevant() should be up-to-date by the time we get here.
913   MOZ_ASSERT(isRelevant == IsCurrent() || IsInEffect(),
914              "Out of date Animation::IsRelevant value");
915 
916   if (isRelevant && !mInEffectSet) {
917     EffectSet* effectSet = EffectSet::GetOrCreateEffectSet(
918         mTarget->mElement, mTarget->mPseudoType);
919     effectSet->AddEffect(*this);
920     mInEffectSet = true;
921     UpdateEffectSet(effectSet);
922     nsIFrame* f = mTarget->mElement->GetPrimaryFrame();
923     while (f) {
924       f->MarkNeedsDisplayItemRebuild();
925       f = f->GetNextContinuation();
926     }
927   } else if (!isRelevant && mInEffectSet) {
928     UnregisterTarget();
929   }
930 }
931 
UnregisterTarget()932 void KeyframeEffectReadOnly::UnregisterTarget() {
933   if (!mInEffectSet) {
934     return;
935   }
936 
937   EffectSet* effectSet =
938       EffectSet::GetEffectSet(mTarget->mElement, mTarget->mPseudoType);
939   MOZ_ASSERT(effectSet,
940              "If mInEffectSet is true, there must be an EffectSet"
941              " on the target element");
942   mInEffectSet = false;
943   if (effectSet) {
944     effectSet->RemoveEffect(*this);
945 
946     if (effectSet->IsEmpty()) {
947       EffectSet::DestroyEffectSet(mTarget->mElement, mTarget->mPseudoType);
948     }
949   }
950   nsIFrame* f = mTarget->mElement->GetPrimaryFrame();
951   while (f) {
952     f->MarkNeedsDisplayItemRebuild();
953     f = f->GetNextContinuation();
954   }
955 }
956 
RequestRestyle(EffectCompositor::RestyleType aRestyleType)957 void KeyframeEffectReadOnly::RequestRestyle(
958     EffectCompositor::RestyleType aRestyleType) {
959   if (!mTarget) {
960     return;
961   }
962   nsPresContext* presContext =
963       nsContentUtils::GetContextForContent(mTarget->mElement);
964   if (presContext && mAnimation) {
965     presContext->EffectCompositor()->RequestRestyle(
966         mTarget->mElement, mTarget->mPseudoType, aRestyleType,
967         mAnimation->CascadeLevel());
968   }
969 }
970 
971 already_AddRefed<nsStyleContext>
GetTargetStyleContext()972 KeyframeEffectReadOnly::GetTargetStyleContext() {
973   if (!GetRenderedDocument()) {
974     return nullptr;
975   }
976 
977   MOZ_ASSERT(mTarget,
978              "Should only have a document when we have a target element");
979 
980   nsAtom* pseudo =
981       mTarget->mPseudoType < CSSPseudoElementType::Count
982           ? nsCSSPseudoElements::GetPseudoAtom(mTarget->mPseudoType)
983           : nullptr;
984 
985   return nsComputedDOMStyle::GetStyleContext(mTarget->mElement, pseudo);
986 }
987 
988 #ifdef DEBUG
DumpAnimationProperties(nsTArray<AnimationProperty> & aAnimationProperties)989 void DumpAnimationProperties(
990     nsTArray<AnimationProperty>& aAnimationProperties) {
991   for (auto& p : aAnimationProperties) {
992     printf("%s\n", nsCSSProps::GetStringValue(p.mProperty).get());
993     for (auto& s : p.mSegments) {
994       nsString fromValue, toValue;
995       s.mFromValue.SerializeSpecifiedValue(p.mProperty, fromValue);
996       s.mToValue.SerializeSpecifiedValue(p.mProperty, toValue);
997       printf("  %f..%f: %s..%s\n", s.mFromKey, s.mToKey,
998              NS_ConvertUTF16toUTF8(fromValue).get(),
999              NS_ConvertUTF16toUTF8(toValue).get());
1000     }
1001   }
1002 }
1003 #endif
1004 
1005 /* static */ already_AddRefed<KeyframeEffectReadOnly>
Constructor(const GlobalObject & aGlobal,const Nullable<ElementOrCSSPseudoElement> & aTarget,JS::Handle<JSObject * > aKeyframes,const UnrestrictedDoubleOrKeyframeEffectOptions & aOptions,ErrorResult & aRv)1006 KeyframeEffectReadOnly::Constructor(
1007     const GlobalObject& aGlobal,
1008     const Nullable<ElementOrCSSPseudoElement>& aTarget,
1009     JS::Handle<JSObject*> aKeyframes,
1010     const UnrestrictedDoubleOrKeyframeEffectOptions& aOptions,
1011     ErrorResult& aRv) {
1012   return ConstructKeyframeEffect<KeyframeEffectReadOnly>(
1013       aGlobal, aTarget, aKeyframes, aOptions, aRv);
1014 }
1015 
1016 /* static */ already_AddRefed<KeyframeEffectReadOnly>
Constructor(const GlobalObject & aGlobal,KeyframeEffectReadOnly & aSource,ErrorResult & aRv)1017 KeyframeEffectReadOnly::Constructor(const GlobalObject& aGlobal,
1018                                     KeyframeEffectReadOnly& aSource,
1019                                     ErrorResult& aRv) {
1020   return ConstructKeyframeEffect<KeyframeEffectReadOnly>(aGlobal, aSource, aRv);
1021 }
1022 
GetTarget(Nullable<OwningElementOrCSSPseudoElement> & aRv) const1023 void KeyframeEffectReadOnly::GetTarget(
1024     Nullable<OwningElementOrCSSPseudoElement>& aRv) const {
1025   if (!mTarget) {
1026     aRv.SetNull();
1027     return;
1028   }
1029 
1030   switch (mTarget->mPseudoType) {
1031     case CSSPseudoElementType::before:
1032     case CSSPseudoElementType::after:
1033       aRv.SetValue().SetAsCSSPseudoElement() =
1034           CSSPseudoElement::GetCSSPseudoElement(mTarget->mElement,
1035                                                 mTarget->mPseudoType);
1036       break;
1037 
1038     case CSSPseudoElementType::NotPseudo:
1039       aRv.SetValue().SetAsElement() = mTarget->mElement;
1040       break;
1041 
1042     default:
1043       NS_NOTREACHED("Animation of unsupported pseudo-type");
1044       aRv.SetNull();
1045   }
1046 }
1047 
CreatePropertyValue(nsCSSPropertyID aProperty,float aOffset,const Maybe<ComputedTimingFunction> & aTimingFunction,const AnimationValue & aValue,dom::CompositeOperation aComposite,AnimationPropertyValueDetails & aResult)1048 static void CreatePropertyValue(
1049     nsCSSPropertyID aProperty, float aOffset,
1050     const Maybe<ComputedTimingFunction>& aTimingFunction,
1051     const AnimationValue& aValue, dom::CompositeOperation aComposite,
1052     AnimationPropertyValueDetails& aResult) {
1053   aResult.mOffset = aOffset;
1054 
1055   if (!aValue.IsNull()) {
1056     nsString stringValue;
1057     aValue.SerializeSpecifiedValue(aProperty, stringValue);
1058     aResult.mValue.Construct(stringValue);
1059   }
1060 
1061   if (aTimingFunction) {
1062     aResult.mEasing.Construct();
1063     aTimingFunction->AppendToString(aResult.mEasing.Value());
1064   } else {
1065     aResult.mEasing.Construct(NS_LITERAL_STRING("linear"));
1066   }
1067 
1068   aResult.mComposite = aComposite;
1069 }
1070 
GetProperties(nsTArray<AnimationPropertyDetails> & aProperties,ErrorResult & aRv) const1071 void KeyframeEffectReadOnly::GetProperties(
1072     nsTArray<AnimationPropertyDetails>& aProperties, ErrorResult& aRv) const {
1073   for (const AnimationProperty& property : mProperties) {
1074     AnimationPropertyDetails propertyDetails;
1075     propertyDetails.mProperty =
1076         NS_ConvertASCIItoUTF16(nsCSSProps::GetStringValue(property.mProperty));
1077     propertyDetails.mRunningOnCompositor = property.mIsRunningOnCompositor;
1078 
1079     nsAutoString localizedString;
1080     if (property.mPerformanceWarning &&
1081         property.mPerformanceWarning->ToLocalizedString(localizedString)) {
1082       propertyDetails.mWarning.Construct(localizedString);
1083     }
1084 
1085     if (!propertyDetails.mValues.SetCapacity(property.mSegments.Length(),
1086                                              mozilla::fallible)) {
1087       aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
1088       return;
1089     }
1090 
1091     for (size_t segmentIdx = 0, segmentLen = property.mSegments.Length();
1092          segmentIdx < segmentLen; segmentIdx++) {
1093       const AnimationPropertySegment& segment = property.mSegments[segmentIdx];
1094 
1095       binding_detail::FastAnimationPropertyValueDetails fromValue;
1096       CreatePropertyValue(property.mProperty, segment.mFromKey,
1097                           segment.mTimingFunction, segment.mFromValue,
1098                           segment.mFromComposite, fromValue);
1099       // We don't apply timing functions for zero-length segments, so
1100       // don't return one here.
1101       if (segment.mFromKey == segment.mToKey) {
1102         fromValue.mEasing.Reset();
1103       }
1104       // The following won't fail since we have already allocated the capacity
1105       // above.
1106       propertyDetails.mValues.AppendElement(fromValue, mozilla::fallible);
1107 
1108       // Normally we can ignore the to-value for this segment since it is
1109       // identical to the from-value from the next segment. However, we need
1110       // to add it if either:
1111       // a) this is the last segment, or
1112       // b) the next segment's from-value differs.
1113       if (segmentIdx == segmentLen - 1 ||
1114           property.mSegments[segmentIdx + 1].mFromValue != segment.mToValue) {
1115         binding_detail::FastAnimationPropertyValueDetails toValue;
1116         CreatePropertyValue(property.mProperty, segment.mToKey, Nothing(),
1117                             segment.mToValue, segment.mToComposite, toValue);
1118         // It doesn't really make sense to have a timing function on the
1119         // last property value or before a sudden jump so we just drop the
1120         // easing property altogether.
1121         toValue.mEasing.Reset();
1122         propertyDetails.mValues.AppendElement(toValue, mozilla::fallible);
1123       }
1124     }
1125 
1126     aProperties.AppendElement(propertyDetails);
1127   }
1128 }
1129 
GetKeyframes(JSContext * & aCx,nsTArray<JSObject * > & aResult,ErrorResult & aRv)1130 void KeyframeEffectReadOnly::GetKeyframes(JSContext*& aCx,
1131                                           nsTArray<JSObject*>& aResult,
1132                                           ErrorResult& aRv) {
1133   MOZ_ASSERT(aResult.IsEmpty());
1134   MOZ_ASSERT(!aRv.Failed());
1135 
1136   if (!aResult.SetCapacity(mKeyframes.Length(), mozilla::fallible)) {
1137     aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
1138     return;
1139   }
1140 
1141   bool isServo = mDocument->IsStyledByServo();
1142   bool isCSSAnimation = mAnimation && mAnimation->AsCSSAnimation();
1143 
1144   // For Servo, when we have CSS Animation @keyframes with variables, we convert
1145   // shorthands to longhands if needed, and store a reference to the unparsed
1146   // value. When it comes time to serialize, however, what do you serialize for
1147   // a longhand that comes from a variable reference in a shorthand? Servo says,
1148   // "an empty string" which is not particularly helpful.
1149   //
1150   // We should just store shorthands as-is (bug 1391537) and then return the
1151   // variable references, but for now, since we don't do that, and in order to
1152   // be consistent with Gecko, we just expand the variables (assuming we have
1153   // enough context to do so). For that we need to grab the style context so we
1154   // know what custom property values to provide.
1155   RefPtr<nsStyleContext> styleContext;
1156   if (isServo && isCSSAnimation) {
1157     // The following will flush style but that's ok since if you update
1158     // a variable's computed value, you expect to see that updated value in the
1159     // result of getKeyframes().
1160     //
1161     // If we don't have a target, the following will return null. In that case
1162     // we might end up returning variables as-is or empty string. That should be
1163     // acceptable however, since such a case is rare and this is only
1164     // short-term (and unshipped) behavior until bug 1391537 is fixed.
1165     styleContext = GetTargetStyleContext();
1166   }
1167 
1168   for (const Keyframe& keyframe : mKeyframes) {
1169     // Set up a dictionary object for the explicit members
1170     BaseComputedKeyframe keyframeDict;
1171     if (keyframe.mOffset) {
1172       keyframeDict.mOffset.SetValue(keyframe.mOffset.value());
1173     }
1174     MOZ_ASSERT(keyframe.mComputedOffset != Keyframe::kComputedOffsetNotSet,
1175                "Invalid computed offset");
1176     keyframeDict.mComputedOffset.Construct(keyframe.mComputedOffset);
1177     if (keyframe.mTimingFunction) {
1178       keyframeDict.mEasing.Truncate();
1179       keyframe.mTimingFunction.ref().AppendToString(keyframeDict.mEasing);
1180     }  // else if null, leave easing as its default "linear".
1181 
1182     if (keyframe.mComposite) {
1183       keyframeDict.mComposite.SetValue(keyframe.mComposite.value());
1184     }
1185 
1186     JS::Rooted<JS::Value> keyframeJSValue(aCx);
1187     if (!ToJSValue(aCx, keyframeDict, &keyframeJSValue)) {
1188       aRv.Throw(NS_ERROR_FAILURE);
1189       return;
1190     }
1191 
1192     RefPtr<RawServoDeclarationBlock> customProperties;
1193     // A workaround for CSS Animations in servo backend, custom properties in
1194     // keyframe are stored in a servo's declaration block. Find the declaration
1195     // block to resolve CSS variables in the keyframe.
1196     // This workaround will be solved by bug 1391537.
1197     if (isServo && isCSSAnimation) {
1198       for (const PropertyValuePair& propertyValue : keyframe.mPropertyValues) {
1199         if (propertyValue.mProperty ==
1200             nsCSSPropertyID::eCSSPropertyExtra_variable) {
1201           customProperties = propertyValue.mServoDeclarationBlock;
1202           break;
1203         }
1204       }
1205     }
1206 
1207     JS::Rooted<JSObject*> keyframeObject(aCx, &keyframeJSValue.toObject());
1208     for (const PropertyValuePair& propertyValue : keyframe.mPropertyValues) {
1209       nsAutoString stringValue;
1210       if (isServo) {
1211         // Don't serialize the custom properties for this keyframe.
1212         if (propertyValue.mProperty ==
1213             nsCSSPropertyID::eCSSPropertyExtra_variable) {
1214           continue;
1215         }
1216         if (propertyValue.mServoDeclarationBlock) {
1217           const ServoStyleContext* servoStyleContext =
1218               styleContext ? styleContext->AsServo() : nullptr;
1219           Servo_DeclarationBlock_SerializeOneValue(
1220               propertyValue.mServoDeclarationBlock, propertyValue.mProperty,
1221               &stringValue, servoStyleContext, customProperties);
1222         } else {
1223           RawServoAnimationValue* value =
1224               mBaseStyleValuesForServo.GetWeak(propertyValue.mProperty);
1225 
1226           if (value) {
1227             Servo_AnimationValue_Serialize(value, propertyValue.mProperty,
1228                                            &stringValue);
1229           }
1230         }
1231       } else {
1232 #ifdef MOZ_OLD_STYLE
1233         if (nsCSSProps::IsShorthand(propertyValue.mProperty)) {
1234           // nsCSSValue::AppendToString does not accept shorthands properties
1235           // but works with token stream values if we pass eCSSProperty_UNKNOWN
1236           // as the property.
1237           propertyValue.mValue.AppendToString(eCSSProperty_UNKNOWN,
1238                                               stringValue);
1239         } else {
1240           nsCSSValue cssValue = propertyValue.mValue;
1241           if (cssValue.GetUnit() == eCSSUnit_Null) {
1242             // We use an uninitialized nsCSSValue to represent the
1243             // "neutral value". We currently only do this for keyframes
1244             // generated from CSS animations with missing 0%/100% keyframes.
1245             // Furthermore, currently (at least until bug 1339334) keyframes
1246             // generated from CSS animations only contain longhand properties so
1247             // we only need to handle null nsCSSValues for longhand properties.
1248             DebugOnly<bool> uncomputeResult =
1249                 StyleAnimationValue::UncomputeValue(
1250                     propertyValue.mProperty,
1251                     Move(BaseStyle(propertyValue.mProperty).mGecko), cssValue);
1252 
1253             MOZ_ASSERT(uncomputeResult,
1254                        "Unable to get specified value from computed value");
1255             MOZ_ASSERT(cssValue.GetUnit() != eCSSUnit_Null,
1256                        "Got null computed value");
1257           }
1258           cssValue.AppendToString(propertyValue.mProperty, stringValue);
1259         }
1260 #else
1261         MOZ_CRASH("old style system disabled");
1262 #endif
1263       }
1264 
1265       const char* name = nsCSSProps::PropertyIDLName(propertyValue.mProperty);
1266       JS::Rooted<JS::Value> value(aCx);
1267       if (!ToJSValue(aCx, stringValue, &value) ||
1268           !JS_DefineProperty(aCx, keyframeObject, name, value,
1269                              JSPROP_ENUMERATE)) {
1270         aRv.Throw(NS_ERROR_FAILURE);
1271         return;
1272       }
1273     }
1274 
1275     aResult.AppendElement(keyframeObject);
1276   }
1277 }
1278 
1279 /* static */ const TimeDuration
OverflowRegionRefreshInterval()1280 KeyframeEffectReadOnly::OverflowRegionRefreshInterval() {
1281   // The amount of time we can wait between updating throttled animations
1282   // on the main thread that influence the overflow region.
1283   static const TimeDuration kOverflowRegionRefreshInterval =
1284       TimeDuration::FromMilliseconds(200);
1285 
1286   return kOverflowRegionRefreshInterval;
1287 }
1288 
CanThrottle() const1289 bool KeyframeEffectReadOnly::CanThrottle() const {
1290   // Unthrottle if we are not in effect or current. This will be the case when
1291   // our owning animation has finished, is idle, or when we are in the delay
1292   // phase (but without a backwards fill). In each case the computed progress
1293   // value produced on each tick will be the same so we will skip requesting
1294   // unnecessary restyles in NotifyAnimationTimingUpdated. Any calls we *do* get
1295   // here will be because of a change in state (e.g. we are newly finished or
1296   // newly no longer in effect) in which case we shouldn't throttle the sample.
1297   if (!IsInEffect() || !IsCurrent()) {
1298     return false;
1299   }
1300 
1301   nsIFrame* frame = GetAnimationFrame();
1302   if (!frame) {
1303     // There are two possible cases here.
1304     // a) No target element
1305     // b) The target element has no frame, e.g. because it is in a display:none
1306     //    subtree.
1307     // In either case we can throttle the animation because there is no
1308     // need to update on the main thread.
1309     return true;
1310   }
1311 
1312   // Unless we are newly in-effect, we can throttle the animation if the
1313   // animation is paint only and the target frame is out of view or the document
1314   // is in background tabs.
1315   if (mInEffectOnLastAnimationTimingUpdate && CanIgnoreIfNotVisible()) {
1316     nsIPresShell* presShell = GetPresShell();
1317     if (presShell && !presShell->IsActive()) {
1318       return true;
1319     }
1320 
1321     const bool isVisibilityHidden =
1322         !frame->IsVisibleOrMayHaveVisibleDescendants();
1323     if ((isVisibilityHidden && !HasVisibilityChange()) ||
1324         frame->IsScrolledOutOfView()) {
1325       // If there are transform change hints, unthrottle the animation
1326       // periodically since it might affect the overflow region.
1327       if (HasTransformThatMightAffectOverflow()) {
1328         // Don't throttle finite transform animations since the animation might
1329         // suddenly come into view and if it was throttled it will be
1330         // out-of-sync.
1331         if (HasFiniteActiveDuration()) {
1332           return false;
1333         }
1334 
1335         return isVisibilityHidden
1336                    ? CanThrottleTransformChangesInScrollable(*frame)
1337                    : CanThrottleTransformChanges(*frame);
1338       }
1339       return true;
1340     }
1341   }
1342 
1343   // First we need to check layer generation and transform overflow
1344   // prior to the property.mIsRunningOnCompositor check because we should
1345   // occasionally unthrottle these animations even if the animations are
1346   // already running on compositor.
1347   for (const LayerAnimationInfo::Record& record :
1348        LayerAnimationInfo::sRecords) {
1349     // Skip properties that are overridden by !important rules.
1350     // (GetEffectiveAnimationOfProperty, as called by
1351     // HasEffectiveAnimationOfProperty, only returns a property which is
1352     // neither overridden by !important rules nor overridden by other
1353     // animation.)
1354     if (!HasEffectiveAnimationOfProperty(record.mProperty)) {
1355       continue;
1356     }
1357 
1358     EffectSet* effectSet =
1359         EffectSet::GetEffectSet(mTarget->mElement, mTarget->mPseudoType);
1360     MOZ_ASSERT(effectSet,
1361                "CanThrottle should be called on an effect "
1362                "associated with a target element");
1363     layers::Layer* layer =
1364         FrameLayerBuilder::GetDedicatedLayer(frame, record.mLayerType);
1365     // Unthrottle if the layer needs to be brought up to date
1366     if (!layer || effectSet->GetAnimationGeneration() !=
1367                       layer->GetAnimationGeneration()) {
1368       return false;
1369     }
1370 
1371     // If this is a transform animation that affects the overflow region,
1372     // we should unthrottle the animation periodically.
1373     if (HasTransformThatMightAffectOverflow() &&
1374         !CanThrottleTransformChangesInScrollable(*frame)) {
1375       return false;
1376     }
1377   }
1378 
1379   for (const AnimationProperty& property : mProperties) {
1380     if (!property.mIsRunningOnCompositor) {
1381       return false;
1382     }
1383   }
1384 
1385   return true;
1386 }
1387 
CanThrottleTransformChanges(const nsIFrame & aFrame) const1388 bool KeyframeEffectReadOnly::CanThrottleTransformChanges(
1389     const nsIFrame& aFrame) const {
1390   TimeStamp now = aFrame.PresContext()->RefreshDriver()->MostRecentRefresh();
1391 
1392   EffectSet* effectSet =
1393       EffectSet::GetEffectSet(mTarget->mElement, mTarget->mPseudoType);
1394   MOZ_ASSERT(effectSet,
1395              "CanThrottleTransformChanges is expected to be called"
1396              " on an effect in an effect set");
1397   MOZ_ASSERT(mAnimation,
1398              "CanThrottleTransformChanges is expected to be called"
1399              " on an effect with a parent animation");
1400   TimeStamp lastSyncTime = effectSet->LastTransformSyncTime();
1401   // If this animation can cause overflow, we can throttle some of the ticks.
1402   return (!lastSyncTime.IsNull() &&
1403           (now - lastSyncTime) < OverflowRegionRefreshInterval());
1404 }
1405 
CanThrottleTransformChangesInScrollable(nsIFrame & aFrame) const1406 bool KeyframeEffectReadOnly::CanThrottleTransformChangesInScrollable(
1407     nsIFrame& aFrame) const {
1408   // If the target element is not associated with any documents, we don't care
1409   // it.
1410   nsIDocument* doc = GetRenderedDocument();
1411   if (!doc) {
1412     return true;
1413   }
1414 
1415   bool hasIntersectionObservers = doc->HasIntersectionObservers();
1416 
1417   // If we know that the animation cannot cause overflow,
1418   // we can just disable flushes for this animation.
1419 
1420   // If we don't show scrollbars and have no intersection observers, we don't
1421   // care about overflow.
1422   if (LookAndFeel::GetInt(LookAndFeel::eIntID_ShowHideScrollbars) == 0 &&
1423       !hasIntersectionObservers) {
1424     return true;
1425   }
1426 
1427   if (CanThrottleTransformChanges(aFrame)) {
1428     return true;
1429   }
1430 
1431   // If we have any intersection observers, we unthrottle this transform
1432   // animation periodically.
1433   if (hasIntersectionObservers) {
1434     return false;
1435   }
1436 
1437   // If the nearest scrollable ancestor has overflow:hidden,
1438   // we don't care about overflow.
1439   nsIScrollableFrame* scrollable =
1440       nsLayoutUtils::GetNearestScrollableFrame(&aFrame);
1441   if (!scrollable) {
1442     return true;
1443   }
1444 
1445   ScrollbarStyles ss = scrollable->GetScrollbarStyles();
1446   if (ss.mVertical == NS_STYLE_OVERFLOW_HIDDEN &&
1447       ss.mHorizontal == NS_STYLE_OVERFLOW_HIDDEN &&
1448       scrollable->GetLogicalScrollPosition() == nsPoint(0, 0)) {
1449     return true;
1450   }
1451 
1452   return false;
1453 }
1454 
GetAnimationFrame() const1455 nsIFrame* KeyframeEffectReadOnly::GetAnimationFrame() const {
1456   if (!mTarget) {
1457     return nullptr;
1458   }
1459 
1460   nsIFrame* frame;
1461   if (mTarget->mPseudoType == CSSPseudoElementType::before) {
1462     frame = nsLayoutUtils::GetBeforeFrame(mTarget->mElement);
1463   } else if (mTarget->mPseudoType == CSSPseudoElementType::after) {
1464     frame = nsLayoutUtils::GetAfterFrame(mTarget->mElement);
1465   } else {
1466     frame = mTarget->mElement->GetPrimaryFrame();
1467     MOZ_ASSERT(mTarget->mPseudoType == CSSPseudoElementType::NotPseudo,
1468                "unknown mTarget->mPseudoType");
1469   }
1470 
1471   if (!frame) {
1472     return nullptr;
1473   }
1474 
1475   return nsLayoutUtils::GetStyleFrame(frame);
1476 }
1477 
GetRenderedDocument() const1478 nsIDocument* KeyframeEffectReadOnly::GetRenderedDocument() const {
1479   if (!mTarget) {
1480     return nullptr;
1481   }
1482   return mTarget->mElement->GetComposedDoc();
1483 }
1484 
GetPresShell() const1485 nsIPresShell* KeyframeEffectReadOnly::GetPresShell() const {
1486   nsIDocument* doc = GetRenderedDocument();
1487   if (!doc) {
1488     return nullptr;
1489   }
1490   return doc->GetShell();
1491 }
1492 
IsGeometricProperty(const nsCSSPropertyID aProperty)1493 /* static */ bool KeyframeEffectReadOnly::IsGeometricProperty(
1494     const nsCSSPropertyID aProperty) {
1495   MOZ_ASSERT(!nsCSSProps::IsShorthand(aProperty),
1496              "Property should be a longhand property");
1497 
1498   switch (aProperty) {
1499     case eCSSProperty_bottom:
1500     case eCSSProperty_height:
1501     case eCSSProperty_left:
1502     case eCSSProperty_margin_bottom:
1503     case eCSSProperty_margin_left:
1504     case eCSSProperty_margin_right:
1505     case eCSSProperty_margin_top:
1506     case eCSSProperty_padding_bottom:
1507     case eCSSProperty_padding_left:
1508     case eCSSProperty_padding_right:
1509     case eCSSProperty_padding_top:
1510     case eCSSProperty_right:
1511     case eCSSProperty_top:
1512     case eCSSProperty_width:
1513       return true;
1514     default:
1515       return false;
1516   }
1517 }
1518 
CanAnimateTransformOnCompositor(const nsIFrame * aFrame,AnimationPerformanceWarning::Type & aPerformanceWarning)1519 /* static */ bool KeyframeEffectReadOnly::CanAnimateTransformOnCompositor(
1520     const nsIFrame* aFrame,
1521     AnimationPerformanceWarning::Type& aPerformanceWarning) {
1522   // Disallow OMTA for preserve-3d transform. Note that we check the style
1523   // property rather than Extend3DContext() since that can recurse back into
1524   // this function via HasOpacity(). See bug 779598.
1525   if (aFrame->Combines3DTransformWithAncestors() ||
1526       aFrame->StyleDisplay()->mTransformStyle ==
1527           NS_STYLE_TRANSFORM_STYLE_PRESERVE_3D) {
1528     aPerformanceWarning =
1529         AnimationPerformanceWarning::Type::TransformPreserve3D;
1530     return false;
1531   }
1532   // Note that testing BackfaceIsHidden() is not a sufficient test for
1533   // what we need for animating backface-visibility correctly if we
1534   // remove the above test for Extend3DContext(); that would require
1535   // looking at backface-visibility on descendants as well. See bug 1186204.
1536   if (aFrame->BackfaceIsHidden()) {
1537     aPerformanceWarning =
1538         AnimationPerformanceWarning::Type::TransformBackfaceVisibilityHidden;
1539     return false;
1540   }
1541   // Async 'transform' animations of aFrames with SVG transforms is not
1542   // supported.  See bug 779599.
1543   if (aFrame->IsSVGTransformed()) {
1544     aPerformanceWarning = AnimationPerformanceWarning::Type::TransformSVG;
1545     return false;
1546   }
1547 
1548   return true;
1549 }
1550 
ShouldBlockAsyncTransformAnimations(const nsIFrame * aFrame,AnimationPerformanceWarning::Type & aPerformanceWarning) const1551 bool KeyframeEffectReadOnly::ShouldBlockAsyncTransformAnimations(
1552     const nsIFrame* aFrame,
1553     AnimationPerformanceWarning::Type& aPerformanceWarning) const {
1554   EffectSet* effectSet =
1555       EffectSet::GetEffectSet(mTarget->mElement, mTarget->mPseudoType);
1556   for (const AnimationProperty& property : mProperties) {
1557     // If there is a property for animations level that is overridden by
1558     // !important rules, it should not block other animations from running
1559     // on the compositor.
1560     // NOTE: We don't currently check for !important rules for properties that
1561     // don't run on the compositor. As result such properties (e.g. margin-left)
1562     // can still block async animations even if they are overridden by
1563     // !important rules.
1564     if (effectSet &&
1565         effectSet->PropertiesWithImportantRules().HasProperty(
1566             property.mProperty) &&
1567         effectSet->PropertiesForAnimationsLevel().HasProperty(
1568             property.mProperty)) {
1569       continue;
1570     }
1571     // Check for geometric properties
1572     if (IsGeometricProperty(property.mProperty)) {
1573       aPerformanceWarning =
1574           AnimationPerformanceWarning::Type::TransformWithGeometricProperties;
1575       return true;
1576     }
1577 
1578     // Check for unsupported transform animations
1579     if (property.mProperty == eCSSProperty_transform) {
1580       if (!CanAnimateTransformOnCompositor(aFrame, aPerformanceWarning)) {
1581         return true;
1582       }
1583     }
1584   }
1585 
1586   // XXX cku temporarily disable async-animation when this frame has any
1587   // individual transforms before bug 1425837 been fixed.
1588   if (aFrame->StyleDisplay()->HasIndividualTransform()) {
1589     return true;
1590   }
1591 
1592   return false;
1593 }
1594 
HasGeometricProperties() const1595 bool KeyframeEffectReadOnly::HasGeometricProperties() const {
1596   for (const AnimationProperty& property : mProperties) {
1597     if (IsGeometricProperty(property.mProperty)) {
1598       return true;
1599     }
1600   }
1601 
1602   return false;
1603 }
1604 
SetPerformanceWarning(nsCSSPropertyID aProperty,const AnimationPerformanceWarning & aWarning)1605 void KeyframeEffectReadOnly::SetPerformanceWarning(
1606     nsCSSPropertyID aProperty, const AnimationPerformanceWarning& aWarning) {
1607   for (AnimationProperty& property : mProperties) {
1608     if (property.mProperty == aProperty &&
1609         (!property.mPerformanceWarning ||
1610          *property.mPerformanceWarning != aWarning)) {
1611       property.mPerformanceWarning = Some(aWarning);
1612 
1613       nsAutoString localizedString;
1614       if (nsLayoutUtils::IsAnimationLoggingEnabled() &&
1615           property.mPerformanceWarning->ToLocalizedString(localizedString)) {
1616         nsAutoCString logMessage = NS_ConvertUTF16toUTF8(localizedString);
1617         AnimationUtils::LogAsyncAnimationFailure(logMessage, mTarget->mElement);
1618       }
1619       return;
1620     }
1621   }
1622 }
1623 
1624 #ifdef MOZ_OLD_STYLE
1625 already_AddRefed<nsStyleContext>
CreateStyleContextForAnimationValue(nsCSSPropertyID aProperty,const AnimationValue & aValue,GeckoStyleContext * aBaseStyleContext)1626 KeyframeEffectReadOnly::CreateStyleContextForAnimationValue(
1627     nsCSSPropertyID aProperty, const AnimationValue& aValue,
1628     GeckoStyleContext* aBaseStyleContext) {
1629   MOZ_ASSERT(aBaseStyleContext,
1630              "CreateStyleContextForAnimationValue needs to be called "
1631              "with a valid GeckoStyleContext");
1632 
1633   RefPtr<AnimValuesStyleRule> styleRule = new AnimValuesStyleRule();
1634   styleRule->AddValue(aProperty, aValue.mGecko);
1635 
1636   nsCOMArray<nsIStyleRule> rules;
1637   rules.AppendObject(styleRule);
1638 
1639   nsStyleSet* styleSet =
1640       aBaseStyleContext->PresContext()->StyleSet()->AsGecko();
1641 
1642   RefPtr<GeckoStyleContext> styleContext =
1643       styleSet->ResolveStyleByAddingRules(aBaseStyleContext, rules);
1644 
1645   // We need to call StyleData to generate cached data for the style context.
1646   // Otherwise CalcStyleDifference returns no meaningful result.
1647   styleContext->StyleData(nsCSSProps::kSIDTable[aProperty]);
1648 
1649   return styleContext.forget();
1650 }
1651 #endif
1652 
1653 already_AddRefed<nsStyleContext>
CreateStyleContextForAnimationValue(nsCSSPropertyID aProperty,const AnimationValue & aValue,const ServoStyleContext * aBaseStyleContext)1654 KeyframeEffectReadOnly::CreateStyleContextForAnimationValue(
1655     nsCSSPropertyID aProperty, const AnimationValue& aValue,
1656     const ServoStyleContext* aBaseStyleContext) {
1657   MOZ_ASSERT(aBaseStyleContext,
1658              "CreateStyleContextForAnimationValue needs to be called "
1659              "with a valid ServoStyleContext");
1660 
1661   ServoStyleSet* styleSet =
1662       aBaseStyleContext->PresContext()->StyleSet()->AsServo();
1663   Element* elementForResolve = EffectCompositor::GetElementToRestyle(
1664       mTarget->mElement, mTarget->mPseudoType);
1665   MOZ_ASSERT(elementForResolve, "The target element shouldn't be null");
1666   return styleSet->ResolveServoStyleByAddingAnimation(
1667       elementForResolve, aBaseStyleContext, aValue.mServo);
1668 }
1669 
1670 template <typename StyleType>
CalculateCumulativeChangeHint(StyleType * aStyleContext)1671 void KeyframeEffectReadOnly::CalculateCumulativeChangeHint(
1672     StyleType* aStyleContext) {
1673   mCumulativeChangeHint = nsChangeHint(0);
1674 
1675   for (const AnimationProperty& property : mProperties) {
1676     // For opacity property we don't produce any change hints that are not
1677     // included in nsChangeHint_Hints_CanIgnoreIfNotVisible so we can throttle
1678     // opacity animations regardless of the change they produce.  This
1679     // optimization is particularly important since it allows us to throttle
1680     // opacity animations with missing 0%/100% keyframes.
1681     if (property.mProperty == eCSSProperty_opacity) {
1682       continue;
1683     }
1684 
1685     for (const AnimationPropertySegment& segment : property.mSegments) {
1686       // In case composite operation is not 'replace' or value is null,
1687       // we can't throttle animations which will not cause any layout changes
1688       // on invisible elements because we can't calculate the change hint for
1689       // such properties until we compose it.
1690       if (!segment.HasReplaceableValues()) {
1691         mCumulativeChangeHint = ~nsChangeHint_Hints_CanIgnoreIfNotVisible;
1692         return;
1693       }
1694       RefPtr<nsStyleContext> fromContext = CreateStyleContextForAnimationValue(
1695           property.mProperty, segment.mFromValue, aStyleContext);
1696       if (!fromContext) {
1697         mCumulativeChangeHint = ~nsChangeHint_Hints_CanIgnoreIfNotVisible;
1698         return;
1699       }
1700 
1701       RefPtr<nsStyleContext> toContext = CreateStyleContextForAnimationValue(
1702           property.mProperty, segment.mToValue, aStyleContext);
1703       if (!toContext) {
1704         mCumulativeChangeHint = ~nsChangeHint_Hints_CanIgnoreIfNotVisible;
1705         return;
1706       }
1707 
1708       uint32_t equalStructs = 0;
1709       uint32_t samePointerStructs = 0;
1710       nsChangeHint changeHint = fromContext->CalcStyleDifference(
1711           toContext, &equalStructs, &samePointerStructs);
1712 
1713       mCumulativeChangeHint |= changeHint;
1714     }
1715   }
1716 }
1717 
SetAnimation(Animation * aAnimation)1718 void KeyframeEffectReadOnly::SetAnimation(Animation* aAnimation) {
1719   if (mAnimation == aAnimation) {
1720     return;
1721   }
1722 
1723   // Restyle for the old animation.
1724   RequestRestyle(EffectCompositor::RestyleType::Layer);
1725 
1726   mAnimation = aAnimation;
1727 
1728   // The order of these function calls is important:
1729   // NotifyAnimationTimingUpdated() need the updated mIsRelevant flag to check
1730   // if it should create the effectSet or not, and MarkCascadeNeedsUpdate()
1731   // needs a valid effectSet, so we should call them in this order.
1732   if (mAnimation) {
1733     mAnimation->UpdateRelevance();
1734   }
1735   NotifyAnimationTimingUpdated();
1736   if (mAnimation) {
1737     MarkCascadeNeedsUpdate();
1738   }
1739 }
1740 
CanIgnoreIfNotVisible() const1741 bool KeyframeEffectReadOnly::CanIgnoreIfNotVisible() const {
1742   if (!AnimationUtils::IsOffscreenThrottlingEnabled()) {
1743     return false;
1744   }
1745 
1746   // FIXME: For further sophisticated optimization we need to check
1747   // change hint on the segment corresponding to computedTiming.progress.
1748   return NS_IsHintSubset(mCumulativeChangeHint,
1749                          nsChangeHint_Hints_CanIgnoreIfNotVisible);
1750 }
1751 
MaybeUpdateFrameForCompositor()1752 void KeyframeEffectReadOnly::MaybeUpdateFrameForCompositor() {
1753   nsIFrame* frame = GetAnimationFrame();
1754   if (!frame) {
1755     return;
1756   }
1757 
1758   // FIXME: Bug 1272495: If this effect does not win in the cascade, the
1759   // NS_FRAME_MAY_BE_TRANSFORMED flag should be removed when the animation
1760   // will be removed from effect set or the transform keyframes are removed
1761   // by setKeyframes. The latter case will be hard to solve though.
1762   for (const AnimationProperty& property : mProperties) {
1763     if (property.mProperty == eCSSProperty_transform) {
1764       frame->AddStateBits(NS_FRAME_MAY_BE_TRANSFORMED);
1765       return;
1766     }
1767   }
1768 }
1769 
MarkCascadeNeedsUpdate()1770 void KeyframeEffectReadOnly::MarkCascadeNeedsUpdate() {
1771   if (!mTarget) {
1772     return;
1773   }
1774 
1775   EffectSet* effectSet =
1776       EffectSet::GetEffectSet(mTarget->mElement, mTarget->mPseudoType);
1777   if (!effectSet) {
1778     return;
1779   }
1780   effectSet->MarkCascadeNeedsUpdate();
1781 }
1782 
HasComputedTimingChanged() const1783 bool KeyframeEffectReadOnly::HasComputedTimingChanged() const {
1784   // Typically we don't need to request a restyle if the progress hasn't
1785   // changed since the last call to ComposeStyle. The one exception is if the
1786   // iteration composite mode is 'accumulate' and the current iteration has
1787   // changed, since that will often produce a different result.
1788   ComputedTiming computedTiming = GetComputedTiming();
1789   return computedTiming.mProgress != mProgressOnLastCompose ||
1790          (mEffectOptions.mIterationComposite ==
1791               IterationCompositeOperation::Accumulate &&
1792           computedTiming.mCurrentIteration != mCurrentIterationOnLastCompose);
1793 }
1794 
ContainsAnimatedScale(const nsIFrame * aFrame) const1795 bool KeyframeEffectReadOnly::ContainsAnimatedScale(
1796     const nsIFrame* aFrame) const {
1797   if (!IsCurrent()) {
1798     return false;
1799   }
1800 
1801   for (const AnimationProperty& prop : mProperties) {
1802     if (prop.mProperty != eCSSProperty_transform) {
1803       continue;
1804     }
1805 
1806     AnimationValue baseStyle = BaseStyle(prop.mProperty);
1807     if (baseStyle.IsNull()) {
1808       // If we failed to get the base style, we consider it has scale value
1809       // here just to be safe.
1810       return true;
1811     }
1812     gfx::Size size = baseStyle.GetScaleValue(aFrame);
1813     if (size != gfx::Size(1.0f, 1.0f)) {
1814       return true;
1815     }
1816 
1817     // This is actually overestimate because there are some cases that combining
1818     // the base value and from/to value produces 1:1 scale. But it doesn't
1819     // really matter.
1820     for (const AnimationPropertySegment& segment : prop.mSegments) {
1821       if (!segment.mFromValue.IsNull()) {
1822         gfx::Size from = segment.mFromValue.GetScaleValue(aFrame);
1823         if (from != gfx::Size(1.0f, 1.0f)) {
1824           return true;
1825         }
1826       }
1827       if (!segment.mToValue.IsNull()) {
1828         gfx::Size to = segment.mToValue.GetScaleValue(aFrame);
1829         if (to != gfx::Size(1.0f, 1.0f)) {
1830           return true;
1831         }
1832       }
1833     }
1834   }
1835 
1836   return false;
1837 }
1838 
UpdateEffectSet(EffectSet * aEffectSet) const1839 void KeyframeEffectReadOnly::UpdateEffectSet(EffectSet* aEffectSet) const {
1840   if (!mInEffectSet) {
1841     return;
1842   }
1843 
1844   EffectSet* effectSet =
1845       aEffectSet
1846           ? aEffectSet
1847           : EffectSet::GetEffectSet(mTarget->mElement, mTarget->mPseudoType);
1848   if (!effectSet) {
1849     return;
1850   }
1851 
1852   nsIFrame* frame = GetAnimationFrame();
1853   if (HasAnimationOfProperty(eCSSProperty_opacity)) {
1854     effectSet->SetMayHaveOpacityAnimation();
1855     if (frame) {
1856       frame->SetMayHaveOpacityAnimation();
1857     }
1858   }
1859   if (HasAnimationOfProperty(eCSSProperty_transform)) {
1860     effectSet->SetMayHaveTransformAnimation();
1861     if (frame) {
1862       frame->SetMayHaveTransformAnimation();
1863     }
1864   }
1865 }
1866 
1867 #ifdef MOZ_OLD_STYLE
1868 template void KeyframeEffectReadOnly::ComposeStyle<
1869     RefPtr<AnimValuesStyleRule>&>(RefPtr<AnimValuesStyleRule>& aAnimationRule,
1870                                   const nsCSSPropertyIDSet& aPropertiesToSkip);
1871 #endif
1872 
1873 template void KeyframeEffectReadOnly::ComposeStyle<RawServoAnimationValueMap&>(
1874     RawServoAnimationValueMap& aAnimationValues,
1875     const nsCSSPropertyIDSet& aPropertiesToSkip);
1876 
1877 }  // namespace dom
1878 }  // namespace mozilla
1879