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