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