Coverage Report

Created: 2018-09-25 14:53

/src/mozilla-central/dom/animation/AnimationEffect.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/AnimationEffect.h"
8
#include "mozilla/dom/AnimationEffectBinding.h"
9
10
#include "mozilla/dom/Animation.h"
11
#include "mozilla/dom/KeyframeEffect.h"
12
#include "mozilla/AnimationUtils.h"
13
#include "mozilla/FloatingPoint.h"
14
15
namespace mozilla {
16
namespace dom {
17
18
NS_IMPL_CYCLE_COLLECTION_CLASS(AnimationEffect)
19
0
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(AnimationEffect)
20
0
  NS_IMPL_CYCLE_COLLECTION_UNLINK(mDocument, mAnimation)
21
0
  NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
22
0
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
23
24
0
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(AnimationEffect)
25
0
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDocument, mAnimation)
26
0
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
27
28
NS_IMPL_CYCLE_COLLECTION_TRACE_WRAPPERCACHE(AnimationEffect)
29
30
NS_IMPL_CYCLE_COLLECTING_ADDREF(AnimationEffect)
31
NS_IMPL_CYCLE_COLLECTING_RELEASE(AnimationEffect)
32
33
0
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(AnimationEffect)
34
0
  NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
35
0
  NS_INTERFACE_MAP_ENTRY(nsISupports)
36
0
NS_INTERFACE_MAP_END
37
38
AnimationEffect::AnimationEffect(nsIDocument* aDocument,
39
                                 const TimingParams& aTiming)
40
  : mDocument(aDocument)
41
  , mTiming(aTiming)
42
0
{
43
0
}
44
45
0
AnimationEffect::~AnimationEffect() = default;
46
47
// https://drafts.csswg.org/web-animations/#current
48
bool
49
AnimationEffect::IsCurrent() const
50
0
{
51
0
  if (!mAnimation || mAnimation->PlayState() == AnimationPlayState::Finished) {
52
0
    return false;
53
0
  }
54
0
55
0
  ComputedTiming computedTiming = GetComputedTiming();
56
0
  return computedTiming.mPhase == ComputedTiming::AnimationPhase::Before ||
57
0
         computedTiming.mPhase == ComputedTiming::AnimationPhase::Active;
58
0
}
59
60
// https://drafts.csswg.org/web-animations/#in-effect
61
bool
62
AnimationEffect::IsInEffect() const
63
0
{
64
0
  ComputedTiming computedTiming = GetComputedTiming();
65
0
  return !computedTiming.mProgress.IsNull();
66
0
}
67
68
void
69
AnimationEffect::SetSpecifiedTiming(const TimingParams& aTiming)
70
0
{
71
0
  if (mTiming == aTiming) {
72
0
    return;
73
0
  }
74
0
75
0
  mTiming = aTiming;
76
0
77
0
  if (mAnimation) {
78
0
    Maybe<nsAutoAnimationMutationBatch> mb;
79
0
    if (AsKeyframeEffect() && AsKeyframeEffect()->GetTarget()) {
80
0
      mb.emplace(AsKeyframeEffect()->GetTarget()->mElement->OwnerDoc());
81
0
    }
82
0
83
0
    mAnimation->NotifyEffectTimingUpdated();
84
0
85
0
    if (mAnimation->IsRelevant()) {
86
0
      nsNodeUtils::AnimationChanged(mAnimation);
87
0
    }
88
0
89
0
    if (AsKeyframeEffect()) {
90
0
      AsKeyframeEffect()->RequestRestyle(EffectCompositor::RestyleType::Layer);
91
0
    }
92
0
  }
93
0
  // For keyframe effects, NotifyEffectTimingUpdated above will eventually cause
94
0
  // KeyframeEffect::NotifyAnimationTimingUpdated to be called so it can
95
0
  // update its registration with the target element as necessary.
96
0
}
97
98
ComputedTiming
99
AnimationEffect::GetComputedTimingAt(
100
    const Nullable<TimeDuration>& aLocalTime,
101
    const TimingParams& aTiming,
102
    double aPlaybackRate)
103
0
{
104
0
  static const StickyTimeDuration zeroDuration;
105
0
106
0
  // Always return the same object to benefit from return-value optimization.
107
0
  ComputedTiming result;
108
0
109
0
  if (aTiming.Duration()) {
110
0
    MOZ_ASSERT(aTiming.Duration().ref() >= zeroDuration,
111
0
               "Iteration duration should be positive");
112
0
    result.mDuration = aTiming.Duration().ref();
113
0
  }
114
0
115
0
  MOZ_ASSERT(aTiming.Iterations() >= 0.0 && !IsNaN(aTiming.Iterations()),
116
0
             "mIterations should be nonnegative & finite, as ensured by "
117
0
             "ValidateIterations or CSSParser");
118
0
  result.mIterations = aTiming.Iterations();
119
0
120
0
  MOZ_ASSERT(aTiming.IterationStart() >= 0.0,
121
0
             "mIterationStart should be nonnegative, as ensured by "
122
0
             "ValidateIterationStart");
123
0
  result.mIterationStart = aTiming.IterationStart();
124
0
125
0
  result.mActiveDuration = aTiming.ActiveDuration();
126
0
  result.mEndTime = aTiming.EndTime();
127
0
  result.mFill = aTiming.Fill() == dom::FillMode::Auto ?
128
0
                 dom::FillMode::None :
129
0
                 aTiming.Fill();
130
0
131
0
  // The default constructor for ComputedTiming sets all other members to
132
0
  // values consistent with an animation that has not been sampled.
133
0
  if (aLocalTime.IsNull()) {
134
0
    return result;
135
0
  }
136
0
  const TimeDuration& localTime = aLocalTime.Value();
137
0
138
0
  StickyTimeDuration beforeActiveBoundary =
139
0
    std::max(std::min(StickyTimeDuration(aTiming.Delay()), result.mEndTime),
140
0
             zeroDuration);
141
0
142
0
  StickyTimeDuration activeAfterBoundary =
143
0
    std::max(std::min(StickyTimeDuration(aTiming.Delay() +
144
0
                                         result.mActiveDuration),
145
0
                      result.mEndTime),
146
0
             zeroDuration);
147
0
148
0
  if (localTime > activeAfterBoundary ||
149
0
      (aPlaybackRate >= 0 && localTime == activeAfterBoundary)) {
150
0
    result.mPhase = ComputedTiming::AnimationPhase::After;
151
0
    if (!result.FillsForwards()) {
152
0
      // The animation isn't active or filling at this time.
153
0
      return result;
154
0
    }
155
0
    result.mActiveTime =
156
0
      std::max(std::min(StickyTimeDuration(localTime - aTiming.Delay()),
157
0
                        result.mActiveDuration),
158
0
               zeroDuration);
159
0
  } else if (localTime < beforeActiveBoundary ||
160
0
             (aPlaybackRate < 0 && localTime == beforeActiveBoundary)) {
161
0
    result.mPhase = ComputedTiming::AnimationPhase::Before;
162
0
    if (!result.FillsBackwards()) {
163
0
      // The animation isn't active or filling at this time.
164
0
      return result;
165
0
    }
166
0
    result.mActiveTime
167
0
      = std::max(StickyTimeDuration(localTime - aTiming.Delay()),
168
0
                 zeroDuration);
169
0
  } else {
170
0
    MOZ_ASSERT(result.mActiveDuration,
171
0
               "How can we be in the middle of a zero-duration interval?");
172
0
    result.mPhase = ComputedTiming::AnimationPhase::Active;
173
0
    result.mActiveTime = localTime - aTiming.Delay();
174
0
  }
175
0
176
0
  // Convert active time to a multiple of iterations.
177
0
  // https://drafts.csswg.org/web-animations/#overall-progress
178
0
  double overallProgress;
179
0
  if (!result.mDuration) {
180
0
    overallProgress = result.mPhase == ComputedTiming::AnimationPhase::Before
181
0
                      ? 0.0
182
0
                      : result.mIterations;
183
0
  } else {
184
0
    overallProgress = result.mActiveTime / result.mDuration;
185
0
  }
186
0
187
0
  // Factor in iteration start offset.
188
0
  if (IsFinite(overallProgress)) {
189
0
    overallProgress += result.mIterationStart;
190
0
  }
191
0
192
0
  // Determine the 0-based index of the current iteration.
193
0
  // https://drafts.csswg.org/web-animations/#current-iteration
194
0
  result.mCurrentIteration =
195
0
    (result.mIterations >= UINT64_MAX
196
0
     && result.mPhase == ComputedTiming::AnimationPhase::After)
197
0
    || overallProgress >= UINT64_MAX
198
0
    ? UINT64_MAX // In GetComputedTimingDictionary(),
199
0
                 // we will convert this into Infinity
200
0
    : static_cast<uint64_t>(overallProgress);
201
0
202
0
  // Convert the overall progress to a fraction of a single iteration--the
203
0
  // simply iteration progress.
204
0
  // https://drafts.csswg.org/web-animations/#simple-iteration-progress
205
0
  double progress = IsFinite(overallProgress)
206
0
                    ? fmod(overallProgress, 1.0)
207
0
                    : fmod(result.mIterationStart, 1.0);
208
0
209
0
  // When we are at the end of the active interval and the end of an iteration
210
0
  // we need to report the end of the final iteration and not the start of the
211
0
  // next iteration. We *don't* want to do this, however, when we have
212
0
  // a zero-iteration animation.
213
0
  if (progress == 0.0 &&
214
0
      (result.mPhase == ComputedTiming::AnimationPhase::After ||
215
0
       result.mPhase == ComputedTiming::AnimationPhase::Active) &&
216
0
      result.mActiveTime == result.mActiveDuration &&
217
0
      result.mIterations != 0.0) {
218
0
    // The only way we can reach the end of the active interval and have
219
0
    // a progress of zero and a current iteration of zero, is if we have a zero
220
0
    // iteration count -- something we should have detected above.
221
0
    MOZ_ASSERT(result.mCurrentIteration != 0,
222
0
               "Should not have zero current iteration");
223
0
    progress = 1.0;
224
0
    if (result.mCurrentIteration != UINT64_MAX) {
225
0
      result.mCurrentIteration--;
226
0
    }
227
0
  }
228
0
229
0
  // Factor in the direction.
230
0
  bool thisIterationReverse = false;
231
0
  switch (aTiming.Direction()) {
232
0
    case PlaybackDirection::Normal:
233
0
      thisIterationReverse = false;
234
0
      break;
235
0
    case PlaybackDirection::Reverse:
236
0
      thisIterationReverse = true;
237
0
      break;
238
0
    case PlaybackDirection::Alternate:
239
0
      thisIterationReverse = (result.mCurrentIteration & 1) == 1;
240
0
      break;
241
0
    case PlaybackDirection::Alternate_reverse:
242
0
      thisIterationReverse = (result.mCurrentIteration & 1) == 0;
243
0
      break;
244
0
    default:
245
0
      MOZ_ASSERT_UNREACHABLE("Unknown PlaybackDirection type");
246
0
  }
247
0
  if (thisIterationReverse) {
248
0
    progress = 1.0 - progress;
249
0
  }
250
0
251
0
  // Calculate the 'before flag' which we use when applying step timing
252
0
  // functions.
253
0
  if ((result.mPhase == ComputedTiming::AnimationPhase::After &&
254
0
       thisIterationReverse) ||
255
0
      (result.mPhase == ComputedTiming::AnimationPhase::Before &&
256
0
       !thisIterationReverse)) {
257
0
    result.mBeforeFlag = ComputedTimingFunction::BeforeFlag::Set;
258
0
  }
259
0
260
0
  // Apply the easing.
261
0
  if (aTiming.TimingFunction()) {
262
0
    progress = aTiming.TimingFunction()->GetValue(progress, result.mBeforeFlag);
263
0
  }
264
0
265
0
  MOZ_ASSERT(IsFinite(progress), "Progress value should be finite");
266
0
  result.mProgress.SetValue(progress);
267
0
  return result;
268
0
}
269
270
ComputedTiming
271
AnimationEffect::GetComputedTiming(const TimingParams* aTiming) const
272
0
{
273
0
  double playbackRate = mAnimation ? mAnimation->PlaybackRate() : 1;
274
0
  return GetComputedTimingAt(GetLocalTime(),
275
0
                             aTiming ? *aTiming : SpecifiedTiming(),
276
0
                             playbackRate);
277
0
}
278
279
// Helper function for generating an (Computed)EffectTiming dictionary
280
static void
281
GetEffectTimingDictionary(const TimingParams& aTiming, EffectTiming& aRetVal)
282
0
{
283
0
  aRetVal.mDelay = aTiming.Delay().ToMilliseconds();
284
0
  aRetVal.mEndDelay = aTiming.EndDelay().ToMilliseconds();
285
0
  aRetVal.mFill = aTiming.Fill();
286
0
  aRetVal.mIterationStart = aTiming.IterationStart();
287
0
  aRetVal.mIterations = aTiming.Iterations();
288
0
  if (aTiming.Duration()) {
289
0
    aRetVal.mDuration.SetAsUnrestrictedDouble() =
290
0
      aTiming.Duration()->ToMilliseconds();
291
0
  }
292
0
  aRetVal.mDirection = aTiming.Direction();
293
0
  if (aTiming.TimingFunction()) {
294
0
    aRetVal.mEasing.Truncate();
295
0
    aTiming.TimingFunction()->AppendToString(aRetVal.mEasing);
296
0
  }
297
0
}
298
299
void
300
AnimationEffect::GetTiming(EffectTiming& aRetVal) const
301
0
{
302
0
  GetEffectTimingDictionary(SpecifiedTiming(), aRetVal);
303
0
}
304
305
void
306
AnimationEffect::GetComputedTimingAsDict(ComputedEffectTiming& aRetVal) const
307
0
{
308
0
  // Specified timing
309
0
  GetEffectTimingDictionary(SpecifiedTiming(), aRetVal);
310
0
311
0
  // Computed timing
312
0
  double playbackRate = mAnimation ? mAnimation->PlaybackRate() : 1;
313
0
  const Nullable<TimeDuration> currentTime = GetLocalTime();
314
0
  ComputedTiming computedTiming =
315
0
    GetComputedTimingAt(currentTime, SpecifiedTiming(), playbackRate);
316
0
317
0
  aRetVal.mDuration.SetAsUnrestrictedDouble() =
318
0
    computedTiming.mDuration.ToMilliseconds();
319
0
  aRetVal.mFill = computedTiming.mFill;
320
0
  aRetVal.mActiveDuration = computedTiming.mActiveDuration.ToMilliseconds();
321
0
  aRetVal.mEndTime = computedTiming.mEndTime.ToMilliseconds();
322
0
  aRetVal.mLocalTime = AnimationUtils::TimeDurationToDouble(currentTime);
323
0
  aRetVal.mProgress = computedTiming.mProgress;
324
0
325
0
  if (!aRetVal.mProgress.IsNull()) {
326
0
    // Convert the returned currentIteration into Infinity if we set
327
0
    // (uint64_t) computedTiming.mCurrentIteration to UINT64_MAX
328
0
    double iteration = computedTiming.mCurrentIteration == UINT64_MAX
329
0
                       ? PositiveInfinity<double>()
330
0
                       : static_cast<double>(computedTiming.mCurrentIteration);
331
0
    aRetVal.mCurrentIteration.SetValue(iteration);
332
0
  }
333
0
}
334
335
void
336
AnimationEffect::UpdateTiming(const OptionalEffectTiming& aTiming,
337
                              ErrorResult& aRv)
338
0
{
339
0
  TimingParams timing =
340
0
    TimingParams::MergeOptionalEffectTiming(mTiming, aTiming, mDocument, aRv);
341
0
  if (aRv.Failed()) {
342
0
    return;
343
0
  }
344
0
345
0
  SetSpecifiedTiming(timing);
346
0
}
347
348
Nullable<TimeDuration>
349
AnimationEffect::GetLocalTime() const
350
0
{
351
0
  // Since the *animation* start time is currently always zero, the local
352
0
  // time is equal to the parent time.
353
0
  Nullable<TimeDuration> result;
354
0
  if (mAnimation) {
355
0
    result = mAnimation->GetCurrentTime();
356
0
  }
357
0
  return result;
358
0
}
359
360
} // namespace dom
361
} // namespace mozilla