Bug 1769952 - Fix running raptor on a Win10-64 VM r=sparky
[gecko.git] / dom / animation / KeyframeEffect.cpp
blob2c2b63f29246b5ed4dd3a1f82c1cf4f24b957472
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
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"
44 namespace mozilla {
46 void AnimationProperty::SetPerformanceWarning(
47 const AnimationPerformanceWarning& aWarning, const dom::Element* aElement) {
48 if (mPerformanceWarning && *mPerformanceWarning == aWarning) {
49 return;
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) {
64 return false;
66 if (mServoDeclarationBlock == aOther.mServoDeclarationBlock) {
67 return true;
69 if (!mServoDeclarationBlock || !aOther.mServoDeclarationBlock) {
70 return false;
72 return Servo_DeclarationBlock_Equals(mServoDeclarationBlock,
73 aOther.mServoDeclarationBlock);
76 namespace dom {
78 NS_IMPL_CYCLE_COLLECTION_INHERITED(KeyframeEffect, AnimationEffect,
79 mTarget.mElement)
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) {
121 return;
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) {
138 return;
141 mEffectOptions.mComposite = aComposite;
143 if (mAnimation && mAnimation->IsRelevant()) {
144 MutationObservers::NotifyAnimationChanged(mAnimation);
147 if (mTarget) {
148 RefPtr<ComputedStyle> computedStyle = GetTargetComputedStyle(Flush::None);
149 if (computedStyle) {
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()
159 : nullptr);
161 if (mAnimation) {
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
181 // status here.
182 bool isRelevant = mAnimation && mAnimation->IsRelevant();
183 if (!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).
211 if (!inEffect) {
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()) {
220 return false;
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;
231 return true;
234 // https://drafts.csswg.org/web-animations/#dom-keyframeeffect-setkeyframes
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;
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)) {
251 return;
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).
263 if (aStyle) {
264 UpdateProperties(aStyle);
268 void KeyframeEffect::ReplaceTransitionStartValue(AnimationValue&& aStartValue) {
269 if (!aStartValue.mServo) {
270 return;
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) {
278 return;
281 // Likewise, check that the keyframes are of the expected shape.
282 if (mKeyframes.Length() != 2 || mKeyframes[0].mPropertyValues.Length() != 1) {
283 return;
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;
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) {
312 continue;
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;
321 return result;
323 return nullptr;
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)) {
331 return true;
334 return false;
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()) {
345 return properties;
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)) {
358 continue;
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;
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;
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;
388 return properties;
391 nsCSSPropertyIDSet KeyframeEffect::GetPropertySet() const {
392 nsCSSPropertyIDSet result;
394 for (const AnimationProperty& property : mProperties) {
395 result.AddProperty(property.mProperty);
398 return result;
401 #ifdef DEBUG
402 bool SpecifiedKeyframeArraysAreEqual(const nsTArray<Keyframe>& aA,
403 const nsTArray<Keyframe>& aB) {
404 if (aA.Length() != aB.Length()) {
405 return false;
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;
417 return true;
419 #endif
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;
429 return false;
431 void KeyframeEffect::UpdateProperties(const ComputedStyle* aStyle) {
432 MOZ_ASSERT(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
449 // style data.
450 if (mNeedsStyleData && mTarget && mTarget.mElement->HasServoData()) {
451 CalculateCumulativeChangeHint(aStyle);
453 return;
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);
466 UpdateEffectSet();
468 mHasCurrentColor = false;
470 for (AnimationProperty& property : mProperties) {
471 property.mIsRunningOnCompositor =
472 runningOnCompositorProperties.HasProperty(property.mProperty);
474 if (property.mProperty == eCSSProperty_background_color &&
475 !mHasCurrentColor) {
476 if (HasCurrentColor(property.mSegments)) {
477 mHasCurrentColor = true;
478 break;
483 CalculateCumulativeChangeHint(aStyle);
485 MarkCascadeNeedsUpdate();
487 if (mAnimation) {
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;
501 if (!mTarget) {
502 return;
505 BaseValuesHashmap previousBaseStyles;
506 if (aBaseStylesChanged != nullptr) {
507 previousBaseStyles = std::move(mBaseValues);
510 mBaseValues.Clear();
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 &&
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;
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;
552 break;
556 if (!hasAdditiveValues) {
557 return;
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) {
566 return;
568 aBaseComputedStyle = aPresContext->StyleSet()->GetBaseContextForElement(
569 animatingElement, aComputedStyle);
571 RefPtr<RawServoAnimationValue> baseValue =
572 Servo_ComputedValues_ExtractAnimationValue(aBaseComputedStyle,
573 aProperty.mProperty)
574 .Consume();
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) {
589 auto* opaqueTable =
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()) {
603 return;
606 for (size_t propIdx = 0, propEnd = mProperties.Length(); propIdx != propEnd;
607 ++propIdx) {
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)) {
615 continue;
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(),
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;
630 ++segment;
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
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);
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) {
665 return true;
668 return false;
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();
688 return;
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();
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();
730 if (!frame) {
731 return;
734 nsIWidget* widget = frame->GetNearestWidget();
735 if (!widget) {
736 return;
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()) {
768 return result;
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
776 // dictionary.
777 result.mIterationComposite = options.mIterationComposite;
778 result.mComposite = options.mComposite;
780 result.mPseudoType = PseudoStyleType::NotPseudo;
781 if (DOMStringIsNull(options.mPseudoElement)) {
782 return result;
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;
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()));
803 return result;
806 template <class OptionsType>
807 /* static */
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.
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());
822 if (!doc) {
823 aRv.Throw(NS_ERROR_FAILURE);
824 return nullptr;
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;
834 TimingParams timingParams = TimingParams::FromOptionsUnion(aOptions, aRv);
835 if (aRv.Failed()) {
836 return nullptr;
839 RefPtr<KeyframeEffect> effect = new KeyframeEffect(
840 doc, OwningAnimationTarget(aTarget, effectOptions.mPseudoType),
841 std::move(timingParams), effectOptions);
843 effect->SetKeyframes(aGlobal.Context(), aKeyframes, aRv);
844 if (aRv.Failed()) {
845 return nullptr;
848 return effect.forget();
851 nsTArray<AnimationProperty> KeyframeEffect::BuildProperties(
852 const ComputedStyle* aStyle) {
853 MOZ_ASSERT(aStyle);
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;
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);
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
880 mKeyframes = std::move(keyframesCopy);
881 return result;
884 template <typename FrameEnumFunc>
885 static void EnumerateContinuationsOrIBSplitSiblings(nsIFrame* aFrame,
886 FrameEnumFunc&& aFunc) {
887 while (aFrame) {
888 aFunc(aFrame);
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.
899 return;
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();
910 RequestRestyle(EffectCompositor::RestyleType::Layer);
912 nsAutoAnimationMutationBatch mb(mTarget.mElement->OwnerDoc());
913 if (mAnimation) {
914 MutationObservers::NotifyAnimationRemoved(mAnimation);
918 mTarget = newTarget;
920 if (mTarget) {
921 UpdateTargetRegistration();
922 RefPtr<ComputedStyle> computedStyle = GetTargetComputedStyle(Flush::None);
923 if (computedStyle) {
924 UpdateProperties(computedStyle);
927 RequestRestyle(EffectCompositor::RestyleType::Layer);
929 nsAutoAnimationMutationBatch mb(mTarget.mElement->OwnerDoc());
930 if (mAnimation) {
931 MutationObservers::NotifyAnimationAdded(mAnimation);
932 mAnimation->ReschedulePendingTasks();
936 if (mAnimation) {
937 mAnimation->NotifyEffectTargetUpdated();
941 void KeyframeEffect::UpdateTargetRegistration() {
942 if (!mTarget) {
943 return;
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);
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();
971 void KeyframeEffect::UnregisterTarget() {
972 if (!mInEffectSet) {
973 return;
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);
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) {
996 if (!mTarget) {
997 return;
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()) {
1011 return nullptr;
1014 MOZ_ASSERT(mTarget,
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);
1026 #ifdef DEBUG
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());
1041 #endif
1043 /* static */
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);
1052 /* static */
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);
1061 /* static */
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;
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,
1086 ErrorResult& aRv) {
1087 if (DOMStringIsNull(aPseudoElement)) {
1088 UpdateTarget(mTarget.mElement, PseudoStyleType::NotPseudo);
1089 return;
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;
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;
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());
1131 } else {
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);
1158 return;
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);
1180 return;
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;
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);
1217 return;
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);
1270 return;
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;
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;
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);
1304 if (value) {
1305 Servo_AnimationValue_Serialize(value, propertyValue.mProperty, rawSet,
1306 &stringValue);
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);
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;
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()) {
1354 return false;
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;
1364 root = parent;
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) {
1372 return false;
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()) {
1384 return false;
1386 if (IsDefinitivelyInvisibleDueToOpacity(aFrame)) {
1387 return true;
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()) {
1397 return false;
1400 PresShell* presShell = GetPresShell();
1401 if (presShell && !presShell->IsActive()) {
1402 return true;
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();
1413 if (!invisible) {
1414 return false;
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;
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;
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()) {
1443 return false;
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;
1457 // Do not throttle any animations during print preview.
1458 if (frame->PresContext()->IsPrintingOrPrintPreview()) {
1459 return false;
1462 if (CanThrottleIfNotVisible(*frame)) {
1463 return true;
1466 EffectSet* effectSet = nullptr;
1467 for (const AnimationProperty& property : mProperties) {
1468 if (!property.mIsRunningOnCompositor) {
1469 return false;
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");
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) {
1495 return false;
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;
1506 return true;
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
1529 // it.
1530 Document* doc = GetRenderedDocument();
1531 if (!doc) {
1532 return true;
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()) {
1540 return true;
1543 if (CanThrottleOverflowChanges(aFrame)) {
1544 return true;
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;
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;
1562 return false;
1565 nsIFrame* KeyframeEffect::GetStyleFrame() const {
1566 nsIFrame* frame = GetPrimaryFrame();
1567 if (!frame) {
1568 return nullptr;
1571 return nsLayoutUtils::GetStyleFrame(frame);
1574 nsIFrame* KeyframeEffect::GetPrimaryFrame() const {
1575 nsIFrame* frame = nullptr;
1576 if (!mTarget) {
1577 return frame;
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");
1592 return frame;
1595 Document* KeyframeEffect::GetRenderedDocument() const {
1596 if (!mTarget) {
1597 return nullptr;
1599 return mTarget.mElement->GetComposedDoc();
1602 PresShell* KeyframeEffect::GetPresShell() const {
1603 Document* doc = GetRenderedDocument();
1604 if (!doc) {
1605 return nullptr;
1607 return doc->GetPresShell();
1610 /* static */
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:
1630 return true;
1631 default:
1632 return false;
1636 /* static */
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);
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;
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;
1664 return true;
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;
1687 return true;
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();
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;
1713 // Check for geometric properties
1714 if (enableMainthreadSynchronizationWithGeometricAnimations &&
1715 IsGeometricProperty(property.mProperty)) {
1716 aPerformanceWarning =
1717 AnimationPerformanceWarning::Type::TransformWithGeometricProperties;
1718 return true;
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;
1730 return false;
1733 bool KeyframeEffect::HasGeometricProperties() const {
1734 for (const AnimationProperty& property : mProperties) {
1735 if (IsGeometricProperty(property.mProperty)) {
1736 return true;
1740 return false;
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;
1751 property.SetPerformanceWarning(aWarning, mTarget.mElement);
1752 curr.RemoveProperty(property.mProperty);
1753 if (curr.IsEmpty()) {
1754 return;
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
1770 // longer exists.
1771 if (!elementForResolve) {
1772 return nullptr;
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)
1787 : nullptr;
1788 if (!presContext) {
1789 // Change hints make no sense if we're not rendered.
1791 // Actually, we cannot even post them anywhere.
1792 mNeedsStyleData = true;
1793 return;
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;
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;
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;
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;
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;
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) {
1857 return;
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.
1869 if (mAnimation) {
1870 mAnimation->UpdateRelevance();
1872 NotifyAnimationTimingUpdated(PostRestyleMode::IfNeeded);
1873 if (mAnimation) {
1874 MarkCascadeNeedsUpdate();
1878 bool KeyframeEffect::CanIgnoreIfNotVisible() const {
1879 if (!StaticPrefs::dom_animations_offscreen_throttling()) {
1880 return false;
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() {
1890 if (!mTarget) {
1891 return;
1894 EffectSet* effectSet =
1895 EffectSet::GetEffectSet(mTarget.mElement, mTarget.mPseudoType);
1896 if (!effectSet) {
1897 return;
1899 effectSet->MarkCascadeNeedsUpdate();
1902 /* static */
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");
1932 if (!IsCurrent()) {
1933 return false;
1936 if (!mAnimation ||
1937 mAnimation->ReplaceState() == AnimationReplaceState::Removed) {
1938 return false;
1941 for (const AnimationProperty& prop : mProperties) {
1942 if (prop.mProperty != eCSSProperty_transform &&
1943 prop.mProperty != eCSSProperty_scale &&
1944 prop.mProperty != eCSSProperty_rotate) {
1945 continue;
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;
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;
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;
1975 return false;
1978 void KeyframeEffect::UpdateEffectSet(EffectSet* aEffectSet) const {
1979 if (!mInEffectSet) {
1980 return;
1983 EffectSet* effectSet =
1984 aEffectSet
1985 ? aEffectSet
1986 : EffectSet::GetEffectSet(mTarget.mElement, mTarget.mPseudoType);
1987 if (!effectSet) {
1988 return;
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;
2087 } // namespace dom
2088 } // namespace mozilla