| 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 | |