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/. */
7 #include "mozilla/dom/AnimationEffect.h"
8 #include "mozilla/dom/AnimationEffectBinding.h"
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"
17 namespace mozilla::dom
{
19 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(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
25 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(AnimationEffect
)
26 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDocument
, mAnimation
)
27 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
29 NS_IMPL_CYCLE_COLLECTING_ADDREF(AnimationEffect
)
30 NS_IMPL_CYCLE_COLLECTING_RELEASE(AnimationEffect
)
32 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(AnimationEffect
)
33 NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
34 NS_INTERFACE_MAP_ENTRY(nsISupports
)
37 AnimationEffect::AnimationEffect(Document
* aDocument
, TimingParams
&& aTiming
)
38 : mDocument(aDocument
), mTiming(std::move(aTiming
)) {
39 mRTPCallerType
= mDocument
->GetScopeObject()->GetRTPCallerType();
42 AnimationEffect::~AnimationEffect() = default;
44 nsISupports
* AnimationEffect::GetParentObject() const {
45 return ToSupports(mDocument
);
48 // https://drafts.csswg.org/web-animations/#current
49 bool AnimationEffect::IsCurrent() const {
50 if (!mAnimation
|| mAnimation
->PlayState() == AnimationPlayState::Finished
) {
54 ComputedTiming computedTiming
= GetComputedTiming();
55 if (computedTiming
.mPhase
== ComputedTiming::AnimationPhase::Active
) {
59 return (mAnimation
->PlaybackRate() > 0 &&
60 computedTiming
.mPhase
== ComputedTiming::AnimationPhase::Before
) ||
61 (mAnimation
->PlaybackRate() < 0 &&
62 computedTiming
.mPhase
== ComputedTiming::AnimationPhase::After
);
65 // https://drafts.csswg.org/web-animations/#in-effect
66 bool AnimationEffect::IsInEffect() const {
67 ComputedTiming computedTiming
= GetComputedTiming();
68 return !computedTiming
.mProgress
.IsNull();
71 void AnimationEffect::SetSpecifiedTiming(TimingParams
&& aTiming
) {
72 if (mTiming
== aTiming
) {
78 UpdateNormalizedTiming();
81 Maybe
<nsAutoAnimationMutationBatch
> mb
;
82 if (AsKeyframeEffect() && AsKeyframeEffect()->GetAnimationTarget()) {
83 mb
.emplace(AsKeyframeEffect()->GetAnimationTarget().mElement
->OwnerDoc());
86 mAnimation
->NotifyEffectTimingUpdated();
88 if (mAnimation
->IsRelevant()) {
89 MutationObservers::NotifyAnimationChanged(mAnimation
);
92 if (AsKeyframeEffect()) {
93 AsKeyframeEffect()->RequestRestyle(EffectCompositor::RestyleType::Layer
);
97 // For keyframe effects, NotifyEffectTimingUpdated above will eventually
98 // cause KeyframeEffect::NotifyAnimationTimingUpdated to be called so it can
99 // update its registration with the target element as necessary.
102 ComputedTiming
AnimationEffect::GetComputedTimingAt(
103 const Nullable
<TimeDuration
>& aLocalTime
, const TimingParams
& aTiming
,
104 double aPlaybackRate
,
105 Animation::ProgressTimelinePosition aProgressTimelinePosition
) {
106 static const StickyTimeDuration zeroDuration
;
108 // Always return the same object to benefit from return-value optimization.
109 ComputedTiming result
;
111 if (aTiming
.Duration()) {
112 MOZ_ASSERT(aTiming
.Duration().ref() >= zeroDuration
,
113 "Iteration duration should be positive");
114 result
.mDuration
= aTiming
.Duration().ref();
117 MOZ_ASSERT(aTiming
.Iterations() >= 0.0 && !std::isnan(aTiming
.Iterations()),
118 "mIterations should be nonnegative & finite, as ensured by "
119 "ValidateIterations or CSSParser");
120 result
.mIterations
= aTiming
.Iterations();
122 MOZ_ASSERT(aTiming
.IterationStart() >= 0.0,
123 "mIterationStart should be nonnegative, as ensured by "
124 "ValidateIterationStart");
125 result
.mIterationStart
= aTiming
.IterationStart();
127 result
.mActiveDuration
= aTiming
.ActiveDuration();
128 result
.mEndTime
= aTiming
.EndTime();
129 result
.mFill
= aTiming
.Fill() == dom::FillMode::Auto
? dom::FillMode::None
132 // The default constructor for ComputedTiming sets all other members to
133 // values consistent with an animation that has not been sampled.
134 if (aLocalTime
.IsNull()) {
137 const TimeDuration
& localTime
= aLocalTime
.Value();
138 const bool atProgressTimelineBoundary
=
139 aProgressTimelinePosition
==
140 Animation::ProgressTimelinePosition::Boundary
;
142 StickyTimeDuration beforeActiveBoundary
= aTiming
.CalcBeforeActiveBoundary();
143 StickyTimeDuration activeAfterBoundary
= aTiming
.CalcActiveAfterBoundary();
145 if (localTime
> activeAfterBoundary
||
146 (aPlaybackRate
>= 0 && localTime
== activeAfterBoundary
&&
147 !atProgressTimelineBoundary
)) {
148 result
.mPhase
= ComputedTiming::AnimationPhase::After
;
149 if (!result
.FillsForwards()) {
150 // The animation isn't active or filling at this time.
154 std::max(std::min(StickyTimeDuration(localTime
- aTiming
.Delay()),
155 result
.mActiveDuration
),
157 } else if (localTime
< beforeActiveBoundary
||
158 (aPlaybackRate
< 0 && localTime
== beforeActiveBoundary
&&
159 !atProgressTimelineBoundary
)) {
160 result
.mPhase
= ComputedTiming::AnimationPhase::Before
;
161 if (!result
.FillsBackwards()) {
162 // The animation isn't active or filling at this time.
166 std::max(StickyTimeDuration(localTime
- aTiming
.Delay()), zeroDuration
);
168 // Note: For progress-based timeline, it's possible to have a zero active
169 // duration with active phase.
170 result
.mPhase
= ComputedTiming::AnimationPhase::Active
;
171 result
.mActiveTime
= localTime
- aTiming
.Delay();
174 // Convert active time to a multiple of iterations.
175 // https://drafts.csswg.org/web-animations/#overall-progress
176 double overallProgress
;
177 if (!result
.mDuration
) {
178 overallProgress
= result
.mPhase
== ComputedTiming::AnimationPhase::Before
180 : result
.mIterations
;
182 overallProgress
= result
.mActiveTime
/ result
.mDuration
;
185 // Factor in iteration start offset.
186 if (std::isfinite(overallProgress
)) {
187 overallProgress
+= result
.mIterationStart
;
190 // Determine the 0-based index of the current iteration.
191 // https://drafts.csswg.org/web-animations/#current-iteration
192 result
.mCurrentIteration
=
193 (result
.mIterations
>= double(UINT64_MAX
) &&
194 result
.mPhase
== ComputedTiming::AnimationPhase::After
) ||
195 overallProgress
>= double(UINT64_MAX
)
196 ? UINT64_MAX
// In GetComputedTimingDictionary(),
197 // we will convert this into Infinity
198 : static_cast<uint64_t>(std::max(overallProgress
, 0.0));
200 // Convert the overall progress to a fraction of a single iteration--the
201 // simply iteration progress.
202 // https://drafts.csswg.org/web-animations/#simple-iteration-progress
203 double progress
= std::isfinite(overallProgress
)
204 ? fmod(overallProgress
, 1.0)
205 : fmod(result
.mIterationStart
, 1.0);
207 // When we are at the end of the active interval and the end of an iteration
208 // we need to report the end of the final iteration and not the start of the
209 // next iteration. We *don't* want to do this, however, when we have
210 // a zero-iteration animation.
211 if (progress
== 0.0 &&
212 (result
.mPhase
== ComputedTiming::AnimationPhase::After
||
213 result
.mPhase
== ComputedTiming::AnimationPhase::Active
) &&
214 result
.mActiveTime
== result
.mActiveDuration
&&
215 result
.mIterations
!= 0.0) {
216 // The only way we can reach the end of the active interval and have
217 // a progress of zero and a current iteration of zero, is if we have a
218 // zero iteration count -- something we should have detected above.
219 MOZ_ASSERT(result
.mCurrentIteration
!= 0,
220 "Should not have zero current iteration");
222 if (result
.mCurrentIteration
!= UINT64_MAX
) {
223 result
.mCurrentIteration
--;
227 // Factor in the direction.
228 bool thisIterationReverse
= false;
229 switch (aTiming
.Direction()) {
230 case PlaybackDirection::Normal
:
231 thisIterationReverse
= false;
233 case PlaybackDirection::Reverse
:
234 thisIterationReverse
= true;
236 case PlaybackDirection::Alternate
:
237 thisIterationReverse
= (result
.mCurrentIteration
& 1) == 1;
239 case PlaybackDirection::Alternate_reverse
:
240 thisIterationReverse
= (result
.mCurrentIteration
& 1) == 0;
243 MOZ_ASSERT_UNREACHABLE("Unknown PlaybackDirection type");
245 if (thisIterationReverse
) {
246 progress
= 1.0 - progress
;
249 // Calculate the 'before flag' which we use when applying step timing
251 if ((result
.mPhase
== ComputedTiming::AnimationPhase::After
&&
252 thisIterationReverse
) ||
253 (result
.mPhase
== ComputedTiming::AnimationPhase::Before
&&
254 !thisIterationReverse
)) {
255 result
.mBeforeFlag
= true;
259 if (const auto& fn
= aTiming
.TimingFunction()) {
260 progress
= fn
->At(progress
, result
.mBeforeFlag
);
263 MOZ_ASSERT(std::isfinite(progress
), "Progress value should be finite");
264 result
.mProgress
.SetValue(progress
);
268 ComputedTiming
AnimationEffect::GetComputedTiming(
269 const TimingParams
* aTiming
) const {
270 const double playbackRate
= mAnimation
? mAnimation
->PlaybackRate() : 1;
271 const auto progressTimelinePosition
=
272 mAnimation
? mAnimation
->AtProgressTimelineBoundary()
273 : Animation::ProgressTimelinePosition::NotBoundary
;
274 return GetComputedTimingAt(GetLocalTime(),
275 aTiming
? *aTiming
: NormalizedTiming(),
276 playbackRate
, progressTimelinePosition
);
279 // Helper function for generating an (Computed)EffectTiming dictionary
280 static void GetEffectTimingDictionary(const TimingParams
& aTiming
,
281 EffectTiming
& aRetVal
) {
282 aRetVal
.mDelay
= aTiming
.Delay().ToMilliseconds();
283 aRetVal
.mEndDelay
= aTiming
.EndDelay().ToMilliseconds();
284 aRetVal
.mFill
= aTiming
.Fill();
285 aRetVal
.mIterationStart
= aTiming
.IterationStart();
286 aRetVal
.mIterations
= aTiming
.Iterations();
287 if (aTiming
.Duration()) {
288 aRetVal
.mDuration
.SetAsUnrestrictedDouble() =
289 aTiming
.Duration()->ToMilliseconds();
291 aRetVal
.mDirection
= aTiming
.Direction();
292 if (aTiming
.TimingFunction()) {
293 aRetVal
.mEasing
.Truncate();
294 aTiming
.TimingFunction()->AppendToString(aRetVal
.mEasing
);
298 void AnimationEffect::GetTiming(EffectTiming
& aRetVal
) const {
299 GetEffectTimingDictionary(SpecifiedTiming(), aRetVal
);
302 void AnimationEffect::GetComputedTimingAsDict(
303 ComputedEffectTiming
& aRetVal
) const {
305 GetEffectTimingDictionary(SpecifiedTiming(), aRetVal
);
308 double playbackRate
= mAnimation
? mAnimation
->PlaybackRate() : 1;
309 const Nullable
<TimeDuration
> currentTime
= GetLocalTime();
310 const auto progressTimelinePosition
=
311 mAnimation
? mAnimation
->AtProgressTimelineBoundary()
312 : Animation::ProgressTimelinePosition::NotBoundary
;
313 ComputedTiming computedTiming
= GetComputedTimingAt(
314 currentTime
, SpecifiedTiming(), playbackRate
, progressTimelinePosition
);
316 aRetVal
.mDuration
.SetAsUnrestrictedDouble() =
317 computedTiming
.mDuration
.ToMilliseconds();
318 aRetVal
.mFill
= computedTiming
.mFill
;
319 aRetVal
.mActiveDuration
= computedTiming
.mActiveDuration
.ToMilliseconds();
320 aRetVal
.mEndTime
= computedTiming
.mEndTime
.ToMilliseconds();
322 AnimationUtils::TimeDurationToDouble(currentTime
, mRTPCallerType
);
323 aRetVal
.mProgress
= computedTiming
.mProgress
;
325 if (!aRetVal
.mProgress
.IsNull()) {
326 // Convert the returned currentIteration into Infinity if we set
327 // (uint64_t) computedTiming.mCurrentIteration to UINT64_MAX
329 computedTiming
.mCurrentIteration
== UINT64_MAX
330 ? PositiveInfinity
<double>()
331 : static_cast<double>(computedTiming
.mCurrentIteration
);
332 aRetVal
.mCurrentIteration
.SetValue(iteration
);
336 void AnimationEffect::UpdateTiming(const OptionalEffectTiming
& aTiming
,
338 TimingParams timing
=
339 TimingParams::MergeOptionalEffectTiming(mTiming
, aTiming
, aRv
);
344 SetSpecifiedTiming(std::move(timing
));
347 void AnimationEffect::UpdateNormalizedTiming() {
348 mNormalizedTiming
.reset();
350 if (!mAnimation
|| !mAnimation
->UsingScrollTimeline()) {
354 // Since `mAnimation` has a scroll timeline, we can be sure `GetTimeline()`
355 // and `TimelineDuration()` will not return null.
356 mNormalizedTiming
.emplace(
357 mTiming
.Normalize(mAnimation
->GetTimeline()->TimelineDuration().Value()));
360 Nullable
<TimeDuration
> AnimationEffect::GetLocalTime() const {
361 // Since the *animation* start time is currently always zero, the local
362 // time is equal to the parent time.
363 Nullable
<TimeDuration
> result
;
365 result
= mAnimation
->GetCurrentTimeAsDuration();
370 } // namespace mozilla::dom