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/. */
7 #include "mozilla/dom/KeyframeEffect.h"
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
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"
46 void AnimationProperty::SetPerformanceWarning(
47 const AnimationPerformanceWarning
& aWarning
, const dom::Element
* aElement
) {
48 if (mPerformanceWarning
&& *mPerformanceWarning
== aWarning
) {
52 mPerformanceWarning
= Some(aWarning
);
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
);
62 bool PropertyValuePair::operator==(const PropertyValuePair
& aOther
) const {
63 if (mProperty
!= aOther
.mProperty
) {
66 if (mServoDeclarationBlock
== aOther
.mServoDeclarationBlock
) {
69 if (!mServoDeclarationBlock
|| !aOther
.mServoDeclarationBlock
) {
72 return Servo_DeclarationBlock_Equals(mServoDeclarationBlock
,
73 aOther
.mServoDeclarationBlock
);
78 NS_IMPL_CYCLE_COLLECTION_INHERITED(KeyframeEffect
, AnimationEffect
,
81 NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(KeyframeEffect
, AnimationEffect
)
82 NS_IMPL_CYCLE_COLLECTION_TRACE_END
84 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(KeyframeEffect
)
85 NS_INTERFACE_MAP_END_INHERITING(AnimationEffect
)
87 NS_IMPL_ADDREF_INHERITED(KeyframeEffect
, AnimationEffect
)
88 NS_IMPL_RELEASE_INHERITED(KeyframeEffect
, AnimationEffect
)
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
) {}
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()) {}
109 JSObject
* KeyframeEffect::WrapObject(JSContext
* aCx
,
110 JS::Handle
<JSObject
*> aGivenProto
) {
111 return KeyframeEffect_Binding::Wrap(aCx
, this, aGivenProto
);
114 IterationCompositeOperation
KeyframeEffect::IterationComposite() const {
115 return mEffectOptions
.mIterationComposite
;
118 void KeyframeEffect::SetIterationComposite(
119 const IterationCompositeOperation
& aIterationComposite
) {
120 if (mEffectOptions
.mIterationComposite
== aIterationComposite
) {
124 if (mAnimation
&& mAnimation
->IsRelevant()) {
125 MutationObservers::NotifyAnimationChanged(mAnimation
);
128 mEffectOptions
.mIterationComposite
= aIterationComposite
;
129 RequestRestyle(EffectCompositor::RestyleType::Layer
);
132 CompositeOperation
KeyframeEffect::Composite() const {
133 return mEffectOptions
.mComposite
;
136 void KeyframeEffect::SetComposite(const CompositeOperation
& aComposite
) {
137 if (mEffectOptions
.mComposite
== aComposite
) {
141 mEffectOptions
.mComposite
= aComposite
;
143 if (mAnimation
&& mAnimation
->IsRelevant()) {
144 MutationObservers::NotifyAnimationChanged(mAnimation
);
148 RefPtr
<ComputedStyle
> computedStyle
= GetTargetComputedStyle(Flush::None
);
150 UpdateProperties(computedStyle
);
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()
162 mAnimation
->NotifyEffectTimingUpdated();
164 if (mAnimation
->IsRelevant()) {
165 MutationObservers::NotifyAnimationChanged(mAnimation
);
168 RequestRestyle(EffectCompositor::RestyleType::Layer
);
172 void KeyframeEffect::NotifyAnimationTimingUpdated(
173 PostRestyleMode aPostRestyle
) {
174 UpdateTargetRegistration();
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
182 bool isRelevant
= mAnimation
&& mAnimation
->IsRelevant();
184 ResetIsRunningOnCompositor();
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
);
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
;
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).
212 mProgressOnLastCompose
.SetNull();
213 mCurrentIterationOnLastCompose
= 0;
217 static bool KeyframesEqualIgnoringComputedOffsets(
218 const nsTArray
<Keyframe
>& aLhs
, const nsTArray
<Keyframe
>& aRhs
) {
219 if (aLhs
.Length() != aRhs
.Length()) {
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
) {
234 // https://drafts.csswg.org/web-animations/#dom-keyframeeffect-setkeyframes
235 void KeyframeEffect::SetKeyframes(JSContext
* aContext
,
236 JS::Handle
<JSObject
*> aKeyframes
,
238 nsTArray
<Keyframe
> keyframes
= KeyframeUtils::GetKeyframesFromObject(
239 aContext
, mDocument
, aKeyframes
, "KeyframeEffect.setKeyframes", aRv
);
244 RefPtr
<ComputedStyle
> style
= GetTargetComputedStyle(Flush::None
);
245 SetKeyframes(std::move(keyframes
), style
);
248 void KeyframeEffect::SetKeyframes(nsTArray
<Keyframe
>&& aKeyframes
,
249 const ComputedStyle
* aStyle
) {
250 if (KeyframesEqualIgnoringComputedOffsets(aKeyframes
, mKeyframes
)) {
254 mKeyframes
= std::move(aKeyframes
);
255 KeyframeUtils::DistributeKeyframes(mKeyframes
);
257 if (mAnimation
&& mAnimation
->IsRelevant()) {
258 MutationObservers::NotifyAnimationChanged(mAnimation
);
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).
264 UpdateProperties(aStyle
);
268 void KeyframeEffect::ReplaceTransitionStartValue(AnimationValue
&& aStartValue
) {
269 if (!aStartValue
.mServo
) {
273 // A typical transition should have a single property and a single segment.
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) {
281 // Likewise, check that the keyframes are of the expected shape.
282 if (mKeyframes
.Length() != 2 || mKeyframes
[0].mPropertyValues
.Length() != 1) {
286 // Check that the value we are about to substitute in is actually for the
288 if (Servo_AnimationValue_GetPropertyId(aStartValue
.mServo
) !=
289 mProperties
[0].mProperty
) {
293 mKeyframes
[0].mPropertyValues
[0].mServoDeclarationBlock
=
294 Servo_AnimationValue_Uncompute(aStartValue
.mServo
).Consume();
295 mProperties
[0].mSegments
[0].mFromValue
= std::move(aStartValue
);
298 static bool IsEffectiveProperty(const EffectSet
& aEffects
,
299 nsCSSPropertyID aProperty
) {
300 return !aEffects
.PropertiesWithImportantRules().HasProperty(aProperty
) ||
301 !aEffects
.PropertiesForAnimationsLevel().HasProperty(aProperty
);
304 const AnimationProperty
* KeyframeEffect::GetEffectiveAnimationOfProperty(
305 nsCSSPropertyID aProperty
, const EffectSet
& aEffects
) const {
306 MOZ_ASSERT(mTarget
&&
307 &aEffects
== EffectSet::GetEffectSet(mTarget
.mElement
,
308 mTarget
.mPseudoType
));
310 for (const AnimationProperty
& property
: mProperties
) {
311 if (aProperty
!= property
.mProperty
) {
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
)) {
326 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
)) {
337 nsCSSPropertyIDSet
KeyframeEffect::GetPropertiesForCompositor(
338 EffectSet
& aEffects
, const nsIFrame
* aFrame
) const {
339 MOZ_ASSERT(&aEffects
==
340 EffectSet::GetEffectSet(mTarget
.mElement
, mTarget
.mPseudoType
));
342 nsCSSPropertyIDSet properties
;
344 if (!mAnimation
|| !mAnimation
->IsRelevant()) {
348 static constexpr nsCSSPropertyIDSet compositorAnimatables
=
349 nsCSSPropertyIDSet::CompositorAnimatables();
350 static constexpr nsCSSPropertyIDSet transformLikeProperties
=
351 nsCSSPropertyIDSet::TransformLikeProperties();
353 nsCSSPropertyIDSet transformSet
;
354 AnimationPerformanceWarning::Type dummyWarning
;
356 for (const AnimationProperty
& property
: mProperties
) {
357 if (!compositorAnimatables
.HasProperty(property
.mProperty
)) {
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
);
369 KeyframeEffect::MatchForCompositor matchResult
= IsMatchForCompositor(
370 nsCSSPropertyIDSet
{property
.mProperty
}, aFrame
, aEffects
, dummyWarning
);
372 KeyframeEffect::MatchForCompositor::NoAndBlockThisProperty
||
373 matchResult
== KeyframeEffect::MatchForCompositor::No
) {
376 properties
.AddProperty(property
.mProperty
);
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
;
391 nsCSSPropertyIDSet
KeyframeEffect::GetPropertySet() const {
392 nsCSSPropertyIDSet result
;
394 for (const AnimationProperty
& property
: mProperties
) {
395 result
.AddProperty(property
.mProperty
);
402 bool SpecifiedKeyframeArraysAreEqual(const nsTArray
<Keyframe
>& aA
,
403 const nsTArray
<Keyframe
>& aB
) {
404 if (aA
.Length() != aB
.Length()) {
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
) {
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())) {
431 void KeyframeEffect::UpdateProperties(const ComputedStyle
* aStyle
) {
434 nsTArray
<AnimationProperty
> properties
= BuildProperties(aStyle
);
436 bool propertiesChanged
= mProperties
!= properties
;
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);
444 if (!propertiesChanged
) {
445 if (baseStylesChanged
) {
446 RequestRestyle(EffectCompositor::RestyleType::Layer
);
448 // Check if we need to update the cumulative change hint because we now have
450 if (mNeedsStyleData
&& mTarget
&& mTarget
.mElement
->HasServoData()) {
451 CalculateCumulativeChangeHint(aStyle
);
456 // Preserve the state of the mIsRunningOnCompositor flag.
457 nsCSSPropertyIDSet runningOnCompositorProperties
;
459 for (const AnimationProperty
& property
: mProperties
) {
460 if (property
.mIsRunningOnCompositor
) {
461 runningOnCompositorProperties
.AddProperty(property
.mProperty
);
465 mProperties
= std::move(properties
);
468 mHasCurrentColor
= false;
470 for (AnimationProperty
& property
: mProperties
) {
471 property
.mIsRunningOnCompositor
=
472 runningOnCompositorProperties
.HasProperty(property
.mProperty
);
474 if (property
.mProperty
== eCSSProperty_background_color
&&
476 if (HasCurrentColor(property
.mSegments
)) {
477 mHasCurrentColor
= true;
483 CalculateCumulativeChangeHint(aStyle
);
485 MarkCascadeNeedsUpdate();
488 mAnimation
->NotifyEffectPropertiesUpdated();
491 RequestRestyle(EffectCompositor::RestyleType::Layer
);
494 void KeyframeEffect::EnsureBaseStyles(
495 const ComputedStyle
* aComputedValues
,
496 const nsTArray
<AnimationProperty
>& aProperties
, bool* aBaseStylesChanged
) {
497 if (aBaseStylesChanged
!= nullptr) {
498 *aBaseStylesChanged
= false;
505 BaseValuesHashmap previousBaseStyles
;
506 if (aBaseStylesChanged
!= nullptr) {
507 previousBaseStyles
= std::move(mBaseValues
);
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.
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");
528 RefPtr
<ComputedStyle
> baseComputedStyle
;
529 for (const AnimationProperty
& property
: aProperties
) {
530 EnsureBaseStyle(property
, presContext
, aComputedValues
, baseComputedStyle
);
533 if (aBaseStylesChanged
!= nullptr &&
535 mBaseValues
.cbegin(), mBaseValues
.cend(), [&](const auto& entry
) {
536 return AnimationValue(entry
.GetData()) !=
537 AnimationValue(previousBaseStyles
.Get(entry
.GetKey()));
539 *aBaseStylesChanged
= true;
543 void KeyframeEffect::EnsureBaseStyle(
544 const AnimationProperty
& aProperty
, nsPresContext
* aPresContext
,
545 const ComputedStyle
* aComputedStyle
,
546 RefPtr
<ComputedStyle
>& aBaseComputedStyle
) {
547 bool hasAdditiveValues
= false;
549 for (const AnimationPropertySegment
& segment
: aProperty
.mSegments
) {
550 if (!segment
.HasReplaceableValues()) {
551 hasAdditiveValues
= true;
556 if (!hasAdditiveValues
) {
560 if (!aBaseComputedStyle
) {
561 MOZ_ASSERT(mTarget
, "Should have a valid target");
563 Element
* animatingElement
= EffectCompositor::GetElementToRestyle(
564 mTarget
.mElement
, mTarget
.mPseudoType
);
565 if (!animatingElement
) {
568 aBaseComputedStyle
= aPresContext
->StyleSet()->GetBaseContextForElement(
569 animatingElement
, aComputedStyle
);
571 RefPtr
<RawServoAnimationValue
> baseValue
=
572 Servo_ComputedValues_ExtractAnimationValue(aBaseComputedStyle
,
575 mBaseValues
.InsertOrUpdate(aProperty
.mProperty
, std::move(baseValue
));
578 void KeyframeEffect::WillComposeStyle() {
579 ComputedTiming computedTiming
= GetComputedTiming();
580 mProgressOnLastCompose
= computedTiming
.mProgress
;
581 mCurrentIterationOnLastCompose
= computedTiming
.mCurrentIteration
;
584 void KeyframeEffect::ComposeStyleRule(
585 RawServoAnimationValueMap
& aAnimationValues
,
586 const AnimationProperty
& aProperty
,
587 const AnimationPropertySegment
& aSegment
,
588 const ComputedTiming
& aComputedTiming
) {
590 reinterpret_cast<RawServoAnimationValueTable
*>(&mBaseValues
);
591 Servo_AnimationCompose(&aAnimationValues
, opaqueTable
, aProperty
.mProperty
,
592 &aSegment
, &aProperty
.mSegments
.LastElement(),
593 &aComputedTiming
, mEffectOptions
.mIterationComposite
);
596 void KeyframeEffect::ComposeStyle(RawServoAnimationValueMap
& aComposeResult
,
597 const nsCSSPropertyIDSet
& aPropertiesToSkip
) {
598 ComputedTiming computedTiming
= GetComputedTiming();
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()) {
606 for (size_t propIdx
= 0, propEnd
= mProperties
.Length(); propIdx
!= propEnd
;
608 const AnimationProperty
& prop
= mProperties
[propIdx
];
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");
614 if (aPropertiesToSkip
.HasProperty(prop
.mProperty
)) {
618 MOZ_ASSERT(prop
.mSegments
.Length() > 0,
619 "property should not be in animations if it has no segments");
621 // FIXME: Maybe cache the current segment?
622 const AnimationPropertySegment
*segment
= prop
.mSegments
.Elements(),
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
) {
631 MOZ_ASSERT(segment
->mFromKey
== (segment
- 1)->mToKey
, "incorrect keys");
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");
639 ComposeStyleRule(aComposeResult
, prop
, *segment
, computedTiming
);
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
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
);
658 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
) {
671 void KeyframeEffect::SetIsRunningOnCompositor(nsCSSPropertyID aProperty
,
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.
684 property
.mPerformanceWarning
.reset();
685 } else if (mAnimation
&& mAnimation
->IsPartialPrerendered()) {
686 ResetPartialPrerendered();
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.
706 property
.mPerformanceWarning
.reset();
711 if (!aIsRunning
&& mAnimation
&& mAnimation
->IsPartialPrerendered()) {
712 ResetPartialPrerendered();
716 void KeyframeEffect::ResetIsRunningOnCompositor() {
717 for (AnimationProperty
& property
: mProperties
) {
718 property
.mIsRunningOnCompositor
= false;
721 if (mAnimation
&& mAnimation
->IsPartialPrerendered()) {
722 ResetPartialPrerendered();
726 void KeyframeEffect::ResetPartialPrerendered() {
727 MOZ_ASSERT(mAnimation
&& mAnimation
->IsPartialPrerendered());
729 nsIFrame
* frame
= GetPrimaryFrame();
734 nsIWidget
* widget
= frame
->GetNearestWidget();
739 if (WindowRenderer
* windowRenderer
= widget
->GetWindowRenderer()) {
740 windowRenderer
->RemovePartialPrerenderedAnimation(
741 mAnimation
->IdOnCompositor(), mAnimation
);
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
;
751 static const KeyframeEffectOptions
& KeyframeEffectOptionsFromUnion(
752 const UnrestrictedDoubleOrKeyframeEffectOptions
& aOptions
) {
753 MOZ_ASSERT(aOptions
.IsKeyframeEffectOptions());
754 return aOptions
.GetAsKeyframeEffectOptions();
757 static const KeyframeEffectOptions
& KeyframeEffectOptionsFromUnion(
758 const UnrestrictedDoubleOrKeyframeAnimationOptions
& aOptions
) {
759 MOZ_ASSERT(aOptions
.IsKeyframeAnimationOptions());
760 return aOptions
.GetAsKeyframeAnimationOptions();
763 template <class OptionsType
>
764 static KeyframeEffectParams
KeyframeEffectParamsFromUnion(
765 const OptionsType
& aOptions
, CallerType aCallerType
, ErrorResult
& aRv
) {
766 KeyframeEffectParams result
;
767 if (aOptions
.IsUnrestrictedDouble()) {
771 const KeyframeEffectOptions
& options
=
772 KeyframeEffectOptionsFromUnion(aOptions
);
774 // If dom.animations-api.compositing.enabled is turned off,
775 // iterationComposite and composite are the default value 'replace' in the
777 result
.mIterationComposite
= options
.mIterationComposite
;
778 result
.mComposite
= options
.mComposite
;
780 result
.mPseudoType
= PseudoStyleType::NotPseudo
;
781 if (DOMStringIsNull(options
.mPseudoElement
)) {
785 Maybe
<PseudoStyleType
> pseudoType
=
786 nsCSSPseudoElements::GetPseudoType(options
.mPseudoElement
);
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()));
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()));
806 template <class OptionsType
>
808 already_AddRefed
<KeyframeEffect
> KeyframeEffect::ConstructKeyframeEffect(
809 const GlobalObject
& aGlobal
, Element
* aTarget
,
810 JS::Handle
<JSObject
*> aKeyframes
, const OptionsType
& aOptions
,
812 // We should get the document from `aGlobal` instead of the current Realm
813 // to make this works in Xray case.
815 // In all non-Xray cases, `aGlobal` matches the current Realm, so this
816 // matches the spec behavior.
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());
823 aRv
.Throw(NS_ERROR_FAILURE
);
827 KeyframeEffectParams effectOptions
=
828 KeyframeEffectParamsFromUnion(aOptions
, aGlobal
.CallerType(), aRv
);
829 // An invalid Pseudo-element aborts all further steps.
834 TimingParams timingParams
= TimingParams::FromOptionsUnion(aOptions
, aRv
);
839 RefPtr
<KeyframeEffect
> effect
= new KeyframeEffect(
840 doc
, OwningAnimationTarget(aTarget
, effectOptions
.mPseudoType
),
841 std::move(timingParams
), effectOptions
);
843 effect
->SetKeyframes(aGlobal
.Context(), aKeyframes
, aRv
);
848 return effect
.forget();
851 nsTArray
<AnimationProperty
> KeyframeEffect::BuildProperties(
852 const ComputedStyle
* aStyle
) {
855 nsTArray
<AnimationProperty
> result
;
856 // If mTarget is false (i.e. mTarget.mElement is null), return an empty
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());
870 result
= KeyframeUtils::GetAnimationPropertiesFromKeyframes(
871 keyframesCopy
, mTarget
.mElement
, mTarget
.mPseudoType
, aStyle
,
872 mEffectOptions
.mComposite
);
875 MOZ_ASSERT(SpecifiedKeyframeArraysAreEqual(mKeyframes
, keyframesCopy
),
876 "Apart from the computed offset members, the keyframes array"
877 " should not be modified");
880 mKeyframes
= std::move(keyframesCopy
);
884 template <typename FrameEnumFunc
>
885 static void EnumerateContinuationsOrIBSplitSiblings(nsIFrame
* aFrame
,
886 FrameEnumFunc
&& aFunc
) {
889 aFrame
= nsLayoutUtils::GetNextContinuationOrIBSplitSibling(aFrame
);
893 void KeyframeEffect::UpdateTarget(Element
* aElement
,
894 PseudoStyleType aPseudoType
) {
895 OwningAnimationTarget
newTarget(aElement
, aPseudoType
);
897 if (mTarget
== newTarget
) {
898 // Assign the same target, skip it.
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();
910 RequestRestyle(EffectCompositor::RestyleType::Layer
);
912 nsAutoAnimationMutationBatch
mb(mTarget
.mElement
->OwnerDoc());
914 MutationObservers::NotifyAnimationRemoved(mAnimation
);
921 UpdateTargetRegistration();
922 RefPtr
<ComputedStyle
> computedStyle
= GetTargetComputedStyle(Flush::None
);
924 UpdateProperties(computedStyle
);
927 RequestRestyle(EffectCompositor::RestyleType::Layer
);
929 nsAutoAnimationMutationBatch
mb(mTarget
.mElement
->OwnerDoc());
931 MutationObservers::NotifyAnimationAdded(mAnimation
);
932 mAnimation
->ReschedulePendingTasks();
937 mAnimation
->NotifyEffectTargetUpdated();
941 void KeyframeEffect::UpdateTargetRegistration() {
946 bool isRelevant
= mAnimation
&& mAnimation
->IsRelevant();
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");
957 if (isRelevant
&& !mInEffectSet
) {
958 EffectSet
* effectSet
=
959 EffectSet::GetOrCreateEffectSet(mTarget
.mElement
, mTarget
.mPseudoType
);
960 effectSet
->AddEffect(*this);
962 UpdateEffectSet(effectSet
);
963 nsIFrame
* frame
= GetPrimaryFrame();
964 EnumerateContinuationsOrIBSplitSiblings(
965 frame
, [](nsIFrame
* aFrame
) { aFrame
->MarkNeedsDisplayItemRebuild(); });
966 } else if (!isRelevant
&& mInEffectSet
) {
971 void KeyframeEffect::UnregisterTarget() {
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;
983 effectSet
->RemoveEffect(*this);
985 if (effectSet
->IsEmpty()) {
986 EffectSet::DestroyEffectSet(mTarget
.mElement
, mTarget
.mPseudoType
);
989 nsIFrame
* frame
= GetPrimaryFrame();
990 EnumerateContinuationsOrIBSplitSiblings(
991 frame
, [](nsIFrame
* aFrame
) { aFrame
->MarkNeedsDisplayItemRebuild(); });
994 void KeyframeEffect::RequestRestyle(
995 EffectCompositor::RestyleType aRestyleType
) {
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());
1008 already_AddRefed
<ComputedStyle
> KeyframeEffect::GetTargetComputedStyle(
1009 Flush aFlushType
) const {
1010 if (!GetRenderedDocument()) {
1015 "Should only have a document when we have a target element");
1017 OwningAnimationTarget
kungfuDeathGrip(mTarget
.mElement
, mTarget
.mPseudoType
);
1019 return aFlushType
== Flush::Style
1020 ? nsComputedDOMStyle::GetComputedStyle(mTarget
.mElement
,
1021 mTarget
.mPseudoType
)
1022 : nsComputedDOMStyle::GetComputedStyleNoFlush(mTarget
.mElement
,
1023 mTarget
.mPseudoType
);
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(),
1044 already_AddRefed
<KeyframeEffect
> KeyframeEffect::Constructor(
1045 const GlobalObject
& aGlobal
, Element
* aTarget
,
1046 JS::Handle
<JSObject
*> aKeyframes
,
1047 const UnrestrictedDoubleOrKeyframeEffectOptions
& aOptions
,
1049 return ConstructKeyframeEffect(aGlobal
, aTarget
, aKeyframes
, aOptions
, aRv
);
1053 already_AddRefed
<KeyframeEffect
> KeyframeEffect::Constructor(
1054 const GlobalObject
& aGlobal
, Element
* aTarget
,
1055 JS::Handle
<JSObject
*> aKeyframes
,
1056 const UnrestrictedDoubleOrKeyframeAnimationOptions
& aOptions
,
1058 return ConstructKeyframeEffect(aGlobal
, aTarget
, aKeyframes
, aOptions
, aRv
);
1062 already_AddRefed
<KeyframeEffect
> KeyframeEffect::Constructor(
1063 const GlobalObject
& aGlobal
, KeyframeEffect
& aSource
, ErrorResult
& aRv
) {
1064 Document
* doc
= AnimationUtils::GetCurrentRealmDocument(aGlobal
.Context());
1066 aRv
.Throw(NS_ERROR_FAILURE
);
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
;
1082 return effect
.forget();
1085 void KeyframeEffect::SetPseudoElement(const nsAString
& aPseudoElement
,
1087 if (DOMStringIsNull(aPseudoElement
)) {
1088 UpdateTarget(mTarget
.mElement
, PseudoStyleType::NotPseudo
);
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()));
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()));
1112 UpdateTarget(mTarget
.mElement
, *pseudoType
);
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
;
1122 if (!aValue
.IsNull()) {
1123 nsAutoCString stringValue
;
1124 aValue
.SerializeSpecifiedValue(aProperty
, aRawSet
, stringValue
);
1125 aResult
.mValue
.Construct(stringValue
);
1128 if (aTimingFunction
) {
1129 aResult
.mEasing
.Construct();
1130 aTimingFunction
->AppendToString(aResult
.mEasing
.Value());
1132 aResult
.mEasing
.Construct("linear"_ns
);
1135 aResult
.mComposite
= aComposite
;
1138 void KeyframeEffect::GetProperties(
1139 nsTArray
<AnimationPropertyDetails
>& aProperties
, ErrorResult
& aRv
) const {
1140 const RawServoStyleSet
* rawSet
=
1141 mDocument
->StyleSetForPresShellOrMediaQueryEvaluation()->RawSet();
1143 for (const AnimationProperty
& property
: mProperties
) {
1144 AnimationPropertyDetails propertyDetails
;
1145 propertyDetails
.mProperty
=
1146 NS_ConvertASCIItoUTF16(nsCSSProps::GetStringValue(property
.mProperty
));
1147 propertyDetails
.mRunningOnCompositor
= property
.mIsRunningOnCompositor
;
1149 nsAutoString localizedString
;
1150 if (property
.mPerformanceWarning
&&
1151 property
.mPerformanceWarning
->ToLocalizedString(localizedString
)) {
1152 propertyDetails
.mWarning
.Construct(localizedString
);
1155 if (!propertyDetails
.mValues
.SetCapacity(property
.mSegments
.Length(),
1156 mozilla::fallible
)) {
1157 aRv
.Throw(NS_ERROR_OUT_OF_MEMORY
);
1161 for (size_t segmentIdx
= 0, segmentLen
= property
.mSegments
.Length();
1162 segmentIdx
< segmentLen
; segmentIdx
++) {
1163 const AnimationPropertySegment
& segment
= property
.mSegments
[segmentIdx
];
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();
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
);
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
,
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
);
1206 aProperties
.AppendElement(propertyDetails
);
1210 void KeyframeEffect::GetKeyframes(JSContext
* aCx
, nsTArray
<JSObject
*>& aResult
,
1211 ErrorResult
& aRv
) const {
1212 MOZ_ASSERT(aResult
.IsEmpty());
1213 MOZ_ASSERT(!aRv
.Failed());
1215 if (!aResult
.SetCapacity(mKeyframes
.Length(), mozilla::fallible
)) {
1216 aRv
.Throw(NS_ERROR_OUT_OF_MEMORY
);
1220 bool isCSSAnimation
= mAnimation
&& mAnimation
->AsCSSAnimation();
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.
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().
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
);
1246 const RawServoStyleSet
* rawSet
=
1247 mDocument
->StyleSetForPresShellOrMediaQueryEvaluation()->RawSet();
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());
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".
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
;
1267 JS::Rooted
<JS::Value
> keyframeJSValue(aCx
);
1268 if (!ToJSValue(aCx
, keyframeDict
, &keyframeJSValue
)) {
1269 aRv
.Throw(NS_ERROR_FAILURE
);
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
;
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
) {
1296 if (propertyValue
.mServoDeclarationBlock
) {
1297 Servo_DeclarationBlock_SerializeOneValue(
1298 propertyValue
.mServoDeclarationBlock
, propertyValue
.mProperty
,
1299 &stringValue
, computedStyle
, customProperties
, rawSet
);
1301 RawServoAnimationValue
* value
=
1302 mBaseValues
.GetWeak(propertyValue
.mProperty
);
1305 Servo_AnimationValue_Serialize(value
, propertyValue
.mProperty
, rawSet
,
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
:
1322 case nsCSSPropertyID::eCSSProperty_float
:
1323 // FIXME: Bug 1582314: Should handle cssFloat manually if we remove it
1324 // from nsCSSProps::PropertyIDLName().
1326 name
= nsCSSProps::PropertyIDLName(propertyValue
.mProperty
);
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
);
1338 aResult
.AppendElement(keyframeObject
);
1342 /* static */ const TimeDuration
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);
1349 return kOverflowRegionRefreshInterval
;
1352 static bool IsDefinitivelyInvisibleDueToOpacity(const nsIFrame
& aFrame
) {
1353 if (!aFrame
.Style()->IsInOpacityZeroSubtree()) {
1357 // Find the root of the opacity: 0 subtree.
1358 const nsIFrame
* root
= &aFrame
;
1360 auto* parent
= root
->GetInFlowParent();
1361 if (!parent
|| !parent
->Style()->IsInOpacityZeroSubtree()) {
1367 MOZ_ASSERT(root
&& root
->Style()->IsInOpacityZeroSubtree());
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
) {
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();
1381 static bool CanOptimizeAwayDueToOpacity(const KeyframeEffect
& aEffect
,
1382 const nsIFrame
& aFrame
) {
1383 if (!aFrame
.Style()->IsInOpacityZeroSubtree()) {
1386 if (IsDefinitivelyInvisibleDueToOpacity(aFrame
)) {
1389 return !aEffect
.HasOpacityChange() && !aFrame
.HasAnimationOfOpacity();
1392 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()) {
1400 PresShell
* presShell
= GetPresShell();
1401 if (presShell
&& !presShell
->IsActive()) {
1405 const bool isVisibilityHidden
=
1406 !aFrame
.IsVisibleOrMayHaveVisibleDescendants();
1407 const bool canOptimizeAwayVisibility
=
1408 isVisibilityHidden
&& !HasVisibilityChange();
1410 const bool invisible
= canOptimizeAwayVisibility
||
1411 CanOptimizeAwayDueToOpacity(*this, aFrame
) ||
1412 aFrame
.IsScrolledOutOfView();
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()) {
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()) {
1430 return isVisibilityHidden
? CanThrottleOverflowChangesInScrollable(aFrame
)
1431 : CanThrottleOverflowChanges(aFrame
);
1434 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()) {
1446 nsIFrame
* const frame
= GetStyleFrame();
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
1452 // In either case we can throttle the animation because there is no
1453 // need to update on the main thread.
1457 // Do not throttle any animations during print preview.
1458 if (frame
->PresContext()->IsPrintingOrPrintPreview()) {
1462 if (CanThrottleIfNotVisible(*frame
)) {
1466 EffectSet
* effectSet
= nullptr;
1467 for (const AnimationProperty
& property
: mProperties
) {
1468 if (!property
.mIsRunningOnCompositor
) {
1472 MOZ_ASSERT(nsCSSPropertyIDSet::CompositorAnimatables().HasProperty(
1473 property
.mProperty
),
1474 "The property should be able to run on the compositor");
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");
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");
1486 DisplayItemType displayItemType
=
1487 LayerAnimationInfo::GetDisplayItemTypeForProperty(property
.mProperty
);
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
) {
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
)) {
1509 bool KeyframeEffect::CanThrottleOverflowChanges(const nsIFrame
& aFrame
) const {
1510 TimeStamp now
= aFrame
.PresContext()->RefreshDriver()->MostRecentRefresh();
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());
1526 bool KeyframeEffect::CanThrottleOverflowChangesInScrollable(
1527 nsIFrame
& aFrame
) const {
1528 // If the target element is not associated with any documents, we don't care
1530 Document
* doc
= GetRenderedDocument();
1535 // If we know that the animation cannot cause overflow,
1536 // we can just disable flushes for this animation.
1538 // If we have no intersection observers, we don't care about overflow.
1539 if (!doc
->HasIntersectionObservers()) {
1543 if (CanThrottleOverflowChanges(aFrame
)) {
1547 // If the nearest scrollable ancestor has overflow:hidden,
1548 // we don't care about overflow.
1549 nsIScrollableFrame
* scrollable
=
1550 nsLayoutUtils::GetNearestScrollableFrame(&aFrame
);
1555 ScrollStyles ss
= scrollable
->GetScrollStyles();
1556 if (ss
.mVertical
== StyleOverflow::Hidden
&&
1557 ss
.mHorizontal
== StyleOverflow::Hidden
&&
1558 scrollable
->GetLogicalScrollPosition() == nsPoint(0, 0)) {
1565 nsIFrame
* KeyframeEffect::GetStyleFrame() const {
1566 nsIFrame
* frame
= GetPrimaryFrame();
1571 return nsLayoutUtils::GetStyleFrame(frame
);
1574 nsIFrame
* KeyframeEffect::GetPrimaryFrame() const {
1575 nsIFrame
* frame
= nullptr;
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
);
1587 frame
= mTarget
.mElement
->GetPrimaryFrame();
1588 MOZ_ASSERT(mTarget
.mPseudoType
== PseudoStyleType::NotPseudo
,
1589 "unknown mTarget.mPseudoType");
1595 Document
* KeyframeEffect::GetRenderedDocument() const {
1599 return mTarget
.mElement
->GetComposedDoc();
1602 PresShell
* KeyframeEffect::GetPresShell() const {
1603 Document
* doc
= GetRenderedDocument();
1607 return doc
->GetPresShell();
1611 bool KeyframeEffect::IsGeometricProperty(const nsCSSPropertyID aProperty
) {
1612 MOZ_ASSERT(!nsCSSProps::IsShorthand(aProperty
),
1613 "Property should be a longhand property");
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
:
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
1645 const nsIFrame
* primaryFrame
=
1646 nsLayoutUtils::GetPrimaryFrameFromStyleFrame(aFrame
);
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
;
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
;
1667 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.
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
;
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
=
1695 dom_animations_mainthread_synchronization_with_geometric_animations() &&
1696 !mAnimation
->UsingScrollTimeline();
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.
1707 effectSet
->PropertiesWithImportantRules().HasProperty(
1708 property
.mProperty
) &&
1709 effectSet
->PropertiesForAnimationsLevel().HasProperty(
1710 property
.mProperty
)) {
1713 // Check for geometric properties
1714 if (enableMainthreadSynchronizationWithGeometricAnimations
&&
1715 IsGeometricProperty(property
.mProperty
)) {
1716 aPerformanceWarning
=
1717 AnimationPerformanceWarning::Type::TransformWithGeometricProperties
;
1721 // Check for unsupported transform animations
1722 if (LayerAnimationInfo::GetCSSPropertiesFor(DisplayItemType::TYPE_TRANSFORM
)
1723 .HasProperty(property
.mProperty
)) {
1724 if (!CanAnimateTransformOnCompositor(aFrame
, aPerformanceWarning
)) {
1733 bool KeyframeEffect::HasGeometricProperties() const {
1734 for (const AnimationProperty
& property
: mProperties
) {
1735 if (IsGeometricProperty(property
.mProperty
)) {
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
)) {
1751 property
.SetPerformanceWarning(aWarning
, mTarget
.mElement
);
1752 curr
.RemoveProperty(property
.mProperty
);
1753 if (curr
.IsEmpty()) {
1759 already_AddRefed
<ComputedStyle
>
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");
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
1771 if (!elementForResolve
) {
1775 ServoStyleSet
* styleSet
= aPresContext
->StyleSet();
1776 return styleSet
->ResolveServoStyleByAddingAnimation(
1777 elementForResolve
, aBaseComputedStyle
, aValue
.mServo
);
1780 void KeyframeEffect::CalculateCumulativeChangeHint(
1781 const ComputedStyle
* aComputedStyle
) {
1782 mCumulativeChangeHint
= nsChangeHint(0);
1783 mNeedsStyleData
= false;
1785 nsPresContext
* presContext
=
1786 mTarget
? nsContentUtils::GetContextForContent(mTarget
.mElement
)
1789 // Change hints make no sense if we're not rendered.
1791 // Actually, we cannot even post them anywhere.
1792 mNeedsStyleData
= true;
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
) {
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
;
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
1823 mCumulativeChangeHint
|=
1824 nsChangeHint_ComprehensiveAddOrRemoveTransform
|
1825 nsChangeHint_UpdatePostTransformOverflow
|
1826 nsChangeHint_UpdateTransformLayer
;
1830 RefPtr
<ComputedStyle
> fromContext
= CreateComputedStyleForAnimationValue(
1831 property
.mProperty
, segment
.mFromValue
, presContext
, aComputedStyle
);
1833 mCumulativeChangeHint
= ~nsChangeHint_Hints_CanIgnoreIfNotVisible
;
1834 mNeedsStyleData
= true;
1838 RefPtr
<ComputedStyle
> toContext
= CreateComputedStyleForAnimationValue(
1839 property
.mProperty
, segment
.mToValue
, presContext
, aComputedStyle
);
1841 mCumulativeChangeHint
= ~nsChangeHint_Hints_CanIgnoreIfNotVisible
;
1842 mNeedsStyleData
= true;
1846 uint32_t equalStructs
= 0;
1847 nsChangeHint changeHint
=
1848 fromContext
->CalcStyleDifference(*toContext
, &equalStructs
);
1850 mCumulativeChangeHint
|= changeHint
;
1855 void KeyframeEffect::SetAnimation(Animation
* aAnimation
) {
1856 if (mAnimation
== aAnimation
) {
1860 // Restyle for the old animation.
1861 RequestRestyle(EffectCompositor::RestyleType::Layer
);
1863 mAnimation
= aAnimation
;
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.
1870 mAnimation
->UpdateRelevance();
1872 NotifyAnimationTimingUpdated(PostRestyleMode::IfNeeded
);
1874 MarkCascadeNeedsUpdate();
1878 bool KeyframeEffect::CanIgnoreIfNotVisible() const {
1879 if (!StaticPrefs::dom_animations_offscreen_throttling()) {
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
);
1889 void KeyframeEffect::MarkCascadeNeedsUpdate() {
1894 EffectSet
* effectSet
=
1895 EffectSet::GetEffectSet(mTarget
.mElement
, mTarget
.mPseudoType
);
1899 effectSet
->MarkCascadeNeedsUpdate();
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
);
1917 bool KeyframeEffect::HasComputedTimingChanged() const {
1918 ComputedTiming computedTiming
= GetComputedTiming();
1919 return HasComputedTimingChanged(
1920 computedTiming
, mEffectOptions
.mIterationComposite
,
1921 mProgressOnLastCompose
, mCurrentIterationOnLastCompose
);
1924 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");
1937 mAnimation
->ReplaceState() == AnimationReplaceState::Removed
) {
1941 for (const AnimationProperty
& prop
: mProperties
) {
1942 if (prop
.mProperty
!= eCSSProperty_transform
&&
1943 prop
.mProperty
!= eCSSProperty_scale
&&
1944 prop
.mProperty
!= eCSSProperty_rotate
) {
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
)) {
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
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
)) {
1966 if (!segment
.mToValue
.IsNull()) {
1967 gfx::Size to
= segment
.mToValue
.GetScaleValue(aFrame
);
1968 if (to
!= gfx::Size(1.0f
, 1.0f
)) {
1978 void KeyframeEffect::UpdateEffectSet(EffectSet
* aEffectSet
) const {
1979 if (!mInEffectSet
) {
1983 EffectSet
* effectSet
=
1986 : EffectSet::GetEffectSet(mTarget
.mElement
, mTarget
.mPseudoType
);
1991 nsIFrame
* styleFrame
= GetStyleFrame();
1992 if (HasAnimationOfPropertySet(nsCSSPropertyIDSet::OpacityProperties())) {
1993 effectSet
->SetMayHaveOpacityAnimation();
1994 EnumerateContinuationsOrIBSplitSiblings(styleFrame
, [](nsIFrame
* aFrame
) {
1995 aFrame
->SetMayHaveOpacityAnimation();
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.
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();
2014 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
);
2020 if (!mAnimation
->IsRelevant()) {
2021 return KeyframeEffect::MatchForCompositor::No
;
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
;
2032 if (mAnimation
->UsingScrollTimeline()) {
2033 const ScrollTimeline
* scrollTimeline
=
2034 mAnimation
->GetTimeline()->AsScrollTimeline();
2035 // We don't send this animation to the compositor if
2036 // 1. the APZ is disabled entirely or for the source, or
2037 // 2. the associated scroll-timeline is inactive, or
2038 // 3. the scrolling direction is not available (i.e. no scroll range).
2039 // 4. the scroll style of the scroller is overflow:hidden.
2040 if (!scrollTimeline
->APZIsActiveForSource() ||
2041 !scrollTimeline
->IsActive() ||
2042 !scrollTimeline
->ScrollingDirectionIsAvailable() ||
2043 scrollTimeline
->SourceScrollStyle() == StyleOverflow::Hidden
) {
2044 return KeyframeEffect::MatchForCompositor::No
;
2048 if (!HasEffectiveAnimationOfPropertySet(aPropertySet
, aEffects
)) {
2049 return KeyframeEffect::MatchForCompositor::No
;
2052 // If we know that the animation is not visible, we don't need to send the
2053 // animation to the compositor.
2054 if (!aFrame
->IsVisibleOrMayHaveVisibleDescendants() ||
2055 IsDefinitivelyInvisibleDueToOpacity(*aFrame
) ||
2056 aFrame
->IsScrolledOutOfView()) {
2057 return KeyframeEffect::MatchForCompositor::NoAndBlockThisProperty
;
2060 if (aPropertySet
.HasProperty(eCSSProperty_background_color
)) {
2061 if (!StaticPrefs::gfx_omta_background_color()) {
2062 return KeyframeEffect::MatchForCompositor::No
;
2065 // We don't yet support off-main-thread background-color animations on
2066 // canvas frame or on <html> or <body> which genarate
2067 // nsDisplayCanvasBackgroundColor or nsDisplaySolidColor display item.
2068 if (nsCSSRendering::IsCanvasFrame(aFrame
) ||
2069 (aFrame
->GetContent() &&
2070 (aFrame
->GetContent()->IsHTMLElement(nsGkAtoms::body
) ||
2071 aFrame
->GetContent()->IsHTMLElement(nsGkAtoms::html
)))) {
2072 return KeyframeEffect::MatchForCompositor::No
;
2076 // We can't run this background color animation on the compositor if there
2077 // is any `current-color` keyframe.
2078 if (mHasCurrentColor
) {
2079 aPerformanceWarning
= AnimationPerformanceWarning::Type::HasCurrentColor
;
2080 return KeyframeEffect::MatchForCompositor::NoAndBlockThisProperty
;
2083 return mAnimation
->IsPlaying() ? KeyframeEffect::MatchForCompositor::Yes
2084 : KeyframeEffect::MatchForCompositor::IfNeeded
;
2088 } // namespace mozilla