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