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
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "mozilla/TimingParams.h"
9 #include "mozilla/AnimationUtils.h"
10 #include "mozilla/dom/AnimatableBinding.h"
11 #include "mozilla/dom/Document.h"
12 #include "mozilla/dom/KeyframeAnimationOptionsBinding.h"
13 #include "mozilla/dom/KeyframeEffectBinding.h"
14 #include "mozilla/ServoCSSParser.h"
18 template <class OptionsType
>
19 static const dom::EffectTiming
& GetTimingProperties(
20 const OptionsType
& aOptions
);
24 const dom::EffectTiming
& GetTimingProperties(
25 const dom::UnrestrictedDoubleOrKeyframeEffectOptions
& aOptions
) {
26 MOZ_ASSERT(aOptions
.IsKeyframeEffectOptions());
27 return aOptions
.GetAsKeyframeEffectOptions();
32 const dom::EffectTiming
& GetTimingProperties(
33 const dom::UnrestrictedDoubleOrKeyframeAnimationOptions
& aOptions
) {
34 MOZ_ASSERT(aOptions
.IsKeyframeAnimationOptions());
35 return aOptions
.GetAsKeyframeAnimationOptions();
38 template <class OptionsType
>
40 TimingParams
TimingParams::FromOptionsType(const OptionsType
& aOptions
,
44 if (aOptions
.IsUnrestrictedDouble()) {
45 double durationInMs
= aOptions
.GetAsUnrestrictedDouble();
46 if (durationInMs
>= 0) {
47 result
.mDuration
.emplace(
48 StickyTimeDuration::FromMilliseconds(durationInMs
));
50 nsPrintfCString
error("Duration value %g is less than 0", durationInMs
);
51 aRv
.ThrowTypeError(error
);
56 const dom::EffectTiming
& timing
= GetTimingProperties(aOptions
);
57 result
= FromEffectTiming(timing
, aRv
);
64 TimingParams
TimingParams::FromOptionsUnion(
65 const dom::UnrestrictedDoubleOrKeyframeEffectOptions
& aOptions
,
67 return FromOptionsType(aOptions
, aRv
);
71 TimingParams
TimingParams::FromOptionsUnion(
72 const dom::UnrestrictedDoubleOrKeyframeAnimationOptions
& aOptions
,
74 return FromOptionsType(aOptions
, aRv
);
78 TimingParams
TimingParams::FromEffectTiming(
79 const dom::EffectTiming
& aEffectTiming
, ErrorResult
& aRv
) {
82 Maybe
<StickyTimeDuration
> duration
=
83 TimingParams::ParseDuration(aEffectTiming
.mDuration
, aRv
);
87 TimingParams::ValidateIterationStart(aEffectTiming
.mIterationStart
, aRv
);
91 TimingParams::ValidateIterations(aEffectTiming
.mIterations
, aRv
);
95 Maybe
<StyleComputedTimingFunction
> easing
=
96 ParseEasing(aEffectTiming
.mEasing
, aRv
);
101 result
.mDuration
= duration
;
102 result
.mDelay
= TimeDuration::FromMilliseconds(aEffectTiming
.mDelay
);
103 result
.mEndDelay
= TimeDuration::FromMilliseconds(aEffectTiming
.mEndDelay
);
104 result
.mIterations
= aEffectTiming
.mIterations
;
105 result
.mIterationStart
= aEffectTiming
.mIterationStart
;
106 result
.mDirection
= aEffectTiming
.mDirection
;
107 result
.mFill
= aEffectTiming
.mFill
;
108 result
.mFunction
= std::move(easing
);
116 TimingParams
TimingParams::MergeOptionalEffectTiming(
117 const TimingParams
& aSource
, const dom::OptionalEffectTiming
& aEffectTiming
,
119 MOZ_ASSERT(!aRv
.Failed(), "Initially return value should be ok");
121 TimingParams result
= aSource
;
123 // Check for errors first
125 Maybe
<StickyTimeDuration
> duration
;
126 if (aEffectTiming
.mDuration
.WasPassed()) {
128 TimingParams::ParseDuration(aEffectTiming
.mDuration
.Value(), aRv
);
134 if (aEffectTiming
.mIterationStart
.WasPassed()) {
135 TimingParams::ValidateIterationStart(aEffectTiming
.mIterationStart
.Value(),
142 if (aEffectTiming
.mIterations
.WasPassed()) {
143 TimingParams::ValidateIterations(aEffectTiming
.mIterations
.Value(), aRv
);
149 Maybe
<StyleComputedTimingFunction
> easing
;
150 if (aEffectTiming
.mEasing
.WasPassed()) {
151 easing
= ParseEasing(aEffectTiming
.mEasing
.Value(), aRv
);
159 if (aEffectTiming
.mDuration
.WasPassed()) {
160 result
.mDuration
= duration
;
162 if (aEffectTiming
.mDelay
.WasPassed()) {
164 TimeDuration::FromMilliseconds(aEffectTiming
.mDelay
.Value());
166 if (aEffectTiming
.mEndDelay
.WasPassed()) {
168 TimeDuration::FromMilliseconds(aEffectTiming
.mEndDelay
.Value());
170 if (aEffectTiming
.mIterations
.WasPassed()) {
171 result
.mIterations
= aEffectTiming
.mIterations
.Value();
173 if (aEffectTiming
.mIterationStart
.WasPassed()) {
174 result
.mIterationStart
= aEffectTiming
.mIterationStart
.Value();
176 if (aEffectTiming
.mDirection
.WasPassed()) {
177 result
.mDirection
= aEffectTiming
.mDirection
.Value();
179 if (aEffectTiming
.mFill
.WasPassed()) {
180 result
.mFill
= aEffectTiming
.mFill
.Value();
182 if (aEffectTiming
.mEasing
.WasPassed()) {
183 result
.mFunction
= easing
;
192 Maybe
<StyleComputedTimingFunction
> TimingParams::ParseEasing(
193 const nsACString
& aEasing
, ErrorResult
& aRv
) {
194 auto timingFunction
= StyleComputedTimingFunction::LinearKeyword();
195 if (!ServoCSSParser::ParseEasing(aEasing
, timingFunction
)) {
196 aRv
.ThrowTypeError
<dom::MSG_INVALID_EASING_ERROR
>(aEasing
);
200 if (timingFunction
.IsLinearKeyword()) {
204 return Some(std::move(timingFunction
));
207 bool TimingParams::operator==(const TimingParams
& aOther
) const {
208 // We don't compare mActiveDuration and mEndTime because they are calculated
209 // from other timing parameters.
210 return mDuration
== aOther
.mDuration
&& mDelay
== aOther
.mDelay
&&
211 mEndDelay
== aOther
.mEndDelay
&& mIterations
== aOther
.mIterations
&&
212 mIterationStart
== aOther
.mIterationStart
&&
213 mDirection
== aOther
.mDirection
&& mFill
== aOther
.mFill
&&
214 mFunction
== aOther
.mFunction
;
217 // FIXME: This is a tentative way to normalize the timing which is defined in
218 // [web-animations-2] [1]. I borrow this implementation and some concepts for
219 // the edge cases from Chromium [2] so we can match the behavior with them. The
220 // implementation here ignores the case of percentage of start delay, end delay,
221 // and duration because Gecko doesn't support them. We may have to update the
222 // calculation if the spec issue [3] gets any update.
225 // https://drafts.csswg.org/web-animations-2/#time-based-animation-to-a-proportional-animation
226 // [2] https://chromium-review.googlesource.com/c/chromium/src/+/2992387
227 // [3] https://github.com/w3c/csswg-drafts/issues/4862
228 TimingParams
TimingParams::Normalize(
229 const TimeDuration
& aTimelineDuration
) const {
230 MOZ_ASSERT(aTimelineDuration
,
231 "the timeline duration of scroll-timeline is always non-zero now");
233 TimingParams
normalizedTiming(*this);
235 // Handle iteration duration value of "auto" first.
236 // FIXME: Bug 1676794: Gecko doesn't support `animation-duration:auto` and we
237 // don't support JS-generated scroll animations, so we don't fall into this
238 // case for now. Need to check this again after we support ScrollTimeline
241 // If the iteration duration is auto, then:
242 // Set start delay and end delay to 0, as it is not possible to mix time
244 normalizedTiming
.mDelay
= TimeDuration();
245 normalizedTiming
.mEndDelay
= TimeDuration();
246 normalizedTiming
.Update();
247 return normalizedTiming
;
250 if (mEndTime
.IsZero()) {
251 // mEndTime of zero causes division by zero so we handle it here.
253 // FIXME: The spec doesn't mention this case, so we might have to update
254 // this based on the spec issue,
255 // https://github.com/w3c/csswg-drafts/issues/7459.
256 normalizedTiming
.mDelay
= TimeDuration();
257 normalizedTiming
.mEndDelay
= TimeDuration();
258 normalizedTiming
.mDuration
= Some(TimeDuration());
259 } else if (mEndTime
== TimeDuration::Forever()) {
260 // The iteration count or duration may be infinite; however, start and
261 // end delays are strictly finite. Thus, in the limit when end time
262 // approaches infinity:
263 // start delay / end time = finite / infinite = 0
264 // end delay / end time = finite / infinite = 0
265 // iteration duration / end time = 1 / iteration count
266 // This condition can be reached by switching to a scroll timeline on
267 // an existing infinite duration animation.
269 // FIXME: The spec doesn't mention this case, so we might have to update
270 // this based on the spec issue,
271 // https://github.com/w3c/csswg-drafts/issues/7459.
272 normalizedTiming
.mDelay
= TimeDuration();
273 normalizedTiming
.mEndDelay
= TimeDuration();
274 normalizedTiming
.mDuration
=
275 Some(aTimelineDuration
.MultDouble(1.0 / mIterations
));
277 // Convert to percentages then multiply by the timeline duration.
278 const double endTimeInSec
= mEndTime
.ToSeconds();
279 normalizedTiming
.mDelay
=
280 aTimelineDuration
.MultDouble(mDelay
.ToSeconds() / endTimeInSec
);
281 normalizedTiming
.mEndDelay
=
282 aTimelineDuration
.MultDouble(mEndDelay
.ToSeconds() / endTimeInSec
);
283 normalizedTiming
.mDuration
= Some(StickyTimeDuration(
284 aTimelineDuration
.MultDouble(mDuration
->ToSeconds() / endTimeInSec
)));
287 normalizedTiming
.Update();
288 return normalizedTiming
;
291 } // namespace mozilla