Backed out 8 changesets (bug 1873776) for causing vendor failures. CLOSED TREE
[gecko.git] / dom / animation / AnimationEffect.cpp
blob6ef8c30d494fdb61fd81485045c4911c3187ae3c
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)
35 NS_INTERFACE_MAP_END
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) {
51 return false;
54 ComputedTiming computedTiming = GetComputedTiming();
55 if (computedTiming.mPhase == ComputedTiming::AnimationPhase::Active) {
56 return true;
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) {
73 return;
76 mTiming = aTiming;
78 UpdateNormalizedTiming();
80 if (mAnimation) {
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
130 : aTiming.Fill();
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()) {
135 return result;
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.
151 return result;
153 result.mActiveTime =
154 std::max(std::min(StickyTimeDuration(localTime - aTiming.Delay()),
155 result.mActiveDuration),
156 zeroDuration);
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.
163 return result;
165 result.mActiveTime =
166 std::max(StickyTimeDuration(localTime - aTiming.Delay()), zeroDuration);
167 } else {
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
179 ? 0.0
180 : result.mIterations;
181 } else {
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");
221 progress = 1.0;
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;
232 break;
233 case PlaybackDirection::Reverse:
234 thisIterationReverse = true;
235 break;
236 case PlaybackDirection::Alternate:
237 thisIterationReverse = (result.mCurrentIteration & 1) == 1;
238 break;
239 case PlaybackDirection::Alternate_reverse:
240 thisIterationReverse = (result.mCurrentIteration & 1) == 0;
241 break;
242 default:
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
250 // functions.
251 if ((result.mPhase == ComputedTiming::AnimationPhase::After &&
252 thisIterationReverse) ||
253 (result.mPhase == ComputedTiming::AnimationPhase::Before &&
254 !thisIterationReverse)) {
255 result.mBeforeFlag = true;
258 // Apply the easing.
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);
265 return result;
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 {
304 // Specified timing
305 GetEffectTimingDictionary(SpecifiedTiming(), aRetVal);
307 // Computed timing
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();
321 aRetVal.mLocalTime =
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
328 double iteration =
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,
337 ErrorResult& aRv) {
338 TimingParams timing =
339 TimingParams::MergeOptionalEffectTiming(mTiming, aTiming, aRv);
340 if (aRv.Failed()) {
341 return;
344 SetSpecifiedTiming(std::move(timing));
347 void AnimationEffect::UpdateNormalizedTiming() {
348 mNormalizedTiming.reset();
350 if (!mAnimation || !mAnimation->UsingScrollTimeline()) {
351 return;
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;
364 if (mAnimation) {
365 result = mAnimation->GetCurrentTimeAsDuration();
367 return result;
370 } // namespace mozilla::dom