Source/WebCore/ChangeLog

 12020-04-22 Antoine Quint <graouts@apple.com>
 2
 3 [Web Animations] Coordinate "update animations and send events" procedure across multiple timelines
 4 https://bugs.webkit.org/show_bug.cgi?id=202109
 5 <rdar://problem/59470821>
 6
 7 Reviewed by NOBODY (OOPS!).
 8
 9 So far, although we did manage multiple animation timelines per document, we mostly operated under the assumption that there really was a single timeline.
 10 In this patch we make the "update animations and send events" procedure, which is central to the lifecycle of animations, work with multiple timelines such
 11 that a single microtask checkpoint is performed even with multiple timelines, whereas we would perform one per timeline before. To do this, we move much of
 12 the logic DocumentTimeline::updateAnimationsAndSendEvents() to DocumentTimelinesController where each step is run across each timeline, rather than running
 13 all steps for each timeline one after the other, respecting the single microtask checkpoint in the middle of the process.
 14
 15 To minimize code churn at this stage, we still keep a fair bit of logic in DocumentTimeline and, while we remove updateAnimationsAndSendEvents(),
 16 internalUpdateAnimationsAndSendEvents() and updateCurrentTime(), we expose three methods that allow to run the pre-flight sequence in
 17 documentWillUpdateAnimationsAndSendEvents(), collect pending events in prepareForPendingAnimationEventsDispatch() and run the post-flight sequence
 18 in documentDidUpdateAnimationsAndSendEvents().
 19
 20 None of the logic changes, this is just moving code around. In the future, more patches will move code from DocumentTimeline up to DocumentTimelinesController
 21 such that events are enqueued there, and animation scheduling as well. But this already lets us pass a new test that used to flakily reject promises in the WPT
 22 test web-animations/timing-model/timelines/update-and-send-events.html.
 23
 24 * animation/AnimationTimeline.h:
 25 (WebCore::AnimationTimeline::relevantAnimations const):
 26 (WebCore::AnimationTimeline::allAnimations const):
 27 * animation/DocumentTimeline.cpp:
 28 (WebCore::DocumentTimeline::documentWillUpdateAnimationsAndSendEvents):
 29 (WebCore::DocumentTimeline::documentDidUpdateAnimationsAndSendEvents):
 30 (WebCore::DocumentTimeline::prepareForPendingAnimationEventsDispatch):
 31 (WebCore::DocumentTimeline::updateCurrentTime): Deleted.
 32 (WebCore::DocumentTimeline::updateAnimationsAndSendEvents): Deleted.
 33 (WebCore::DocumentTimeline::internalUpdateAnimationsAndSendEvents): Deleted.
 34 * animation/DocumentTimeline.h:
 35 * animation/DocumentTimelinesController.cpp:
 36 (WebCore::DocumentTimelinesController::DocumentTimelinesController):
 37 (WebCore::DocumentTimelinesController::updateAnimationsAndSendEvents):
 38 * animation/DocumentTimelinesController.h:
 39 * animation/WebAnimationTypes.h:
 40 * dom/Document.cpp:
 41 (WebCore::Document::ensureTimelinesController):
 42
1432020-04-21 Antoine Quint <graouts@apple.com>
244
345 [Web Animations] Add a supporting object for Document to manage timelines

Source/WebCore/animation/AnimationTimeline.h

@@class AnimationTimeline : public RefCounted<AnimationTimeline>, public CanMakeWe
5050public:
5151 virtual bool isDocumentTimeline() const { return false; }
5252
 53 AnimationCollection relevantAnimations() const { return m_animations; }
 54 Vector<WeakPtr<WebAnimation>> allAnimations() const { return m_allAnimations; }
 55
5356 void forgetAnimation(WebAnimation*);
5457 virtual void animationTimingDidChange(WebAnimation&);
5558 virtual void removeAnimation(WebAnimation&);

Source/WebCore/animation/DocumentTimeline.cpp

3333#include "DeclarativeAnimation.h"
3434#include "Document.h"
3535#include "DocumentTimelinesController.h"
36 #include "EventLoop.h"
3736#include "EventNames.h"
3837#include "GraphicsLayer.h"
3938#include "KeyframeEffect.h"

@@bool DocumentTimeline::shouldRunUpdateAnimationsAndSendEventsIgnoringSuspensionS
362361 return !m_animations.isEmpty() || !m_pendingAnimationEvents.isEmpty() || !m_acceleratedAnimationsPendingRunningStateChange.isEmpty();
363362}
364363
365 void DocumentTimeline::updateCurrentTime(DOMHighResTimeStamp timestamp)
 364DocumentTimeline::ShouldUpdateAnimationsAndSendEvents DocumentTimeline::documentWillUpdateAnimationsAndSendEvents(DOMHighResTimeStamp timestamp)
366365{
367366 // We need to freeze the current time even if no animation is running.
368367 // document.timeline.currentTime may be called from a rAF callback and
369368 // it has to match the rAF timestamp.
370369 if (!m_isSuspended || !shouldRunUpdateAnimationsAndSendEventsIgnoringSuspensionState())
371370 cacheCurrentTime(timestamp);
372 }
373371
374 void DocumentTimeline::updateAnimationsAndSendEvents()
375 {
376372 // Updating animations and sending events may invalidate the timing of some animations, so we must set the m_animationResolutionScheduled
377373 // flag to false prior to running that procedure to allow animation with timing model updates to schedule updates.
378  m_animationResolutionScheduled = false;
379 
380  if (m_isSuspended)
381  return;
382 
383  if (!shouldRunUpdateAnimationsAndSendEventsIgnoringSuspensionState())
384  return;
385 
386  internalUpdateAnimationsAndSendEvents();
387  applyPendingAcceleratedAnimations();
 374 bool wasAnimationResolutionScheduled = std::exchange(m_animationResolutionScheduled, false);
388375
389  if (!m_animationResolutionScheduled)
390  scheduleNextTick();
391 }
 376 if (!wasAnimationResolutionScheduled || m_isSuspended || !shouldRunUpdateAnimationsAndSendEventsIgnoringSuspensionState())
 377 return DocumentTimeline::ShouldUpdateAnimationsAndSendEvents::No;
392378
393 void DocumentTimeline::internalUpdateAnimationsAndSendEvents()
394 {
395379 m_numberOfAnimationTimelineInvalidationsForTesting++;
396380
397381 // enqueueAnimationEvent() calls scheduleAnimationResolution() to ensure that the "update animations and send events"

@@void DocumentTimeline::internalUpdateAnimationsAndSendEvents()
399383 // this procedure is running should not schedule animation resolution until the event queue has been cleared.
400384 m_shouldScheduleAnimationResolutionForNewPendingEvents = false;
401385
402  // https://drafts.csswg.org/web-animations/#update-animations-and-send-events
403 
404  // 1. Update the current time of all timelines associated with doc passing now as the timestamp.
405 
406  Vector<RefPtr<WebAnimation>> animationsToRemove;
407  Vector<RefPtr<CSSTransition>> completedTransitions;
408 
409  for (auto& animation : m_animations) {
410  if (animation->timeline() != this) {
411  ASSERT(!animation->timeline());
412  animationsToRemove.append(animation);
413  continue;
414  }
415 
416  // This will notify the animation that timing has changed and will call automatically
417  // schedule invalidation if required for this animation.
418  animation->tick();
419 
420  if (!animation->isRelevant() && !animation->needsTick())
421  animationsToRemove.append(animation);
422 
423  if (!animation->needsTick() && is<CSSTransition>(animation) && animation->playState() == WebAnimation::PlayState::Finished) {
424  auto* transition = downcast<CSSTransition>(animation.get());
425  if (transition->owningElement())
426  completedTransitions.append(transition);
427  }
428  }
429 
430  // 2. Remove replaced animations for doc.
431  removeReplacedAnimations();
432 
433  // 3. Perform a microtask checkpoint.
434  if (auto document = makeRefPtr(this->document()))
435  document->eventLoop().performMicrotaskCheckpoint();
436 
437  // 4. Let events to dispatch be a copy of doc's pending animation event queue.
438  // 5. Clear doc's pending animation event queue.
439  auto pendingAnimationEvents = WTFMove(m_pendingAnimationEvents);
440  m_shouldScheduleAnimationResolutionForNewPendingEvents = true;
441 
442  // 6. Perform a stable sort of the animation events in events to dispatch as follows.
443  std::stable_sort(pendingAnimationEvents.begin(), pendingAnimationEvents.end(), [] (const Ref<AnimationEventBase>& lhs, const Ref<AnimationEventBase>& rhs) {
444  // 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
445  // and events whose scheduled event time is unresolved sort before events with a resolved scheduled event time.
446  // 2. Within events with equal scheduled event times, sort by their composite order. FIXME: We don't do this.
447  if (lhs->timelineTime() && !rhs->timelineTime())
448  return false;
449  if (!lhs->timelineTime() && rhs->timelineTime())
450  return true;
451  if (!lhs->timelineTime() && !rhs->timelineTime())
452  return true;
453  return lhs->timelineTime().value() < rhs->timelineTime().value();
454  });
455 
456  // 7. Dispatch each of the events in events to dispatch at their corresponding target using the order established in the previous step.
457  for (auto& pendingAnimationEvent : pendingAnimationEvents)
458  pendingAnimationEvent->target()->dispatchEvent(pendingAnimationEvent);
 386 return DocumentTimeline::ShouldUpdateAnimationsAndSendEvents::Yes;
 387}
459388
460  // This will cancel any scheduled invalidation if we end up removing all animations.
461  for (auto& animation : animationsToRemove) {
462  // An animation that was initially marked as irrelevant may have changed while we were sending events, so we run the same
463  // check that we ran to add it to animationsToRemove in the first place.
464  if (!animation->isRelevant() && !animation->needsTick())
465  removeAnimation(*animation);
466  }
 389void DocumentTimeline::documentDidUpdateAnimationsAndSendEvents()
 390{
 391 applyPendingAcceleratedAnimations();
467392
468  // Now that animations that needed removal have been removed, let's update the list of completed transitions.
469  // This needs to happen after dealing with the list of animations to remove as the animation may have been
470  // removed from the list of completed transitions otherwise.
471  for (auto& completedTransition : completedTransitions)
472  transitionDidComplete(completedTransition);
 393 if (!m_animationResolutionScheduled)
 394 scheduleNextTick();
473395}
474396
475397bool DocumentTimeline::animationCanBeRemoved(WebAnimation& animation)

@@void DocumentTimeline::enqueueAnimationEvent(AnimationEventBase& event)
735657 scheduleAnimationResolution();
736658}
737659
 660AnimationEvents DocumentTimeline::prepareForPendingAnimationEventsDispatch()
 661{
 662 m_shouldScheduleAnimationResolutionForNewPendingEvents = true;
 663 return std::exchange(m_pendingAnimationEvents, { });
 664}
 665
738666Vector<std::pair<String, double>> DocumentTimeline::acceleratedAnimationsForElement(Element& element) const
739667{
740668 auto* renderer = element.renderer();

Source/WebCore/animation/DocumentTimeline.h

@@public:
5858 void removeAnimation(WebAnimation&) override;
5959 void animationWasAddedToElement(WebAnimation&, Element&) final;
6060 void animationWasRemovedFromElement(WebAnimation&, Element&) final;
 61 void transitionDidComplete(RefPtr<CSSTransition>);
6162
6263 // If possible, compute the visual extent of any transform animation on the given renderer
6364 // using the given rect, returning the result in the rect. Return false if there is some

@@public:
7273
7374 void enqueueAnimationEvent(AnimationEventBase&);
7475
75  bool scheduledUpdate() const { return m_animationResolutionScheduled; }
76  void updateCurrentTime(DOMHighResTimeStamp timestamp);
77  void updateAnimationsAndSendEvents();
 76 enum class ShouldUpdateAnimationsAndSendEvents : uint8_t { Yes, No };
 77 ShouldUpdateAnimationsAndSendEvents documentWillUpdateAnimationsAndSendEvents(DOMHighResTimeStamp);
 78 void removeReplacedAnimations();
 79 AnimationEvents prepareForPendingAnimationEventsDispatch();
 80 void documentDidUpdateAnimationsAndSendEvents();
7881
7982 void updateThrottlingState();
8083 WEBCORE_EXPORT Seconds animationInterval() const;

@@private:
98101 void clearTickScheduleTimer();
99102 void internalUpdateAnimationsAndSendEvents();
100103 void updateListOfElementsWithRunningAcceleratedAnimationsForElement(Element&);
101  void transitionDidComplete(RefPtr<CSSTransition>);
102104 void scheduleNextTick();
103  void removeReplacedAnimations();
104105 bool animationCanBeRemoved(WebAnimation&);
105106 bool shouldRunUpdateAnimationsAndSendEventsIgnoringSuspensionState() const;
106107

@@private:
108109 GenericTaskQueue<Timer> m_currentTimeClearingTaskQueue;
109110 HashSet<RefPtr<WebAnimation>> m_acceleratedAnimationsPendingRunningStateChange;
110111 HashSet<Element*> m_elementsWithRunningAcceleratedAnimations;
111  Vector<Ref<AnimationEventBase>> m_pendingAnimationEvents;
 112 AnimationEvents m_pendingAnimationEvents;
112113 WeakPtr<Document> m_document;
113114 Markable<Seconds, Seconds::MarkableTraits> m_cachedCurrentTime;
114115 Seconds m_originTime;

Source/WebCore/animation/DocumentTimelinesController.cpp

2626#include "config.h"
2727#include "DocumentTimelinesController.h"
2828
 29#include "AnimationEventBase.h"
 30#include "CSSTransition.h"
 31#include "Document.h"
2932#include "DocumentTimeline.h"
 33#include "EventLoop.h"
 34#include "WebAnimation.h"
 35#include "WebAnimationTypes.h"
3036
3137namespace WebCore {
3238
33 DocumentTimelinesController::DocumentTimelinesController()
 39DocumentTimelinesController::DocumentTimelinesController(Document& document)
 40 : m_document(document)
3441{
3542}
3643

@@void DocumentTimelinesController::updateAnimationsAndSendEvents(DOMHighResTimeSt
5865{
5966 ASSERT(!m_timelines.hasNullReferences());
6067
61  // We need to copy m_timelines before iterating over its members since calling updateAnimationsAndSendEvents() may mutate m_timelines.
62  Vector<RefPtr<DocumentTimeline>> timelines;
63  bool shouldUpdateAnimations = false;
64  for (auto& timeline : m_timelines) {
65  if (timeline.scheduledUpdate())
66  shouldUpdateAnimations = true;
67  timelines.append(&timeline);
 68 // We need to copy m_timelines before iterating over its members since the steps in this procedure may mutate m_timelines.
 69 Vector<RefPtr<DocumentTimeline>> protectedTimelines;
 70 for (auto& timeline : m_timelines)
 71 protectedTimelines.append(&timeline);
 72
 73 // 1. Update the current time of all timelines associated with doc passing now as the timestamp.
 74 Vector<DocumentTimeline*> timelinesToUpdate;
 75 Vector<RefPtr<WebAnimation>> animationsToRemove;
 76 Vector<RefPtr<CSSTransition>> completedTransitions;
 77 for (auto& timeline : protectedTimelines) {
 78 auto shouldUpdateAnimationsAndSendEvents = timeline->documentWillUpdateAnimationsAndSendEvents(timestamp);
 79 if (shouldUpdateAnimationsAndSendEvents == DocumentTimeline::ShouldUpdateAnimationsAndSendEvents::No)
 80 continue;
 81
 82 timelinesToUpdate.append(timeline.get());
 83
 84 for (auto& animation : timeline->relevantAnimations()) {
 85 if (animation->timeline() != timeline.get()) {
 86 ASSERT(!animation->timeline());
 87 animationsToRemove.append(animation);
 88 continue;
 89 }
 90
 91 // This will notify the animation that timing has changed and will call automatically
 92 // schedule invalidation if required for this animation.
 93 animation->tick();
 94
 95 if (!animation->isRelevant() && !animation->needsTick())
 96 animationsToRemove.append(animation);
 97
 98 if (!animation->needsTick() && is<CSSTransition>(animation) && animation->playState() == WebAnimation::PlayState::Finished) {
 99 auto* transition = downcast<CSSTransition>(animation.get());
 100 if (transition->owningElement())
 101 completedTransitions.append(transition);
 102 }
 103 }
68104 }
69105
70  for (auto& timeline : timelines) {
71  timeline->updateCurrentTime(timestamp);
72  if (shouldUpdateAnimations)
73  timeline->updateAnimationsAndSendEvents();
 106 if (timelinesToUpdate.isEmpty())
 107 return;
 108
 109 // 2. Remove replaced animations for doc.
 110 for (auto& timeline : m_timelines)
 111 timeline.removeReplacedAnimations();
 112
 113 // 3. Perform a microtask checkpoint.
 114 auto document = makeRefPtr(m_document);
 115 document->eventLoop().performMicrotaskCheckpoint();
 116
 117 // 4. Let events to dispatch be a copy of doc's pending animation event queue.
 118 // 5. Clear doc's pending animation event queue.
 119 AnimationEvents events;
 120 for (auto& timeline : timelinesToUpdate)
 121 events.appendVector(timeline->prepareForPendingAnimationEventsDispatch());
 122
 123 // 6. Perform a stable sort of the animation events in events to dispatch as follows.
 124 std::stable_sort(events.begin(), events.end(), [] (const Ref<AnimationEventBase>& lhs, const Ref<AnimationEventBase>& rhs) {
 125 // 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
 126 // and events whose scheduled event time is unresolved sort before events with a resolved scheduled event time.
 127 // 2. Within events with equal scheduled event times, sort by their composite order. FIXME: We don't do this.
 128 if (lhs->timelineTime() && !rhs->timelineTime())
 129 return false;
 130 if (!lhs->timelineTime() && rhs->timelineTime())
 131 return true;
 132 if (!lhs->timelineTime() && !rhs->timelineTime())
 133 return true;
 134 return lhs->timelineTime().value() < rhs->timelineTime().value();
 135 });
 136
 137 // 7. Dispatch each of the events in events to dispatch at their corresponding target using the order established in the previous step.
 138 for (auto& event : events)
 139 event->target()->dispatchEvent(event);
 140
 141 // This will cancel any scheduled invalidation if we end up removing all animations.
 142 for (auto& animation : animationsToRemove) {
 143 // An animation that was initially marked as irrelevant may have changed while we were sending events, so we run the same
 144 // check that we ran to add it to animationsToRemove in the first place.
 145 if (!animation->isRelevant() && !animation->needsTick())
 146 animation->timeline()->removeAnimation(*animation);
74147 }
 148
 149 // Now that animations that needed removal have been removed, let's update the list of completed transitions.
 150 // This needs to happen after dealing with the list of animations to remove as the animation may have been
 151 // removed from the list of completed transitions otherwise.
 152 for (auto& completedTransition : completedTransitions)
 153 downcast<DocumentTimeline>(*completedTransition->timeline()).transitionDidComplete(completedTransition);
 154
 155 for (auto& timeline : timelinesToUpdate)
 156 timeline->documentDidUpdateAnimationsAndSendEvents();
75157}
76158
77159} // namespace WebCore

Source/WebCore/animation/DocumentTimelinesController.h

3030
3131namespace WebCore {
3232
 33class CSSTransition;
 34class Document;
3335class DocumentTimeline;
 36class WebAnimation;
3437
3538class DocumentTimelinesController {
3639 WTF_MAKE_FAST_ALLOCATED;
3740public:
38  explicit DocumentTimelinesController();
 41 explicit DocumentTimelinesController(Document&);
3942 ~DocumentTimelinesController();
4043
4144 void addTimeline(DocumentTimeline&);

@@public:
4447 void updateAnimationsAndSendEvents(DOMHighResTimeStamp);
4548
4649private:
 50 struct AnimationsToProcess {
 51 Vector<RefPtr<WebAnimation>> animationsToRemove;
 52 Vector<RefPtr<CSSTransition>> completedTransitions;
 53 };
 54
4755 WeakHashSet<DocumentTimeline> m_timelines;
 56 Document& m_document;
4857};
4958
5059} // namespace WebCore
51 

Source/WebCore/animation/WebAnimationTypes.h

3232
3333namespace WebCore {
3434
 35class AnimationEventBase;
3536class AnimationList;
3637class CSSAnimation;
3738class CSSTransition;

@@struct WebAnimationsMarkableDoubleTraits {
5253using MarkableDouble = Markable<double, WebAnimationsMarkableDoubleTraits>;
5354
5455using AnimationCollection = ListHashSet<RefPtr<WebAnimation>>;
 56using AnimationEvents = Vector<Ref<AnimationEventBase>>;
5557using PropertyToTransitionMap = HashMap<CSSPropertyID, RefPtr<CSSTransition>>;
5658using CSSAnimationCollection = ListHashSet<RefPtr<CSSAnimation>>;
5759

Source/WebCore/dom/Document.cpp

@@void Document::setConsoleMessageListener(RefPtr<StringCallback>&& listener)
80808080DocumentTimelinesController& Document::ensureTimelinesController()
80818081{
80828082 if (!m_timelinesController)
8083  m_timelinesController = makeUnique<DocumentTimelinesController>();
 8083 m_timelinesController = makeUnique<DocumentTimelinesController>(*this);
80848084 return *m_timelinesController.get();
80858085}
80868086

LayoutTests/ChangeLog

 12020-04-22 Antoine Quint <graouts@apple.com>
 2
 3 [Web Animations] Coordinate "update animations and send events" procedure across multiple timelines
 4 https://bugs.webkit.org/show_bug.cgi?id=202109
 5 <rdar://problem/59470821>
 6
 7 Reviewed by NOBODY (OOPS!).
 8
 9 Remove the flaky expectation for the improved test.
 10
 11 * TestExpectations:
 12
1132020-04-22 Diego Pino Garcia <dpino@igalia.com>
214
315 [GTK] Gardening, update test expectations

LayoutTests/imported/w3c/ChangeLog

 12020-04-22 Antoine Quint <graouts@apple.com>
 2
 3 [Web Animations] Coordinate "update animations and send events" procedure across multiple timelines
 4 https://bugs.webkit.org/show_bug.cgi?id=202109
 5 <rdar://problem/59470821>
 6
 7 Reviewed by NOBODY (OOPS!).
 8
 9 Mark a new test as PASS which shows that we correctly perform a single microstask checkpoint when updating multiple timelines.
 10
 11 * web-platform-tests/web-animations/timing-model/timelines/update-and-send-events-expected.txt:
 12
1132020-04-21 Sergio Villar Senin <svillar@igalia.com>
214
315 Import latest changes from web-platform-test/webxr/resources to enable testing in WebKit

LayoutTests/TestExpectations

@@webkit.org/b/207261 [ Debug ] imported/w3c/web-platform-tests/css/css-background
40644064
40654065webkit.org/b/207262 imported/w3c/web-platform-tests/web-animations/timing-model/animations/sync-start-times.html [ Pass ImageOnlyFailure ]
40664066
4067 webkit.org/b/202109 imported/w3c/web-platform-tests/web-animations/timing-model/timelines/update-and-send-events.html [ Pass Failure ]
4068 
40694067# WebXR is being implemented
40704068webkit.org/b/208988 imported/w3c/web-platform-tests/webxr [ Skip ]
40714069

LayoutTests/imported/w3c/web-platform-tests/web-animations/timing-model/timelines/update-and-send-events-expected.txt

@@FAIL Sorts cancel events by composite order assert_array_equals: cancel events s
66PASS Queues a cancel event in transitionstart event callback
77PASS Sorts events for the same transition
88PASS Playback events with the same timeline retain the order in which they arequeued
9 FAIL All timelines are updated before running microtasks promise_test: Unhandled rejection with value: object "AbortError: The operation was aborted."
 9PASS All timelines are updated before running microtasks
1010