Backed out 8 changesets (bug 1873776) for causing vendor failures. CLOSED TREE
[gecko.git] / dom / animation / TimingParams.cpp
blob7ab78f52f1ff92d04b1b12e95169f07aecf2dc32
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"
16 namespace mozilla {
18 template <class OptionsType>
19 static const dom::EffectTiming& GetTimingProperties(
20 const OptionsType& aOptions);
22 template <>
23 /* static */
24 const dom::EffectTiming& GetTimingProperties(
25 const dom::UnrestrictedDoubleOrKeyframeEffectOptions& aOptions) {
26 MOZ_ASSERT(aOptions.IsKeyframeEffectOptions());
27 return aOptions.GetAsKeyframeEffectOptions();
30 template <>
31 /* static */
32 const dom::EffectTiming& GetTimingProperties(
33 const dom::UnrestrictedDoubleOrKeyframeAnimationOptions& aOptions) {
34 MOZ_ASSERT(aOptions.IsKeyframeAnimationOptions());
35 return aOptions.GetAsKeyframeAnimationOptions();
38 template <class OptionsType>
39 /* static */
40 TimingParams TimingParams::FromOptionsType(const OptionsType& aOptions,
41 ErrorResult& aRv) {
42 TimingParams result;
44 if (aOptions.IsUnrestrictedDouble()) {
45 double durationInMs = aOptions.GetAsUnrestrictedDouble();
46 if (durationInMs >= 0) {
47 result.mDuration.emplace(
48 StickyTimeDuration::FromMilliseconds(durationInMs));
49 } else {
50 nsPrintfCString error("Duration value %g is less than 0", durationInMs);
51 aRv.ThrowTypeError(error);
52 return result;
54 result.Update();
55 } else {
56 const dom::EffectTiming& timing = GetTimingProperties(aOptions);
57 result = FromEffectTiming(timing, aRv);
60 return result;
63 /* static */
64 TimingParams TimingParams::FromOptionsUnion(
65 const dom::UnrestrictedDoubleOrKeyframeEffectOptions& aOptions,
66 ErrorResult& aRv) {
67 return FromOptionsType(aOptions, aRv);
70 /* static */
71 TimingParams TimingParams::FromOptionsUnion(
72 const dom::UnrestrictedDoubleOrKeyframeAnimationOptions& aOptions,
73 ErrorResult& aRv) {
74 return FromOptionsType(aOptions, aRv);
77 /* static */
78 TimingParams TimingParams::FromEffectTiming(
79 const dom::EffectTiming& aEffectTiming, ErrorResult& aRv) {
80 TimingParams result;
82 Maybe<StickyTimeDuration> duration =
83 TimingParams::ParseDuration(aEffectTiming.mDuration, aRv);
84 if (aRv.Failed()) {
85 return result;
87 TimingParams::ValidateIterationStart(aEffectTiming.mIterationStart, aRv);
88 if (aRv.Failed()) {
89 return result;
91 TimingParams::ValidateIterations(aEffectTiming.mIterations, aRv);
92 if (aRv.Failed()) {
93 return result;
95 Maybe<StyleComputedTimingFunction> easing =
96 ParseEasing(aEffectTiming.mEasing, aRv);
97 if (aRv.Failed()) {
98 return result;
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);
110 result.Update();
112 return result;
115 /* static */
116 TimingParams TimingParams::MergeOptionalEffectTiming(
117 const TimingParams& aSource, const dom::OptionalEffectTiming& aEffectTiming,
118 ErrorResult& aRv) {
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()) {
127 duration =
128 TimingParams::ParseDuration(aEffectTiming.mDuration.Value(), aRv);
129 if (aRv.Failed()) {
130 return result;
134 if (aEffectTiming.mIterationStart.WasPassed()) {
135 TimingParams::ValidateIterationStart(aEffectTiming.mIterationStart.Value(),
136 aRv);
137 if (aRv.Failed()) {
138 return result;
142 if (aEffectTiming.mIterations.WasPassed()) {
143 TimingParams::ValidateIterations(aEffectTiming.mIterations.Value(), aRv);
144 if (aRv.Failed()) {
145 return result;
149 Maybe<StyleComputedTimingFunction> easing;
150 if (aEffectTiming.mEasing.WasPassed()) {
151 easing = ParseEasing(aEffectTiming.mEasing.Value(), aRv);
152 if (aRv.Failed()) {
153 return result;
157 // Assign values
159 if (aEffectTiming.mDuration.WasPassed()) {
160 result.mDuration = duration;
162 if (aEffectTiming.mDelay.WasPassed()) {
163 result.mDelay =
164 TimeDuration::FromMilliseconds(aEffectTiming.mDelay.Value());
166 if (aEffectTiming.mEndDelay.WasPassed()) {
167 result.mEndDelay =
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;
186 result.Update();
188 return result;
191 /* static */
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);
197 return Nothing();
200 if (timingFunction.IsLinearKeyword()) {
201 return Nothing();
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.
224 // [1]
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
239 // interface.
240 if (!mDuration) {
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
243 // and proportions.
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));
276 } else {
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