| 1 | /* |
| 2 | * Copyright (C) 2017 Apple Inc. All rights reserved. |
| 3 | * |
| 4 | * Redistribution and use in source and binary forms, with or without |
| 5 | * modification, are permitted provided that the following conditions |
| 6 | * are met: |
| 7 | * 1. Redistributions of source code must retain the above copyright |
| 8 | * notice, this list of conditions and the following disclaimer. |
| 9 | * 2. Redistributions in binary form must reproduce the above copyright |
| 10 | * notice, this list of conditions and the following disclaimer in the |
| 11 | * documentation and/or other materials provided with the distribution. |
| 12 | * |
| 13 | * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY |
| 14 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
| 15 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
| 16 | * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR |
| 17 | * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
| 18 | * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
| 19 | * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR |
| 20 | * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY |
| 21 | * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| 22 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| 23 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| 24 | */ |
| 25 | |
| 26 | #include "config.h" |
| 27 | #include "DocumentTimeline.h" |
| 28 | |
| 29 | #include "AnimationPlaybackEvent.h" |
| 30 | #include "CSSAnimation.h" |
| 31 | #include "CSSPropertyAnimation.h" |
| 32 | #include "CSSTransition.h" |
| 33 | #include "DOMWindow.h" |
| 34 | #include "DeclarativeAnimation.h" |
| 35 | #include "Document.h" |
| 36 | #include "GraphicsLayer.h" |
| 37 | #include "KeyframeEffect.h" |
| 38 | #include "Microtasks.h" |
| 39 | #include "Node.h" |
| 40 | #include "Page.h" |
| 41 | #include "PseudoElement.h" |
| 42 | #include "RenderElement.h" |
| 43 | #include "RenderLayer.h" |
| 44 | #include "RenderLayerBacking.h" |
| 45 | |
| 46 | static const Seconds defaultAnimationInterval { 15_ms }; |
| 47 | static const Seconds throttledAnimationInterval { 30_ms }; |
| 48 | |
| 49 | namespace WebCore { |
| 50 | |
| 51 | Ref<DocumentTimeline> DocumentTimeline::create(Document& document) |
| 52 | { |
| 53 | return adoptRef(*new DocumentTimeline(document, 0_s)); |
| 54 | } |
| 55 | |
| 56 | Ref<DocumentTimeline> DocumentTimeline::create(Document& document, DocumentTimelineOptions&& options) |
| 57 | { |
| 58 | return adoptRef(*new DocumentTimeline(document, Seconds::fromMilliseconds(options.originTime))); |
| 59 | } |
| 60 | |
| 61 | DocumentTimeline::DocumentTimeline(Document& document, Seconds originTime) |
| 62 | : AnimationTimeline() |
| 63 | , m_tickScheduleTimer(*this, &DocumentTimeline::scheduleAnimationResolution) |
| 64 | , m_document(&document) |
| 65 | , m_originTime(originTime) |
| 66 | { |
| 67 | if (m_document && m_document->page() && !m_document->page()->isVisible()) |
| 68 | suspendAnimations(); |
| 69 | } |
| 70 | |
| 71 | DocumentTimeline::~DocumentTimeline() = default; |
| 72 | |
| 73 | void DocumentTimeline::detachFromDocument() |
| 74 | { |
| 75 | m_currentTimeClearingTaskQueue.close(); |
| 76 | m_elementsWithRunningAcceleratedAnimations.clear(); |
| 77 | |
| 78 | auto& animationsToRemove = m_animations; |
| 79 | while (!animationsToRemove.isEmpty()) |
| 80 | animationsToRemove.first()->remove(); |
| 81 | |
| 82 | unscheduleAnimationResolution(); |
| 83 | m_document = nullptr; |
| 84 | } |
| 85 | |
| 86 | static inline bool compareDeclarativeAnimationOwningElementPositionsInDocumentTreeOrder(Element* lhsOwningElement, Element* rhsOwningElement) |
| 87 | { |
| 88 | // With regard to pseudo-elements, the sort order is as follows: |
| 89 | // - element |
| 90 | // - ::before |
| 91 | // - ::after |
| 92 | // - element children |
| 93 | |
| 94 | // We could be comparing two pseudo-elements that are hosted on the same element. |
| 95 | if (is<PseudoElement>(lhsOwningElement) && is<PseudoElement>(rhsOwningElement)) { |
| 96 | auto* lhsPseudoElement = downcast<PseudoElement>(lhsOwningElement); |
| 97 | auto* rhsPseudoElement = downcast<PseudoElement>(rhsOwningElement); |
| 98 | if (lhsPseudoElement->hostElement() == rhsPseudoElement->hostElement()) |
| 99 | return lhsPseudoElement->isBeforePseudoElement(); |
| 100 | } |
| 101 | |
| 102 | // Or comparing a pseudo-element that is compared to another non-pseudo element, in which case |
| 103 | // we want to see if it's hosted on that other element, and if not use its host element to compare. |
| 104 | if (is<PseudoElement>(lhsOwningElement)) { |
| 105 | auto* lhsHostElement = downcast<PseudoElement>(lhsOwningElement)->hostElement(); |
| 106 | if (rhsOwningElement == lhsHostElement) |
| 107 | return false; |
| 108 | lhsOwningElement = lhsHostElement; |
| 109 | } |
| 110 | |
| 111 | if (is<PseudoElement>(rhsOwningElement)) { |
| 112 | auto* rhsHostElement = downcast<PseudoElement>(rhsOwningElement)->hostElement(); |
| 113 | if (lhsOwningElement == rhsHostElement) |
| 114 | return true; |
| 115 | rhsOwningElement = rhsHostElement; |
| 116 | } |
| 117 | |
| 118 | return lhsOwningElement->compareDocumentPosition(*rhsOwningElement) & Node::DOCUMENT_POSITION_FOLLOWING; |
| 119 | } |
| 120 | |
| 121 | Vector<RefPtr<WebAnimation>> DocumentTimeline::getAnimations() const |
| 122 | { |
| 123 | ASSERT(m_document); |
| 124 | |
| 125 | Vector<RefPtr<WebAnimation>> cssTransitions; |
| 126 | Vector<RefPtr<WebAnimation>> cssAnimations; |
| 127 | Vector<RefPtr<WebAnimation>> webAnimations; |
| 128 | |
| 129 | // First, let's get all qualifying animations in their right group. |
| 130 | for (const auto& animation : m_allAnimations) { |
| 131 | if (!animation || !animation->isRelevant() || animation->timeline() != this || !is<KeyframeEffect>(animation->effect())) |
| 132 | continue; |
| 133 | |
| 134 | auto* target = downcast<KeyframeEffect>(animation->effect())->target(); |
| 135 | if (!target || !target->isDescendantOf(*m_document)) |
| 136 | continue; |
| 137 | |
| 138 | if (is<CSSTransition>(animation.get()) && downcast<CSSTransition>(animation.get())->owningElement()) |
| 139 | cssTransitions.append(animation.get()); |
| 140 | else if (is<CSSAnimation>(animation.get()) && downcast<CSSAnimation>(animation.get())->owningElement()) |
| 141 | cssAnimations.append(animation.get()); |
| 142 | else |
| 143 | webAnimations.append(animation.get()); |
| 144 | } |
| 145 | |
| 146 | // Now sort CSS Transitions by their composite order. |
| 147 | std::sort(cssTransitions.begin(), cssTransitions.end(), [](auto& lhs, auto& rhs) { |
| 148 | // https://drafts.csswg.org/css-transitions-2/#animation-composite-order |
| 149 | auto* lhsTransition = downcast<CSSTransition>(lhs.get()); |
| 150 | auto* rhsTransition = downcast<CSSTransition>(rhs.get()); |
| 151 | |
| 152 | auto* lhsOwningElement = lhsTransition->owningElement(); |
| 153 | auto* rhsOwningElement = rhsTransition->owningElement(); |
| 154 | |
| 155 | // If the owning element of A and B differs, sort A and B by tree order of their corresponding owning elements. |
| 156 | if (lhsOwningElement != rhsOwningElement) |
| 157 | return compareDeclarativeAnimationOwningElementPositionsInDocumentTreeOrder(lhsOwningElement, rhsOwningElement); |
| 158 | |
| 159 | // Otherwise, if A and B have different transition generation values, sort by their corresponding transition generation in ascending order. |
| 160 | if (lhsTransition->generationTime() != rhsTransition->generationTime()) |
| 161 | return lhsTransition->generationTime() < rhsTransition->generationTime(); |
| 162 | |
| 163 | // Otherwise, sort A and B in ascending order by the Unicode codepoints that make up the expanded transition property name of each transition |
| 164 | // (i.e. without attempting case conversion and such that ‘-moz-column-width’ sorts before ‘column-width’). |
| 165 | return lhsTransition->transitionProperty().utf8() < rhsTransition->transitionProperty().utf8(); |
| 166 | }); |
| 167 | |
| 168 | // Now sort CSS Animations by their composite order. |
| 169 | std::sort(cssAnimations.begin(), cssAnimations.end(), [](auto& lhs, auto& rhs) { |
| 170 | // https://drafts.csswg.org/css-animations-2/#animation-composite-order |
| 171 | auto* lhsOwningElement = downcast<CSSAnimation>(lhs.get())->owningElement(); |
| 172 | auto* rhsOwningElement = downcast<CSSAnimation>(rhs.get())->owningElement(); |
| 173 | |
| 174 | // If the owning element of A and B differs, sort A and B by tree order of their corresponding owning elements. |
| 175 | if (lhsOwningElement != rhsOwningElement) |
| 176 | return compareDeclarativeAnimationOwningElementPositionsInDocumentTreeOrder(lhsOwningElement, rhsOwningElement); |
| 177 | |
| 178 | // Otherwise, sort A and B based on their position in the computed value of the animation-name property of the (common) owning element. |
| 179 | // In our case, this matches the time at which the animations were created and thus their relative position in m_allAnimations. |
| 180 | return false; |
| 181 | }); |
| 182 | |
| 183 | // Finally, we can concatenate the sorted CSS Transitions, CSS Animations and Web Animations in their relative composite order. |
| 184 | Vector<RefPtr<WebAnimation>> animations; |
| 185 | animations.appendRange(cssTransitions.begin(), cssTransitions.end()); |
| 186 | animations.appendRange(cssAnimations.begin(), cssAnimations.end()); |
| 187 | animations.appendRange(webAnimations.begin(), webAnimations.end()); |
| 188 | return animations; |
| 189 | } |
| 190 | |
| 191 | void DocumentTimeline::updateThrottlingState() |
| 192 | { |
| 193 | scheduleAnimationResolution(); |
| 194 | } |
| 195 | |
| 196 | Seconds DocumentTimeline::animationInterval() const |
| 197 | { |
| 198 | if (!m_document || !m_document->page()) |
| 199 | return Seconds::infinity(); |
| 200 | return m_document->page()->isLowPowerModeEnabled() ? throttledAnimationInterval : defaultAnimationInterval; |
| 201 | } |
| 202 | |
| 203 | void DocumentTimeline::suspendAnimations() |
| 204 | { |
| 205 | if (animationsAreSuspended()) |
| 206 | return; |
| 207 | |
| 208 | if (!m_cachedCurrentTime) |
| 209 | m_cachedCurrentTime = Seconds(liveCurrentTime()); |
| 210 | |
| 211 | for (const auto& animation : m_animations) |
| 212 | animation->setSuspended(true); |
| 213 | |
| 214 | m_isSuspended = true; |
| 215 | |
| 216 | applyPendingAcceleratedAnimations(); |
| 217 | |
| 218 | unscheduleAnimationResolution(); |
| 219 | } |
| 220 | |
| 221 | void DocumentTimeline::resumeAnimations() |
| 222 | { |
| 223 | if (!animationsAreSuspended()) |
| 224 | return; |
| 225 | |
| 226 | m_cachedCurrentTime = WTF::nullopt; |
| 227 | |
| 228 | m_isSuspended = false; |
| 229 | |
| 230 | for (const auto& animation : m_animations) |
| 231 | animation->setSuspended(false); |
| 232 | |
| 233 | scheduleAnimationResolution(); |
| 234 | } |
| 235 | |
| 236 | bool DocumentTimeline::animationsAreSuspended() |
| 237 | { |
| 238 | return m_isSuspended; |
| 239 | } |
| 240 | |
| 241 | unsigned DocumentTimeline::numberOfActiveAnimationsForTesting() const |
| 242 | { |
| 243 | unsigned count = 0; |
| 244 | for (const auto& animation : m_animations) { |
| 245 | if (!animation->isSuspended()) |
| 246 | ++count; |
| 247 | } |
| 248 | return count; |
| 249 | } |
| 250 | |
| 251 | DOMHighResTimeStamp DocumentTimeline::liveCurrentTime() const |
| 252 | { |
| 253 | return m_document->domWindow()->nowTimestamp(); |
| 254 | } |
| 255 | |
| 256 | Optional<Seconds> DocumentTimeline::currentTime() |
| 257 | { |
| 258 | if (!m_document || !m_document->domWindow()) |
| 259 | return AnimationTimeline::currentTime(); |
| 260 | |
| 261 | auto& mainDocumentTimeline = m_document->timeline(); |
| 262 | if (&mainDocumentTimeline != this) { |
| 263 | if (auto mainDocumentTimelineCurrentTime = mainDocumentTimeline.currentTime()) |
| 264 | return *mainDocumentTimelineCurrentTime - m_originTime; |
| 265 | return WTF::nullopt; |
| 266 | } |
| 267 | |
| 268 | if (!m_cachedCurrentTime) |
| 269 | cacheCurrentTime(liveCurrentTime()); |
| 270 | |
| 271 | return m_cachedCurrentTime.value() - m_originTime; |
| 272 | } |
| 273 | |
| 274 | void DocumentTimeline::cacheCurrentTime(DOMHighResTimeStamp newCurrentTime) |
| 275 | { |
| 276 | m_cachedCurrentTime = Seconds(newCurrentTime); |
| 277 | // We want to be sure to keep this time cached until we've both finished running JS and finished updating |
| 278 | // animations, so we schedule the invalidation task and register a whenIdle callback on the VM, which will |
| 279 | // fire syncronously if no JS is running. |
| 280 | m_waitingOnVMIdle = true; |
| 281 | if (!m_currentTimeClearingTaskQueue.hasPendingTasks()) |
| 282 | m_currentTimeClearingTaskQueue.enqueueTask(std::bind(&DocumentTimeline::maybeClearCachedCurrentTime, this)); |
| 283 | m_document->vm().whenIdle([this, protectedThis = makeRefPtr(this)]() { |
| 284 | m_waitingOnVMIdle = false; |
| 285 | maybeClearCachedCurrentTime(); |
| 286 | }); |
| 287 | } |
| 288 | |
| 289 | void DocumentTimeline::maybeClearCachedCurrentTime() |
| 290 | { |
| 291 | // We want to make sure we only clear the cached current time if we're not currently running |
| 292 | // JS or waiting on all current animation updating code to have completed. This is so that |
| 293 | // we're guaranteed to have a consistent current time reported for all work happening in a given |
| 294 | // JS frame or throughout updating animations in WebCore. |
| 295 | if (!m_isSuspended && !m_waitingOnVMIdle && !m_currentTimeClearingTaskQueue.hasPendingTasks()) |
| 296 | m_cachedCurrentTime = WTF::nullopt; |
| 297 | } |
| 298 | |
| 299 | void DocumentTimeline::animationTimingDidChange(WebAnimation& animation) |
| 300 | { |
| 301 | AnimationTimeline::animationTimingDidChange(animation); |
| 302 | scheduleAnimationResolution(); |
| 303 | } |
| 304 | |
| 305 | void DocumentTimeline::removeAnimation(WebAnimation& animation) |
| 306 | { |
| 307 | AnimationTimeline::removeAnimation(animation); |
| 308 | |
| 309 | if (m_animations.isEmpty()) |
| 310 | unscheduleAnimationResolution(); |
| 311 | } |
| 312 | |
| 313 | void DocumentTimeline::scheduleAnimationResolution() |
| 314 | { |
| 315 | if (m_isSuspended || m_animations.isEmpty() || m_animationResolutionScheduled) |
| 316 | return; |
| 317 | |
| 318 | if (!m_document || !m_document->page()) |
| 319 | return; |
| 320 | |
| 321 | m_document->page()->renderingUpdateScheduler().scheduleRenderingUpdate(); |
| 322 | m_animationResolutionScheduled = true; |
| 323 | } |
| 324 | |
| 325 | void DocumentTimeline::unscheduleAnimationResolution() |
| 326 | { |
| 327 | m_tickScheduleTimer.stop(); |
| 328 | m_animationResolutionScheduled = false; |
| 329 | } |
| 330 | |
| 331 | void DocumentTimeline::updateAnimationsAndSendEvents(DOMHighResTimeStamp timestamp) |
| 332 | { |
| 333 | // We need to freeze the current time even if no animation is running. |
| 334 | // document.timeline.currentTime may be called from a rAF callback and |
| 335 | // it has to match the rAF timestamp. |
| 336 | if (!m_isSuspended) |
| 337 | cacheCurrentTime(timestamp); |
| 338 | |
| 339 | if (m_isSuspended || m_animations.isEmpty() || !m_animationResolutionScheduled) |
| 340 | return; |
| 341 | |
| 342 | internalUpdateAnimationsAndSendEvents(); |
| 343 | applyPendingAcceleratedAnimations(); |
| 344 | |
| 345 | m_animationResolutionScheduled = false; |
| 346 | scheduleNextTick(); |
| 347 | } |
| 348 | |
| 349 | void DocumentTimeline::internalUpdateAnimationsAndSendEvents() |
| 350 | { |
| 351 | m_numberOfAnimationTimelineInvalidationsForTesting++; |
| 352 | |
| 353 | // https://drafts.csswg.org/web-animations/#update-animations-and-send-events |
| 354 | |
| 355 | // 1. Update the current time of all timelines associated with doc passing now as the timestamp. |
| 356 | |
| 357 | Vector<RefPtr<WebAnimation>> animationsToRemove; |
| 358 | Vector<RefPtr<CSSTransition>> completedTransitions; |
| 359 | |
| 360 | for (auto& animation : m_animations) { |
| 361 | if (animation->timeline() != this) { |
| 362 | ASSERT(!animation->timeline()); |
| 363 | animationsToRemove.append(animation); |
| 364 | continue; |
| 365 | } |
| 366 | |
| 367 | // This will notify the animation that timing has changed and will call automatically |
| 368 | // schedule invalidation if required for this animation. |
| 369 | animation->tick(); |
| 370 | |
| 371 | if (!animation->isRelevant() && !animation->needsTick()) |
| 372 | animationsToRemove.append(animation); |
| 373 | |
| 374 | if (!animation->needsTick() && is<CSSTransition>(animation) && animation->playState() == WebAnimation::PlayState::Finished) { |
| 375 | auto* transition = downcast<CSSTransition>(animation.get()); |
| 376 | if (transition->owningElement()) |
| 377 | completedTransitions.append(transition); |
| 378 | } |
| 379 | } |
| 380 | |
| 381 | // 2. Perform a microtask checkpoint. |
| 382 | MicrotaskQueue::mainThreadQueue().performMicrotaskCheckpoint(); |
| 383 | |
| 384 | // 3. Let events to dispatch be a copy of doc's pending animation event queue. |
| 385 | // 4. Clear doc's pending animation event queue. |
| 386 | auto pendingAnimationEvents = WTFMove(m_pendingAnimationEvents); |
| 387 | |
| 388 | // 5. Perform a stable sort of the animation events in events to dispatch as follows. |
| 389 | std::stable_sort(pendingAnimationEvents.begin(), pendingAnimationEvents.end(), [] (const Ref<AnimationPlaybackEvent>& lhs, const Ref<AnimationPlaybackEvent>& rhs) { |
| 390 | // 1. Sort the events by their scheduled event time such that events that were scheduled to occur earlier, sort before events scheduled to occur later |
| 391 | // and events whose scheduled event time is unresolved sort before events with a resolved scheduled event time. |
| 392 | // 2. Within events with equal scheduled event times, sort by their composite order. FIXME: We don't do this. |
| 393 | if (lhs->timelineTime() && !rhs->timelineTime()) |
| 394 | return false; |
| 395 | if (!lhs->timelineTime() && rhs->timelineTime()) |
| 396 | return true; |
| 397 | if (!lhs->timelineTime() && !rhs->timelineTime()) |
| 398 | return true; |
| 399 | return lhs->timelineTime().value() < rhs->timelineTime().value(); |
| 400 | }); |
| 401 | |
| 402 | // 6. Dispatch each of the events in events to dispatch at their corresponding target using the order established in the previous step. |
| 403 | for (auto& pendingEvent : pendingAnimationEvents) |
| 404 | pendingEvent->target()->dispatchEvent(pendingEvent); |
| 405 | |
| 406 | // This will cancel any scheduled invalidation if we end up removing all animations. |
| 407 | for (auto& animation : animationsToRemove) |
| 408 | removeAnimation(*animation); |
| 409 | |
| 410 | // Now that animations that needed removal have been removed, let's update the list of completed transitions. |
| 411 | // This needs to happen after dealing with the list of animations to remove as the animation may have been |
| 412 | // removed from the list of completed transitions otherwise. |
| 413 | for (auto& completedTransition : completedTransitions) |
| 414 | transitionDidComplete(completedTransition); |
| 415 | } |
| 416 | |
| 417 | void DocumentTimeline::transitionDidComplete(RefPtr<CSSTransition> transition) |
| 418 | { |
| 419 | ASSERT(transition); |
| 420 | removeAnimation(*transition); |
| 421 | if (is<KeyframeEffect>(transition->effect())) { |
| 422 | if (auto* target = downcast<KeyframeEffect>(transition->effect())->target()) { |
| 423 | m_elementToCompletedCSSTransitionByCSSPropertyID.ensure(target, [] { |
| 424 | return HashMap<CSSPropertyID, RefPtr<CSSTransition>> { }; |
| 425 | }).iterator->value.set(transition->property(), transition); |
| 426 | } |
| 427 | } |
| 428 | } |
| 429 | |
| 430 | void DocumentTimeline::scheduleNextTick() |
| 431 | { |
| 432 | // There is no tick to schedule if we don't have any relevant animations. |
| 433 | if (m_animations.isEmpty()) |
| 434 | return; |
| 435 | |
| 436 | for (const auto& animation : m_animations) { |
| 437 | if (!animation->isRunningAccelerated()) { |
| 438 | scheduleAnimationResolution(); |
| 439 | return; |
| 440 | } |
| 441 | } |
| 442 | |
| 443 | Seconds scheduleDelay = Seconds::infinity(); |
| 444 | |
| 445 | for (const auto& animation : m_animations) { |
| 446 | auto animationTimeToNextRequiredTick = animation->timeToNextTick(); |
| 447 | if (animationTimeToNextRequiredTick < animationInterval()) { |
| 448 | scheduleAnimationResolution(); |
| 449 | return; |
| 450 | } |
| 451 | scheduleDelay = std::min(scheduleDelay, animationTimeToNextRequiredTick); |
| 452 | } |
| 453 | |
| 454 | if (scheduleDelay < Seconds::infinity()) |
| 455 | m_tickScheduleTimer.startOneShot(scheduleDelay); |
| 456 | } |
| 457 | |
| 458 | bool DocumentTimeline::computeExtentOfAnimation(RenderElement& renderer, LayoutRect& bounds) const |
| 459 | { |
| 460 | if (!renderer.element()) |
| 461 | return true; |
| 462 | |
| 463 | KeyframeEffect* matchingEffect = nullptr; |
| 464 | for (const auto& animation : animationsForElement(*renderer.element())) { |
| 465 | auto* effect = animation->effect(); |
| 466 | if (is<KeyframeEffect>(effect)) { |
| 467 | auto* keyframeEffect = downcast<KeyframeEffect>(effect); |
| 468 | if (keyframeEffect->animatedProperties().contains(CSSPropertyTransform)) |
| 469 | matchingEffect = downcast<KeyframeEffect>(effect); |
| 470 | } |
| 471 | } |
| 472 | |
| 473 | if (matchingEffect) |
| 474 | return matchingEffect->computeExtentOfTransformAnimation(bounds); |
| 475 | |
| 476 | return true; |
| 477 | } |
| 478 | |
| 479 | bool DocumentTimeline::isRunningAnimationOnRenderer(RenderElement& renderer, CSSPropertyID property) const |
| 480 | { |
| 481 | if (!renderer.element()) |
| 482 | return false; |
| 483 | |
| 484 | for (const auto& animation : animationsForElement(*renderer.element())) { |
| 485 | auto playState = animation->playState(); |
| 486 | if (playState != WebAnimation::PlayState::Running && playState != WebAnimation::PlayState::Paused) |
| 487 | continue; |
| 488 | auto* effect = animation->effect(); |
| 489 | if (is<KeyframeEffect>(effect) && downcast<KeyframeEffect>(effect)->animatedProperties().contains(property)) |
| 490 | return true; |
| 491 | } |
| 492 | |
| 493 | return false; |
| 494 | } |
| 495 | |
| 496 | bool DocumentTimeline::isRunningAcceleratedAnimationOnRenderer(RenderElement& renderer, CSSPropertyID property) const |
| 497 | { |
| 498 | if (!renderer.element()) |
| 499 | return false; |
| 500 | |
| 501 | for (const auto& animation : animationsForElement(*renderer.element())) { |
| 502 | auto playState = animation->playState(); |
| 503 | if (playState != WebAnimation::PlayState::Running && playState != WebAnimation::PlayState::Paused) |
| 504 | continue; |
| 505 | auto* effect = animation->effect(); |
| 506 | if (is<KeyframeEffect>(effect)) { |
| 507 | auto* keyframeEffect = downcast<KeyframeEffect>(effect); |
| 508 | if (keyframeEffect->isRunningAccelerated() && keyframeEffect->animatedProperties().contains(property)) |
| 509 | return true; |
| 510 | } |
| 511 | } |
| 512 | |
| 513 | return false; |
| 514 | } |
| 515 | |
| 516 | std::unique_ptr<RenderStyle> DocumentTimeline::animatedStyleForRenderer(RenderElement& renderer) |
| 517 | { |
| 518 | std::unique_ptr<RenderStyle> result; |
| 519 | |
| 520 | if (auto* element = renderer.element()) { |
| 521 | for (const auto& animation : animationsForElement(*element)) { |
| 522 | if (is<KeyframeEffect>(animation->effect())) |
| 523 | downcast<KeyframeEffect>(animation->effect())->getAnimatedStyle(result); |
| 524 | } |
| 525 | } |
| 526 | |
| 527 | if (!result) |
| 528 | result = RenderStyle::clonePtr(renderer.style()); |
| 529 | |
| 530 | return result; |
| 531 | } |
| 532 | |
| 533 | void DocumentTimeline::animationWasAddedToElement(WebAnimation& animation, Element& element) |
| 534 | { |
| 535 | AnimationTimeline::animationWasAddedToElement(animation, element); |
| 536 | updateListOfElementsWithRunningAcceleratedAnimationsForElement(element); |
| 537 | } |
| 538 | |
| 539 | void DocumentTimeline::animationWasRemovedFromElement(WebAnimation& animation, Element& element) |
| 540 | { |
| 541 | AnimationTimeline::animationWasRemovedFromElement(animation, element); |
| 542 | updateListOfElementsWithRunningAcceleratedAnimationsForElement(element); |
| 543 | } |
| 544 | |
| 545 | void DocumentTimeline::animationAcceleratedRunningStateDidChange(WebAnimation& animation) |
| 546 | { |
| 547 | m_acceleratedAnimationsPendingRunningStateChange.add(&animation); |
| 548 | |
| 549 | if (is<KeyframeEffect>(animation.effect())) { |
| 550 | if (auto* target = downcast<KeyframeEffect>(animation.effect())->target()) |
| 551 | updateListOfElementsWithRunningAcceleratedAnimationsForElement(*target); |
| 552 | } |
| 553 | } |
| 554 | |
| 555 | void DocumentTimeline::updateListOfElementsWithRunningAcceleratedAnimationsForElement(Element& element) |
| 556 | { |
| 557 | auto animations = animationsForElement(element); |
| 558 | |
| 559 | if (animations.isEmpty()) { |
| 560 | m_elementsWithRunningAcceleratedAnimations.remove(&element); |
| 561 | return; |
| 562 | } |
| 563 | |
| 564 | for (const auto& animation : animations) { |
| 565 | if (!animation->isRunningAccelerated()) { |
| 566 | m_elementsWithRunningAcceleratedAnimations.remove(&element); |
| 567 | return; |
| 568 | } |
| 569 | } |
| 570 | |
| 571 | m_elementsWithRunningAcceleratedAnimations.add(&element); |
| 572 | } |
| 573 | |
| 574 | void DocumentTimeline::applyPendingAcceleratedAnimations() |
| 575 | { |
| 576 | auto acceleratedAnimationsPendingRunningStateChange = m_acceleratedAnimationsPendingRunningStateChange; |
| 577 | m_acceleratedAnimationsPendingRunningStateChange.clear(); |
| 578 | |
| 579 | bool hasForcedLayout = false; |
| 580 | for (auto& animation : acceleratedAnimationsPendingRunningStateChange) { |
| 581 | if (!hasForcedLayout) { |
| 582 | auto* effect = animation->effect(); |
| 583 | if (is<KeyframeEffect>(effect)) |
| 584 | hasForcedLayout |= downcast<KeyframeEffect>(effect)->forceLayoutIfNeeded(); |
| 585 | } |
| 586 | animation->applyPendingAcceleratedActions(); |
| 587 | } |
| 588 | } |
| 589 | |
| 590 | bool DocumentTimeline::resolveAnimationsForElement(Element& element, RenderStyle& targetStyle) |
| 591 | { |
| 592 | bool hasNonAcceleratedAnimationProperty = false; |
| 593 | |
| 594 | for (const auto& animation : animationsForElement(element)) { |
| 595 | animation->resolve(targetStyle); |
| 596 | |
| 597 | if (hasNonAcceleratedAnimationProperty) |
| 598 | continue; |
| 599 | |
| 600 | auto* effect = animation->effect(); |
| 601 | if (!effect || !is<KeyframeEffect>(effect)) |
| 602 | continue; |
| 603 | |
| 604 | auto* keyframeEffect = downcast<KeyframeEffect>(effect); |
| 605 | for (auto cssPropertyId : keyframeEffect->animatedProperties()) { |
| 606 | if (!CSSPropertyAnimation::animationOfPropertyIsAccelerated(cssPropertyId)) { |
| 607 | hasNonAcceleratedAnimationProperty = true; |
| 608 | break; |
| 609 | } |
| 610 | } |
| 611 | } |
| 612 | |
| 613 | return !hasNonAcceleratedAnimationProperty; |
| 614 | } |
| 615 | |
| 616 | bool DocumentTimeline::runningAnimationsForElementAreAllAccelerated(Element& element) const |
| 617 | { |
| 618 | return m_elementsWithRunningAcceleratedAnimations.contains(&element); |
| 619 | } |
| 620 | |
| 621 | void DocumentTimeline::enqueueAnimationPlaybackEvent(AnimationPlaybackEvent& event) |
| 622 | { |
| 623 | m_pendingAnimationEvents.append(event); |
| 624 | } |
| 625 | |
| 626 | Vector<std::pair<String, double>> DocumentTimeline::acceleratedAnimationsForElement(Element& element) const |
| 627 | { |
| 628 | auto* renderer = element.renderer(); |
| 629 | if (renderer && renderer->isComposited()) { |
| 630 | auto* compositedRenderer = downcast<RenderBoxModelObject>(renderer); |
| 631 | if (auto* graphicsLayer = compositedRenderer->layer()->backing()->graphicsLayer()) |
| 632 | return graphicsLayer->acceleratedAnimationsForTesting(); |
| 633 | } |
| 634 | return { }; |
| 635 | } |
| 636 | |
| 637 | unsigned DocumentTimeline::numberOfAnimationTimelineInvalidationsForTesting() const |
| 638 | { |
| 639 | return m_numberOfAnimationTimelineInvalidationsForTesting; |
| 640 | } |
| 641 | |
| 642 | } // namespace WebCore |
| 643 | |