Coverage Report

Created: 2018-09-25 14:53

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