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
46static const Seconds defaultAnimationInterval { 15_ms };
47static const Seconds throttledAnimationInterval { 30_ms };
48
49namespace WebCore {
50
51Ref<DocumentTimeline> DocumentTimeline::create(Document& document)
52{
53 return adoptRef(*new DocumentTimeline(document, 0_s));
54}
55
56Ref<DocumentTimeline> DocumentTimeline::create(Document& document, DocumentTimelineOptions&& options)
57{
58 return adoptRef(*new DocumentTimeline(document, Seconds::fromMilliseconds(options.originTime)));
59}
60
61DocumentTimeline::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
71DocumentTimeline::~DocumentTimeline() = default;
72
73void 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
86static 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
121Vector<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
191void DocumentTimeline::updateThrottlingState()
192{
193 scheduleAnimationResolution();
194}
195
196Seconds 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
203void 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
221void 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
236bool DocumentTimeline::animationsAreSuspended()
237{
238 return m_isSuspended;
239}
240
241unsigned 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
251DOMHighResTimeStamp DocumentTimeline::liveCurrentTime() const
252{
253 return m_document->domWindow()->nowTimestamp();
254}
255
256Optional<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
274void 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
289void 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
299void DocumentTimeline::animationTimingDidChange(WebAnimation& animation)
300{
301 AnimationTimeline::animationTimingDidChange(animation);
302 scheduleAnimationResolution();
303}
304
305void DocumentTimeline::removeAnimation(WebAnimation& animation)
306{
307 AnimationTimeline::removeAnimation(animation);
308
309 if (m_animations.isEmpty())
310 unscheduleAnimationResolution();
311}
312
313void 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().scheduleTimedRenderingUpdate();
322 m_animationResolutionScheduled = true;
323}
324
325void DocumentTimeline::unscheduleAnimationResolution()
326{
327 m_tickScheduleTimer.stop();
328 m_animationResolutionScheduled = false;
329}
330
331void 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
349void 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
417void 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
430void 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
458bool 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
479bool 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
496bool 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
516std::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
533void DocumentTimeline::animationWasAddedToElement(WebAnimation& animation, Element& element)
534{
535 AnimationTimeline::animationWasAddedToElement(animation, element);
536 updateListOfElementsWithRunningAcceleratedAnimationsForElement(element);
537}
538
539void DocumentTimeline::animationWasRemovedFromElement(WebAnimation& animation, Element& element)
540{
541 AnimationTimeline::animationWasRemovedFromElement(animation, element);
542 updateListOfElementsWithRunningAcceleratedAnimationsForElement(element);
543}
544
545void 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
555void 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
574void 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
590bool 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
616bool DocumentTimeline::runningAnimationsForElementAreAllAccelerated(Element& element) const
617{
618 return m_elementsWithRunningAcceleratedAnimations.contains(&element);
619}
620
621void DocumentTimeline::enqueueAnimationPlaybackEvent(AnimationPlaybackEvent& event)
622{
623 m_pendingAnimationEvents.append(event);
624}
625
626Vector<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
637unsigned DocumentTimeline::numberOfAnimationTimelineInvalidationsForTesting() const
638{
639 return m_numberOfAnimationTimelineInvalidationsForTesting;
640}
641
642} // namespace WebCore
643