1/*
2 * Copyright (C) 2007, 2008, 2009 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 *
8 * 1. Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 * notice, this list of conditions and the following disclaimer in the
12 * documentation and/or other materials provided with the distribution.
13 * 3. Neither the name of Apple Inc. ("Apple") nor the names of
14 * its contributors may be used to endorse or promote products derived
15 * from this software without specific prior written permission.
16 *
17 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
18 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
19 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
20 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
21 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
22 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
23 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
24 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 */
28
29#include "config.h"
30#include "AnimationBase.h"
31
32#include "CSSAnimationControllerPrivate.h"
33#include "CSSPrimitiveValue.h"
34#include "CSSPropertyAnimation.h"
35#include "CompositeAnimation.h"
36#include "Document.h"
37#include "FloatConversion.h"
38#include "GeometryUtilities.h"
39#include "Logging.h"
40#include "RenderBox.h"
41#include "RenderStyle.h"
42#include "RenderView.h"
43#include <algorithm>
44#include <wtf/Ref.h>
45
46namespace WebCore {
47
48AnimationBase::AnimationBase(const Animation& animation, Element& element, CompositeAnimation& compositeAnimation)
49 : m_element(&element)
50 , m_compositeAnimation(&compositeAnimation)
51 , m_animation(const_cast<Animation&>(animation))
52{
53 // Compute the total duration
54 if (m_animation->iterationCount() > 0)
55 m_totalDuration = m_animation->duration() * m_animation->iterationCount();
56}
57
58AnimationBase::~AnimationBase() = default;
59
60const RenderStyle& AnimationBase::currentStyle() const
61{
62 if (auto* renderer = this->renderer())
63 return renderer->style();
64 return unanimatedStyle();
65}
66
67RenderElement* AnimationBase::renderer() const
68{
69 return m_element ? m_element->renderer() : nullptr;
70}
71
72void AnimationBase::clear()
73{
74 endAnimation();
75 m_element = nullptr;
76 m_compositeAnimation = nullptr;
77}
78
79void AnimationBase::setNeedsStyleRecalc(Element* element)
80{
81 if (!element || element->document().renderTreeBeingDestroyed())
82 return;
83
84 ASSERT(element->document().pageCacheState() == Document::NotInPageCache);
85 element->invalidateStyle();
86}
87
88double AnimationBase::duration() const
89{
90 return m_animation->duration();
91}
92
93bool AnimationBase::playStatePlaying() const
94{
95 return m_animation->playState() == AnimationPlayState::Playing;
96}
97
98bool AnimationBase::animationsMatch(const Animation& animation) const
99{
100 return m_animation->animationsMatch(animation);
101}
102
103#if !LOG_DISABLED
104static const char* nameForState(AnimationBase::AnimationState state)
105{
106 switch (state) {
107 case AnimationBase::AnimationState::New: return "New";
108 case AnimationBase::AnimationState::StartWaitTimer: return "StartWaitTimer";
109 case AnimationBase::AnimationState::StartWaitStyleAvailable: return "StartWaitStyleAvailable";
110 case AnimationBase::AnimationState::StartWaitResponse: return "StartWaitResponse";
111 case AnimationBase::AnimationState::Looping: return "Looping";
112 case AnimationBase::AnimationState::Ending: return "Ending";
113 case AnimationBase::AnimationState::PausedNew: return "PausedNew";
114 case AnimationBase::AnimationState::PausedWaitTimer: return "PausedWaitTimer";
115 case AnimationBase::AnimationState::PausedWaitStyleAvailable: return "PausedWaitStyleAvailable";
116 case AnimationBase::AnimationState::PausedWaitResponse: return "PausedWaitResponse";
117 case AnimationBase::AnimationState::PausedRun: return "PausedRun";
118 case AnimationBase::AnimationState::Done: return "Done";
119 case AnimationBase::AnimationState::FillingForwards: return "FillingForwards";
120 }
121 return "";
122}
123
124static const char* nameForStateInput(AnimationBase::AnimationStateInput input)
125{
126 switch (input) {
127 case AnimationBase::AnimationStateInput::MakeNew: return "MakeNew";
128 case AnimationBase::AnimationStateInput::StartAnimation: return "StartAnimation";
129 case AnimationBase::AnimationStateInput::RestartAnimation: return "RestartAnimation";
130 case AnimationBase::AnimationStateInput::StartTimerFired: return "StartTimerFired";
131 case AnimationBase::AnimationStateInput::StyleAvailable: return "StyleAvailable";
132 case AnimationBase::AnimationStateInput::StartTimeSet: return "StartTimeSet";
133 case AnimationBase::AnimationStateInput::LoopTimerFired: return "LoopTimerFired";
134 case AnimationBase::AnimationStateInput::EndTimerFired: return "EndTimerFired";
135 case AnimationBase::AnimationStateInput::PauseOverride: return "PauseOverride";
136 case AnimationBase::AnimationStateInput::ResumeOverride: return "ResumeOverride";
137 case AnimationBase::AnimationStateInput::PlayStateRunning: return "PlayStateRunning";
138 case AnimationBase::AnimationStateInput::PlayStatePaused: return "PlayStatePaused";
139 case AnimationBase::AnimationStateInput::EndAnimation: return "EndAnimation";
140 }
141 return "";
142}
143#endif
144
145void AnimationBase::updateStateMachine(AnimationStateInput input, double param)
146{
147 if (!m_compositeAnimation)
148 return;
149
150 // If we get AnimationStateInput::RestartAnimation then we force a new animation, regardless of state.
151 if (input == AnimationStateInput::MakeNew) {
152 if (m_animationState == AnimationState::StartWaitStyleAvailable)
153 m_compositeAnimation->animationController().removeFromAnimationsWaitingForStyle(*this);
154 LOG(Animations, "%p AnimationState %s -> New", this, nameForState(m_animationState));
155 m_animationState = AnimationState::New;
156 m_startTime = WTF::nullopt;
157 m_pauseTime = WTF::nullopt;
158 m_requestedStartTime = 0;
159 m_nextIterationDuration = WTF::nullopt;
160 endAnimation();
161 return;
162 }
163
164 if (input == AnimationStateInput::RestartAnimation) {
165 if (m_animationState == AnimationState::StartWaitStyleAvailable)
166 m_compositeAnimation->animationController().removeFromAnimationsWaitingForStyle(*this);
167 LOG(Animations, "%p AnimationState %s -> New", this, nameForState(m_animationState));
168 m_animationState = AnimationState::New;
169 m_startTime = WTF::nullopt;
170 m_pauseTime = WTF::nullopt;
171 m_requestedStartTime = 0;
172 m_nextIterationDuration = WTF::nullopt;
173 endAnimation();
174
175 if (!paused())
176 updateStateMachine(AnimationStateInput::StartAnimation, -1);
177 return;
178 }
179
180 if (input == AnimationStateInput::EndAnimation) {
181 if (m_animationState == AnimationState::StartWaitStyleAvailable)
182 m_compositeAnimation->animationController().removeFromAnimationsWaitingForStyle(*this);
183 LOG(Animations, "%p AnimationState %s -> Done", this, nameForState(m_animationState));
184 m_animationState = AnimationState::Done;
185 endAnimation();
186 return;
187 }
188
189 if (input == AnimationStateInput::PauseOverride) {
190 if (m_animationState == AnimationState::StartWaitResponse) {
191 // If we are in AnimationState::StartWaitResponse, the animation will get canceled before
192 // we get a response, so move to the next state.
193 endAnimation();
194 updateStateMachine(AnimationStateInput::StartTimeSet, beginAnimationUpdateTime());
195 }
196 return;
197 }
198
199 if (input == AnimationStateInput::ResumeOverride) {
200 if (m_animationState == AnimationState::Looping || m_animationState == AnimationState::Ending) {
201 // Start the animation
202 startAnimation(beginAnimationUpdateTime() - m_startTime.valueOr(0));
203 }
204 return;
205 }
206
207 // Execute state machine
208 switch (m_animationState) {
209 case AnimationState::New:
210 ASSERT(input == AnimationStateInput::StartAnimation || input == AnimationStateInput::PlayStateRunning || input == AnimationStateInput::PlayStatePaused);
211
212 if (input == AnimationStateInput::StartAnimation || input == AnimationStateInput::PlayStateRunning) {
213 m_requestedStartTime = beginAnimationUpdateTime();
214 LOG(Animations, "%p AnimationState %s -> StartWaitTimer", this, nameForState(m_animationState));
215 m_animationState = AnimationState::StartWaitTimer;
216 } else {
217 // We are pausing before we even started.
218 LOG(Animations, "%p AnimationState %s -> AnimationState::PausedNew", this, nameForState(m_animationState));
219 m_animationState = AnimationState::PausedNew;
220 m_pauseTime = WTF::nullopt;
221 }
222
223 break;
224 case AnimationState::StartWaitTimer:
225 ASSERT(input == AnimationStateInput::StartTimerFired || input == AnimationStateInput::PlayStatePaused);
226
227 if (input == AnimationStateInput::StartTimerFired) {
228 ASSERT(param >= 0);
229 // Start timer has fired, tell the animation to start and wait for it to respond with start time
230 LOG(Animations, "%p AnimationState %s -> StartWaitStyleAvailable (time is %f)", this, nameForState(m_animationState), param);
231 m_animationState = AnimationState::StartWaitStyleAvailable;
232 m_compositeAnimation->animationController().addToAnimationsWaitingForStyle(*this);
233
234 // Trigger a render so we can start the animation
235 if (m_element)
236 m_compositeAnimation->animationController().addElementChangeToDispatch(*m_element);
237 } else {
238 ASSERT(!paused());
239 // We're waiting for the start timer to fire and we got a pause. Cancel the timer, pause and wait
240 m_pauseTime = beginAnimationUpdateTime();
241 LOG(Animations, "%p AnimationState %s -> PausedWaitTimer", this, nameForState(m_animationState));
242 m_animationState = AnimationState::PausedWaitTimer;
243 }
244 break;
245 case AnimationState::StartWaitStyleAvailable:
246 ASSERT(input == AnimationStateInput::StyleAvailable || input == AnimationStateInput::PlayStatePaused);
247
248 if (input == AnimationStateInput::StyleAvailable) {
249 // Start timer has fired, tell the animation to start and wait for it to respond with start time
250 LOG(Animations, "%p AnimationState %s -> StartWaitResponse (time is %f)", this, nameForState(m_animationState), param);
251 m_animationState = AnimationState::StartWaitResponse;
252
253 overrideAnimations();
254
255 // Start the animation
256 if (overridden()) {
257 // We won't try to start accelerated animations if we are overridden and
258 // just move on to the next state.
259 LOG(Animations, "%p AnimationState %s -> StartWaitResponse", this, nameForState(m_animationState));
260 m_animationState = AnimationState::StartWaitResponse;
261 m_isAccelerated = false;
262 updateStateMachine(AnimationStateInput::StartTimeSet, beginAnimationUpdateTime());
263 } else {
264 double timeOffset = 0;
265 // If the value for 'animation-delay' is negative then the animation appears to have started in the past.
266 if (m_animation->delay() < 0)
267 timeOffset = -m_animation->delay();
268 bool started = startAnimation(timeOffset);
269
270 m_compositeAnimation->animationController().addToAnimationsWaitingForStartTimeResponse(*this, started);
271 m_isAccelerated = started;
272 }
273 } else {
274 // We're waiting for the style to be available and we got a pause. Pause and wait
275 m_pauseTime = beginAnimationUpdateTime();
276 LOG(Animations, "%p AnimationState %s -> PausedWaitStyleAvailable", this, nameForState(m_animationState));
277 m_animationState = AnimationState::PausedWaitStyleAvailable;
278 }
279 break;
280 case AnimationState::StartWaitResponse:
281 ASSERT(input == AnimationStateInput::StartTimeSet || input == AnimationStateInput::PlayStatePaused);
282
283 if (input == AnimationStateInput::StartTimeSet) {
284 ASSERT(param > -0.001); // Sometimes Core Animation gives us a beginTime slightly into the future.
285 LOG(Animations, "%p AnimationState %s -> StartTimeSet (time is %f)", this, nameForState(m_animationState), param);
286
287 // We have a start time, set it, unless the startTime is already set
288 if (!m_startTime) {
289 m_startTime = param;
290 // If the value for 'animation-delay' is negative then the animation appears to have started in the past.
291 if (m_animation->delay() < 0)
292 m_startTime = m_startTime.value() + m_animation->delay();
293 }
294
295 // Now that we know the start time, fire the start event.
296 onAnimationStart(0); // The elapsedTime is 0.
297
298 // Decide whether to go into looping or ending state
299 goIntoEndingOrLoopingState();
300
301 // Dispatch updateStyleIfNeeded so we can start the animation
302 if (m_element)
303 m_compositeAnimation->animationController().addElementChangeToDispatch(*m_element);
304 } else {
305 // We are pausing while waiting for a start response. Cancel the animation and wait. When
306 // we unpause, we will act as though the start timer just fired
307 m_pauseTime = beginAnimationUpdateTime();
308 pauseAnimation(beginAnimationUpdateTime() - m_startTime.valueOr(0));
309 LOG(Animations, "%p AnimationState %s -> PausedWaitResponse", this, nameForState(m_animationState));
310 m_animationState = AnimationState::PausedWaitResponse;
311 }
312 break;
313 case AnimationState::Looping:
314 ASSERT(input == AnimationStateInput::LoopTimerFired || input == AnimationStateInput::PlayStatePaused);
315
316 if (input == AnimationStateInput::LoopTimerFired) {
317 ASSERT(param >= 0);
318 LOG(Animations, "%p AnimationState %s -> LoopTimerFired (time is %f)", this, nameForState(m_animationState), param);
319
320 // Loop timer fired, loop again or end.
321 onAnimationIteration(param);
322
323 // Decide whether to go into looping or ending state
324 goIntoEndingOrLoopingState();
325 } else {
326 // We are pausing while running. Cancel the animation and wait
327 m_pauseTime = beginAnimationUpdateTime();
328 pauseAnimation(beginAnimationUpdateTime() - m_startTime.valueOr(0));
329 LOG(Animations, "%p AnimationState %s -> PausedRun", this, nameForState(m_animationState));
330 m_animationState = AnimationState::PausedRun;
331 }
332 break;
333 case AnimationState::Ending:
334#if !LOG_DISABLED
335 if (input != AnimationStateInput::EndTimerFired && input != AnimationStateInput::PlayStatePaused)
336 LOG_ERROR("State is AnimationState::Ending, but input is not AnimationStateInput::EndTimerFired or AnimationStateInput::PlayStatePaused. It is %s.", nameForStateInput(input));
337#endif
338 if (input == AnimationStateInput::EndTimerFired) {
339 ASSERT(param >= 0);
340 // End timer fired, finish up
341 onAnimationEnd(param);
342
343 LOG(Animations, "%p AnimationState %s -> Done (time is %f)", this, nameForState(m_animationState), param);
344 m_animationState = AnimationState::Done;
345
346 if (m_element) {
347 if (m_animation->fillsForwards()) {
348 LOG(Animations, "%p AnimationState %s -> FillingForwards", this, nameForState(m_animationState));
349 m_animationState = AnimationState::FillingForwards;
350 } else
351 resumeOverriddenAnimations();
352
353 // Fire off another style change so we can set the final value
354 if (m_element)
355 m_compositeAnimation->animationController().addElementChangeToDispatch(*m_element);
356 }
357 } else {
358 // We are pausing while running. Cancel the animation and wait
359 m_pauseTime = beginAnimationUpdateTime();
360 pauseAnimation(beginAnimationUpdateTime() - m_startTime.valueOr(0));
361 LOG(Animations, "%p AnimationState %s -> PausedRun", this, nameForState(m_animationState));
362 m_animationState = AnimationState::PausedRun;
363 }
364 // |this| may be deleted here
365 break;
366 case AnimationState::PausedWaitTimer:
367 ASSERT(input == AnimationStateInput::PlayStateRunning);
368 ASSERT(paused());
369 // Update the times
370 m_startTime = m_startTime.valueOr(0) + beginAnimationUpdateTime() - m_pauseTime.valueOr(0);
371 m_pauseTime = WTF::nullopt;
372
373 // we were waiting for the start timer to fire, go back and wait again
374 LOG(Animations, "%p AnimationState %s -> New", this, nameForState(m_animationState));
375 m_animationState = AnimationState::New;
376 updateStateMachine(AnimationStateInput::StartAnimation, 0);
377 break;
378 case AnimationState::PausedNew:
379 case AnimationState::PausedWaitResponse:
380 case AnimationState::PausedWaitStyleAvailable:
381 case AnimationState::PausedRun:
382 // We treat these two cases the same. The only difference is that, when we are in
383 // AnimationState::PausedWaitResponse, we don't yet have a valid startTime, so we send 0 to startAnimation.
384 // When the AnimationStateInput::StartTimeSet comes in and we were in AnimationState::PausedRun, we will notice
385 // that we have already set the startTime and will ignore it.
386 ASSERT(input == AnimationStateInput::PlayStatePaused || input == AnimationStateInput::PlayStateRunning || input == AnimationStateInput::StartTimeSet || input == AnimationStateInput::StyleAvailable);
387 ASSERT(paused());
388
389 if (input == AnimationStateInput::PlayStateRunning) {
390 if (m_animationState == AnimationState::PausedNew) {
391 // We were paused before we even started, and now we're supposed
392 // to start, so jump back to the New state and reset.
393 LOG(Animations, "%p AnimationState %s -> AnimationState::New", this, nameForState(m_animationState));
394 m_animationState = AnimationState::New;
395 m_pauseTime = WTF::nullopt;
396 updateStateMachine(input, param);
397 break;
398 }
399
400 // Update the times
401 if (m_animationState == AnimationState::PausedRun)
402 m_startTime = m_startTime.valueOr(0) + beginAnimationUpdateTime() - m_pauseTime.valueOr(0);
403 else
404 m_startTime = 0;
405
406 m_pauseTime = WTF::nullopt;
407
408 if (m_animationState == AnimationState::PausedWaitStyleAvailable) {
409 LOG(Animations, "%p AnimationState %s -> StartWaitStyleAvailable", this, nameForState(m_animationState));
410 m_animationState = AnimationState::StartWaitStyleAvailable;
411 } else {
412 // We were either running or waiting for a begin time response from the animation.
413 // Either way we need to restart the animation (possibly with an offset if we
414 // had already been running) and wait for it to start.
415 LOG(Animations, "%p AnimationState %s -> StartWaitResponse", this, nameForState(m_animationState));
416 m_animationState = AnimationState::StartWaitResponse;
417
418 // Start the animation
419 if (overridden()) {
420 // We won't try to start accelerated animations if we are overridden and
421 // just move on to the next state.
422 updateStateMachine(AnimationStateInput::StartTimeSet, beginAnimationUpdateTime());
423 m_isAccelerated = true;
424 } else {
425 bool started = startAnimation(beginAnimationUpdateTime() - m_startTime.valueOr(0));
426 m_compositeAnimation->animationController().addToAnimationsWaitingForStartTimeResponse(*this, started);
427 m_isAccelerated = started;
428 }
429 }
430 break;
431 }
432
433 if (input == AnimationStateInput::StartTimeSet) {
434 ASSERT(m_animationState == AnimationState::PausedWaitResponse);
435
436 // We are paused but we got the callback that notifies us that an accelerated animation started.
437 // We ignore the start time and just move into the paused-run state.
438 LOG(Animations, "%p AnimationState %s -> PausedRun (time is %f)", this, nameForState(m_animationState), param);
439 m_animationState = AnimationState::PausedRun;
440 ASSERT(!m_startTime);
441 m_startTime = param;
442 m_pauseTime = m_pauseTime.valueOr(0) + param;
443 break;
444 }
445
446 ASSERT(m_animationState == AnimationState::PausedNew || m_animationState == AnimationState::PausedWaitStyleAvailable);
447
448 if (input == AnimationStateInput::PlayStatePaused)
449 break;
450
451 ASSERT(input == AnimationStateInput::StyleAvailable);
452
453 // We are paused but we got the callback that notifies us that style has been updated.
454 // We move to the AnimationState::PausedWaitResponse state
455 LOG(Animations, "%p AnimationState %s -> PausedWaitResponse", this, nameForState(m_animationState));
456 m_animationState = AnimationState::PausedWaitResponse;
457 overrideAnimations();
458 break;
459 case AnimationState::FillingForwards:
460 case AnimationState::Done:
461 // We're done. Stay in this state until we are deleted
462 break;
463 }
464}
465
466void AnimationBase::fireAnimationEventsIfNeeded()
467{
468 if (!m_compositeAnimation)
469 return;
470
471 // If we are waiting for the delay time to expire and it has, go to the next state
472 if (m_animationState != AnimationState::StartWaitTimer && m_animationState != AnimationState::Looping && m_animationState != AnimationState::Ending)
473 return;
474
475 // We have to make sure to keep a ref to the this pointer, because it could get destroyed
476 // during an animation callback that might get called. Since the owner is a CompositeAnimation
477 // and it ref counts this object, we will keep a ref to that instead. That way the AnimationBase
478 // can still access the resources of its CompositeAnimation as needed.
479 Ref<AnimationBase> protectedThis(*this);
480 Ref<CompositeAnimation> protectCompositeAnimation(*m_compositeAnimation);
481
482 // Check for start timeout
483 if (m_animationState == AnimationState::StartWaitTimer) {
484 if (beginAnimationUpdateTime() - m_requestedStartTime >= m_animation->delay())
485 updateStateMachine(AnimationStateInput::StartTimerFired, 0);
486 return;
487 }
488
489 double elapsedDuration = beginAnimationUpdateTime() - m_startTime.valueOr(0);
490
491 // FIXME: we need to ensure that elapsedDuration is never < 0. If it is, this suggests that
492 // we had a recalcStyle() outside of beginAnimationUpdate()/endAnimationUpdate().
493 // Also check in getTimeToNextEvent().
494 elapsedDuration = std::max(elapsedDuration, 0.0);
495
496 // Check for end timeout
497 if (m_totalDuration && elapsedDuration >= m_totalDuration.value()) {
498 // We may still be in AnimationState::Looping if we've managed to skip a
499 // whole iteration, in which case we should jump to the end state.
500 LOG(Animations, "%p AnimationState %s -> Ending", this, nameForState(m_animationState));
501 m_animationState = AnimationState::Ending;
502
503 // Fire an end event
504 updateStateMachine(AnimationStateInput::EndTimerFired, m_totalDuration.value());
505 } else {
506 // Check for iteration timeout
507 if (!m_nextIterationDuration) {
508 // Hasn't been set yet, set it
509 double durationLeft = m_animation->duration() - fmod(elapsedDuration, m_animation->duration());
510 m_nextIterationDuration = elapsedDuration + durationLeft;
511 }
512
513 if (elapsedDuration >= m_nextIterationDuration) {
514 // Set to the next iteration
515 double previous = m_nextIterationDuration.value();
516 double durationLeft = m_animation->duration() - fmod(elapsedDuration, m_animation->duration());
517 m_nextIterationDuration = elapsedDuration + durationLeft;
518
519 // Send the event
520 updateStateMachine(AnimationStateInput::LoopTimerFired, previous);
521 }
522 }
523}
524
525void AnimationBase::updatePlayState(AnimationPlayState playState)
526{
527 if (!m_compositeAnimation)
528 return;
529
530 // When we get here, we can have one of 4 desired states: running, paused, suspended, paused & suspended.
531 // The state machine can be in one of two states: running, paused.
532 // Set the state machine to the desired state.
533 bool pause = playState == AnimationPlayState::Paused || m_compositeAnimation->isSuspended();
534
535 if (pause == paused() && !isNew())
536 return;
537
538 updateStateMachine(pause ? AnimationStateInput::PlayStatePaused : AnimationStateInput::PlayStateRunning, -1);
539}
540
541Optional<Seconds> AnimationBase::timeToNextService()
542{
543 // Returns the time at which next service is required. WTF::nullopt means no service is required. 0 means
544 // service is required now, and > 0 means service is required that many seconds in the future.
545 if (paused() || isNew() || postActive() || fillingForwards())
546 return WTF::nullopt;
547
548 if (m_animationState == AnimationState::StartWaitTimer) {
549 double timeFromNow = m_animation->delay() - (beginAnimationUpdateTime() - m_requestedStartTime);
550 return std::max(Seconds { timeFromNow }, 0_s);
551 }
552
553 fireAnimationEventsIfNeeded();
554
555 // In all other cases, we need service right away.
556 return 0_s;
557}
558
559// Compute the fractional time, taking into account direction.
560// There is no need to worry about iterations, we assume that we would have
561// short circuited above if we were done.
562
563double AnimationBase::fractionalTime(double scale, double elapsedTime, double offset) const
564{
565 double fractionalTime = m_animation->duration() ? (elapsedTime / m_animation->duration()) : 1;
566 // FIXME: startTime can be before the current animation "frame" time. This is to sync with the frame time
567 // concept in AnimationTimeController. So we need to somehow sync the two. Until then, the possible
568 // error is small and will probably not be noticeable. Until we fix this, remove the assert.
569 // https://bugs.webkit.org/show_bug.cgi?id=52037
570 // ASSERT(fractionalTime >= 0);
571 if (fractionalTime < 0)
572 fractionalTime = 0;
573
574 int integralTime = static_cast<int>(fractionalTime);
575 const int integralIterationCount = static_cast<int>(m_animation->iterationCount());
576 const bool iterationCountHasFractional = m_animation->iterationCount() - integralIterationCount;
577 if (m_animation->iterationCount() != Animation::IterationCountInfinite && !iterationCountHasFractional)
578 integralTime = std::min(integralTime, integralIterationCount - 1);
579
580 fractionalTime -= integralTime;
581
582 if (((m_animation->direction() == Animation::AnimationDirectionAlternate) && (integralTime & 1))
583 || ((m_animation->direction() == Animation::AnimationDirectionAlternateReverse) && !(integralTime & 1))
584 || m_animation->direction() == Animation::AnimationDirectionReverse)
585 fractionalTime = 1 - fractionalTime;
586
587 if (scale != 1 || offset)
588 fractionalTime = (fractionalTime - offset) * scale;
589
590 return fractionalTime;
591}
592
593double AnimationBase::progress(double scale, double offset, const TimingFunction* timingFunction) const
594{
595 if (preActive())
596 return 0;
597
598 if (postActive())
599 return 1;
600
601 double elapsedTime = getElapsedTime();
602
603 double duration = m_animation->duration();
604 if (m_animation->iterationCount() > 0)
605 duration *= m_animation->iterationCount();
606
607 if (fillingForwards())
608 elapsedTime = duration;
609
610 double fractionalTime = this->fractionalTime(scale, elapsedTime, offset);
611
612 if (m_animation->iterationCount() > 0 && elapsedTime >= duration) {
613 if (WTF::isIntegral(fractionalTime))
614 return fractionalTime;
615 }
616
617 if (!timingFunction)
618 timingFunction = m_animation->timingFunction();
619
620 return timingFunction->transformTime(fractionalTime, m_animation->duration());
621}
622
623void AnimationBase::getTimeToNextEvent(Seconds& time, bool& isLooping) const
624{
625 // Decide when the end or loop event needs to fire
626 const double elapsedDuration = std::max(beginAnimationUpdateTime() - m_startTime.valueOr(0), 0.0);
627 double durationLeft = 0;
628 double nextIterationTime = m_totalDuration.valueOr(0);
629
630 if (!m_totalDuration || elapsedDuration < m_totalDuration.value()) {
631 durationLeft = m_animation->duration() > 0 ? (m_animation->duration() - fmod(elapsedDuration, m_animation->duration())) : 0;
632 nextIterationTime = elapsedDuration + durationLeft;
633 }
634
635 if (!m_totalDuration || nextIterationTime < m_totalDuration.value()) {
636 // We are not at the end yet
637 ASSERT(nextIterationTime > 0);
638 isLooping = true;
639 } else {
640 // We are at the end
641 isLooping = false;
642 }
643
644 time = Seconds { durationLeft };
645}
646
647void AnimationBase::goIntoEndingOrLoopingState()
648{
649 Seconds t;
650 bool isLooping;
651 getTimeToNextEvent(t, isLooping);
652 LOG(Animations, "%p AnimationState %s -> %s", this, nameForState(m_animationState), isLooping ? "Looping" : "Ending");
653 m_animationState = isLooping ? AnimationState::Looping : AnimationState::Ending;
654}
655
656void AnimationBase::freezeAtTime(double t)
657{
658 if (!m_compositeAnimation)
659 return;
660
661 if (!m_startTime) {
662 // If we haven't started yet, make it as if we started.
663 LOG(Animations, "%p AnimationState %s -> StartWaitResponse", this, nameForState(m_animationState));
664 m_animationState = AnimationState::StartWaitResponse;
665 onAnimationStartResponse(MonotonicTime::now());
666 }
667
668 ASSERT(m_startTime); // If m_startTime is zero, we haven't started yet, so we'll get a bad pause time.
669 if (t <= m_animation->delay())
670 m_pauseTime = m_startTime.valueOr(0);
671 else
672 m_pauseTime = m_startTime.valueOr(0) + t - m_animation->delay();
673
674 if (auto* renderer = this->renderer())
675 renderer->suspendAnimations(MonotonicTime::fromRawSeconds(m_pauseTime.value()));
676}
677
678double AnimationBase::beginAnimationUpdateTime() const
679{
680 if (!m_compositeAnimation)
681 return 0;
682
683 return m_compositeAnimation->animationController().beginAnimationUpdateTime().secondsSinceEpoch().seconds();
684}
685
686double AnimationBase::getElapsedTime() const
687{
688 if (paused()) {
689 double delayOffset = (!m_startTime && m_animation->delay() < 0) ? m_animation->delay() : 0;
690 return m_pauseTime.valueOr(0) - m_startTime.valueOr(0) - delayOffset;
691 }
692
693 if (!m_startTime)
694 return 0;
695
696 if (postActive() || fillingForwards())
697 return m_totalDuration.valueOr(0);
698
699 return beginAnimationUpdateTime() - m_startTime.valueOr(0);
700}
701
702void AnimationBase::setElapsedTime(double time)
703{
704 // FIXME: implement this method
705 UNUSED_PARAM(time);
706}
707
708void AnimationBase::play()
709{
710 // FIXME: implement this method
711}
712
713void AnimationBase::pause()
714{
715 // FIXME: implement this method
716}
717
718static bool containsRotation(const Vector<RefPtr<TransformOperation>>& operations)
719{
720 for (const auto& operation : operations) {
721 if (operation->type() == TransformOperation::ROTATE)
722 return true;
723 }
724 return false;
725}
726
727bool AnimationBase::computeTransformedExtentViaTransformList(const FloatRect& rendererBox, const RenderStyle& style, LayoutRect& bounds) const
728{
729 FloatRect floatBounds = bounds;
730 FloatPoint transformOrigin;
731
732 bool applyTransformOrigin = containsRotation(style.transform().operations()) || style.transform().affectedByTransformOrigin();
733 if (applyTransformOrigin) {
734 transformOrigin.setX(rendererBox.x() + floatValueForLength(style.transformOriginX(), rendererBox.width()));
735 transformOrigin.setY(rendererBox.y() + floatValueForLength(style.transformOriginY(), rendererBox.height()));
736 // Ignore transformOriginZ because we'll bail if we encounter any 3D transforms.
737
738 floatBounds.moveBy(-transformOrigin);
739 }
740
741 for (const auto& operation : style.transform().operations()) {
742 if (operation->type() == TransformOperation::ROTATE) {
743 // For now, just treat this as a full rotation. This could take angle into account to reduce inflation.
744 floatBounds = boundsOfRotatingRect(floatBounds);
745 } else {
746 TransformationMatrix transform;
747 operation->apply(transform, rendererBox.size());
748 if (!transform.isAffine())
749 return false;
750
751 if (operation->type() == TransformOperation::MATRIX || operation->type() == TransformOperation::MATRIX_3D) {
752 TransformationMatrix::Decomposed2Type toDecomp;
753 transform.decompose2(toDecomp);
754 // Any rotation prevents us from using a simple start/end rect union.
755 if (toDecomp.angle)
756 return false;
757 }
758
759 floatBounds = transform.mapRect(floatBounds);
760 }
761 }
762
763 if (applyTransformOrigin)
764 floatBounds.moveBy(transformOrigin);
765
766 bounds = LayoutRect(floatBounds);
767 return true;
768}
769
770bool AnimationBase::computeTransformedExtentViaMatrix(const FloatRect& rendererBox, const RenderStyle& style, LayoutRect& bounds) const
771{
772 TransformationMatrix transform;
773 style.applyTransform(transform, rendererBox, RenderStyle::IncludeTransformOrigin);
774 if (!transform.isAffine())
775 return false;
776
777 TransformationMatrix::Decomposed2Type fromDecomp;
778 transform.decompose2(fromDecomp);
779 // Any rotation prevents us from using a simple start/end rect union.
780 if (fromDecomp.angle)
781 return false;
782
783 bounds = LayoutRect(transform.mapRect(bounds));
784 return true;
785
786}
787
788} // namespace WebCore
789