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