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 | |
33 | namespace WebCore { |
34 | |
35 | AnimationEffect::AnimationEffect() |
36 | : m_timingFunction(LinearTimingFunction::create()) |
37 | { |
38 | } |
39 | |
40 | AnimationEffect::~AnimationEffect() |
41 | { |
42 | } |
43 | |
44 | EffectTiming 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 | |
61 | BasicEffectTiming 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 | |
175 | ComputedEffectTiming 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 | |
361 | ExceptionOr<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 | |
441 | ExceptionOr<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 | |
457 | ExceptionOr<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 | |
473 | void AnimationEffect::setDelay(const Seconds& delay) |
474 | { |
475 | if (m_delay == delay) |
476 | return; |
477 | |
478 | m_delay = delay; |
479 | } |
480 | |
481 | void AnimationEffect::setEndDelay(const Seconds& endDelay) |
482 | { |
483 | if (m_endDelay == endDelay) |
484 | return; |
485 | |
486 | m_endDelay = endDelay; |
487 | } |
488 | |
489 | void AnimationEffect::setFill(FillMode fill) |
490 | { |
491 | if (m_fill == fill) |
492 | return; |
493 | |
494 | m_fill = fill; |
495 | } |
496 | |
497 | void AnimationEffect::setIterationDuration(const Seconds& duration) |
498 | { |
499 | if (m_iterationDuration == duration) |
500 | return; |
501 | |
502 | m_iterationDuration = duration; |
503 | } |
504 | |
505 | void AnimationEffect::setDirection(PlaybackDirection direction) |
506 | { |
507 | if (m_direction == direction) |
508 | return; |
509 | |
510 | m_direction = direction; |
511 | } |
512 | |
513 | void AnimationEffect::setTimingFunction(const RefPtr<TimingFunction>& timingFunction) |
514 | { |
515 | m_timingFunction = timingFunction; |
516 | } |
517 | |
518 | } // namespace WebCore |
519 | |