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