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 "CSSAnimationController.h" |
31 | |
32 | #include "AnimationBase.h" |
33 | #include "AnimationEvent.h" |
34 | #include "CSSAnimationControllerPrivate.h" |
35 | #include "CSSPropertyAnimation.h" |
36 | #include "CSSPropertyParser.h" |
37 | #include "CompositeAnimation.h" |
38 | #include "EventNames.h" |
39 | #include "Frame.h" |
40 | #include "FrameView.h" |
41 | #include "Logging.h" |
42 | #include "PseudoElement.h" |
43 | #include "RenderView.h" |
44 | #include "TransitionEvent.h" |
45 | #include "WebKitAnimationEvent.h" |
46 | #include "WebKitTransitionEvent.h" |
47 | |
48 | namespace WebCore { |
49 | |
50 | // Allow a little more than 60fps to make sure we can at least hit that frame rate. |
51 | static const Seconds animationTimerDelay { 15_ms }; |
52 | // Allow a little more than 30fps to make sure we can at least hit that frame rate. |
53 | static const Seconds animationTimerThrottledDelay { 30_ms }; |
54 | |
55 | class AnimationPrivateUpdateBlock { |
56 | public: |
57 | AnimationPrivateUpdateBlock(CSSAnimationControllerPrivate& animationController) |
58 | : m_animationController(animationController) |
59 | { |
60 | m_animationController.beginAnimationUpdate(); |
61 | } |
62 | |
63 | ~AnimationPrivateUpdateBlock() |
64 | { |
65 | m_animationController.endAnimationUpdate(); |
66 | } |
67 | |
68 | CSSAnimationControllerPrivate& m_animationController; |
69 | }; |
70 | |
71 | CSSAnimationControllerPrivate::CSSAnimationControllerPrivate(Frame& frame) |
72 | : m_animationTimer(*this, &CSSAnimationControllerPrivate::animationTimerFired) |
73 | , m_updateStyleIfNeededDispatcher(*this, &CSSAnimationControllerPrivate::updateStyleIfNeededDispatcherFired) |
74 | , m_frame(frame) |
75 | , m_beginAnimationUpdateCount(0) |
76 | , m_waitingForAsyncStartNotification(false) |
77 | , m_allowsNewAnimationsWhileSuspended(false) |
78 | { |
79 | } |
80 | |
81 | CSSAnimationControllerPrivate::~CSSAnimationControllerPrivate() = default; |
82 | |
83 | CompositeAnimation& CSSAnimationControllerPrivate::ensureCompositeAnimation(Element& element) |
84 | { |
85 | element.setHasCSSAnimation(); |
86 | |
87 | auto result = m_compositeAnimations.ensure(&element, [&] { |
88 | return CompositeAnimation::create(*this); |
89 | }); |
90 | |
91 | if (animationsAreSuspendedForDocument(&element.document())) |
92 | result.iterator->value->suspendAnimations(); |
93 | |
94 | return *result.iterator->value; |
95 | } |
96 | |
97 | bool CSSAnimationControllerPrivate::clear(Element& element) |
98 | { |
99 | ASSERT(element.hasCSSAnimation() == m_compositeAnimations.contains(&element)); |
100 | |
101 | if (!element.hasCSSAnimation()) |
102 | return false; |
103 | element.clearHasCSSAnimation(); |
104 | |
105 | auto it = m_compositeAnimations.find(&element); |
106 | if (it == m_compositeAnimations.end()) |
107 | return false; |
108 | |
109 | LOG(Animations, "CSSAnimationControllerPrivate %p clear: %p" , this, &element); |
110 | |
111 | m_eventsToDispatch.removeAllMatching([&] (const EventToDispatch& info) { |
112 | return info.element.ptr() == &element; |
113 | }); |
114 | |
115 | m_elementChangesToDispatch.removeAllMatching([&](auto& currentElement) { |
116 | return currentElement.ptr() == &element; |
117 | }); |
118 | |
119 | // Return false if we didn't do anything OR we are suspended (so we don't try to |
120 | // do a invalidateStyleForSubtree() when suspended). |
121 | // FIXME: The code below does the opposite of what the comment above says regarding suspended state. |
122 | auto& animation = *it->value; |
123 | bool result = animation.isSuspended(); |
124 | animation.clearElement(); |
125 | |
126 | m_compositeAnimations.remove(it); |
127 | |
128 | return result; |
129 | } |
130 | |
131 | Optional<Seconds> CSSAnimationControllerPrivate::updateAnimations(SetChanged callSetChanged/* = DoNotCallSetChanged*/) |
132 | { |
133 | AnimationPrivateUpdateBlock updateBlock(*this); |
134 | Optional<Seconds> timeToNextService; |
135 | bool calledSetChanged = false; |
136 | |
137 | for (auto& compositeAnimation : m_compositeAnimations) { |
138 | CompositeAnimation& animation = *compositeAnimation.value; |
139 | if (!animation.isSuspended() && animation.hasAnimations()) { |
140 | Optional<Seconds> t = animation.timeToNextService(); |
141 | if (t && (!timeToNextService || t.value() < timeToNextService.value())) |
142 | timeToNextService = t.value(); |
143 | if (timeToNextService && timeToNextService.value() == 0_s) { |
144 | if (callSetChanged != CallSetChanged) |
145 | break; |
146 | |
147 | Element& element = *compositeAnimation.key; |
148 | ASSERT(element.document().pageCacheState() == Document::NotInPageCache); |
149 | element.invalidateStyle(); |
150 | calledSetChanged = true; |
151 | } |
152 | } |
153 | } |
154 | |
155 | if (calledSetChanged) |
156 | m_frame.document()->updateStyleIfNeeded(); |
157 | |
158 | return timeToNextService; |
159 | } |
160 | |
161 | void CSSAnimationControllerPrivate::updateAnimationTimerForElement(Element& element) |
162 | { |
163 | Optional<Seconds> timeToNextService; |
164 | |
165 | const CompositeAnimation* compositeAnimation = m_compositeAnimations.get(&element); |
166 | if (!compositeAnimation->isSuspended() && compositeAnimation->hasAnimations()) |
167 | timeToNextService = compositeAnimation->timeToNextService(); |
168 | |
169 | if (!timeToNextService) |
170 | return; |
171 | |
172 | if (m_animationTimer.isActive() && (m_animationTimer.repeatInterval() || m_animationTimer.nextFireInterval() <= timeToNextService.value())) |
173 | return; |
174 | |
175 | m_animationTimer.startOneShot(timeToNextService.value()); |
176 | } |
177 | |
178 | void CSSAnimationControllerPrivate::updateAnimationTimer(SetChanged callSetChanged/* = DoNotCallSetChanged*/) |
179 | { |
180 | Optional<Seconds> timeToNextService = updateAnimations(callSetChanged); |
181 | |
182 | LOG(Animations, "updateAnimationTimer: timeToNextService is %.2f" , timeToNextService.valueOr(Seconds { -1 }).value()); |
183 | |
184 | // If we don't need service, we want to make sure the timer is no longer running |
185 | if (!timeToNextService) { |
186 | if (m_animationTimer.isActive()) |
187 | m_animationTimer.stop(); |
188 | return; |
189 | } |
190 | |
191 | // If we want service immediately, we start a repeating timer to reduce the overhead of starting |
192 | if (!timeToNextService.value()) { |
193 | auto* page = m_frame.page(); |
194 | bool shouldThrottle = page && page->isLowPowerModeEnabled(); |
195 | Seconds delay = shouldThrottle ? animationTimerThrottledDelay : animationTimerDelay; |
196 | |
197 | if (!m_animationTimer.isActive() || m_animationTimer.repeatInterval() != delay) |
198 | m_animationTimer.startRepeating(delay); |
199 | return; |
200 | } |
201 | |
202 | // Otherwise, we want to start a one-shot timer so we get here again |
203 | m_animationTimer.startOneShot(timeToNextService.value()); |
204 | } |
205 | |
206 | void CSSAnimationControllerPrivate::updateStyleIfNeededDispatcherFired() |
207 | { |
208 | fireEventsAndUpdateStyle(); |
209 | } |
210 | |
211 | void CSSAnimationControllerPrivate::fireEventsAndUpdateStyle() |
212 | { |
213 | // Protect the frame from getting destroyed in the event handler |
214 | Ref<Frame> protector(m_frame); |
215 | |
216 | bool updateStyle = !m_eventsToDispatch.isEmpty() || !m_elementChangesToDispatch.isEmpty(); |
217 | |
218 | // fire all the events |
219 | Vector<EventToDispatch> eventsToDispatch = WTFMove(m_eventsToDispatch); |
220 | for (auto& event : eventsToDispatch) { |
221 | Element& element = event.element; |
222 | if (event.eventType == eventNames().transitionendEvent) |
223 | element.dispatchEvent(TransitionEvent::create(event.eventType, event.name, event.elapsedTime, PseudoElement::pseudoElementNameForEvents(element.pseudoId()))); |
224 | else |
225 | element.dispatchEvent(AnimationEvent::create(event.eventType, event.name, event.elapsedTime)); |
226 | } |
227 | |
228 | for (auto& change : m_elementChangesToDispatch) |
229 | change->invalidateStyle(); |
230 | |
231 | m_elementChangesToDispatch.clear(); |
232 | |
233 | if (updateStyle) |
234 | m_frame.document()->updateStyleIfNeeded(); |
235 | } |
236 | |
237 | void CSSAnimationControllerPrivate::startUpdateStyleIfNeededDispatcher() |
238 | { |
239 | if (!m_updateStyleIfNeededDispatcher.isActive()) |
240 | m_updateStyleIfNeededDispatcher.startOneShot(0_s); |
241 | } |
242 | |
243 | void CSSAnimationControllerPrivate::addEventToDispatch(Element& element, const AtomicString& eventType, const String& name, double elapsedTime) |
244 | { |
245 | m_eventsToDispatch.append({ element, eventType, name, elapsedTime }); |
246 | startUpdateStyleIfNeededDispatcher(); |
247 | } |
248 | |
249 | void CSSAnimationControllerPrivate::addElementChangeToDispatch(Element& element) |
250 | { |
251 | m_elementChangesToDispatch.append(element); |
252 | ASSERT(m_elementChangesToDispatch.last()->document().pageCacheState() == Document::NotInPageCache); |
253 | startUpdateStyleIfNeededDispatcher(); |
254 | } |
255 | |
256 | void CSSAnimationControllerPrivate::animationFrameCallbackFired() |
257 | { |
258 | Optional<Seconds> timeToNextService = updateAnimations(CallSetChanged); |
259 | |
260 | if (timeToNextService) |
261 | m_frame.document()->view()->scheduleAnimation(); |
262 | } |
263 | |
264 | void CSSAnimationControllerPrivate::animationTimerFired() |
265 | { |
266 | // We need to keep the frame alive, since it owns us. |
267 | Ref<Frame> protector(m_frame); |
268 | |
269 | // The animation timer might fire before the layout timer, in |
270 | // which case we might create some animations with incorrect |
271 | // values if we don't layout first. |
272 | if (m_requiresLayout) { |
273 | if (auto* frameView = m_frame.document()->view()) { |
274 | if (frameView->needsLayout()) |
275 | frameView->forceLayout(); |
276 | } |
277 | m_requiresLayout = false; |
278 | } |
279 | |
280 | // Make sure animationUpdateTime is updated, so that it is current even if no |
281 | // styleChange has happened (e.g. accelerated animations) |
282 | AnimationPrivateUpdateBlock updateBlock(*this); |
283 | |
284 | // When the timer fires, all we do is call setChanged on all DOM nodes with running animations and then do an immediate |
285 | // updateStyleIfNeeded. It will then call back to us with new information. |
286 | updateAnimationTimer(CallSetChanged); |
287 | |
288 | // Fire events right away, to avoid a flash of unanimated style after an animation completes, and before |
289 | // the 'end' event fires. |
290 | fireEventsAndUpdateStyle(); |
291 | } |
292 | |
293 | bool CSSAnimationControllerPrivate::isRunningAnimationOnRenderer(RenderElement& renderer, CSSPropertyID property) const |
294 | { |
295 | if (!renderer.element()) |
296 | return false; |
297 | auto* animation = m_compositeAnimations.get(renderer.element()); |
298 | if (!animation) |
299 | return false; |
300 | return animation->isAnimatingProperty(property, false); |
301 | } |
302 | |
303 | bool CSSAnimationControllerPrivate::isRunningAcceleratedAnimationOnRenderer(RenderElement& renderer, CSSPropertyID property) const |
304 | { |
305 | if (!renderer.element()) |
306 | return false; |
307 | auto* animation = m_compositeAnimations.get(renderer.element()); |
308 | if (!animation) |
309 | return false; |
310 | return animation->isAnimatingProperty(property, true); |
311 | } |
312 | |
313 | void CSSAnimationControllerPrivate::updateThrottlingState() |
314 | { |
315 | updateAnimationTimer(); |
316 | |
317 | for (auto* childFrame = m_frame.tree().firstChild(); childFrame; childFrame = childFrame->tree().nextSibling()) |
318 | childFrame->animation().updateThrottlingState(); |
319 | } |
320 | |
321 | Seconds CSSAnimationControllerPrivate::animationInterval() const |
322 | { |
323 | if (!m_animationTimer.isActive()) |
324 | return Seconds { INFINITY }; |
325 | |
326 | return Seconds { m_animationTimer.repeatInterval() }; |
327 | } |
328 | |
329 | void CSSAnimationControllerPrivate::suspendAnimations() |
330 | { |
331 | if (isSuspended()) |
332 | return; |
333 | |
334 | suspendAnimationsForDocument(m_frame.document()); |
335 | |
336 | // Traverse subframes |
337 | for (Frame* child = m_frame.tree().firstChild(); child; child = child->tree().nextSibling()) |
338 | child->animation().suspendAnimations(); |
339 | |
340 | m_isSuspended = true; |
341 | } |
342 | |
343 | void CSSAnimationControllerPrivate::resumeAnimations() |
344 | { |
345 | if (!isSuspended()) |
346 | return; |
347 | |
348 | resumeAnimationsForDocument(m_frame.document()); |
349 | |
350 | // Traverse subframes |
351 | for (Frame* child = m_frame.tree().firstChild(); child; child = child->tree().nextSibling()) |
352 | child->animation().resumeAnimations(); |
353 | |
354 | m_isSuspended = false; |
355 | } |
356 | |
357 | bool CSSAnimationControllerPrivate::animationsAreSuspendedForDocument(Document* document) |
358 | { |
359 | return isSuspended() || m_suspendedDocuments.contains(document); |
360 | } |
361 | |
362 | void CSSAnimationControllerPrivate::detachFromDocument(Document* document) |
363 | { |
364 | m_suspendedDocuments.remove(document); |
365 | } |
366 | |
367 | void CSSAnimationControllerPrivate::suspendAnimationsForDocument(Document* document) |
368 | { |
369 | if (animationsAreSuspendedForDocument(document)) |
370 | return; |
371 | |
372 | m_suspendedDocuments.add(document); |
373 | |
374 | AnimationPrivateUpdateBlock updateBlock(*this); |
375 | |
376 | for (auto& animation : m_compositeAnimations) { |
377 | if (&animation.key->document() == document) |
378 | animation.value->suspendAnimations(); |
379 | } |
380 | |
381 | updateAnimationTimer(); |
382 | } |
383 | |
384 | void CSSAnimationControllerPrivate::resumeAnimationsForDocument(Document* document) |
385 | { |
386 | if (!animationsAreSuspendedForDocument(document)) |
387 | return; |
388 | |
389 | detachFromDocument(document); |
390 | |
391 | AnimationPrivateUpdateBlock updateBlock(*this); |
392 | |
393 | for (auto& animation : m_compositeAnimations) { |
394 | if (&animation.key->document() == document) |
395 | animation.value->resumeAnimations(); |
396 | } |
397 | |
398 | updateAnimationTimer(); |
399 | } |
400 | |
401 | void CSSAnimationControllerPrivate::startAnimationsIfNotSuspended(Document* document) |
402 | { |
403 | if (!animationsAreSuspendedForDocument(document) || allowsNewAnimationsWhileSuspended()) |
404 | resumeAnimationsForDocument(document); |
405 | } |
406 | |
407 | void CSSAnimationControllerPrivate::setAllowsNewAnimationsWhileSuspended(bool allowed) |
408 | { |
409 | m_allowsNewAnimationsWhileSuspended = allowed; |
410 | } |
411 | |
412 | bool CSSAnimationControllerPrivate::pauseAnimationAtTime(Element& element, const AtomicString& name, double t) |
413 | { |
414 | CompositeAnimation& compositeAnimation = ensureCompositeAnimation(element); |
415 | if (compositeAnimation.pauseAnimationAtTime(name, t)) { |
416 | element.invalidateStyle(); |
417 | startUpdateStyleIfNeededDispatcher(); |
418 | return true; |
419 | } |
420 | |
421 | return false; |
422 | } |
423 | |
424 | bool CSSAnimationControllerPrivate::pauseTransitionAtTime(Element& element, const String& property, double t) |
425 | { |
426 | CompositeAnimation& compositeAnimation = ensureCompositeAnimation(element); |
427 | if (compositeAnimation.pauseTransitionAtTime(cssPropertyID(property), t)) { |
428 | element.invalidateStyle(); |
429 | startUpdateStyleIfNeededDispatcher(); |
430 | return true; |
431 | } |
432 | |
433 | return false; |
434 | } |
435 | |
436 | MonotonicTime CSSAnimationControllerPrivate::beginAnimationUpdateTime() |
437 | { |
438 | ASSERT(m_beginAnimationUpdateCount); |
439 | if (!m_beginAnimationUpdateTime) |
440 | m_beginAnimationUpdateTime = MonotonicTime::now(); |
441 | |
442 | return m_beginAnimationUpdateTime.value(); |
443 | } |
444 | |
445 | void CSSAnimationControllerPrivate::beginAnimationUpdate() |
446 | { |
447 | if (!m_beginAnimationUpdateCount) |
448 | m_beginAnimationUpdateTime = WTF::nullopt; |
449 | ++m_beginAnimationUpdateCount; |
450 | } |
451 | |
452 | void CSSAnimationControllerPrivate::endAnimationUpdate() |
453 | { |
454 | ASSERT(m_beginAnimationUpdateCount > 0); |
455 | if (m_beginAnimationUpdateCount == 1) { |
456 | styleAvailable(); |
457 | if (!m_waitingForAsyncStartNotification) |
458 | startTimeResponse(beginAnimationUpdateTime()); |
459 | } |
460 | --m_beginAnimationUpdateCount; |
461 | } |
462 | |
463 | void CSSAnimationControllerPrivate::receivedStartTimeResponse(MonotonicTime time) |
464 | { |
465 | LOG(Animations, "CSSAnimationControllerPrivate %p receivedStartTimeResponse %f" , this, time.secondsSinceEpoch().seconds()); |
466 | |
467 | m_waitingForAsyncStartNotification = false; |
468 | startTimeResponse(time); |
469 | } |
470 | |
471 | std::unique_ptr<RenderStyle> CSSAnimationControllerPrivate::animatedStyleForElement(Element& element) |
472 | { |
473 | auto* animation = m_compositeAnimations.get(&element); |
474 | if (!animation) |
475 | return nullptr; |
476 | |
477 | AnimationPrivateUpdateBlock animationUpdateBlock(*this); |
478 | |
479 | auto animatingStyle = animation->getAnimatedStyle(); |
480 | if (!animatingStyle) |
481 | return nullptr; |
482 | |
483 | return animatingStyle; |
484 | } |
485 | |
486 | bool CSSAnimationControllerPrivate::computeExtentOfAnimation(Element& element, LayoutRect& bounds) const |
487 | { |
488 | auto* animation = m_compositeAnimations.get(&element); |
489 | if (!animation) |
490 | return true; |
491 | |
492 | if (!animation->isAnimatingProperty(CSSPropertyTransform, false)) |
493 | return true; |
494 | |
495 | return animation->computeExtentOfTransformAnimation(bounds); |
496 | } |
497 | |
498 | unsigned CSSAnimationControllerPrivate::numberOfActiveAnimations(Document* document) const |
499 | { |
500 | unsigned count = 0; |
501 | |
502 | for (auto& animation : m_compositeAnimations) { |
503 | if (&animation.key->document() == document) |
504 | count += animation.value->numberOfActiveAnimations(); |
505 | } |
506 | |
507 | return count; |
508 | } |
509 | |
510 | void CSSAnimationControllerPrivate::addToAnimationsWaitingForStyle(AnimationBase& animation) |
511 | { |
512 | m_animationsWaitingForStartTimeResponse.remove(&animation); |
513 | m_animationsWaitingForStyle.add(&animation); |
514 | } |
515 | |
516 | void CSSAnimationControllerPrivate::removeFromAnimationsWaitingForStyle(AnimationBase& animationToRemove) |
517 | { |
518 | m_animationsWaitingForStyle.remove(&animationToRemove); |
519 | } |
520 | |
521 | void CSSAnimationControllerPrivate::styleAvailable() |
522 | { |
523 | // Go through list of waiters and send them on their way |
524 | for (const auto& waitingAnimation : m_animationsWaitingForStyle) |
525 | waitingAnimation->styleAvailable(); |
526 | |
527 | m_animationsWaitingForStyle.clear(); |
528 | } |
529 | |
530 | void CSSAnimationControllerPrivate::addToAnimationsWaitingForStartTimeResponse(AnimationBase& animation, bool willGetResponse) |
531 | { |
532 | // If willGetResponse is true, it means this animation is actually waiting for a response |
533 | // (which will come in as a call to notifyAnimationStarted()). |
534 | // In that case we don't need to add it to this list. We just set a waitingForAResponse flag |
535 | // which says we are waiting for the response. If willGetResponse is false, this animation |
536 | // is not waiting for a response for itself, but rather for a notifyXXXStarted() call for |
537 | // another animation to which it will sync. |
538 | // |
539 | // When endAnimationUpdate() is called we check to see if the waitingForAResponse flag is |
540 | // true. If so, we just return and will do our work when the first notifyXXXStarted() call |
541 | // comes in. If it is false, we will not be getting a notifyXXXStarted() call, so we will |
542 | // do our work right away. In both cases we call the onAnimationStartResponse() method |
543 | // on each animation. In the first case we send in the time we got from notifyXXXStarted(). |
544 | // In the second case, we just pass in the beginAnimationUpdateTime(). |
545 | // |
546 | // This will synchronize all software and accelerated animations started in the same |
547 | // updateStyleIfNeeded cycle. |
548 | // |
549 | |
550 | if (willGetResponse) |
551 | m_waitingForAsyncStartNotification = true; |
552 | |
553 | m_animationsWaitingForStartTimeResponse.add(&animation); |
554 | } |
555 | |
556 | void CSSAnimationControllerPrivate::removeFromAnimationsWaitingForStartTimeResponse(AnimationBase& animationToRemove) |
557 | { |
558 | m_animationsWaitingForStartTimeResponse.remove(&animationToRemove); |
559 | if (m_animationsWaitingForStartTimeResponse.isEmpty()) |
560 | m_waitingForAsyncStartNotification = false; |
561 | } |
562 | |
563 | void CSSAnimationControllerPrivate::startTimeResponse(MonotonicTime time) |
564 | { |
565 | // Go through list of waiters and send them on their way |
566 | |
567 | for (const auto& animation : m_animationsWaitingForStartTimeResponse) |
568 | animation->onAnimationStartResponse(time); |
569 | |
570 | m_animationsWaitingForStartTimeResponse.clear(); |
571 | m_waitingForAsyncStartNotification = false; |
572 | } |
573 | |
574 | void CSSAnimationControllerPrivate::animationWillBeRemoved(AnimationBase& animation) |
575 | { |
576 | LOG(Animations, "CSSAnimationControllerPrivate %p animationWillBeRemoved: %p" , this, &animation); |
577 | |
578 | removeFromAnimationsWaitingForStyle(animation); |
579 | removeFromAnimationsWaitingForStartTimeResponse(animation); |
580 | |
581 | bool anyAnimationsWaitingForAsyncStart = false; |
582 | for (auto& animation : m_animationsWaitingForStartTimeResponse) { |
583 | if (animation->waitingForStartTime() && animation->isAccelerated()) { |
584 | anyAnimationsWaitingForAsyncStart = true; |
585 | break; |
586 | } |
587 | } |
588 | |
589 | if (!anyAnimationsWaitingForAsyncStart) |
590 | m_waitingForAsyncStartNotification = false; |
591 | } |
592 | |
593 | CSSAnimationController::CSSAnimationController(Frame& frame) |
594 | : m_data(std::make_unique<CSSAnimationControllerPrivate>(frame)) |
595 | { |
596 | } |
597 | |
598 | CSSAnimationController::~CSSAnimationController() = default; |
599 | |
600 | void CSSAnimationController::cancelAnimations(Element& element) |
601 | { |
602 | if (!m_data->clear(element)) |
603 | return; |
604 | |
605 | if (element.document().renderTreeBeingDestroyed()) |
606 | return; |
607 | ASSERT(element.document().pageCacheState() == Document::NotInPageCache); |
608 | element.invalidateStyle(); |
609 | } |
610 | |
611 | AnimationUpdate CSSAnimationController::updateAnimations(Element& element, const RenderStyle& newStyle, const RenderStyle* oldStyle) |
612 | { |
613 | bool hasOrHadAnimations = (oldStyle && oldStyle->hasAnimationsOrTransitions()) || newStyle.hasAnimationsOrTransitions(); |
614 | if (!hasOrHadAnimations) |
615 | return { }; |
616 | |
617 | if (element.document().pageCacheState() != Document::NotInPageCache) |
618 | return { }; |
619 | |
620 | // Don't run transitions when printing. |
621 | if (element.document().renderView()->printing()) |
622 | return { }; |
623 | |
624 | // Fetch our current set of implicit animations from a hashtable. We then compare them |
625 | // against the animations in the style and make sure we're in sync. If destination values |
626 | // have changed, we reset the animation. We then do a blend to get new values and we return |
627 | // a new style. |
628 | |
629 | CompositeAnimation& compositeAnimation = m_data->ensureCompositeAnimation(element); |
630 | auto update = compositeAnimation.animate(element, oldStyle, newStyle); |
631 | |
632 | auto* renderer = element.renderer(); |
633 | if ((renderer && renderer->parent()) || newStyle.animations() || (oldStyle && oldStyle->animations())) { |
634 | auto& frameView = *element.document().view(); |
635 | if (compositeAnimation.hasAnimationThatDependsOnLayout()) |
636 | m_data->setRequiresLayout(); |
637 | m_data->updateAnimationTimerForElement(element); |
638 | frameView.scheduleAnimation(); |
639 | } |
640 | |
641 | return update; |
642 | } |
643 | |
644 | std::unique_ptr<RenderStyle> CSSAnimationController::animatedStyleForRenderer(RenderElement& renderer) |
645 | { |
646 | std::unique_ptr<RenderStyle> result; |
647 | |
648 | if (renderer.style().hasAnimationsOrTransitions() && renderer.element()) |
649 | result = m_data->animatedStyleForElement(*renderer.element()); |
650 | |
651 | if (!result) |
652 | result = RenderStyle::clonePtr(renderer.style()); |
653 | |
654 | return result; |
655 | } |
656 | |
657 | bool CSSAnimationController::computeExtentOfAnimation(RenderElement& renderer, LayoutRect& bounds) const |
658 | { |
659 | if (!renderer.element()) |
660 | return true; |
661 | if (!renderer.style().hasAnimationsOrTransitions()) |
662 | return true; |
663 | |
664 | return m_data->computeExtentOfAnimation(*renderer.element(), bounds); |
665 | } |
666 | |
667 | void CSSAnimationController::notifyAnimationStarted(RenderElement& renderer, MonotonicTime startTime) |
668 | { |
669 | LOG(Animations, "CSSAnimationController %p notifyAnimationStarted on renderer %p, time=%f" , this, &renderer, startTime.secondsSinceEpoch().seconds()); |
670 | UNUSED_PARAM(renderer); |
671 | |
672 | AnimationUpdateBlock animationUpdateBlock(this); |
673 | m_data->receivedStartTimeResponse(startTime); |
674 | } |
675 | |
676 | bool CSSAnimationController::pauseAnimationAtTime(Element& element, const AtomicString& name, double t) |
677 | { |
678 | AnimationUpdateBlock animationUpdateBlock(this); |
679 | return m_data->pauseAnimationAtTime(element, name, t); |
680 | } |
681 | |
682 | unsigned CSSAnimationController::numberOfActiveAnimations(Document* document) const |
683 | { |
684 | return m_data->numberOfActiveAnimations(document); |
685 | } |
686 | |
687 | bool CSSAnimationController::pauseTransitionAtTime(Element& element, const String& property, double t) |
688 | { |
689 | AnimationUpdateBlock animationUpdateBlock(this); |
690 | return m_data->pauseTransitionAtTime(element, property, t); |
691 | } |
692 | |
693 | bool CSSAnimationController::isRunningAnimationOnRenderer(RenderElement& renderer, CSSPropertyID property) const |
694 | { |
695 | if (!renderer.style().hasAnimationsOrTransitions()) |
696 | return false; |
697 | return m_data->isRunningAnimationOnRenderer(renderer, property); |
698 | } |
699 | |
700 | bool CSSAnimationController::isRunningAcceleratedAnimationOnRenderer(RenderElement& renderer, CSSPropertyID property) const |
701 | { |
702 | if (!renderer.style().hasAnimationsOrTransitions()) |
703 | return false; |
704 | return m_data->isRunningAcceleratedAnimationOnRenderer(renderer, property); |
705 | } |
706 | |
707 | bool CSSAnimationController::isSuspended() const |
708 | { |
709 | return m_data->isSuspended(); |
710 | } |
711 | |
712 | void CSSAnimationController::suspendAnimations() |
713 | { |
714 | LOG(Animations, "controller is suspending animations" ); |
715 | m_data->suspendAnimations(); |
716 | } |
717 | |
718 | void CSSAnimationController::resumeAnimations() |
719 | { |
720 | LOG(Animations, "controller is resuming animations" ); |
721 | m_data->resumeAnimations(); |
722 | } |
723 | |
724 | bool CSSAnimationController::allowsNewAnimationsWhileSuspended() const |
725 | { |
726 | return m_data->allowsNewAnimationsWhileSuspended(); |
727 | } |
728 | |
729 | void CSSAnimationController::setAllowsNewAnimationsWhileSuspended(bool allowed) |
730 | { |
731 | m_data->setAllowsNewAnimationsWhileSuspended(allowed); |
732 | } |
733 | |
734 | void CSSAnimationController::serviceAnimations() |
735 | { |
736 | m_data->animationFrameCallbackFired(); |
737 | } |
738 | |
739 | void CSSAnimationController::updateThrottlingState() |
740 | { |
741 | m_data->updateThrottlingState(); |
742 | } |
743 | |
744 | Seconds CSSAnimationController::animationInterval() const |
745 | { |
746 | return m_data->animationInterval(); |
747 | } |
748 | |
749 | bool CSSAnimationController::animationsAreSuspendedForDocument(Document* document) |
750 | { |
751 | return m_data->animationsAreSuspendedForDocument(document); |
752 | } |
753 | |
754 | void CSSAnimationController::detachFromDocument(Document* document) |
755 | { |
756 | return m_data->detachFromDocument(document); |
757 | } |
758 | |
759 | void CSSAnimationController::suspendAnimationsForDocument(Document* document) |
760 | { |
761 | LOG(Animations, "suspending animations for document %p" , document); |
762 | m_data->suspendAnimationsForDocument(document); |
763 | } |
764 | |
765 | void CSSAnimationController::resumeAnimationsForDocument(Document* document) |
766 | { |
767 | LOG(Animations, "resuming animations for document %p" , document); |
768 | AnimationUpdateBlock animationUpdateBlock(this); |
769 | m_data->resumeAnimationsForDocument(document); |
770 | } |
771 | |
772 | void CSSAnimationController::startAnimationsIfNotSuspended(Document* document) |
773 | { |
774 | LOG(Animations, "animations may start for document %p" , document); |
775 | |
776 | AnimationUpdateBlock animationUpdateBlock(this); |
777 | m_data->startAnimationsIfNotSuspended(document); |
778 | } |
779 | |
780 | void CSSAnimationController::beginAnimationUpdate() |
781 | { |
782 | m_data->beginAnimationUpdate(); |
783 | } |
784 | |
785 | void CSSAnimationController::endAnimationUpdate() |
786 | { |
787 | m_data->endAnimationUpdate(); |
788 | } |
789 | |
790 | bool CSSAnimationController::supportsAcceleratedAnimationOfProperty(CSSPropertyID property) |
791 | { |
792 | return CSSPropertyAnimation::animationOfPropertyIsAccelerated(property); |
793 | } |
794 | |
795 | bool CSSAnimationController::hasAnimations() const |
796 | { |
797 | return m_data->hasAnimations(); |
798 | } |
799 | |
800 | } // namespace WebCore |
801 | |