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 | |
46 | namespace WebCore { |
47 | |
48 | AnimationBase::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 | |
58 | AnimationBase::~AnimationBase() = default; |
59 | |
60 | const RenderStyle& AnimationBase::currentStyle() const |
61 | { |
62 | if (auto* renderer = this->renderer()) |
63 | return renderer->style(); |
64 | return unanimatedStyle(); |
65 | } |
66 | |
67 | RenderElement* AnimationBase::renderer() const |
68 | { |
69 | return m_element ? m_element->renderer() : nullptr; |
70 | } |
71 | |
72 | void AnimationBase::clear() |
73 | { |
74 | endAnimation(); |
75 | m_element = nullptr; |
76 | m_compositeAnimation = nullptr; |
77 | } |
78 | |
79 | void 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 | |
88 | double AnimationBase::duration() const |
89 | { |
90 | return m_animation->duration(); |
91 | } |
92 | |
93 | bool AnimationBase::playStatePlaying() const |
94 | { |
95 | return m_animation->playState() == AnimationPlayState::Playing; |
96 | } |
97 | |
98 | bool AnimationBase::animationsMatch(const Animation& animation) const |
99 | { |
100 | return m_animation->animationsMatch(animation); |
101 | } |
102 | |
103 | #if !LOG_DISABLED |
104 | static 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 | |
124 | static 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 | |
145 | void 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 | |
466 | void 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 | |
525 | void 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 | |
541 | Optional<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 | |
563 | double 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 | |
593 | double 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 | |
623 | void 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 | |
647 | void 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 | |
656 | void 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 | |
678 | double AnimationBase::beginAnimationUpdateTime() const |
679 | { |
680 | if (!m_compositeAnimation) |
681 | return 0; |
682 | |
683 | return m_compositeAnimation->animationController().beginAnimationUpdateTime().secondsSinceEpoch().seconds(); |
684 | } |
685 | |
686 | double 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 | |
702 | void AnimationBase::setElapsedTime(double time) |
703 | { |
704 | // FIXME: implement this method |
705 | UNUSED_PARAM(time); |
706 | } |
707 | |
708 | void AnimationBase::play() |
709 | { |
710 | // FIXME: implement this method |
711 | } |
712 | |
713 | void AnimationBase::pause() |
714 | { |
715 | // FIXME: implement this method |
716 | } |
717 | |
718 | static 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 | |
727 | bool 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 | |
770 | bool 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 | |