TimingParams.cpp (10019B)
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/. */ 6 7 #include "mozilla/TimingParams.h" 8 9 #include "mozilla/AnimationUtils.h" 10 #include "mozilla/ServoCSSParser.h" 11 #include "mozilla/dom/AnimatableBinding.h" 12 #include "mozilla/dom/Document.h" 13 #include "mozilla/dom/KeyframeAnimationOptionsBinding.h" 14 #include "mozilla/dom/KeyframeEffectBinding.h" 15 16 namespace mozilla { 17 18 template <class OptionsType> 19 static const dom::EffectTiming& GetTimingProperties( 20 const OptionsType& aOptions); 21 22 template <> 23 /* static */ 24 const dom::EffectTiming& GetTimingProperties( 25 const dom::UnrestrictedDoubleOrKeyframeEffectOptions& aOptions) { 26 MOZ_ASSERT(aOptions.IsKeyframeEffectOptions()); 27 return aOptions.GetAsKeyframeEffectOptions(); 28 } 29 30 template <> 31 /* static */ 32 const dom::EffectTiming& GetTimingProperties( 33 const dom::UnrestrictedDoubleOrKeyframeAnimationOptions& aOptions) { 34 MOZ_ASSERT(aOptions.IsKeyframeAnimationOptions()); 35 return aOptions.GetAsKeyframeAnimationOptions(); 36 } 37 38 template <class OptionsType> 39 /* static */ 40 TimingParams TimingParams::FromOptionsType(const OptionsType& aOptions, 41 ErrorResult& aRv) { 42 TimingParams result; 43 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; 53 } 54 result.Update(); 55 } else { 56 const dom::EffectTiming& timing = GetTimingProperties(aOptions); 57 result = FromEffectTiming(timing, aRv); 58 } 59 60 return result; 61 } 62 63 /* static */ 64 TimingParams TimingParams::FromOptionsUnion( 65 const dom::UnrestrictedDoubleOrKeyframeEffectOptions& aOptions, 66 ErrorResult& aRv) { 67 return FromOptionsType(aOptions, aRv); 68 } 69 70 /* static */ 71 TimingParams TimingParams::FromOptionsUnion( 72 const dom::UnrestrictedDoubleOrKeyframeAnimationOptions& aOptions, 73 ErrorResult& aRv) { 74 return FromOptionsType(aOptions, aRv); 75 } 76 77 /* static */ 78 TimingParams TimingParams::FromEffectTiming( 79 const dom::EffectTiming& aEffectTiming, ErrorResult& aRv) { 80 TimingParams result; 81 82 Maybe<StickyTimeDuration> duration = 83 TimingParams::ParseDuration(aEffectTiming.mDuration, aRv); 84 if (aRv.Failed()) { 85 return result; 86 } 87 TimingParams::ValidateIterationStart(aEffectTiming.mIterationStart, aRv); 88 if (aRv.Failed()) { 89 return result; 90 } 91 TimingParams::ValidateIterations(aEffectTiming.mIterations, aRv); 92 if (aRv.Failed()) { 93 return result; 94 } 95 Maybe<StyleComputedTimingFunction> easing = 96 ParseEasing(aEffectTiming.mEasing, aRv); 97 if (aRv.Failed()) { 98 return result; 99 } 100 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); 109 110 result.Update(); 111 112 return result; 113 } 114 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"); 120 121 TimingParams result = aSource; 122 123 // Check for errors first 124 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; 131 } 132 } 133 134 if (aEffectTiming.mIterationStart.WasPassed()) { 135 TimingParams::ValidateIterationStart(aEffectTiming.mIterationStart.Value(), 136 aRv); 137 if (aRv.Failed()) { 138 return result; 139 } 140 } 141 142 if (aEffectTiming.mIterations.WasPassed()) { 143 TimingParams::ValidateIterations(aEffectTiming.mIterations.Value(), aRv); 144 if (aRv.Failed()) { 145 return result; 146 } 147 } 148 149 Maybe<StyleComputedTimingFunction> easing; 150 if (aEffectTiming.mEasing.WasPassed()) { 151 easing = ParseEasing(aEffectTiming.mEasing.Value(), aRv); 152 if (aRv.Failed()) { 153 return result; 154 } 155 } 156 157 // Assign values 158 159 if (aEffectTiming.mDuration.WasPassed()) { 160 result.mDuration = duration; 161 } 162 if (aEffectTiming.mDelay.WasPassed()) { 163 result.mDelay = 164 TimeDuration::FromMilliseconds(aEffectTiming.mDelay.Value()); 165 } 166 if (aEffectTiming.mEndDelay.WasPassed()) { 167 result.mEndDelay = 168 TimeDuration::FromMilliseconds(aEffectTiming.mEndDelay.Value()); 169 } 170 if (aEffectTiming.mIterations.WasPassed()) { 171 result.mIterations = aEffectTiming.mIterations.Value(); 172 } 173 if (aEffectTiming.mIterationStart.WasPassed()) { 174 result.mIterationStart = aEffectTiming.mIterationStart.Value(); 175 } 176 if (aEffectTiming.mDirection.WasPassed()) { 177 result.mDirection = aEffectTiming.mDirection.Value(); 178 } 179 if (aEffectTiming.mFill.WasPassed()) { 180 result.mFill = aEffectTiming.mFill.Value(); 181 } 182 if (aEffectTiming.mEasing.WasPassed()) { 183 result.mFunction = easing; 184 } 185 186 result.Update(); 187 188 return result; 189 } 190 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(); 198 } 199 200 if (timingFunction.IsLinearKeyword()) { 201 return Nothing(); 202 } 203 204 return Some(std::move(timingFunction)); 205 } 206 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; 215 } 216 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. 222 // 223 // FIXME: Bug 1804775. We may have to update the calculation here after we 224 // introduce animaiton ranges. 225 // 226 // [1] 227 // https://drafts.csswg.org/web-animations-2/#time-based-animation-to-a-proportional-animation 228 // [2] https://chromium-review.googlesource.com/c/chromium/src/+/2992387 229 TimingParams TimingParams::Normalize( 230 const TimeDuration& aTimelineDuration) const { 231 MOZ_ASSERT(aTimelineDuration, 232 "the timeline duration of scroll-timeline is always non-zero now"); 233 234 TimingParams normalizedTiming(*this); 235 236 // Handle iteration duration value of "auto" first. 237 if (!mDuration) { 238 // If the iteration duration is auto, then: 239 // Set start delay and end delay to 0, as it is not possible to mix time 240 // and proportions. 241 normalizedTiming.mDelay = TimeDuration(); 242 normalizedTiming.mEndDelay = TimeDuration(); 243 // FIXME: Bug 1804775. We may have to tweak here once we introduce timeline 244 // range (e.g. animation-range-{start|end}). For now, we use the default 245 // timeline duration as the normalized duration of this timing. 246 normalizedTiming.mDuration.emplace(aTimelineDuration); 247 normalizedTiming.Update(); 248 return normalizedTiming; 249 } 250 251 if (mEndTime.IsZero()) { 252 // mEndTime of zero causes division by zero so we handle it here. 253 // 254 // FIXME: The spec doesn't mention this case, so we might have to update 255 // this based on the spec issue, 256 // https://github.com/w3c/csswg-drafts/issues/7459. 257 normalizedTiming.mDelay = TimeDuration(); 258 normalizedTiming.mEndDelay = TimeDuration(); 259 normalizedTiming.mDuration = Some(TimeDuration()); 260 } else if (mEndTime == TimeDuration::Forever()) { 261 // The iteration count or duration may be infinite; however, start and 262 // end delays are strictly finite. Thus, in the limit when end time 263 // approaches infinity: 264 // start delay / end time = finite / infinite = 0 265 // end delay / end time = finite / infinite = 0 266 // iteration duration / end time = 1 / iteration count 267 // This condition can be reached by switching to a scroll timeline on 268 // an existing infinite duration animation. 269 // 270 // FIXME: The spec doesn't mention this case, so we might have to update 271 // this based on the spec issue, 272 // https://github.com/w3c/csswg-drafts/issues/7459. 273 normalizedTiming.mDelay = TimeDuration(); 274 normalizedTiming.mEndDelay = TimeDuration(); 275 normalizedTiming.mDuration = 276 Some(aTimelineDuration.MultDouble(1.0 / mIterations)); 277 } else { 278 // Convert to percentages then multiply by the timeline duration. 279 const double endTimeInSec = mEndTime.ToSeconds(); 280 normalizedTiming.mDelay = 281 aTimelineDuration.MultDouble(mDelay.ToSeconds() / endTimeInSec); 282 normalizedTiming.mEndDelay = 283 aTimelineDuration.MultDouble(mEndDelay.ToSeconds() / endTimeInSec); 284 normalizedTiming.mDuration = Some(StickyTimeDuration( 285 aTimelineDuration.MultDouble(mDuration->ToSeconds() / endTimeInSec))); 286 } 287 288 normalizedTiming.Update(); 289 return normalizedTiming; 290 } 291 292 } // namespace mozilla