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