1/*
2 * Copyright (C) 2017-2018 Apple Inc. All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
7 * 1. Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 * notice, this list of conditions and the following disclaimer in the
11 * documentation and/or other materials provided with the distribution.
12 *
13 * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
14 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
17 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
20 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
21 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24 */
25
26#include "config.h"
27#include "AnimationEffect.h"
28
29#include "FillMode.h"
30#include "JSComputedEffectTiming.h"
31#include "WebAnimationUtilities.h"
32
33namespace WebCore {
34
35AnimationEffect::AnimationEffect()
36 : m_timingFunction(LinearTimingFunction::create())
37{
38}
39
40AnimationEffect::~AnimationEffect()
41{
42}
43
44EffectTiming AnimationEffect::getTiming() const
45{
46 EffectTiming timing;
47 timing.delay = secondsToWebAnimationsAPITime(m_delay);
48 timing.endDelay = secondsToWebAnimationsAPITime(m_endDelay);
49 timing.fill = m_fill;
50 timing.iterationStart = m_iterationStart;
51 timing.iterations = m_iterations;
52 if (m_iterationDuration == 0_s)
53 timing.duration = "auto";
54 else
55 timing.duration = secondsToWebAnimationsAPITime(m_iterationDuration);
56 timing.direction = m_direction;
57 timing.easing = m_timingFunction->cssText();
58 return timing;
59}
60
61BasicEffectTiming AnimationEffect::getBasicTiming() const
62{
63 // The Web Animations spec introduces a number of animation effect time-related definitions that refer
64 // to each other a fair bit, so rather than implementing them as individual methods, it's more efficient
65 // to return them all as a single BasicEffectTiming.
66
67 auto activeDuration = [this]() -> Seconds {
68 // 3.8.2. Calculating the active duration
69 // https://drafts.csswg.org/web-animations-1/#calculating-the-active-duration
70
71 // The active duration is calculated as follows:
72 // active duration = iteration duration × iteration count
73 // If either the iteration duration or iteration count are zero, the active duration is zero.
74 if (!m_iterationDuration || !m_iterations)
75 return 0_s;
76 return m_iterationDuration * m_iterations;
77 }();
78
79 auto endTime = [this, activeDuration]() -> Seconds {
80 // 3.5.3 The active interval
81 // https://drafts.csswg.org/web-animations-1/#end-time
82
83 // The end time of an animation effect is the result of evaluating max(start delay + active duration + end delay, 0).
84 auto endTime = m_delay + activeDuration + m_endDelay;
85 return endTime > 0_s ? endTime : 0_s;
86 }();
87
88 auto localTime = [this]() -> Optional<Seconds> {
89 // 4.5.4. Local time
90 // https://drafts.csswg.org/web-animations-1/#local-time-section
91
92 // The local time of an animation effect at a given moment is based on the first matching condition from the following:
93 // If the animation effect is associated with an animation, the local time is the current time of the animation.
94 // Otherwise, the local time is unresolved.
95 if (m_animation)
96 return m_animation->currentTime();
97 return WTF::nullopt;
98 }();
99
100 auto phase = [this, endTime, localTime, activeDuration]() -> AnimationEffectPhase {
101 // 3.5.5. Animation effect phases and states
102 // https://drafts.csswg.org/web-animations-1/#animation-effect-phases-and-states
103
104 bool animationIsBackwards = m_animation && m_animation->playbackRate() < 0;
105 auto beforeActiveBoundaryTime = std::max(std::min(m_delay, endTime), 0_s);
106 auto activeAfterBoundaryTime = std::max(std::min(m_delay + activeDuration, endTime), 0_s);
107
108 // (This should be the last statement, but it's more efficient to cache the local time and return right away if it's not resolved.)
109 // Furthermore, it is often convenient to refer to the case when an animation effect is in none of the above phases
110 // as being in the idle phase.
111 if (!localTime)
112 return AnimationEffectPhase::Idle;
113
114 // An animation effect is in the before phase if the animation effect’s local time is not unresolved and
115 // either of the following conditions are met:
116 // 1. the local time is less than the before-active boundary time, or
117 // 2. the animation direction is ‘backwards’ and the local time is equal to the before-active boundary time.
118 if ((*localTime + timeEpsilon) < beforeActiveBoundaryTime || (animationIsBackwards && std::abs(localTime->microseconds() - beforeActiveBoundaryTime.microseconds()) < timeEpsilon.microseconds()))
119 return AnimationEffectPhase::Before;
120
121 // An animation effect is in the after phase if the animation effect’s local time is not unresolved and
122 // either of the following conditions are met:
123 // 1. the local time is greater than the active-after boundary time, or
124 // 2. the animation direction is ‘forwards’ and the local time is equal to the active-after boundary time.
125 if ((*localTime - timeEpsilon) > activeAfterBoundaryTime || (!animationIsBackwards && std::abs(localTime->microseconds() - activeAfterBoundaryTime.microseconds()) < timeEpsilon.microseconds()))
126 return AnimationEffectPhase::After;
127
128 // An animation effect is in the active phase if the animation effect’s local time is not unresolved and it is not
129 // in either the before phase nor the after phase.
130 // (No need to check, we've already established that local time was resolved).
131 return AnimationEffectPhase::Active;
132 }();
133
134 auto activeTime = [this, localTime, phase, activeDuration]() -> Optional<Seconds> {
135 // 3.8.3.1. Calculating the active time
136 // https://drafts.csswg.org/web-animations-1/#calculating-the-active-time
137
138 // The active time is based on the local time and start delay. However, it is only defined
139 // when the animation effect should produce an output and hence depends on its fill mode
140 // and phase as follows,
141
142 // If the animation effect is in the before phase, the result depends on the first matching
143 // condition from the following,
144 if (phase == AnimationEffectPhase::Before) {
145 // If the fill mode is backwards or both, return the result of evaluating
146 // max(local time - start delay, 0).
147 if (m_fill == FillMode::Backwards || m_fill == FillMode::Both)
148 return std::max(*localTime - m_delay, 0_s);
149 // Otherwise, return an unresolved time value.
150 return WTF::nullopt;
151 }
152
153 // If the animation effect is in the active phase, return the result of evaluating local time - start delay.
154 if (phase == AnimationEffectPhase::Active)
155 return *localTime - m_delay;
156
157 // If the animation effect is in the after phase, the result depends on the first matching
158 // condition from the following,
159 if (phase == AnimationEffectPhase::After) {
160 // If the fill mode is forwards or both, return the result of evaluating
161 // max(min(local time - start delay, active duration), 0).
162 if (m_fill == FillMode::Forwards || m_fill == FillMode::Both)
163 return std::max(std::min(*localTime - m_delay, activeDuration), 0_s);
164 // Otherwise, return an unresolved time value.
165 return WTF::nullopt;
166 }
167
168 // Otherwise (the local time is unresolved), return an unresolved time value.
169 return WTF::nullopt;
170 }();
171
172 return { localTime, activeTime, endTime, activeDuration, phase };
173}
174
175ComputedEffectTiming AnimationEffect::getComputedTiming() const
176{
177 // The Web Animations spec introduces a number of animation effect time-related definitions that refer
178 // to each other a fair bit, so rather than implementing them as individual methods, it's more efficient
179 // to return them all as a single ComputedEffectTiming.
180
181 auto basicEffectTiming = getBasicTiming();
182 auto activeTime = basicEffectTiming.activeTime;
183 auto activeDuration = basicEffectTiming.activeDuration;
184 auto phase = basicEffectTiming.phase;
185
186 auto overallProgress = [this, phase, activeTime]() -> Optional<double> {
187 // 3.8.3.2. Calculating the overall progress
188 // https://drafts.csswg.org/web-animations-1/#calculating-the-overall-progress
189
190 // The overall progress describes the number of iterations that have completed (including partial iterations) and is defined as follows:
191
192 // 1. If the active time is unresolved, return unresolved.
193 if (!activeTime)
194 return WTF::nullopt;
195
196 // 2. Calculate an initial value for overall progress based on the first matching condition from below,
197 double overallProgress;
198
199 if (!m_iterationDuration) {
200 // If the iteration duration is zero, if the animation effect is in the before phase, let overall progress be zero,
201 // otherwise, let it be equal to the iteration count.
202 overallProgress = phase == AnimationEffectPhase::Before ? 0 : m_iterations;
203 } else {
204 // Otherwise, let overall progress be the result of calculating active time / iteration duration.
205 overallProgress = secondsToWebAnimationsAPITime(*activeTime) / secondsToWebAnimationsAPITime(m_iterationDuration);
206 }
207
208 // 3. Return the result of calculating overall progress + iteration start.
209 overallProgress += m_iterationStart;
210 return std::abs(overallProgress);
211 }();
212
213 auto simpleIterationProgress = [this, overallProgress, phase, activeTime, activeDuration]() -> Optional<double> {
214 // 3.8.3.3. Calculating the simple iteration progress
215 // https://drafts.csswg.org/web-animations-1/#calculating-the-simple-iteration-progress
216
217 // The simple iteration progress is a fraction of the progress through the current iteration that
218 // ignores transformations to the time introduced by the playback direction or timing functions
219 // applied to the effect, and is calculated as follows:
220
221 // 1. If the overall progress is unresolved, return unresolved.
222 if (!overallProgress)
223 return WTF::nullopt;
224
225 // 2. If overall progress is infinity, let the simple iteration progress be iteration start % 1.0,
226 // otherwise, let the simple iteration progress be overall progress % 1.0.
227 double simpleIterationProgress = std::isinf(*overallProgress) ? fmod(m_iterationStart, 1) : fmod(*overallProgress, 1);
228
229 // 3. If all of the following conditions are true,
230 //
231 // the simple iteration progress calculated above is zero, and
232 // the animation effect is in the active phase or the after phase, and
233 // the active time is equal to the active duration, and
234 // the iteration count is not equal to zero.
235 // let the simple iteration progress be 1.0.
236 if (!simpleIterationProgress && (phase == AnimationEffectPhase::Active || phase == AnimationEffectPhase::After) && std::abs(activeTime->microseconds() - activeDuration.microseconds()) < timeEpsilon.microseconds() && m_iterations)
237 return 1;
238
239 return simpleIterationProgress;
240 }();
241
242 auto currentIteration = [this, activeTime, phase, simpleIterationProgress, overallProgress]() -> Optional<double> {
243 // 3.8.4. Calculating the current iteration
244 // https://drafts.csswg.org/web-animations-1/#calculating-the-current-iteration
245
246 // The current iteration can be calculated using the following steps:
247
248 // 1. If the active time is unresolved, return unresolved.
249 if (!activeTime)
250 return WTF::nullopt;
251
252 // 2. If the animation effect is in the after phase and the iteration count is infinity, return infinity.
253 if (phase == AnimationEffectPhase::After && std::isinf(m_iterations))
254 return std::numeric_limits<double>::infinity();
255
256 // 3. If the simple iteration progress is 1.0, return floor(overall progress) - 1.
257 if (*simpleIterationProgress == 1)
258 return floor(*overallProgress) - 1;
259
260 // 4. Otherwise, return floor(overall progress).
261 return floor(*overallProgress);
262 }();
263
264 auto currentDirection = [this, currentIteration]() -> AnimationEffect::ComputedDirection {
265 // 3.9.1. Calculating the directed progress
266 // https://drafts.csswg.org/web-animations-1/#calculating-the-directed-progress
267
268 // If playback direction is normal, let the current direction be forwards.
269 if (m_direction == PlaybackDirection::Normal)
270 return AnimationEffect::ComputedDirection::Forwards;
271
272 // If playback direction is reverse, let the current direction be reverse.
273 if (m_direction == PlaybackDirection::Reverse)
274 return AnimationEffect::ComputedDirection::Reverse;
275
276 if (!currentIteration)
277 return AnimationEffect::ComputedDirection::Forwards;
278
279 // Otherwise, let d be the current iteration.
280 auto d = *currentIteration;
281 // If playback direction is alternate-reverse increment d by 1.
282 if (m_direction == PlaybackDirection::AlternateReverse)
283 d++;
284 // If d % 2 == 0, let the current direction be forwards, otherwise let the current direction be reverse.
285 // If d is infinity, let the current direction be forwards.
286 if (std::isinf(d) || !fmod(d, 2))
287 return AnimationEffect::ComputedDirection::Forwards;
288 return AnimationEffect::ComputedDirection::Reverse;
289 }();
290
291 auto directedProgress = [simpleIterationProgress, currentDirection]() -> Optional<double> {
292 // 3.9.1. Calculating the directed progress
293 // https://drafts.csswg.org/web-animations-1/#calculating-the-directed-progress
294
295 // The directed progress is calculated from the simple iteration progress using the following steps:
296
297 // 1. If the simple iteration progress is unresolved, return unresolved.
298 if (!simpleIterationProgress)
299 return WTF::nullopt;
300
301 // 2. Calculate the current direction (we implement this as a separate method).
302
303 // 3. If the current direction is forwards then return the simple iteration progress.
304 if (currentDirection == AnimationEffect::ComputedDirection::Forwards)
305 return *simpleIterationProgress;
306
307 // Otherwise, return 1.0 - simple iteration progress.
308 return 1 - *simpleIterationProgress;
309 }();
310
311 auto transformedProgress = [this, directedProgress, currentDirection, phase]() -> Optional<double> {
312 // 3.10.1. Calculating the transformed progress
313 // https://drafts.csswg.org/web-animations-1/#calculating-the-transformed-progress
314
315 // The transformed progress is calculated from the directed progress using the following steps:
316 //
317 // 1. If the directed progress is unresolved, return unresolved.
318 if (!directedProgress)
319 return WTF::nullopt;
320
321 if (auto iterationDuration = m_iterationDuration.seconds()) {
322 bool before = false;
323 // 2. Calculate the value of the before flag as follows:
324 if (is<StepsTimingFunction>(m_timingFunction)) {
325 // 1. Determine the current direction using the procedure defined in §3.9.1 Calculating the directed progress.
326 // 2. If the current direction is forwards, let going forwards be true, otherwise it is false.
327 bool goingForwards = currentDirection == AnimationEffect::ComputedDirection::Forwards;
328 // 3. The before flag is set if the animation effect is in the before phase and going forwards is true;
329 // or if the animation effect is in the after phase and going forwards is false.
330 before = (phase == AnimationEffectPhase::Before && goingForwards) || (phase == AnimationEffectPhase::After && !goingForwards);
331 }
332
333 // 3. Return the result of evaluating the animation effect’s timing function passing directed progress as the
334 // input progress value and before flag as the before flag.
335 return m_timingFunction->transformTime(*directedProgress, iterationDuration, before);
336 }
337
338 return *directedProgress;
339 }();
340
341 ComputedEffectTiming computedTiming;
342 computedTiming.delay = secondsToWebAnimationsAPITime(m_delay);
343 computedTiming.endDelay = secondsToWebAnimationsAPITime(m_endDelay);
344 computedTiming.fill = m_fill == FillMode::Auto ? FillMode::None : m_fill;
345 computedTiming.iterationStart = m_iterationStart;
346 computedTiming.iterations = m_iterations;
347 computedTiming.duration = secondsToWebAnimationsAPITime(m_iterationDuration);
348 computedTiming.direction = m_direction;
349 computedTiming.easing = m_timingFunction->cssText();
350 computedTiming.endTime = secondsToWebAnimationsAPITime(basicEffectTiming.endTime);
351 computedTiming.activeDuration = secondsToWebAnimationsAPITime(activeDuration);
352 if (basicEffectTiming.localTime)
353 computedTiming.localTime = secondsToWebAnimationsAPITime(*basicEffectTiming.localTime);
354 computedTiming.simpleIterationProgress = simpleIterationProgress;
355 computedTiming.progress = transformedProgress;
356 computedTiming.currentIteration = currentIteration;
357 computedTiming.phase = phase;
358 return computedTiming;
359}
360
361ExceptionOr<void> AnimationEffect::updateTiming(Optional<OptionalEffectTiming> timing)
362{
363 // 6.5.4. Updating the timing of an AnimationEffect
364 // https://drafts.csswg.org/web-animations/#updating-animationeffect-timing
365
366 // To update the timing properties of an animation effect, effect, from an EffectTiming or OptionalEffectTiming object, input, perform the following steps:
367 if (!timing)
368 return { };
369
370 // 1. If the iterationStart member of input is present and less than zero, throw a TypeError and abort this procedure.
371 if (timing->iterationStart) {
372 if (timing->iterationStart.value() < 0)
373 return Exception { TypeError };
374 }
375
376 // 2. If the iterations member of input is present, and less than zero or is the value NaN, throw a TypeError and abort this procedure.
377 if (timing->iterations) {
378 if (timing->iterations.value() < 0 || std::isnan(timing->iterations.value()))
379 return Exception { TypeError };
380 }
381
382 // 3. If the duration member of input is present, and less than zero or is the value NaN, throw a TypeError and abort this procedure.
383 // FIXME: should it not throw an exception on a string other than "auto"?
384 if (timing->duration) {
385 if (WTF::holds_alternative<double>(timing->duration.value())) {
386 auto durationAsDouble = WTF::get<double>(timing->duration.value());
387 if (durationAsDouble < 0 || std::isnan(durationAsDouble))
388 return Exception { TypeError };
389 } else {
390 if (WTF::get<String>(timing->duration.value()) != "auto")
391 return Exception { TypeError };
392 }
393 }
394
395 // 4. If the easing member of input is present but cannot be parsed using the <timing-function> production [CSS-EASING-1], throw a TypeError and abort this procedure.
396 if (!timing->easing.isNull()) {
397 auto timingFunctionResult = TimingFunction::createFromCSSText(timing->easing);
398 if (timingFunctionResult.hasException())
399 return timingFunctionResult.releaseException();
400 m_timingFunction = timingFunctionResult.returnValue();
401 }
402
403 // 5. Assign each member present in input to the corresponding timing property of effect as follows:
404 //
405 // delay → start delay
406 // endDelay → end delay
407 // fill → fill mode
408 // iterationStart → iteration start
409 // iterations → iteration count
410 // duration → iteration duration
411 // direction → playback direction
412 // easing → timing function
413
414 if (timing->delay)
415 m_delay = Seconds::fromMilliseconds(timing->delay.value());
416
417 if (timing->endDelay)
418 m_endDelay = Seconds::fromMilliseconds(timing->endDelay.value());
419
420 if (timing->fill)
421 m_fill = timing->fill.value();
422
423 if (timing->iterationStart)
424 m_iterationStart = timing->iterationStart.value();
425
426 if (timing->iterations)
427 m_iterations = timing->iterations.value();
428
429 if (timing->duration)
430 m_iterationDuration = WTF::holds_alternative<double>(timing->duration.value()) ? Seconds::fromMilliseconds(WTF::get<double>(timing->duration.value())) : 0_s;
431
432 if (timing->direction)
433 m_direction = timing->direction.value();
434
435 if (m_animation)
436 m_animation->effectTimingDidChange();
437
438 return { };
439}
440
441ExceptionOr<void> AnimationEffect::setIterationStart(double iterationStart)
442{
443 // https://drafts.csswg.org/web-animations-1/#dom-animationeffecttiming-iterationstart
444 // If an attempt is made to set this attribute to a value less than zero, a TypeError must
445 // be thrown and the value of the iterationStart attribute left unchanged.
446 if (iterationStart < 0)
447 return Exception { TypeError };
448
449 if (m_iterationStart == iterationStart)
450 return { };
451
452 m_iterationStart = iterationStart;
453
454 return { };
455}
456
457ExceptionOr<void> AnimationEffect::setIterations(double iterations)
458{
459 // https://drafts.csswg.org/web-animations-1/#dom-animationeffecttiming-iterations
460 // If an attempt is made to set this attribute to a value less than zero or a NaN value, a
461 // TypeError must be thrown and the value of the iterations attribute left unchanged.
462 if (iterations < 0 || std::isnan(iterations))
463 return Exception { TypeError };
464
465 if (m_iterations == iterations)
466 return { };
467
468 m_iterations = iterations;
469
470 return { };
471}
472
473void AnimationEffect::setDelay(const Seconds& delay)
474{
475 if (m_delay == delay)
476 return;
477
478 m_delay = delay;
479}
480
481void AnimationEffect::setEndDelay(const Seconds& endDelay)
482{
483 if (m_endDelay == endDelay)
484 return;
485
486 m_endDelay = endDelay;
487}
488
489void AnimationEffect::setFill(FillMode fill)
490{
491 if (m_fill == fill)
492 return;
493
494 m_fill = fill;
495}
496
497void AnimationEffect::setIterationDuration(const Seconds& duration)
498{
499 if (m_iterationDuration == duration)
500 return;
501
502 m_iterationDuration = duration;
503}
504
505void AnimationEffect::setDirection(PlaybackDirection direction)
506{
507 if (m_direction == direction)
508 return;
509
510 m_direction = direction;
511}
512
513void AnimationEffect::setTimingFunction(const RefPtr<TimingFunction>& timingFunction)
514{
515 m_timingFunction = timingFunction;
516}
517
518} // namespace WebCore
519