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/KeyframeEffect.h"
8 
9 #include "mozilla/dom/Animation.h"
10 #include "mozilla/dom/KeyframeAnimationOptionsBinding.h"
11 // For UnrestrictedDoubleOrKeyframeAnimationOptions;
12 #include "mozilla/dom/KeyframeEffectBinding.h"
13 #include "mozilla/dom/MutationObservers.h"
14 #include "mozilla/AnimationUtils.h"
15 #include "mozilla/AutoRestore.h"
16 #include "mozilla/ComputedStyleInlines.h"
17 #include "mozilla/EffectSet.h"
18 #include "mozilla/FloatingPoint.h"  // For IsFinite
19 #include "mozilla/LayerAnimationInfo.h"
20 #include "mozilla/LookAndFeel.h"  // For LookAndFeel::GetInt
21 #include "mozilla/KeyframeUtils.h"
22 #include "mozilla/PresShell.h"
23 #include "mozilla/PresShellInlines.h"
24 #include "mozilla/ServoBindings.h"
25 #include "mozilla/StaticPrefs_dom.h"
26 #include "mozilla/StaticPrefs_gfx.h"
27 #include "mozilla/StaticPrefs_layers.h"
28 #include "Layers.h"              // For Layer
29 #include "nsComputedDOMStyle.h"  // nsComputedDOMStyle::GetComputedStyle
30 #include "nsContentUtils.h"
31 #include "nsCSSPropertyIDSet.h"
32 #include "nsCSSProps.h"             // For nsCSSProps::PropHasFlags
33 #include "nsCSSPseudoElements.h"    // For PseudoStyleType
34 #include "nsCSSRendering.h"         // For IsCanvasFrame
35 #include "nsDOMMutationObserver.h"  // For nsAutoAnimationMutationBatch
36 #include "nsIFrame.h"
37 #include "nsIFrameInlines.h"
38 #include "nsIScrollableFrame.h"
39 #include "nsPresContextInlines.h"
40 #include "nsRefreshDriver.h"
41 #include "js/PropertyAndElement.h"  // JS_DefineProperty
42 #include "WindowRenderer.h"
43 
44 namespace mozilla {
45 
SetPerformanceWarning(const AnimationPerformanceWarning & aWarning,const dom::Element * aElement)46 void AnimationProperty::SetPerformanceWarning(
47     const AnimationPerformanceWarning& aWarning, const dom::Element* aElement) {
48   if (mPerformanceWarning && *mPerformanceWarning == aWarning) {
49     return;
50   }
51 
52   mPerformanceWarning = Some(aWarning);
53 
54   nsAutoString localizedString;
55   if (StaticPrefs::layers_offmainthreadcomposition_log_animations() &&
56       mPerformanceWarning->ToLocalizedString(localizedString)) {
57     nsAutoCString logMessage = NS_ConvertUTF16toUTF8(localizedString);
58     AnimationUtils::LogAsyncAnimationFailure(logMessage, aElement);
59   }
60 }
61 
operator ==(const PropertyValuePair & aOther) const62 bool PropertyValuePair::operator==(const PropertyValuePair& aOther) const {
63   if (mProperty != aOther.mProperty) {
64     return false;
65   }
66   if (mServoDeclarationBlock == aOther.mServoDeclarationBlock) {
67     return true;
68   }
69   if (!mServoDeclarationBlock || !aOther.mServoDeclarationBlock) {
70     return false;
71   }
72   return Servo_DeclarationBlock_Equals(mServoDeclarationBlock,
73                                        aOther.mServoDeclarationBlock);
74 }
75 
76 namespace dom {
77 
78 NS_IMPL_CYCLE_COLLECTION_INHERITED(KeyframeEffect, AnimationEffect,
79                                    mTarget.mElement)
80 
NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(KeyframeEffect,AnimationEffect)81 NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(KeyframeEffect, AnimationEffect)
82 NS_IMPL_CYCLE_COLLECTION_TRACE_END
83 
84 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(KeyframeEffect)
85 NS_INTERFACE_MAP_END_INHERITING(AnimationEffect)
86 
87 NS_IMPL_ADDREF_INHERITED(KeyframeEffect, AnimationEffect)
88 NS_IMPL_RELEASE_INHERITED(KeyframeEffect, AnimationEffect)
89 
90 KeyframeEffect::KeyframeEffect(Document* aDocument,
91                                OwningAnimationTarget&& aTarget,
92                                TimingParams&& aTiming,
93                                const KeyframeEffectParams& aOptions)
94     : AnimationEffect(aDocument, std::move(aTiming)),
95       mTarget(std::move(aTarget)),
96       mEffectOptions(aOptions) {}
97 
KeyframeEffect(Document * aDocument,OwningAnimationTarget && aTarget,const KeyframeEffect & aOther)98 KeyframeEffect::KeyframeEffect(Document* aDocument,
99                                OwningAnimationTarget&& aTarget,
100                                const KeyframeEffect& aOther)
101     : AnimationEffect(aDocument, TimingParams{aOther.SpecifiedTiming()}),
102       mTarget(std::move(aTarget)),
103       mEffectOptions{aOther.IterationComposite(), aOther.Composite(),
104                      mTarget.mPseudoType},
105       mKeyframes(aOther.mKeyframes.Clone()),
106       mProperties(aOther.mProperties.Clone()),
107       mBaseValues(aOther.mBaseValues.Clone()) {}
108 
WrapObject(JSContext * aCx,JS::Handle<JSObject * > aGivenProto)109 JSObject* KeyframeEffect::WrapObject(JSContext* aCx,
110                                      JS::Handle<JSObject*> aGivenProto) {
111   return KeyframeEffect_Binding::Wrap(aCx, this, aGivenProto);
112 }
113 
IterationComposite() const114 IterationCompositeOperation KeyframeEffect::IterationComposite() const {
115   return mEffectOptions.mIterationComposite;
116 }
117 
SetIterationComposite(const IterationCompositeOperation & aIterationComposite)118 void KeyframeEffect::SetIterationComposite(
119     const IterationCompositeOperation& aIterationComposite) {
120   if (mEffectOptions.mIterationComposite == aIterationComposite) {
121     return;
122   }
123 
124   if (mAnimation && mAnimation->IsRelevant()) {
125     MutationObservers::NotifyAnimationChanged(mAnimation);
126   }
127 
128   mEffectOptions.mIterationComposite = aIterationComposite;
129   RequestRestyle(EffectCompositor::RestyleType::Layer);
130 }
131 
Composite() const132 CompositeOperation KeyframeEffect::Composite() const {
133   return mEffectOptions.mComposite;
134 }
135 
SetComposite(const CompositeOperation & aComposite)136 void KeyframeEffect::SetComposite(const CompositeOperation& aComposite) {
137   if (mEffectOptions.mComposite == aComposite) {
138     return;
139   }
140 
141   mEffectOptions.mComposite = aComposite;
142 
143   if (mAnimation && mAnimation->IsRelevant()) {
144     MutationObservers::NotifyAnimationChanged(mAnimation);
145   }
146 
147   if (mTarget) {
148     RefPtr<ComputedStyle> computedStyle = GetTargetComputedStyle(Flush::None);
149     if (computedStyle) {
150       UpdateProperties(computedStyle);
151     }
152   }
153 }
154 
NotifySpecifiedTimingUpdated()155 void KeyframeEffect::NotifySpecifiedTimingUpdated() {
156   // Use the same document for a pseudo element and its parent element.
157   // Use nullptr if we don't have mTarget, so disable the mutation batch.
158   nsAutoAnimationMutationBatch mb(mTarget ? mTarget.mElement->OwnerDoc()
159                                           : nullptr);
160 
161   if (mAnimation) {
162     mAnimation->NotifyEffectTimingUpdated();
163 
164     if (mAnimation->IsRelevant()) {
165       MutationObservers::NotifyAnimationChanged(mAnimation);
166     }
167 
168     RequestRestyle(EffectCompositor::RestyleType::Layer);
169   }
170 }
171 
NotifyAnimationTimingUpdated(PostRestyleMode aPostRestyle)172 void KeyframeEffect::NotifyAnimationTimingUpdated(
173     PostRestyleMode aPostRestyle) {
174   UpdateTargetRegistration();
175 
176   // If the effect is not relevant it will be removed from the target
177   // element's effect set. However, effects not in the effect set
178   // will not be included in the set of candidate effects for running on
179   // the compositor and hence they won't have their compositor status
180   // updated. As a result, we need to make sure we clear their compositor
181   // status here.
182   bool isRelevant = mAnimation && mAnimation->IsRelevant();
183   if (!isRelevant) {
184     ResetIsRunningOnCompositor();
185   }
186 
187   // Request restyle if necessary.
188   if (aPostRestyle == PostRestyleMode::IfNeeded && mAnimation &&
189       !mProperties.IsEmpty() && HasComputedTimingChanged()) {
190     EffectCompositor::RestyleType restyleType =
191         CanThrottle() ? EffectCompositor::RestyleType::Throttled
192                       : EffectCompositor::RestyleType::Standard;
193     RequestRestyle(restyleType);
194   }
195 
196   // Detect changes to "in effect" status since we need to recalculate the
197   // animation cascade for this element whenever that changes.
198   // Note that updating mInEffectOnLastAnimationTimingUpdate has to be done
199   // after above CanThrottle() call since the function uses the flag inside it.
200   bool inEffect = IsInEffect();
201   if (inEffect != mInEffectOnLastAnimationTimingUpdate) {
202     MarkCascadeNeedsUpdate();
203     mInEffectOnLastAnimationTimingUpdate = inEffect;
204   }
205 
206   // If we're no longer "in effect", our ComposeStyle method will never be
207   // called and we will never have a chance to update mProgressOnLastCompose
208   // and mCurrentIterationOnLastCompose.
209   // We clear them here to ensure that if we later become "in effect" we will
210   // request a restyle (above).
211   if (!inEffect) {
212     mProgressOnLastCompose.SetNull();
213     mCurrentIterationOnLastCompose = 0;
214   }
215 }
216 
KeyframesEqualIgnoringComputedOffsets(const nsTArray<Keyframe> & aLhs,const nsTArray<Keyframe> & aRhs)217 static bool KeyframesEqualIgnoringComputedOffsets(
218     const nsTArray<Keyframe>& aLhs, const nsTArray<Keyframe>& aRhs) {
219   if (aLhs.Length() != aRhs.Length()) {
220     return false;
221   }
222 
223   for (size_t i = 0, len = aLhs.Length(); i < len; ++i) {
224     const Keyframe& a = aLhs[i];
225     const Keyframe& b = aRhs[i];
226     if (a.mOffset != b.mOffset || a.mTimingFunction != b.mTimingFunction ||
227         a.mPropertyValues != b.mPropertyValues) {
228       return false;
229     }
230   }
231   return true;
232 }
233 
234 // https://drafts.csswg.org/web-animations/#dom-keyframeeffect-setkeyframes
SetKeyframes(JSContext * aContext,JS::Handle<JSObject * > aKeyframes,ErrorResult & aRv)235 void KeyframeEffect::SetKeyframes(JSContext* aContext,
236                                   JS::Handle<JSObject*> aKeyframes,
237                                   ErrorResult& aRv) {
238   nsTArray<Keyframe> keyframes = KeyframeUtils::GetKeyframesFromObject(
239       aContext, mDocument, aKeyframes, "KeyframeEffect.setKeyframes", aRv);
240   if (aRv.Failed()) {
241     return;
242   }
243 
244   RefPtr<ComputedStyle> style = GetTargetComputedStyle(Flush::None);
245   SetKeyframes(std::move(keyframes), style);
246 }
247 
SetKeyframes(nsTArray<Keyframe> && aKeyframes,const ComputedStyle * aStyle)248 void KeyframeEffect::SetKeyframes(nsTArray<Keyframe>&& aKeyframes,
249                                   const ComputedStyle* aStyle) {
250   if (KeyframesEqualIgnoringComputedOffsets(aKeyframes, mKeyframes)) {
251     return;
252   }
253 
254   mKeyframes = std::move(aKeyframes);
255   KeyframeUtils::DistributeKeyframes(mKeyframes);
256 
257   if (mAnimation && mAnimation->IsRelevant()) {
258     MutationObservers::NotifyAnimationChanged(mAnimation);
259   }
260 
261   // We need to call UpdateProperties() unless the target element doesn't have
262   // style (e.g. the target element is not associated with any document).
263   if (aStyle) {
264     UpdateProperties(aStyle);
265   }
266 }
267 
ReplaceTransitionStartValue(AnimationValue && aStartValue)268 void KeyframeEffect::ReplaceTransitionStartValue(AnimationValue&& aStartValue) {
269   if (!aStartValue.mServo) {
270     return;
271   }
272 
273   // A typical transition should have a single property and a single segment.
274   //
275   // (And for atypical transitions, that is, those updated by script, we don't
276   // apply the replacing behavior.)
277   if (mProperties.Length() != 1 || mProperties[0].mSegments.Length() != 1) {
278     return;
279   }
280 
281   // Likewise, check that the keyframes are of the expected shape.
282   if (mKeyframes.Length() != 2 || mKeyframes[0].mPropertyValues.Length() != 1) {
283     return;
284   }
285 
286   // Check that the value we are about to substitute in is actually for the
287   // same property.
288   if (Servo_AnimationValue_GetPropertyId(aStartValue.mServo) !=
289       mProperties[0].mProperty) {
290     return;
291   }
292 
293   mKeyframes[0].mPropertyValues[0].mServoDeclarationBlock =
294       Servo_AnimationValue_Uncompute(aStartValue.mServo).Consume();
295   mProperties[0].mSegments[0].mFromValue = std::move(aStartValue);
296 }
297 
IsEffectiveProperty(const EffectSet & aEffects,nsCSSPropertyID aProperty)298 static bool IsEffectiveProperty(const EffectSet& aEffects,
299                                 nsCSSPropertyID aProperty) {
300   return !aEffects.PropertiesWithImportantRules().HasProperty(aProperty) ||
301          !aEffects.PropertiesForAnimationsLevel().HasProperty(aProperty);
302 }
303 
GetEffectiveAnimationOfProperty(nsCSSPropertyID aProperty,const EffectSet & aEffects) const304 const AnimationProperty* KeyframeEffect::GetEffectiveAnimationOfProperty(
305     nsCSSPropertyID aProperty, const EffectSet& aEffects) const {
306   MOZ_ASSERT(mTarget &&
307              &aEffects == EffectSet::GetEffectSet(mTarget.mElement,
308                                                   mTarget.mPseudoType));
309 
310   for (const AnimationProperty& property : mProperties) {
311     if (aProperty != property.mProperty) {
312       continue;
313     }
314 
315     const AnimationProperty* result = nullptr;
316     // Only include the property if it is not overridden by !important rules in
317     // the transitions level.
318     if (IsEffectiveProperty(aEffects, property.mProperty)) {
319       result = &property;
320     }
321     return result;
322   }
323   return nullptr;
324 }
325 
HasEffectiveAnimationOfPropertySet(const nsCSSPropertyIDSet & aPropertySet,const EffectSet & aEffectSet) const326 bool KeyframeEffect::HasEffectiveAnimationOfPropertySet(
327     const nsCSSPropertyIDSet& aPropertySet, const EffectSet& aEffectSet) const {
328   for (const AnimationProperty& property : mProperties) {
329     if (aPropertySet.HasProperty(property.mProperty) &&
330         IsEffectiveProperty(aEffectSet, property.mProperty)) {
331       return true;
332     }
333   }
334   return false;
335 }
336 
GetPropertiesForCompositor(EffectSet & aEffects,const nsIFrame * aFrame) const337 nsCSSPropertyIDSet KeyframeEffect::GetPropertiesForCompositor(
338     EffectSet& aEffects, const nsIFrame* aFrame) const {
339   MOZ_ASSERT(&aEffects ==
340              EffectSet::GetEffectSet(mTarget.mElement, mTarget.mPseudoType));
341 
342   nsCSSPropertyIDSet properties;
343 
344   if (!mAnimation || !mAnimation->IsRelevant()) {
345     return properties;
346   }
347 
348   static constexpr nsCSSPropertyIDSet compositorAnimatables =
349       nsCSSPropertyIDSet::CompositorAnimatables();
350   static constexpr nsCSSPropertyIDSet transformLikeProperties =
351       nsCSSPropertyIDSet::TransformLikeProperties();
352 
353   nsCSSPropertyIDSet transformSet;
354   AnimationPerformanceWarning::Type dummyWarning;
355 
356   for (const AnimationProperty& property : mProperties) {
357     if (!compositorAnimatables.HasProperty(property.mProperty)) {
358       continue;
359     }
360 
361     // Transform-like properties are combined together on the compositor so we
362     // need to evaluate them as a group. We build up a separate set here then
363     // evaluate it as a separate step below.
364     if (transformLikeProperties.HasProperty(property.mProperty)) {
365       transformSet.AddProperty(property.mProperty);
366       continue;
367     }
368 
369     KeyframeEffect::MatchForCompositor matchResult = IsMatchForCompositor(
370         nsCSSPropertyIDSet{property.mProperty}, aFrame, aEffects, dummyWarning);
371     if (matchResult ==
372             KeyframeEffect::MatchForCompositor::NoAndBlockThisProperty ||
373         matchResult == KeyframeEffect::MatchForCompositor::No) {
374       continue;
375     }
376     properties.AddProperty(property.mProperty);
377   }
378 
379   if (!transformSet.IsEmpty()) {
380     KeyframeEffect::MatchForCompositor matchResult =
381         IsMatchForCompositor(transformSet, aFrame, aEffects, dummyWarning);
382     if (matchResult == KeyframeEffect::MatchForCompositor::Yes ||
383         matchResult == KeyframeEffect::MatchForCompositor::IfNeeded) {
384       properties |= transformSet;
385     }
386   }
387 
388   return properties;
389 }
390 
GetPropertySet() const391 nsCSSPropertyIDSet KeyframeEffect::GetPropertySet() const {
392   nsCSSPropertyIDSet result;
393 
394   for (const AnimationProperty& property : mProperties) {
395     result.AddProperty(property.mProperty);
396   }
397 
398   return result;
399 }
400 
401 #ifdef DEBUG
SpecifiedKeyframeArraysAreEqual(const nsTArray<Keyframe> & aA,const nsTArray<Keyframe> & aB)402 bool SpecifiedKeyframeArraysAreEqual(const nsTArray<Keyframe>& aA,
403                                      const nsTArray<Keyframe>& aB) {
404   if (aA.Length() != aB.Length()) {
405     return false;
406   }
407 
408   for (size_t i = 0; i < aA.Length(); i++) {
409     const Keyframe& a = aA[i];
410     const Keyframe& b = aB[i];
411     if (a.mOffset != b.mOffset || a.mTimingFunction != b.mTimingFunction ||
412         a.mPropertyValues != b.mPropertyValues) {
413       return false;
414     }
415   }
416 
417   return true;
418 }
419 #endif
420 
HasCurrentColor(const nsTArray<AnimationPropertySegment> & aSegments)421 static bool HasCurrentColor(
422     const nsTArray<AnimationPropertySegment>& aSegments) {
423   for (const AnimationPropertySegment& segment : aSegments) {
424     if ((!segment.mFromValue.IsNull() && segment.mFromValue.IsCurrentColor()) ||
425         (!segment.mToValue.IsNull() && segment.mToValue.IsCurrentColor())) {
426       return true;
427     }
428   }
429   return false;
430 }
UpdateProperties(const ComputedStyle * aStyle)431 void KeyframeEffect::UpdateProperties(const ComputedStyle* aStyle) {
432   MOZ_ASSERT(aStyle);
433 
434   nsTArray<AnimationProperty> properties = BuildProperties(aStyle);
435 
436   bool propertiesChanged = mProperties != properties;
437 
438   // We need to update base styles even if any properties are not changed at all
439   // since base styles might have been changed due to parent style changes, etc.
440   bool baseStylesChanged = false;
441   EnsureBaseStyles(aStyle, properties,
442                    !propertiesChanged ? &baseStylesChanged : nullptr);
443 
444   if (!propertiesChanged) {
445     if (baseStylesChanged) {
446       RequestRestyle(EffectCompositor::RestyleType::Layer);
447     }
448     // Check if we need to update the cumulative change hint because we now have
449     // style data.
450     if (mNeedsStyleData && mTarget && mTarget.mElement->HasServoData()) {
451       CalculateCumulativeChangeHint(aStyle);
452     }
453     return;
454   }
455 
456   // Preserve the state of the mIsRunningOnCompositor flag.
457   nsCSSPropertyIDSet runningOnCompositorProperties;
458 
459   for (const AnimationProperty& property : mProperties) {
460     if (property.mIsRunningOnCompositor) {
461       runningOnCompositorProperties.AddProperty(property.mProperty);
462     }
463   }
464 
465   mProperties = std::move(properties);
466   UpdateEffectSet();
467 
468   mHasCurrentColor = false;
469 
470   for (AnimationProperty& property : mProperties) {
471     property.mIsRunningOnCompositor =
472         runningOnCompositorProperties.HasProperty(property.mProperty);
473 
474     if (property.mProperty == eCSSProperty_background_color &&
475         !mHasCurrentColor) {
476       if (HasCurrentColor(property.mSegments)) {
477         mHasCurrentColor = true;
478         break;
479       }
480     }
481   }
482 
483   CalculateCumulativeChangeHint(aStyle);
484 
485   MarkCascadeNeedsUpdate();
486 
487   if (mAnimation) {
488     mAnimation->NotifyEffectPropertiesUpdated();
489   }
490 
491   RequestRestyle(EffectCompositor::RestyleType::Layer);
492 }
493 
EnsureBaseStyles(const ComputedStyle * aComputedValues,const nsTArray<AnimationProperty> & aProperties,bool * aBaseStylesChanged)494 void KeyframeEffect::EnsureBaseStyles(
495     const ComputedStyle* aComputedValues,
496     const nsTArray<AnimationProperty>& aProperties, bool* aBaseStylesChanged) {
497   if (aBaseStylesChanged != nullptr) {
498     *aBaseStylesChanged = false;
499   }
500 
501   if (!mTarget) {
502     return;
503   }
504 
505   BaseValuesHashmap previousBaseStyles;
506   if (aBaseStylesChanged != nullptr) {
507     previousBaseStyles = std::move(mBaseValues);
508   }
509 
510   mBaseValues.Clear();
511 
512   nsPresContext* presContext =
513       nsContentUtils::GetContextForContent(mTarget.mElement);
514   // If |aProperties| is empty we're not going to dereference |presContext| so
515   // we don't care if it is nullptr.
516   //
517   // We could just return early when |aProperties| is empty and save looking up
518   // the pres context, but that won't save any effort normally since we don't
519   // call this function if we have no keyframes to begin with. Furthermore, the
520   // case where |presContext| is nullptr is so rare (we've only ever seen in
521   // fuzzing, and even then we've never been able to reproduce it reliably)
522   // it's not worth the runtime cost of an extra branch.
523   MOZ_ASSERT(presContext || aProperties.IsEmpty(),
524              "Typically presContext should not be nullptr but if it is"
525              " we should have also failed to calculate the computed values"
526              " passed-in as aProperties");
527 
528   RefPtr<ComputedStyle> baseComputedStyle;
529   for (const AnimationProperty& property : aProperties) {
530     EnsureBaseStyle(property, presContext, aComputedValues, baseComputedStyle);
531   }
532 
533   if (aBaseStylesChanged != nullptr &&
534       std::any_of(
535           mBaseValues.cbegin(), mBaseValues.cend(), [&](const auto& entry) {
536             return AnimationValue(entry.GetData()) !=
537                    AnimationValue(previousBaseStyles.Get(entry.GetKey()));
538           })) {
539     *aBaseStylesChanged = true;
540   }
541 }
542 
EnsureBaseStyle(const AnimationProperty & aProperty,nsPresContext * aPresContext,const ComputedStyle * aComputedStyle,RefPtr<ComputedStyle> & aBaseComputedStyle)543 void KeyframeEffect::EnsureBaseStyle(
544     const AnimationProperty& aProperty, nsPresContext* aPresContext,
545     const ComputedStyle* aComputedStyle,
546     RefPtr<ComputedStyle>& aBaseComputedStyle) {
547   bool hasAdditiveValues = false;
548 
549   for (const AnimationPropertySegment& segment : aProperty.mSegments) {
550     if (!segment.HasReplaceableValues()) {
551       hasAdditiveValues = true;
552       break;
553     }
554   }
555 
556   if (!hasAdditiveValues) {
557     return;
558   }
559 
560   if (!aBaseComputedStyle) {
561     MOZ_ASSERT(mTarget, "Should have a valid target");
562 
563     Element* animatingElement = EffectCompositor::GetElementToRestyle(
564         mTarget.mElement, mTarget.mPseudoType);
565     if (!animatingElement) {
566       return;
567     }
568     aBaseComputedStyle = aPresContext->StyleSet()->GetBaseContextForElement(
569         animatingElement, aComputedStyle);
570   }
571   RefPtr<RawServoAnimationValue> baseValue =
572       Servo_ComputedValues_ExtractAnimationValue(aBaseComputedStyle,
573                                                  aProperty.mProperty)
574           .Consume();
575   mBaseValues.InsertOrUpdate(aProperty.mProperty, std::move(baseValue));
576 }
577 
WillComposeStyle()578 void KeyframeEffect::WillComposeStyle() {
579   ComputedTiming computedTiming = GetComputedTiming();
580   mProgressOnLastCompose = computedTiming.mProgress;
581   mCurrentIterationOnLastCompose = computedTiming.mCurrentIteration;
582 }
583 
ComposeStyleRule(RawServoAnimationValueMap & aAnimationValues,const AnimationProperty & aProperty,const AnimationPropertySegment & aSegment,const ComputedTiming & aComputedTiming)584 void KeyframeEffect::ComposeStyleRule(
585     RawServoAnimationValueMap& aAnimationValues,
586     const AnimationProperty& aProperty,
587     const AnimationPropertySegment& aSegment,
588     const ComputedTiming& aComputedTiming) {
589   auto* opaqueTable =
590       reinterpret_cast<RawServoAnimationValueTable*>(&mBaseValues);
591   Servo_AnimationCompose(&aAnimationValues, opaqueTable, aProperty.mProperty,
592                          &aSegment, &aProperty.mSegments.LastElement(),
593                          &aComputedTiming, mEffectOptions.mIterationComposite);
594 }
595 
ComposeStyle(RawServoAnimationValueMap & aComposeResult,const nsCSSPropertyIDSet & aPropertiesToSkip)596 void KeyframeEffect::ComposeStyle(RawServoAnimationValueMap& aComposeResult,
597                                   const nsCSSPropertyIDSet& aPropertiesToSkip) {
598   ComputedTiming computedTiming = GetComputedTiming();
599 
600   // If the progress is null, we don't have fill data for the current
601   // time so we shouldn't animate.
602   if (computedTiming.mProgress.IsNull()) {
603     return;
604   }
605 
606   for (size_t propIdx = 0, propEnd = mProperties.Length(); propIdx != propEnd;
607        ++propIdx) {
608     const AnimationProperty& prop = mProperties[propIdx];
609 
610     MOZ_ASSERT(prop.mSegments[0].mFromKey == 0.0, "incorrect first from key");
611     MOZ_ASSERT(prop.mSegments[prop.mSegments.Length() - 1].mToKey == 1.0,
612                "incorrect last to key");
613 
614     if (aPropertiesToSkip.HasProperty(prop.mProperty)) {
615       continue;
616     }
617 
618     MOZ_ASSERT(prop.mSegments.Length() > 0,
619                "property should not be in animations if it has no segments");
620 
621     // FIXME: Maybe cache the current segment?
622     const AnimationPropertySegment *segment = prop.mSegments.Elements(),
623                                    *segmentEnd =
624                                        segment + prop.mSegments.Length();
625     while (segment->mToKey <= computedTiming.mProgress.Value()) {
626       MOZ_ASSERT(segment->mFromKey <= segment->mToKey, "incorrect keys");
627       if ((segment + 1) == segmentEnd) {
628         break;
629       }
630       ++segment;
631       MOZ_ASSERT(segment->mFromKey == (segment - 1)->mToKey, "incorrect keys");
632     }
633     MOZ_ASSERT(segment->mFromKey <= segment->mToKey, "incorrect keys");
634     MOZ_ASSERT(segment >= prop.mSegments.Elements() &&
635                    size_t(segment - prop.mSegments.Elements()) <
636                        prop.mSegments.Length(),
637                "out of array bounds");
638 
639     ComposeStyleRule(aComposeResult, prop, *segment, computedTiming);
640   }
641 
642   // If the animation produces a change hint that affects the overflow region,
643   // we need to record the current time to unthrottle the animation
644   // periodically when the animation is being throttled because it's scrolled
645   // out of view.
646   if (HasPropertiesThatMightAffectOverflow()) {
647     nsPresContext* presContext =
648         nsContentUtils::GetContextForContent(mTarget.mElement);
649     EffectSet* effectSet =
650         EffectSet::GetEffectSet(mTarget.mElement, mTarget.mPseudoType);
651     if (presContext && effectSet) {
652       TimeStamp now = presContext->RefreshDriver()->MostRecentRefresh();
653       effectSet->UpdateLastOverflowAnimationSyncTime(now);
654     }
655   }
656 }
657 
IsRunningOnCompositor() const658 bool KeyframeEffect::IsRunningOnCompositor() const {
659   // We consider animation is running on compositor if there is at least
660   // one property running on compositor.
661   // Animation.IsRunningOnCompotitor will return more fine grained
662   // information in bug 1196114.
663   for (const AnimationProperty& property : mProperties) {
664     if (property.mIsRunningOnCompositor) {
665       return true;
666     }
667   }
668   return false;
669 }
670 
SetIsRunningOnCompositor(nsCSSPropertyID aProperty,bool aIsRunning)671 void KeyframeEffect::SetIsRunningOnCompositor(nsCSSPropertyID aProperty,
672                                               bool aIsRunning) {
673   MOZ_ASSERT(
674       nsCSSProps::PropHasFlags(aProperty, CSSPropFlags::CanAnimateOnCompositor),
675       "Property being animated on compositor is a recognized "
676       "compositor-animatable property");
677   for (AnimationProperty& property : mProperties) {
678     if (property.mProperty == aProperty) {
679       property.mIsRunningOnCompositor = aIsRunning;
680       // We currently only set a performance warning message when animations
681       // cannot be run on the compositor, so if this animation is running
682       // on the compositor we don't need a message.
683       if (aIsRunning) {
684         property.mPerformanceWarning.reset();
685       } else if (mAnimation && mAnimation->IsPartialPrerendered()) {
686         ResetPartialPrerendered();
687       }
688       return;
689     }
690   }
691 }
692 
SetIsRunningOnCompositor(const nsCSSPropertyIDSet & aPropertySet,bool aIsRunning)693 void KeyframeEffect::SetIsRunningOnCompositor(
694     const nsCSSPropertyIDSet& aPropertySet, bool aIsRunning) {
695   for (AnimationProperty& property : mProperties) {
696     if (aPropertySet.HasProperty(property.mProperty)) {
697       MOZ_ASSERT(nsCSSProps::PropHasFlags(property.mProperty,
698                                           CSSPropFlags::CanAnimateOnCompositor),
699                  "Property being animated on compositor is a recognized "
700                  "compositor-animatable property");
701       property.mIsRunningOnCompositor = aIsRunning;
702       // We currently only set a performance warning message when animations
703       // cannot be run on the compositor, so if this animation is running
704       // on the compositor we don't need a message.
705       if (aIsRunning) {
706         property.mPerformanceWarning.reset();
707       }
708     }
709   }
710 
711   if (!aIsRunning && mAnimation && mAnimation->IsPartialPrerendered()) {
712     ResetPartialPrerendered();
713   }
714 }
715 
ResetIsRunningOnCompositor()716 void KeyframeEffect::ResetIsRunningOnCompositor() {
717   for (AnimationProperty& property : mProperties) {
718     property.mIsRunningOnCompositor = false;
719   }
720 
721   if (mAnimation && mAnimation->IsPartialPrerendered()) {
722     ResetPartialPrerendered();
723   }
724 }
725 
ResetPartialPrerendered()726 void KeyframeEffect::ResetPartialPrerendered() {
727   MOZ_ASSERT(mAnimation && mAnimation->IsPartialPrerendered());
728 
729   nsIFrame* frame = GetPrimaryFrame();
730   if (!frame) {
731     return;
732   }
733 
734   nsIWidget* widget = frame->GetNearestWidget();
735   if (!widget) {
736     return;
737   }
738 
739   if (WindowRenderer* windowRenderer = widget->GetWindowRenderer()) {
740     windowRenderer->RemovePartialPrerenderedAnimation(
741         mAnimation->IdOnCompositor(), mAnimation);
742   }
743 }
744 
IsSupportedPseudoForWebAnimation(PseudoStyleType aType)745 static bool IsSupportedPseudoForWebAnimation(PseudoStyleType aType) {
746   // FIXME: Bug 1615469: Support first-line and first-letter for Web Animation.
747   return aType == PseudoStyleType::before || aType == PseudoStyleType::after ||
748          aType == PseudoStyleType::marker;
749 }
750 
KeyframeEffectOptionsFromUnion(const UnrestrictedDoubleOrKeyframeEffectOptions & aOptions)751 static const KeyframeEffectOptions& KeyframeEffectOptionsFromUnion(
752     const UnrestrictedDoubleOrKeyframeEffectOptions& aOptions) {
753   MOZ_ASSERT(aOptions.IsKeyframeEffectOptions());
754   return aOptions.GetAsKeyframeEffectOptions();
755 }
756 
KeyframeEffectOptionsFromUnion(const UnrestrictedDoubleOrKeyframeAnimationOptions & aOptions)757 static const KeyframeEffectOptions& KeyframeEffectOptionsFromUnion(
758     const UnrestrictedDoubleOrKeyframeAnimationOptions& aOptions) {
759   MOZ_ASSERT(aOptions.IsKeyframeAnimationOptions());
760   return aOptions.GetAsKeyframeAnimationOptions();
761 }
762 
763 template <class OptionsType>
KeyframeEffectParamsFromUnion(const OptionsType & aOptions,CallerType aCallerType,ErrorResult & aRv)764 static KeyframeEffectParams KeyframeEffectParamsFromUnion(
765     const OptionsType& aOptions, CallerType aCallerType, ErrorResult& aRv) {
766   KeyframeEffectParams result;
767   if (aOptions.IsUnrestrictedDouble()) {
768     return result;
769   }
770 
771   const KeyframeEffectOptions& options =
772       KeyframeEffectOptionsFromUnion(aOptions);
773 
774   // If dom.animations-api.compositing.enabled is turned off,
775   // iterationComposite and composite are the default value 'replace' in the
776   // dictionary.
777   result.mIterationComposite = options.mIterationComposite;
778   result.mComposite = options.mComposite;
779 
780   result.mPseudoType = PseudoStyleType::NotPseudo;
781   if (DOMStringIsNull(options.mPseudoElement)) {
782     return result;
783   }
784 
785   Maybe<PseudoStyleType> pseudoType =
786       nsCSSPseudoElements::GetPseudoType(options.mPseudoElement);
787   if (!pseudoType) {
788     // Per the spec, we throw SyntaxError for syntactically invalid pseudos.
789     aRv.ThrowSyntaxError(
790         nsPrintfCString("'%s' is a syntactically invalid pseudo-element.",
791                         NS_ConvertUTF16toUTF8(options.mPseudoElement).get()));
792     return result;
793   }
794 
795   result.mPseudoType = *pseudoType;
796   if (!IsSupportedPseudoForWebAnimation(result.mPseudoType)) {
797     // Per the spec, we throw SyntaxError for unsupported pseudos.
798     aRv.ThrowSyntaxError(
799         nsPrintfCString("'%s' is an unsupported pseudo-element.",
800                         NS_ConvertUTF16toUTF8(options.mPseudoElement).get()));
801   }
802 
803   return result;
804 }
805 
806 template <class OptionsType>
807 /* static */
ConstructKeyframeEffect(const GlobalObject & aGlobal,Element * aTarget,JS::Handle<JSObject * > aKeyframes,const OptionsType & aOptions,ErrorResult & aRv)808 already_AddRefed<KeyframeEffect> KeyframeEffect::ConstructKeyframeEffect(
809     const GlobalObject& aGlobal, Element* aTarget,
810     JS::Handle<JSObject*> aKeyframes, const OptionsType& aOptions,
811     ErrorResult& aRv) {
812   // We should get the document from `aGlobal` instead of the current Realm
813   // to make this works in Xray case.
814   //
815   // In all non-Xray cases, `aGlobal` matches the current Realm, so this
816   // matches the spec behavior.
817   //
818   // In Xray case, the new objects should be created using the document of
819   // the target global, but the KeyframeEffect constructors are called in the
820   // caller's compartment to access `aKeyframes` object.
821   Document* doc = AnimationUtils::GetDocumentFromGlobal(aGlobal.Get());
822   if (!doc) {
823     aRv.Throw(NS_ERROR_FAILURE);
824     return nullptr;
825   }
826 
827   KeyframeEffectParams effectOptions =
828       KeyframeEffectParamsFromUnion(aOptions, aGlobal.CallerType(), aRv);
829   // An invalid Pseudo-element aborts all further steps.
830   if (aRv.Failed()) {
831     return nullptr;
832   }
833 
834   TimingParams timingParams = TimingParams::FromOptionsUnion(aOptions, aRv);
835   if (aRv.Failed()) {
836     return nullptr;
837   }
838 
839   RefPtr<KeyframeEffect> effect = new KeyframeEffect(
840       doc, OwningAnimationTarget(aTarget, effectOptions.mPseudoType),
841       std::move(timingParams), effectOptions);
842 
843   effect->SetKeyframes(aGlobal.Context(), aKeyframes, aRv);
844   if (aRv.Failed()) {
845     return nullptr;
846   }
847 
848   return effect.forget();
849 }
850 
BuildProperties(const ComputedStyle * aStyle)851 nsTArray<AnimationProperty> KeyframeEffect::BuildProperties(
852     const ComputedStyle* aStyle) {
853   MOZ_ASSERT(aStyle);
854 
855   nsTArray<AnimationProperty> result;
856   // If mTarget is false (i.e. mTarget.mElement is null), return an empty
857   // property array.
858   if (!mTarget) {
859     return result;
860   }
861 
862   // When GetComputedKeyframeValues or GetAnimationPropertiesFromKeyframes
863   // calculate computed values from |mKeyframes|, they could possibly
864   // trigger a subsequent restyle in which we rebuild animations. If that
865   // happens we could find that |mKeyframes| is overwritten while it is
866   // being iterated over. Normally that shouldn't happen but just in case we
867   // make a copy of |mKeyframes| first and iterate over that instead.
868   auto keyframesCopy(mKeyframes.Clone());
869 
870   result = KeyframeUtils::GetAnimationPropertiesFromKeyframes(
871       keyframesCopy, mTarget.mElement, mTarget.mPseudoType, aStyle,
872       mEffectOptions.mComposite);
873 
874 #ifdef DEBUG
875   MOZ_ASSERT(SpecifiedKeyframeArraysAreEqual(mKeyframes, keyframesCopy),
876              "Apart from the computed offset members, the keyframes array"
877              " should not be modified");
878 #endif
879 
880   mKeyframes = std::move(keyframesCopy);
881   return result;
882 }
883 
884 template <typename FrameEnumFunc>
EnumerateContinuationsOrIBSplitSiblings(nsIFrame * aFrame,FrameEnumFunc && aFunc)885 static void EnumerateContinuationsOrIBSplitSiblings(nsIFrame* aFrame,
886                                                     FrameEnumFunc&& aFunc) {
887   while (aFrame) {
888     aFunc(aFrame);
889     aFrame = nsLayoutUtils::GetNextContinuationOrIBSplitSibling(aFrame);
890   }
891 }
892 
UpdateTarget(Element * aElement,PseudoStyleType aPseudoType)893 void KeyframeEffect::UpdateTarget(Element* aElement,
894                                   PseudoStyleType aPseudoType) {
895   OwningAnimationTarget newTarget(aElement, aPseudoType);
896 
897   if (mTarget == newTarget) {
898     // Assign the same target, skip it.
899     return;
900   }
901 
902   if (mTarget) {
903     // Call ResetIsRunningOnCompositor() prior to UnregisterTarget() since
904     // ResetIsRunningOnCompositor() might try to get the EffectSet associated
905     // with this keyframe effect to remove partial pre-render animation from
906     // the layer manager.
907     ResetIsRunningOnCompositor();
908     UnregisterTarget();
909 
910     RequestRestyle(EffectCompositor::RestyleType::Layer);
911 
912     nsAutoAnimationMutationBatch mb(mTarget.mElement->OwnerDoc());
913     if (mAnimation) {
914       MutationObservers::NotifyAnimationRemoved(mAnimation);
915     }
916   }
917 
918   mTarget = newTarget;
919 
920   if (mTarget) {
921     UpdateTargetRegistration();
922     RefPtr<ComputedStyle> computedStyle = GetTargetComputedStyle(Flush::None);
923     if (computedStyle) {
924       UpdateProperties(computedStyle);
925     }
926 
927     RequestRestyle(EffectCompositor::RestyleType::Layer);
928 
929     nsAutoAnimationMutationBatch mb(mTarget.mElement->OwnerDoc());
930     if (mAnimation) {
931       MutationObservers::NotifyAnimationAdded(mAnimation);
932       mAnimation->ReschedulePendingTasks();
933     }
934   }
935 
936   if (mAnimation) {
937     mAnimation->NotifyEffectTargetUpdated();
938   }
939 }
940 
UpdateTargetRegistration()941 void KeyframeEffect::UpdateTargetRegistration() {
942   if (!mTarget) {
943     return;
944   }
945 
946   bool isRelevant = mAnimation && mAnimation->IsRelevant();
947 
948   // Animation::IsRelevant() returns a cached value. It only updates when
949   // something calls Animation::UpdateRelevance. Whenever our timing changes,
950   // we should be notifying our Animation before calling this, so
951   // Animation::IsRelevant() should be up-to-date by the time we get here.
952   MOZ_ASSERT(isRelevant ==
953                  ((IsCurrent() || IsInEffect()) && mAnimation &&
954                   mAnimation->ReplaceState() != AnimationReplaceState::Removed),
955              "Out of date Animation::IsRelevant value");
956 
957   if (isRelevant && !mInEffectSet) {
958     EffectSet* effectSet =
959         EffectSet::GetOrCreateEffectSet(mTarget.mElement, mTarget.mPseudoType);
960     effectSet->AddEffect(*this);
961     mInEffectSet = true;
962     UpdateEffectSet(effectSet);
963     nsIFrame* frame = GetPrimaryFrame();
964     EnumerateContinuationsOrIBSplitSiblings(
965         frame, [](nsIFrame* aFrame) { aFrame->MarkNeedsDisplayItemRebuild(); });
966   } else if (!isRelevant && mInEffectSet) {
967     UnregisterTarget();
968   }
969 }
970 
UnregisterTarget()971 void KeyframeEffect::UnregisterTarget() {
972   if (!mInEffectSet) {
973     return;
974   }
975 
976   EffectSet* effectSet =
977       EffectSet::GetEffectSet(mTarget.mElement, mTarget.mPseudoType);
978   MOZ_ASSERT(effectSet,
979              "If mInEffectSet is true, there must be an EffectSet"
980              " on the target element");
981   mInEffectSet = false;
982   if (effectSet) {
983     effectSet->RemoveEffect(*this);
984 
985     if (effectSet->IsEmpty()) {
986       EffectSet::DestroyEffectSet(mTarget.mElement, mTarget.mPseudoType);
987     }
988   }
989   nsIFrame* frame = GetPrimaryFrame();
990   EnumerateContinuationsOrIBSplitSiblings(
991       frame, [](nsIFrame* aFrame) { aFrame->MarkNeedsDisplayItemRebuild(); });
992 }
993 
RequestRestyle(EffectCompositor::RestyleType aRestyleType)994 void KeyframeEffect::RequestRestyle(
995     EffectCompositor::RestyleType aRestyleType) {
996   if (!mTarget) {
997     return;
998   }
999   nsPresContext* presContext =
1000       nsContentUtils::GetContextForContent(mTarget.mElement);
1001   if (presContext && mAnimation) {
1002     presContext->EffectCompositor()->RequestRestyle(
1003         mTarget.mElement, mTarget.mPseudoType, aRestyleType,
1004         mAnimation->CascadeLevel());
1005   }
1006 }
1007 
GetTargetComputedStyle(Flush aFlushType) const1008 already_AddRefed<ComputedStyle> KeyframeEffect::GetTargetComputedStyle(
1009     Flush aFlushType) const {
1010   if (!GetRenderedDocument()) {
1011     return nullptr;
1012   }
1013 
1014   MOZ_ASSERT(mTarget,
1015              "Should only have a document when we have a target element");
1016 
1017   OwningAnimationTarget kungfuDeathGrip(mTarget.mElement, mTarget.mPseudoType);
1018 
1019   return aFlushType == Flush::Style
1020              ? nsComputedDOMStyle::GetComputedStyle(mTarget.mElement,
1021                                                     mTarget.mPseudoType)
1022              : nsComputedDOMStyle::GetComputedStyleNoFlush(mTarget.mElement,
1023                                                            mTarget.mPseudoType);
1024 }
1025 
1026 #ifdef DEBUG
DumpAnimationProperties(const RawServoStyleSet * aRawSet,nsTArray<AnimationProperty> & aAnimationProperties)1027 void DumpAnimationProperties(
1028     const RawServoStyleSet* aRawSet,
1029     nsTArray<AnimationProperty>& aAnimationProperties) {
1030   for (auto& p : aAnimationProperties) {
1031     printf("%s\n", nsCString(nsCSSProps::GetStringValue(p.mProperty)).get());
1032     for (auto& s : p.mSegments) {
1033       nsAutoCString fromValue, toValue;
1034       s.mFromValue.SerializeSpecifiedValue(p.mProperty, aRawSet, fromValue);
1035       s.mToValue.SerializeSpecifiedValue(p.mProperty, aRawSet, toValue);
1036       printf("  %f..%f: %s..%s\n", s.mFromKey, s.mToKey, fromValue.get(),
1037              toValue.get());
1038     }
1039   }
1040 }
1041 #endif
1042 
1043 /* static */
Constructor(const GlobalObject & aGlobal,Element * aTarget,JS::Handle<JSObject * > aKeyframes,const UnrestrictedDoubleOrKeyframeEffectOptions & aOptions,ErrorResult & aRv)1044 already_AddRefed<KeyframeEffect> KeyframeEffect::Constructor(
1045     const GlobalObject& aGlobal, Element* aTarget,
1046     JS::Handle<JSObject*> aKeyframes,
1047     const UnrestrictedDoubleOrKeyframeEffectOptions& aOptions,
1048     ErrorResult& aRv) {
1049   return ConstructKeyframeEffect(aGlobal, aTarget, aKeyframes, aOptions, aRv);
1050 }
1051 
1052 /* static */
Constructor(const GlobalObject & aGlobal,Element * aTarget,JS::Handle<JSObject * > aKeyframes,const UnrestrictedDoubleOrKeyframeAnimationOptions & aOptions,ErrorResult & aRv)1053 already_AddRefed<KeyframeEffect> KeyframeEffect::Constructor(
1054     const GlobalObject& aGlobal, Element* aTarget,
1055     JS::Handle<JSObject*> aKeyframes,
1056     const UnrestrictedDoubleOrKeyframeAnimationOptions& aOptions,
1057     ErrorResult& aRv) {
1058   return ConstructKeyframeEffect(aGlobal, aTarget, aKeyframes, aOptions, aRv);
1059 }
1060 
1061 /* static */
Constructor(const GlobalObject & aGlobal,KeyframeEffect & aSource,ErrorResult & aRv)1062 already_AddRefed<KeyframeEffect> KeyframeEffect::Constructor(
1063     const GlobalObject& aGlobal, KeyframeEffect& aSource, ErrorResult& aRv) {
1064   Document* doc = AnimationUtils::GetCurrentRealmDocument(aGlobal.Context());
1065   if (!doc) {
1066     aRv.Throw(NS_ERROR_FAILURE);
1067     return nullptr;
1068   }
1069 
1070   // Create a new KeyframeEffect object with aSource's target,
1071   // iteration composite operation, composite operation, and spacing mode.
1072   // The constructor creates a new AnimationEffect object by
1073   // aSource's TimingParams.
1074   // Note: we don't need to re-throw exceptions since the value specified on
1075   //       aSource's timing object can be assumed valid.
1076   RefPtr<KeyframeEffect> effect =
1077       new KeyframeEffect(doc, OwningAnimationTarget{aSource.mTarget}, aSource);
1078   // Copy cumulative change hint. mCumulativeChangeHint should be the same as
1079   // the source one because both of targets are the same.
1080   effect->mCumulativeChangeHint = aSource.mCumulativeChangeHint;
1081 
1082   return effect.forget();
1083 }
1084 
SetPseudoElement(const nsAString & aPseudoElement,ErrorResult & aRv)1085 void KeyframeEffect::SetPseudoElement(const nsAString& aPseudoElement,
1086                                       ErrorResult& aRv) {
1087   if (DOMStringIsNull(aPseudoElement)) {
1088     UpdateTarget(mTarget.mElement, PseudoStyleType::NotPseudo);
1089     return;
1090   }
1091 
1092   // Note: GetPseudoType() returns Some(NotPseudo) for the null string,
1093   // so we handle null case before this.
1094   Maybe<PseudoStyleType> pseudoType =
1095       nsCSSPseudoElements::GetPseudoType(aPseudoElement);
1096   if (!pseudoType || *pseudoType == PseudoStyleType::NotPseudo) {
1097     // Per the spec, we throw SyntaxError for syntactically invalid pseudos.
1098     aRv.ThrowSyntaxError(
1099         nsPrintfCString("'%s' is a syntactically invalid pseudo-element.",
1100                         NS_ConvertUTF16toUTF8(aPseudoElement).get()));
1101     return;
1102   }
1103 
1104   if (!IsSupportedPseudoForWebAnimation(*pseudoType)) {
1105     // Per the spec, we throw SyntaxError for unsupported pseudos.
1106     aRv.ThrowSyntaxError(
1107         nsPrintfCString("'%s' is an unsupported pseudo-element.",
1108                         NS_ConvertUTF16toUTF8(aPseudoElement).get()));
1109     return;
1110   }
1111 
1112   UpdateTarget(mTarget.mElement, *pseudoType);
1113 }
1114 
CreatePropertyValue(nsCSSPropertyID aProperty,float aOffset,const Maybe<ComputedTimingFunction> & aTimingFunction,const AnimationValue & aValue,dom::CompositeOperation aComposite,const RawServoStyleSet * aRawSet,AnimationPropertyValueDetails & aResult)1115 static void CreatePropertyValue(
1116     nsCSSPropertyID aProperty, float aOffset,
1117     const Maybe<ComputedTimingFunction>& aTimingFunction,
1118     const AnimationValue& aValue, dom::CompositeOperation aComposite,
1119     const RawServoStyleSet* aRawSet, AnimationPropertyValueDetails& aResult) {
1120   aResult.mOffset = aOffset;
1121 
1122   if (!aValue.IsNull()) {
1123     nsAutoCString stringValue;
1124     aValue.SerializeSpecifiedValue(aProperty, aRawSet, stringValue);
1125     aResult.mValue.Construct(stringValue);
1126   }
1127 
1128   if (aTimingFunction) {
1129     aResult.mEasing.Construct();
1130     aTimingFunction->AppendToString(aResult.mEasing.Value());
1131   } else {
1132     aResult.mEasing.Construct("linear"_ns);
1133   }
1134 
1135   aResult.mComposite = aComposite;
1136 }
1137 
GetProperties(nsTArray<AnimationPropertyDetails> & aProperties,ErrorResult & aRv) const1138 void KeyframeEffect::GetProperties(
1139     nsTArray<AnimationPropertyDetails>& aProperties, ErrorResult& aRv) const {
1140   const RawServoStyleSet* rawSet =
1141       mDocument->StyleSetForPresShellOrMediaQueryEvaluation()->RawSet();
1142 
1143   for (const AnimationProperty& property : mProperties) {
1144     AnimationPropertyDetails propertyDetails;
1145     propertyDetails.mProperty =
1146         NS_ConvertASCIItoUTF16(nsCSSProps::GetStringValue(property.mProperty));
1147     propertyDetails.mRunningOnCompositor = property.mIsRunningOnCompositor;
1148 
1149     nsAutoString localizedString;
1150     if (property.mPerformanceWarning &&
1151         property.mPerformanceWarning->ToLocalizedString(localizedString)) {
1152       propertyDetails.mWarning.Construct(localizedString);
1153     }
1154 
1155     if (!propertyDetails.mValues.SetCapacity(property.mSegments.Length(),
1156                                              mozilla::fallible)) {
1157       aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
1158       return;
1159     }
1160 
1161     for (size_t segmentIdx = 0, segmentLen = property.mSegments.Length();
1162          segmentIdx < segmentLen; segmentIdx++) {
1163       const AnimationPropertySegment& segment = property.mSegments[segmentIdx];
1164 
1165       binding_detail::FastAnimationPropertyValueDetails fromValue;
1166       CreatePropertyValue(property.mProperty, segment.mFromKey,
1167                           segment.mTimingFunction, segment.mFromValue,
1168                           segment.mFromComposite, rawSet, fromValue);
1169       // We don't apply timing functions for zero-length segments, so
1170       // don't return one here.
1171       if (segment.mFromKey == segment.mToKey) {
1172         fromValue.mEasing.Reset();
1173       }
1174       // Even though we called SetCapacity before, this could fail, since we
1175       // might add multiple elements to propertyDetails.mValues for an element
1176       // of property.mSegments in the cases mentioned below.
1177       if (!propertyDetails.mValues.AppendElement(fromValue,
1178                                                  mozilla::fallible)) {
1179         aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
1180         return;
1181       }
1182 
1183       // Normally we can ignore the to-value for this segment since it is
1184       // identical to the from-value from the next segment. However, we need
1185       // to add it if either:
1186       // a) this is the last segment, or
1187       // b) the next segment's from-value differs.
1188       if (segmentIdx == segmentLen - 1 ||
1189           property.mSegments[segmentIdx + 1].mFromValue != segment.mToValue) {
1190         binding_detail::FastAnimationPropertyValueDetails toValue;
1191         CreatePropertyValue(property.mProperty, segment.mToKey, Nothing(),
1192                             segment.mToValue, segment.mToComposite, rawSet,
1193                             toValue);
1194         // It doesn't really make sense to have a timing function on the
1195         // last property value or before a sudden jump so we just drop the
1196         // easing property altogether.
1197         toValue.mEasing.Reset();
1198         if (!propertyDetails.mValues.AppendElement(toValue,
1199                                                    mozilla::fallible)) {
1200           aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
1201           return;
1202         }
1203       }
1204     }
1205 
1206     aProperties.AppendElement(propertyDetails);
1207   }
1208 }
1209 
GetKeyframes(JSContext * aCx,nsTArray<JSObject * > & aResult,ErrorResult & aRv) const1210 void KeyframeEffect::GetKeyframes(JSContext* aCx, nsTArray<JSObject*>& aResult,
1211                                   ErrorResult& aRv) const {
1212   MOZ_ASSERT(aResult.IsEmpty());
1213   MOZ_ASSERT(!aRv.Failed());
1214 
1215   if (!aResult.SetCapacity(mKeyframes.Length(), mozilla::fallible)) {
1216     aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
1217     return;
1218   }
1219 
1220   bool isCSSAnimation = mAnimation && mAnimation->AsCSSAnimation();
1221 
1222   // For Servo, when we have CSS Animation @keyframes with variables, we convert
1223   // shorthands to longhands if needed, and store a reference to the unparsed
1224   // value. When it comes time to serialize, however, what do you serialize for
1225   // a longhand that comes from a variable reference in a shorthand? Servo says,
1226   // "an empty string" which is not particularly helpful.
1227   //
1228   // We should just store shorthands as-is (bug 1391537) and then return the
1229   // variable references, but for now, since we don't do that, and in order to
1230   // be consistent with Gecko, we just expand the variables (assuming we have
1231   // enough context to do so). For that we need to grab the ComputedStyle so we
1232   // know what custom property values to provide.
1233   RefPtr<ComputedStyle> computedStyle;
1234   if (isCSSAnimation) {
1235     // The following will flush style but that's ok since if you update
1236     // a variable's computed value, you expect to see that updated value in the
1237     // result of getKeyframes().
1238     //
1239     // If we don't have a target, the following will return null. In that case
1240     // we might end up returning variables as-is or empty string. That should be
1241     // acceptable however, since such a case is rare and this is only
1242     // short-term (and unshipped) behavior until bug 1391537 is fixed.
1243     computedStyle = GetTargetComputedStyle(Flush::Style);
1244   }
1245 
1246   const RawServoStyleSet* rawSet =
1247       mDocument->StyleSetForPresShellOrMediaQueryEvaluation()->RawSet();
1248 
1249   for (const Keyframe& keyframe : mKeyframes) {
1250     // Set up a dictionary object for the explicit members
1251     BaseComputedKeyframe keyframeDict;
1252     if (keyframe.mOffset) {
1253       keyframeDict.mOffset.SetValue(keyframe.mOffset.value());
1254     }
1255     MOZ_ASSERT(keyframe.mComputedOffset != Keyframe::kComputedOffsetNotSet,
1256                "Invalid computed offset");
1257     keyframeDict.mComputedOffset.Construct(keyframe.mComputedOffset);
1258     if (keyframe.mTimingFunction) {
1259       keyframeDict.mEasing.Truncate();
1260       keyframe.mTimingFunction.ref().AppendToString(keyframeDict.mEasing);
1261     }  // else if null, leave easing as its default "linear".
1262 
1263     // With the pref off (i.e. dom.animations-api.compositing.enabled:false),
1264     // the dictionary-to-JS conversion will skip this member entirely.
1265     keyframeDict.mComposite = keyframe.mComposite;
1266 
1267     JS::Rooted<JS::Value> keyframeJSValue(aCx);
1268     if (!ToJSValue(aCx, keyframeDict, &keyframeJSValue)) {
1269       aRv.Throw(NS_ERROR_FAILURE);
1270       return;
1271     }
1272 
1273     RefPtr<RawServoDeclarationBlock> customProperties;
1274     // A workaround for CSS Animations in servo backend, custom properties in
1275     // keyframe are stored in a servo's declaration block. Find the declaration
1276     // block to resolve CSS variables in the keyframe.
1277     // This workaround will be solved by bug 1391537.
1278     if (isCSSAnimation) {
1279       for (const PropertyValuePair& propertyValue : keyframe.mPropertyValues) {
1280         if (propertyValue.mProperty ==
1281             nsCSSPropertyID::eCSSPropertyExtra_variable) {
1282           customProperties = propertyValue.mServoDeclarationBlock;
1283           break;
1284         }
1285       }
1286     }
1287 
1288     JS::Rooted<JSObject*> keyframeObject(aCx, &keyframeJSValue.toObject());
1289     for (const PropertyValuePair& propertyValue : keyframe.mPropertyValues) {
1290       nsAutoCString stringValue;
1291       // Don't serialize the custom properties for this keyframe.
1292       if (propertyValue.mProperty ==
1293           nsCSSPropertyID::eCSSPropertyExtra_variable) {
1294         continue;
1295       }
1296       if (propertyValue.mServoDeclarationBlock) {
1297         Servo_DeclarationBlock_SerializeOneValue(
1298             propertyValue.mServoDeclarationBlock, propertyValue.mProperty,
1299             &stringValue, computedStyle, customProperties, rawSet);
1300       } else {
1301         RawServoAnimationValue* value =
1302             mBaseValues.GetWeak(propertyValue.mProperty);
1303 
1304         if (value) {
1305           Servo_AnimationValue_Serialize(value, propertyValue.mProperty, rawSet,
1306                                          &stringValue);
1307         }
1308       }
1309 
1310       // Basically, we need to do the mapping:
1311       // * eCSSProperty_offset => "cssOffset"
1312       // * eCSSProperty_float => "cssFloat"
1313       // This means if property refers to the CSS "offset"/"float" property,
1314       // return the string "cssOffset"/"cssFloat". (So avoid overlapping
1315       // "offset" property in BaseKeyframe.)
1316       // https://drafts.csswg.org/web-animations/#property-name-conversion
1317       const char* name = nullptr;
1318       switch (propertyValue.mProperty) {
1319         case nsCSSPropertyID::eCSSProperty_offset:
1320           name = "cssOffset";
1321           break;
1322         case nsCSSPropertyID::eCSSProperty_float:
1323           // FIXME: Bug 1582314: Should handle cssFloat manually if we remove it
1324           // from nsCSSProps::PropertyIDLName().
1325         default:
1326           name = nsCSSProps::PropertyIDLName(propertyValue.mProperty);
1327       }
1328 
1329       JS::Rooted<JS::Value> value(aCx);
1330       if (!NonVoidUTF8StringToJsval(aCx, stringValue, &value) ||
1331           !JS_DefineProperty(aCx, keyframeObject, name, value,
1332                              JSPROP_ENUMERATE)) {
1333         aRv.Throw(NS_ERROR_FAILURE);
1334         return;
1335       }
1336     }
1337 
1338     aResult.AppendElement(keyframeObject);
1339   }
1340 }
1341 
1342 /* static */ const TimeDuration
OverflowRegionRefreshInterval()1343 KeyframeEffect::OverflowRegionRefreshInterval() {
1344   // The amount of time we can wait between updating throttled animations
1345   // on the main thread that influence the overflow region.
1346   static const TimeDuration kOverflowRegionRefreshInterval =
1347       TimeDuration::FromMilliseconds(200);
1348 
1349   return kOverflowRegionRefreshInterval;
1350 }
1351 
IsDefinitivelyInvisibleDueToOpacity(const nsIFrame & aFrame)1352 static bool IsDefinitivelyInvisibleDueToOpacity(const nsIFrame& aFrame) {
1353   if (!aFrame.Style()->IsInOpacityZeroSubtree()) {
1354     return false;
1355   }
1356 
1357   // Find the root of the opacity: 0 subtree.
1358   const nsIFrame* root = &aFrame;
1359   while (true) {
1360     auto* parent = root->GetInFlowParent();
1361     if (!parent || !parent->Style()->IsInOpacityZeroSubtree()) {
1362       break;
1363     }
1364     root = parent;
1365   }
1366 
1367   MOZ_ASSERT(root && root->Style()->IsInOpacityZeroSubtree());
1368 
1369   // If aFrame is the root of the opacity: zero subtree, we can't prove we can
1370   // optimize it away, because it may have an opacity animation itself.
1371   if (root == &aFrame) {
1372     return false;
1373   }
1374 
1375   // Even if we're in an opacity: zero subtree, if the root of the subtree may
1376   // have an opacity animation, we can't optimize us away, as we may become
1377   // visible ourselves.
1378   return !root->HasAnimationOfOpacity();
1379 }
1380 
CanOptimizeAwayDueToOpacity(const KeyframeEffect & aEffect,const nsIFrame & aFrame)1381 static bool CanOptimizeAwayDueToOpacity(const KeyframeEffect& aEffect,
1382                                         const nsIFrame& aFrame) {
1383   if (!aFrame.Style()->IsInOpacityZeroSubtree()) {
1384     return false;
1385   }
1386   if (IsDefinitivelyInvisibleDueToOpacity(aFrame)) {
1387     return true;
1388   }
1389   return !aEffect.HasOpacityChange() && !aFrame.HasAnimationOfOpacity();
1390 }
1391 
CanThrottleIfNotVisible(nsIFrame & aFrame) const1392 bool KeyframeEffect::CanThrottleIfNotVisible(nsIFrame& aFrame) const {
1393   // Unless we are newly in-effect, we can throttle the animation if the
1394   // animation is paint only and the target frame is out of view or the document
1395   // is in background tabs.
1396   if (!mInEffectOnLastAnimationTimingUpdate || !CanIgnoreIfNotVisible()) {
1397     return false;
1398   }
1399 
1400   PresShell* presShell = GetPresShell();
1401   if (presShell && !presShell->IsActive()) {
1402     return true;
1403   }
1404 
1405   const bool isVisibilityHidden =
1406       !aFrame.IsVisibleOrMayHaveVisibleDescendants();
1407   const bool canOptimizeAwayVisibility =
1408       isVisibilityHidden && !HasVisibilityChange();
1409 
1410   const bool invisible = canOptimizeAwayVisibility ||
1411                          CanOptimizeAwayDueToOpacity(*this, aFrame) ||
1412                          aFrame.IsScrolledOutOfView();
1413   if (!invisible) {
1414     return false;
1415   }
1416 
1417   // If there are no overflow change hints, we don't need to worry about
1418   // unthrottling the animation periodically to update scrollbar positions for
1419   // the overflow region.
1420   if (!HasPropertiesThatMightAffectOverflow()) {
1421     return true;
1422   }
1423 
1424   // Don't throttle finite animations since the animation might suddenly
1425   // come into view and if it was throttled it will be out-of-sync.
1426   if (HasFiniteActiveDuration()) {
1427     return false;
1428   }
1429 
1430   return isVisibilityHidden ? CanThrottleOverflowChangesInScrollable(aFrame)
1431                             : CanThrottleOverflowChanges(aFrame);
1432 }
1433 
CanThrottle() const1434 bool KeyframeEffect::CanThrottle() const {
1435   // Unthrottle if we are not in effect or current. This will be the case when
1436   // our owning animation has finished, is idle, or when we are in the delay
1437   // phase (but without a backwards fill). In each case the computed progress
1438   // value produced on each tick will be the same so we will skip requesting
1439   // unnecessary restyles in NotifyAnimationTimingUpdated. Any calls we *do* get
1440   // here will be because of a change in state (e.g. we are newly finished or
1441   // newly no longer in effect) in which case we shouldn't throttle the sample.
1442   if (!IsInEffect() || !IsCurrent()) {
1443     return false;
1444   }
1445 
1446   nsIFrame* const frame = GetStyleFrame();
1447   if (!frame) {
1448     // There are two possible cases here.
1449     // a) No target element
1450     // b) The target element has no frame, e.g. because it is in a display:none
1451     //    subtree.
1452     // In either case we can throttle the animation because there is no
1453     // need to update on the main thread.
1454     return true;
1455   }
1456 
1457   // Do not throttle any animations during print preview.
1458   if (frame->PresContext()->IsPrintingOrPrintPreview()) {
1459     return false;
1460   }
1461 
1462   if (CanThrottleIfNotVisible(*frame)) {
1463     return true;
1464   }
1465 
1466   EffectSet* effectSet = nullptr;
1467   for (const AnimationProperty& property : mProperties) {
1468     if (!property.mIsRunningOnCompositor) {
1469       return false;
1470     }
1471 
1472     MOZ_ASSERT(nsCSSPropertyIDSet::CompositorAnimatables().HasProperty(
1473                    property.mProperty),
1474                "The property should be able to run on the compositor");
1475     if (!effectSet) {
1476       effectSet =
1477           EffectSet::GetEffectSet(mTarget.mElement, mTarget.mPseudoType);
1478       MOZ_ASSERT(effectSet,
1479                  "CanThrottle should be called on an effect "
1480                  "associated with a target element");
1481     }
1482     MOZ_ASSERT(HasEffectiveAnimationOfProperty(property.mProperty, *effectSet),
1483                "There should be an effective animation of the property while "
1484                "it is marked as being run on the compositor");
1485 
1486     DisplayItemType displayItemType =
1487         LayerAnimationInfo::GetDisplayItemTypeForProperty(property.mProperty);
1488 
1489     // Note that AnimationInfo::GetGenarationFromFrame() is supposed to work
1490     // with the primary frame instead of the style frame.
1491     Maybe<uint64_t> generation = layers::AnimationInfo::GetGenerationFromFrame(
1492         GetPrimaryFrame(), displayItemType);
1493     // Unthrottle if the animation needs to be brought up to date
1494     if (!generation || effectSet->GetAnimationGeneration() != *generation) {
1495       return false;
1496     }
1497 
1498     // If this is a transform animation that affects the overflow region,
1499     // we should unthrottle the animation periodically.
1500     if (HasPropertiesThatMightAffectOverflow() &&
1501         !CanThrottleOverflowChangesInScrollable(*frame)) {
1502       return false;
1503     }
1504   }
1505 
1506   return true;
1507 }
1508 
CanThrottleOverflowChanges(const nsIFrame & aFrame) const1509 bool KeyframeEffect::CanThrottleOverflowChanges(const nsIFrame& aFrame) const {
1510   TimeStamp now = aFrame.PresContext()->RefreshDriver()->MostRecentRefresh();
1511 
1512   EffectSet* effectSet =
1513       EffectSet::GetEffectSet(mTarget.mElement, mTarget.mPseudoType);
1514   MOZ_ASSERT(effectSet,
1515              "CanOverflowTransformChanges is expected to be called"
1516              " on an effect in an effect set");
1517   MOZ_ASSERT(mAnimation,
1518              "CanOverflowTransformChanges is expected to be called"
1519              " on an effect with a parent animation");
1520   TimeStamp lastSyncTime = effectSet->LastOverflowAnimationSyncTime();
1521   // If this animation can cause overflow, we can throttle some of the ticks.
1522   return (!lastSyncTime.IsNull() &&
1523           (now - lastSyncTime) < OverflowRegionRefreshInterval());
1524 }
1525 
CanThrottleOverflowChangesInScrollable(nsIFrame & aFrame) const1526 bool KeyframeEffect::CanThrottleOverflowChangesInScrollable(
1527     nsIFrame& aFrame) const {
1528   // If the target element is not associated with any documents, we don't care
1529   // it.
1530   Document* doc = GetRenderedDocument();
1531   if (!doc) {
1532     return true;
1533   }
1534 
1535   // If we know that the animation cannot cause overflow,
1536   // we can just disable flushes for this animation.
1537 
1538   // If we have no intersection observers, we don't care about overflow.
1539   if (!doc->HasIntersectionObservers()) {
1540     return true;
1541   }
1542 
1543   if (CanThrottleOverflowChanges(aFrame)) {
1544     return true;
1545   }
1546 
1547   // If the nearest scrollable ancestor has overflow:hidden,
1548   // we don't care about overflow.
1549   nsIScrollableFrame* scrollable =
1550       nsLayoutUtils::GetNearestScrollableFrame(&aFrame);
1551   if (!scrollable) {
1552     return true;
1553   }
1554 
1555   ScrollStyles ss = scrollable->GetScrollStyles();
1556   if (ss.mVertical == StyleOverflow::Hidden &&
1557       ss.mHorizontal == StyleOverflow::Hidden &&
1558       scrollable->GetLogicalScrollPosition() == nsPoint(0, 0)) {
1559     return true;
1560   }
1561 
1562   return false;
1563 }
1564 
GetStyleFrame() const1565 nsIFrame* KeyframeEffect::GetStyleFrame() const {
1566   nsIFrame* frame = GetPrimaryFrame();
1567   if (!frame) {
1568     return nullptr;
1569   }
1570 
1571   return nsLayoutUtils::GetStyleFrame(frame);
1572 }
1573 
GetPrimaryFrame() const1574 nsIFrame* KeyframeEffect::GetPrimaryFrame() const {
1575   nsIFrame* frame = nullptr;
1576   if (!mTarget) {
1577     return frame;
1578   }
1579 
1580   if (mTarget.mPseudoType == PseudoStyleType::before) {
1581     frame = nsLayoutUtils::GetBeforeFrame(mTarget.mElement);
1582   } else if (mTarget.mPseudoType == PseudoStyleType::after) {
1583     frame = nsLayoutUtils::GetAfterFrame(mTarget.mElement);
1584   } else if (mTarget.mPseudoType == PseudoStyleType::marker) {
1585     frame = nsLayoutUtils::GetMarkerFrame(mTarget.mElement);
1586   } else {
1587     frame = mTarget.mElement->GetPrimaryFrame();
1588     MOZ_ASSERT(mTarget.mPseudoType == PseudoStyleType::NotPseudo,
1589                "unknown mTarget.mPseudoType");
1590   }
1591 
1592   return frame;
1593 }
1594 
GetRenderedDocument() const1595 Document* KeyframeEffect::GetRenderedDocument() const {
1596   if (!mTarget) {
1597     return nullptr;
1598   }
1599   return mTarget.mElement->GetComposedDoc();
1600 }
1601 
GetPresShell() const1602 PresShell* KeyframeEffect::GetPresShell() const {
1603   Document* doc = GetRenderedDocument();
1604   if (!doc) {
1605     return nullptr;
1606   }
1607   return doc->GetPresShell();
1608 }
1609 
1610 /* static */
IsGeometricProperty(const nsCSSPropertyID aProperty)1611 bool KeyframeEffect::IsGeometricProperty(const nsCSSPropertyID aProperty) {
1612   MOZ_ASSERT(!nsCSSProps::IsShorthand(aProperty),
1613              "Property should be a longhand property");
1614 
1615   switch (aProperty) {
1616     case eCSSProperty_bottom:
1617     case eCSSProperty_height:
1618     case eCSSProperty_left:
1619     case eCSSProperty_margin_bottom:
1620     case eCSSProperty_margin_left:
1621     case eCSSProperty_margin_right:
1622     case eCSSProperty_margin_top:
1623     case eCSSProperty_padding_bottom:
1624     case eCSSProperty_padding_left:
1625     case eCSSProperty_padding_right:
1626     case eCSSProperty_padding_top:
1627     case eCSSProperty_right:
1628     case eCSSProperty_top:
1629     case eCSSProperty_width:
1630       return true;
1631     default:
1632       return false;
1633   }
1634 }
1635 
1636 /* static */
CanAnimateTransformOnCompositor(const nsIFrame * aFrame,AnimationPerformanceWarning::Type & aPerformanceWarning)1637 bool KeyframeEffect::CanAnimateTransformOnCompositor(
1638     const nsIFrame* aFrame,
1639     AnimationPerformanceWarning::Type& aPerformanceWarning /* out */) {
1640   // In some cases, such as when we are simply collecting all the compositor
1641   // animations regardless of the frame on which they run in order to calculate
1642   // change hints, |aFrame| will be the style frame. However, even in that case
1643   // we should look at the primary frame since that is where the transform will
1644   // be applied.
1645   const nsIFrame* primaryFrame =
1646       nsLayoutUtils::GetPrimaryFrameFromStyleFrame(aFrame);
1647 
1648   // Note that testing BackfaceIsHidden() is not a sufficient test for
1649   // what we need for animating backface-visibility correctly if we
1650   // remove the above test for Extend3DContext(); that would require
1651   // looking at backface-visibility on descendants as well. See bug 1186204.
1652   if (primaryFrame->BackfaceIsHidden()) {
1653     aPerformanceWarning =
1654         AnimationPerformanceWarning::Type::TransformBackfaceVisibilityHidden;
1655     return false;
1656   }
1657   // Async 'transform' animations of aFrames with SVG transforms is not
1658   // supported.  See bug 779599.
1659   if (primaryFrame->IsSVGTransformed()) {
1660     aPerformanceWarning = AnimationPerformanceWarning::Type::TransformSVG;
1661     return false;
1662   }
1663 
1664   return true;
1665 }
1666 
ShouldBlockAsyncTransformAnimations(const nsIFrame * aFrame,const nsCSSPropertyIDSet & aPropertySet,AnimationPerformanceWarning::Type & aPerformanceWarning) const1667 bool KeyframeEffect::ShouldBlockAsyncTransformAnimations(
1668     const nsIFrame* aFrame, const nsCSSPropertyIDSet& aPropertySet,
1669     AnimationPerformanceWarning::Type& aPerformanceWarning /* out */) const {
1670   EffectSet* effectSet =
1671       EffectSet::GetEffectSet(mTarget.mElement, mTarget.mPseudoType);
1672   // The various transform properties ('transform', 'scale' etc.) get combined
1673   // on the compositor.
1674   //
1675   // As a result, if we have an animation of 'scale' and 'translate', but the
1676   // 'translate' property is covered by an !important rule, we will not be
1677   // able to combine the result on the compositor since we won't have the
1678   // !important rule to incorporate. In that case we should run all the
1679   // transform-related animations on the main thread (where we have the
1680   // !important rule).
1681   nsCSSPropertyIDSet blockedProperties =
1682       effectSet->PropertiesWithImportantRules().Intersect(
1683           effectSet->PropertiesForAnimationsLevel());
1684   if (blockedProperties.Intersects(aPropertySet)) {
1685     aPerformanceWarning =
1686         AnimationPerformanceWarning::Type::TransformIsBlockedByImportantRules;
1687     return true;
1688   }
1689 
1690   MOZ_ASSERT(mAnimation);
1691   // Note: If the geometric animations are using scroll-timeline, we don't need
1692   // to synchronize transform animations with them.
1693   const bool enableMainthreadSynchronizationWithGeometricAnimations =
1694       StaticPrefs::
1695           dom_animations_mainthread_synchronization_with_geometric_animations() &&
1696       !mAnimation->UsingScrollTimeline();
1697 
1698   for (const AnimationProperty& property : mProperties) {
1699     // If there is a property for animations level that is overridden by
1700     // !important rules, it should not block other animations from running
1701     // on the compositor.
1702     // NOTE: We don't currently check for !important rules for properties that
1703     // don't run on the compositor. As result such properties (e.g. margin-left)
1704     // can still block async animations even if they are overridden by
1705     // !important rules.
1706     if (effectSet &&
1707         effectSet->PropertiesWithImportantRules().HasProperty(
1708             property.mProperty) &&
1709         effectSet->PropertiesForAnimationsLevel().HasProperty(
1710             property.mProperty)) {
1711       continue;
1712     }
1713     // Check for geometric properties
1714     if (enableMainthreadSynchronizationWithGeometricAnimations &&
1715         IsGeometricProperty(property.mProperty)) {
1716       aPerformanceWarning =
1717           AnimationPerformanceWarning::Type::TransformWithGeometricProperties;
1718       return true;
1719     }
1720 
1721     // Check for unsupported transform animations
1722     if (LayerAnimationInfo::GetCSSPropertiesFor(DisplayItemType::TYPE_TRANSFORM)
1723             .HasProperty(property.mProperty)) {
1724       if (!CanAnimateTransformOnCompositor(aFrame, aPerformanceWarning)) {
1725         return true;
1726       }
1727     }
1728   }
1729 
1730   return false;
1731 }
1732 
HasGeometricProperties() const1733 bool KeyframeEffect::HasGeometricProperties() const {
1734   for (const AnimationProperty& property : mProperties) {
1735     if (IsGeometricProperty(property.mProperty)) {
1736       return true;
1737     }
1738   }
1739 
1740   return false;
1741 }
1742 
SetPerformanceWarning(const nsCSSPropertyIDSet & aPropertySet,const AnimationPerformanceWarning & aWarning)1743 void KeyframeEffect::SetPerformanceWarning(
1744     const nsCSSPropertyIDSet& aPropertySet,
1745     const AnimationPerformanceWarning& aWarning) {
1746   nsCSSPropertyIDSet curr = aPropertySet;
1747   for (AnimationProperty& property : mProperties) {
1748     if (!curr.HasProperty(property.mProperty)) {
1749       continue;
1750     }
1751     property.SetPerformanceWarning(aWarning, mTarget.mElement);
1752     curr.RemoveProperty(property.mProperty);
1753     if (curr.IsEmpty()) {
1754       return;
1755     }
1756   }
1757 }
1758 
1759 already_AddRefed<ComputedStyle>
CreateComputedStyleForAnimationValue(nsCSSPropertyID aProperty,const AnimationValue & aValue,nsPresContext * aPresContext,const ComputedStyle * aBaseComputedStyle)1760 KeyframeEffect::CreateComputedStyleForAnimationValue(
1761     nsCSSPropertyID aProperty, const AnimationValue& aValue,
1762     nsPresContext* aPresContext, const ComputedStyle* aBaseComputedStyle) {
1763   MOZ_ASSERT(aBaseComputedStyle,
1764              "CreateComputedStyleForAnimationValue needs to be called "
1765              "with a valid ComputedStyle");
1766 
1767   Element* elementForResolve = EffectCompositor::GetElementToRestyle(
1768       mTarget.mElement, mTarget.mPseudoType);
1769   // The element may be null if, for example, we target a pseudo-element that no
1770   // longer exists.
1771   if (!elementForResolve) {
1772     return nullptr;
1773   }
1774 
1775   ServoStyleSet* styleSet = aPresContext->StyleSet();
1776   return styleSet->ResolveServoStyleByAddingAnimation(
1777       elementForResolve, aBaseComputedStyle, aValue.mServo);
1778 }
1779 
CalculateCumulativeChangeHint(const ComputedStyle * aComputedStyle)1780 void KeyframeEffect::CalculateCumulativeChangeHint(
1781     const ComputedStyle* aComputedStyle) {
1782   mCumulativeChangeHint = nsChangeHint(0);
1783   mNeedsStyleData = false;
1784 
1785   nsPresContext* presContext =
1786       mTarget ? nsContentUtils::GetContextForContent(mTarget.mElement)
1787               : nullptr;
1788   if (!presContext) {
1789     // Change hints make no sense if we're not rendered.
1790     //
1791     // Actually, we cannot even post them anywhere.
1792     mNeedsStyleData = true;
1793     return;
1794   }
1795 
1796   for (const AnimationProperty& property : mProperties) {
1797     // For opacity property we don't produce any change hints that are not
1798     // included in nsChangeHint_Hints_CanIgnoreIfNotVisible so we can throttle
1799     // opacity animations regardless of the change they produce.  This
1800     // optimization is particularly important since it allows us to throttle
1801     // opacity animations with missing 0%/100% keyframes.
1802     if (property.mProperty == eCSSProperty_opacity) {
1803       continue;
1804     }
1805 
1806     for (const AnimationPropertySegment& segment : property.mSegments) {
1807       // In case composite operation is not 'replace' or value is null,
1808       // we can't throttle animations which will not cause any layout changes
1809       // on invisible elements because we can't calculate the change hint for
1810       // such properties until we compose it.
1811       if (!segment.HasReplaceableValues()) {
1812         if (!nsCSSPropertyIDSet::TransformLikeProperties().HasProperty(
1813                 property.mProperty)) {
1814           mCumulativeChangeHint = ~nsChangeHint_Hints_CanIgnoreIfNotVisible;
1815           return;
1816         }
1817         // We try a little harder to optimize transform animations simply
1818         // because they are so common (the second-most commonly animated
1819         // property at the time of writing).  So if we encounter a transform
1820         // segment that needs composing with the underlying value, we just add
1821         // all the change hints a transform animation is known to be able to
1822         // generate.
1823         mCumulativeChangeHint |=
1824             nsChangeHint_ComprehensiveAddOrRemoveTransform |
1825             nsChangeHint_UpdatePostTransformOverflow |
1826             nsChangeHint_UpdateTransformLayer;
1827         continue;
1828       }
1829 
1830       RefPtr<ComputedStyle> fromContext = CreateComputedStyleForAnimationValue(
1831           property.mProperty, segment.mFromValue, presContext, aComputedStyle);
1832       if (!fromContext) {
1833         mCumulativeChangeHint = ~nsChangeHint_Hints_CanIgnoreIfNotVisible;
1834         mNeedsStyleData = true;
1835         return;
1836       }
1837 
1838       RefPtr<ComputedStyle> toContext = CreateComputedStyleForAnimationValue(
1839           property.mProperty, segment.mToValue, presContext, aComputedStyle);
1840       if (!toContext) {
1841         mCumulativeChangeHint = ~nsChangeHint_Hints_CanIgnoreIfNotVisible;
1842         mNeedsStyleData = true;
1843         return;
1844       }
1845 
1846       uint32_t equalStructs = 0;
1847       nsChangeHint changeHint =
1848           fromContext->CalcStyleDifference(*toContext, &equalStructs);
1849 
1850       mCumulativeChangeHint |= changeHint;
1851     }
1852   }
1853 }
1854 
SetAnimation(Animation * aAnimation)1855 void KeyframeEffect::SetAnimation(Animation* aAnimation) {
1856   if (mAnimation == aAnimation) {
1857     return;
1858   }
1859 
1860   // Restyle for the old animation.
1861   RequestRestyle(EffectCompositor::RestyleType::Layer);
1862 
1863   mAnimation = aAnimation;
1864 
1865   // The order of these function calls is important:
1866   // NotifyAnimationTimingUpdated() need the updated mIsRelevant flag to check
1867   // if it should create the effectSet or not, and MarkCascadeNeedsUpdate()
1868   // needs a valid effectSet, so we should call them in this order.
1869   if (mAnimation) {
1870     mAnimation->UpdateRelevance();
1871   }
1872   NotifyAnimationTimingUpdated(PostRestyleMode::IfNeeded);
1873   if (mAnimation) {
1874     MarkCascadeNeedsUpdate();
1875   }
1876 }
1877 
CanIgnoreIfNotVisible() const1878 bool KeyframeEffect::CanIgnoreIfNotVisible() const {
1879   if (!StaticPrefs::dom_animations_offscreen_throttling()) {
1880     return false;
1881   }
1882 
1883   // FIXME: For further sophisticated optimization we need to check
1884   // change hint on the segment corresponding to computedTiming.progress.
1885   return NS_IsHintSubset(mCumulativeChangeHint,
1886                          nsChangeHint_Hints_CanIgnoreIfNotVisible);
1887 }
1888 
MarkCascadeNeedsUpdate()1889 void KeyframeEffect::MarkCascadeNeedsUpdate() {
1890   if (!mTarget) {
1891     return;
1892   }
1893 
1894   EffectSet* effectSet =
1895       EffectSet::GetEffectSet(mTarget.mElement, mTarget.mPseudoType);
1896   if (!effectSet) {
1897     return;
1898   }
1899   effectSet->MarkCascadeNeedsUpdate();
1900 }
1901 
1902 /* static */
HasComputedTimingChanged(const ComputedTiming & aComputedTiming,IterationCompositeOperation aIterationComposite,const Nullable<double> & aProgressOnLastCompose,uint64_t aCurrentIterationOnLastCompose)1903 bool KeyframeEffect::HasComputedTimingChanged(
1904     const ComputedTiming& aComputedTiming,
1905     IterationCompositeOperation aIterationComposite,
1906     const Nullable<double>& aProgressOnLastCompose,
1907     uint64_t aCurrentIterationOnLastCompose) {
1908   // Typically we don't need to request a restyle if the progress hasn't
1909   // changed since the last call to ComposeStyle. The one exception is if the
1910   // iteration composite mode is 'accumulate' and the current iteration has
1911   // changed, since that will often produce a different result.
1912   return aComputedTiming.mProgress != aProgressOnLastCompose ||
1913          (aIterationComposite == IterationCompositeOperation::Accumulate &&
1914           aComputedTiming.mCurrentIteration != aCurrentIterationOnLastCompose);
1915 }
1916 
HasComputedTimingChanged() const1917 bool KeyframeEffect::HasComputedTimingChanged() const {
1918   ComputedTiming computedTiming = GetComputedTiming();
1919   return HasComputedTimingChanged(
1920       computedTiming, mEffectOptions.mIterationComposite,
1921       mProgressOnLastCompose, mCurrentIterationOnLastCompose);
1922 }
1923 
ContainsAnimatedScale(const nsIFrame * aFrame) const1924 bool KeyframeEffect::ContainsAnimatedScale(const nsIFrame* aFrame) const {
1925   // For display:table content, transform animations run on the table wrapper
1926   // frame. If we are being passed a frame that doesn't support transforms
1927   // (i.e. the inner table frame) we could just return false, but it possibly
1928   // means we looked up the wrong EffectSet so for now we just assert instead.
1929   MOZ_ASSERT(aFrame && aFrame->IsFrameOfType(nsIFrame::eSupportsCSSTransforms),
1930              "We should be passed a frame that supports transforms");
1931 
1932   if (!IsCurrent()) {
1933     return false;
1934   }
1935 
1936   if (!mAnimation ||
1937       mAnimation->ReplaceState() == AnimationReplaceState::Removed) {
1938     return false;
1939   }
1940 
1941   for (const AnimationProperty& prop : mProperties) {
1942     if (prop.mProperty != eCSSProperty_transform &&
1943         prop.mProperty != eCSSProperty_scale &&
1944         prop.mProperty != eCSSProperty_rotate) {
1945       continue;
1946     }
1947 
1948     AnimationValue baseStyle = BaseStyle(prop.mProperty);
1949     if (!baseStyle.IsNull()) {
1950       gfx::Size size = baseStyle.GetScaleValue(aFrame);
1951       if (size != gfx::Size(1.0f, 1.0f)) {
1952         return true;
1953       }
1954     }
1955 
1956     // This is actually overestimate because there are some cases that combining
1957     // the base value and from/to value produces 1:1 scale. But it doesn't
1958     // really matter.
1959     for (const AnimationPropertySegment& segment : prop.mSegments) {
1960       if (!segment.mFromValue.IsNull()) {
1961         gfx::Size from = segment.mFromValue.GetScaleValue(aFrame);
1962         if (from != gfx::Size(1.0f, 1.0f)) {
1963           return true;
1964         }
1965       }
1966       if (!segment.mToValue.IsNull()) {
1967         gfx::Size to = segment.mToValue.GetScaleValue(aFrame);
1968         if (to != gfx::Size(1.0f, 1.0f)) {
1969           return true;
1970         }
1971       }
1972     }
1973   }
1974 
1975   return false;
1976 }
1977 
UpdateEffectSet(EffectSet * aEffectSet) const1978 void KeyframeEffect::UpdateEffectSet(EffectSet* aEffectSet) const {
1979   if (!mInEffectSet) {
1980     return;
1981   }
1982 
1983   EffectSet* effectSet =
1984       aEffectSet
1985           ? aEffectSet
1986           : EffectSet::GetEffectSet(mTarget.mElement, mTarget.mPseudoType);
1987   if (!effectSet) {
1988     return;
1989   }
1990 
1991   nsIFrame* styleFrame = GetStyleFrame();
1992   if (HasAnimationOfPropertySet(nsCSSPropertyIDSet::OpacityProperties())) {
1993     effectSet->SetMayHaveOpacityAnimation();
1994     EnumerateContinuationsOrIBSplitSiblings(styleFrame, [](nsIFrame* aFrame) {
1995       aFrame->SetMayHaveOpacityAnimation();
1996     });
1997   }
1998 
1999   nsIFrame* primaryFrame = GetPrimaryFrame();
2000   if (HasAnimationOfPropertySet(
2001           nsCSSPropertyIDSet::TransformLikeProperties())) {
2002     effectSet->SetMayHaveTransformAnimation();
2003     // For table frames, it's not clear if we should iterate over the
2004     // continuations of the table wrapper or the inner table frame.
2005     //
2006     // Fortunately, this is not currently an issue because we only split tables
2007     // when printing (page breaks) where we don't run animations.
2008     EnumerateContinuationsOrIBSplitSiblings(primaryFrame, [](nsIFrame* aFrame) {
2009       aFrame->SetMayHaveTransformAnimation();
2010     });
2011   }
2012 }
2013 
IsMatchForCompositor(const nsCSSPropertyIDSet & aPropertySet,const nsIFrame * aFrame,const EffectSet & aEffects,AnimationPerformanceWarning::Type & aPerformanceWarning) const2014 KeyframeEffect::MatchForCompositor KeyframeEffect::IsMatchForCompositor(
2015     const nsCSSPropertyIDSet& aPropertySet, const nsIFrame* aFrame,
2016     const EffectSet& aEffects,
2017     AnimationPerformanceWarning::Type& aPerformanceWarning /* out */) const {
2018   MOZ_ASSERT(mAnimation);
2019 
2020   if (!mAnimation->IsRelevant()) {
2021     return KeyframeEffect::MatchForCompositor::No;
2022   }
2023 
2024   if (mAnimation->ShouldBeSynchronizedWithMainThread(aPropertySet, aFrame,
2025                                                      aPerformanceWarning)) {
2026     // For a given |aFrame|, we don't want some animations of |aProperty| to
2027     // run on the compositor and others to run on the main thread, so if any
2028     // need to be synchronized with the main thread, run them all there.
2029     return KeyframeEffect::MatchForCompositor::NoAndBlockThisProperty;
2030   }
2031 
2032   // Unconditionally disable OMTA for scroll-timeline.
2033   // FIXME: Bug 1737180: Once we support OMTA for scroll-timeline, we can just
2034   // drop this.
2035   if (mAnimation->UsingScrollTimeline()) {
2036     return KeyframeEffect::MatchForCompositor::No;
2037   }
2038 
2039   if (!HasEffectiveAnimationOfPropertySet(aPropertySet, aEffects)) {
2040     return KeyframeEffect::MatchForCompositor::No;
2041   }
2042 
2043   // If we know that the animation is not visible, we don't need to send the
2044   // animation to the compositor.
2045   if (!aFrame->IsVisibleOrMayHaveVisibleDescendants() ||
2046       IsDefinitivelyInvisibleDueToOpacity(*aFrame) ||
2047       aFrame->IsScrolledOutOfView()) {
2048     return KeyframeEffect::MatchForCompositor::NoAndBlockThisProperty;
2049   }
2050 
2051   if (aPropertySet.HasProperty(eCSSProperty_background_color)) {
2052     if (!StaticPrefs::gfx_omta_background_color()) {
2053       return KeyframeEffect::MatchForCompositor::No;
2054     }
2055 
2056     // We don't yet support off-main-thread background-color animations on
2057     // canvas frame or on <html> or <body> which genarate
2058     // nsDisplayCanvasBackgroundColor or nsDisplaySolidColor display item.
2059     if (nsCSSRendering::IsCanvasFrame(aFrame) ||
2060         (aFrame->GetContent() &&
2061          (aFrame->GetContent()->IsHTMLElement(nsGkAtoms::body) ||
2062           aFrame->GetContent()->IsHTMLElement(nsGkAtoms::html)))) {
2063       return KeyframeEffect::MatchForCompositor::No;
2064     }
2065   }
2066 
2067   // We can't run this background color animation on the compositor if there
2068   // is any `current-color` keyframe.
2069   if (mHasCurrentColor) {
2070     aPerformanceWarning = AnimationPerformanceWarning::Type::HasCurrentColor;
2071     return KeyframeEffect::MatchForCompositor::NoAndBlockThisProperty;
2072   }
2073 
2074   return mAnimation->IsPlaying() ? KeyframeEffect::MatchForCompositor::Yes
2075                                  : KeyframeEffect::MatchForCompositor::IfNeeded;
2076 }
2077 
2078 }  // namespace dom
2079 }  // namespace mozilla
2080