[Web Animations] Implement the update animations and send events procedure
authorgraouts@webkit.org <graouts@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Tue, 30 Oct 2018 09:30:03 +0000 (09:30 +0000)
committergraouts@webkit.org <graouts@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Tue, 30 Oct 2018 09:30:03 +0000 (09:30 +0000)
https://bugs.webkit.org/show_bug.cgi?id=191013
<rdar://problem/45620495>

Reviewed by Dean Jackson.

LayoutTests/imported/mozilla:

Progression in a couple of getAnimations() tests for CSS Animations.

* css-animations/test_document-get-animations-expected.txt:

LayoutTests/imported/w3c:

Progressions in a couple of Web Animations Web Platform Tests.

* web-platform-tests/web-animations/timing-model/animations/current-time-expected.txt:
* web-platform-tests/web-animations/timing-model/animations/updating-the-finished-state-expected.txt:

Source/WebCore:

While we implemented the various parts of what the Web Animations specification refers to as the "update animations and send events"
procedure, we did not implement it as one function and in the correct order, specifically updating animations and sending events in
two separate tasks. We now have a single method on DocumentTimeline which runs as the DisplayRefreshMonitor fires to update each
"relevant" animation with the current time, perform a microtask checkpoint and dispatch events.

Implementing this procedure allowed us to make several enhancements:

1. We introduce the concept of a "relevant" animation, which is essentially an animation that is either pending or playing. All animations
in a different state are no longer owned by the DocumentTimeline and can thus be destroyed if the developer doesn't hold references in JS.
Maintaining such a list guarantees that we're only updating animations that would have changed state since the last time the "update animations
and send events" procedure was run. Note that DeclarativeAnimation instances are also considered to be relevant if they have queued DOM events
to dispatch as they could otherwise be destroyed before they can fully dispatch them.

2. We no longer conflate the timing model and effects. Until now the way we would update animations was to go through all elements for which
we had a registered animation, invalidate their style and finally forcing a style update on the document. We had a separate data structure where
we help animations without targets so we update these as well in a separate pass, in order to make sure that promises and events would fire for
them as expected. We now let the "update animations and send events" procedure update the timing of all relevant animations and let individual
animation effects invalidate their style as needed, the document style invalidation happening naturally without DocumentTimeline forcing it.

3. We use a single step to schedule the update of animations, which is to register for a display refresh monitor update provided a "relevant"
animation is known since the previous update. Until now we first had an "timing model invalidation" task scheduled upon any change of an animation's
timing model, which would then create a timer to the earliest moment any listed animation would require an update, finally registering a display
refresh monitor update, which used at least GenericTaskQueue<Timer> and potentially two, whereas we use none right now.

4. We allow for a display refresh monitor update to be canceled should the number of "relevant" animations since the last update goes back to 0.

To facilitate all of this, we have changed the m_animations ListHashSet to contain only the "relevant" animations, and no longer every animation created
that has this DocumentTimeline set as their "timeline" property. To keep this list current, every single change that changes a given animation's timing
ends up calling AnimationTimeline::animationTimingDidChange() passing the animation as the sole parameter and adding this animation to m_animations. We
immediately schedule a display refresh monitor update if one wasn't already scheduled. Then, when running the "update animations and send events"
procedure, we call a new WebAnimation::tick() method on each of those animations, which updates this animation's effect and relevance, using the newly
computed relevance to identify whether this animation should be kept in the m_animations ListHashSet.

This is only the first step towards a more efficient update and ownership model of animations by the document timeline since animations created as CSS
Animations and CSS Transitions are committed through CSS have dedicated data structures that are not updated in this particular patch, but this will be
addressed in a followup to keep this already significant patch smaller. Another issue that will be addressed later is the ability to not schedule display
refresh monitor udpates when only accelerated animations are running.

* animation/AnimationTimeline.cpp:
(WebCore::AnimationTimeline::animationTimingDidChange): Called by animations when any aspect of their timing model changes. The provided animation is then
added to the m_animations list unless its timeline is no longer this timeline.
(WebCore::AnimationTimeline::removeAnimation): Remove the provided animation from m_animations and remove any animation registered on the element-specific
animation lists if this animation has an effect with a target.
(WebCore::AnimationTimeline::animationWasAddedToElement): We no longer need to worry about the m_animationsWithoutTarget data structure since we removed it.
(WebCore::removeCSSTransitionFromMap): Fix a bug where we would remove any CSSTransition in the provided map that had a matching transition-property instead
of checking the CSSTransition registered for this transition-property was indeed the provided CSSTransition. The other code changes in this patch made this
code now cause regressions in the Web Platform Tests.
(WebCore::AnimationTimeline::animationWasRemovedFromElement): Stop updating m_animationsWithoutTarget since it no longer exists.
(WebCore::AnimationTimeline::elementWasRemoved):
(WebCore::AnimationTimeline::updateCSSAnimationsForElement): Fix a small error that caused a regression in the Web Platform Tests where we could attempt to
call setBackingAnimation() on a nullptr instead of a valid CSSAnimation.
(WebCore::AnimationTimeline::cancelOrRemoveDeclarativeAnimation):
(WebCore::AnimationTimeline::addAnimation): Deleted.
* animation/AnimationTimeline.h:
(WebCore::AnimationTimeline::hasElementAnimations const): Deleted.
(WebCore::AnimationTimeline:: const): Deleted.
(WebCore::AnimationTimeline::elementToAnimationsMap): Deleted.
(WebCore::AnimationTimeline::elementToCSSAnimationsMap): Deleted.
(WebCore::AnimationTimeline::elementToCSSTransitionsMap): Deleted.
* animation/CSSTransition.cpp:
(WebCore::CSSTransition::canBeListed const): Deleted.
* animation/CSSTransition.h:
* animation/DeclarativeAnimation.cpp:
(WebCore::DeclarativeAnimation::tick): Call the superclass's method and queue any necessary DOM events reflecting the timing model changes.
(WebCore::DeclarativeAnimation::needsTick const): Call the superclass's method and return true also if we have pending events since otherwise this animation
could be removed from m_animations on its AnimationTimeline and potentially destroyed before the GenericEventQueue had a chance to dispatch all events.
(WebCore::DeclarativeAnimation::startTime const): We removed the custom binding for this IDL property and renamed the method from bindingsStartTime to startTime.
(WebCore::DeclarativeAnimation::setStartTime): We removed the custom binding for this IDL property and renamed the method from setBindingsStartTime to setStartTime.
(WebCore::DeclarativeAnimation::bindingsStartTime const): Deleted.
(WebCore::DeclarativeAnimation::setBindingsStartTime): Deleted.
* animation/DeclarativeAnimation.h:
* animation/DocumentAnimationScheduler.cpp:
(WebCore::DocumentAnimationScheduler::unscheduleWebAnimationsResolution): Add a method to mark that we no longer need a display refresh monitor update for this
document's animation timeline. This is called when m_animations becomes empty.
* animation/DocumentAnimationScheduler.h:
* animation/DocumentTimeline.cpp:
(WebCore::DocumentTimeline::DocumentTimeline):
(WebCore::DocumentTimeline::detachFromDocument): Stop clearing two task queues and a timer that no longer exist and instead only clear the task queue to clear
the cached current time, which we queue any time we generate a new one (see DocumentTimeline::currentTime).
(WebCore::DocumentTimeline::getAnimations const): Use isRelevant() instead of canBeListed().
(WebCore::DocumentTimeline::updateThrottlingState):
(WebCore::DocumentTimeline::suspendAnimations):
(WebCore::DocumentTimeline::resumeAnimations):
(WebCore::DocumentTimeline::numberOfActiveAnimationsForTesting const):
(WebCore::DocumentTimeline::currentTime): Queue a task in the new m_currentTimeClearingTaskQueue task queue to clear the current time that we've generated and cached
in the next run loop (provided all pending JS execution has also completed).
(WebCore::DocumentTimeline::maybeClearCachedCurrentTime):
(WebCore::DocumentTimeline::scheduleAnimationResolutionIfNeeded): Schedule a display refresh monitor update if we are not suspended and have "relevant" animations.
(WebCore::DocumentTimeline::animationTimingDidChange): Call scheduleAnimationResolutionIfNeeded() after calling the superclass's implementation.
(WebCore::DocumentTimeline::removeAnimation): Call unscheduleAnimationResolution() if the list of "relevant" animations is now empty.
(WebCore::DocumentTimeline::unscheduleAnimationResolution): Unschedule a pending display refresh monitor update.
(WebCore::DocumentTimeline::animationResolutionTimerFired):
(WebCore::DocumentTimeline::updateAnimationsAndSendEvents): Implement the "update animations and send events" procedure as specified by the Web Animations spec.
During this procedure, we call tick() on all animations listed in m_animations and create a list of animations to remove from that list if this animation is no
longer relevant following the call to tick().
(WebCore::DocumentTimeline::enqueueAnimationPlaybackEvent):
(WebCore::DocumentTimeline::timingModelDidChange): Deleted.
(WebCore::DocumentTimeline::scheduleInvalidationTaskIfNeeded): Deleted.
(WebCore::DocumentTimeline::performInvalidationTask): Deleted.
(WebCore::DocumentTimeline::updateAnimationSchedule): Deleted.
(WebCore::DocumentTimeline::animationScheduleTimerFired): Deleted.
(WebCore::DocumentTimeline::updateAnimations): Deleted.
(WebCore::compareAnimationPlaybackEvents): Deleted.
(WebCore::DocumentTimeline::performEventDispatchTask): Deleted.
* animation/DocumentTimeline.h:
* animation/WebAnimation.cpp: The majority of the changes to this class is that we call the new timingDidChange() method when any code that modifies the timing model
is run. We also remove methods to set the pending play and pause tasks as well as the animation's start time and hold time since any time we're changing these instance
variables, we later already have a call to update the timing model and we were doing more work than needed. As a result we no longer need an internal method to set the
start time and can stop requiring a custom IDL binding for the "startTime" property.
(WebCore::WebAnimation::effectTimingPropertiesDidChange):
(WebCore::WebAnimation::setEffect):
(WebCore::WebAnimation::setEffectInternal):
(WebCore::WebAnimation::setTimeline):
(WebCore::WebAnimation::setTimelineInternal):
(WebCore::WebAnimation::startTime const):
(WebCore::WebAnimation::setStartTime):
(WebCore::WebAnimation::silentlySetCurrentTime):
(WebCore::WebAnimation::setCurrentTime):
(WebCore::WebAnimation::setPlaybackRate):
(WebCore::WebAnimation::cancel):
(WebCore::WebAnimation::resetPendingTasks):
(WebCore::WebAnimation::finish):
(WebCore::WebAnimation::timingDidChange): New method called any time a timing property changed where we run the "update the finished state" procedure and notify the
animation's timeline that its timing changed so that it can be considered the next time the "update animations and send events" procedure runs.
(WebCore::WebAnimation::invalidateEffect):
(WebCore::WebAnimation::updateFinishedState): Update the animation's relevance after running the procedure as specified.
(WebCore::WebAnimation::play):
(WebCore::WebAnimation::runPendingPlayTask):
(WebCore::WebAnimation::pause):
(WebCore::WebAnimation::runPendingPauseTask):
(WebCore::WebAnimation::needsTick const):
(WebCore::WebAnimation::tick): New method called during the "update animations and send events" procedure where we run the "update the finished state" procedure and run
the pending play and pause tasks.
(WebCore::WebAnimation::resolve):
(WebCore::WebAnimation::updateRelevance):
(WebCore::WebAnimation::computeRelevance):
(WebCore::WebAnimation::timingModelDidChange): Deleted.
(WebCore::WebAnimation::setHoldTime): Deleted.
(WebCore::WebAnimation::bindingsStartTime const): Deleted.
(WebCore::WebAnimation::setBindingsStartTime): Deleted.
(WebCore::WebAnimation::setTimeToRunPendingPlayTask): Deleted.
(WebCore::WebAnimation::setTimeToRunPendingPauseTask): Deleted.
(WebCore::WebAnimation::updatePendingTasks): Deleted.
(WebCore::WebAnimation::timeToNextRequiredTick const): Deleted.
(WebCore::WebAnimation::runPendingTasks): Deleted.
(WebCore::WebAnimation::canBeListed const): Deleted.
* animation/WebAnimation.h:
(WebCore::WebAnimation::isRelevant const):
(WebCore::WebAnimation::hasPendingPlayTask const):
(WebCore::WebAnimation::isEffectInvalidationSuspended):
* animation/WebAnimation.idl:
* dom/Element.cpp:
(WebCore::Element::getAnimations): Use isRelevant() instead of canBeListed().

LayoutTests:

Several tests that broke when turning Web Animations CSS Integration on by default are now passing. In the case of one test, we had to ensure
that the final animation frame had been committed before terminating the test or there would be a tiny image reference issue.

* TestExpectations:
* fast/layers/no-clipping-overflow-hidden-added-after-transform.html:

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@237587 268f45cc-cd09-0410-ab3c-d52691b4dbfc

23 files changed:
LayoutTests/ChangeLog
LayoutTests/TestExpectations
LayoutTests/fast/layers/no-clipping-overflow-hidden-added-after-transform.html
LayoutTests/imported/mozilla/ChangeLog
LayoutTests/imported/mozilla/css-animations/test_document-get-animations-expected.txt
LayoutTests/imported/w3c/ChangeLog
LayoutTests/imported/w3c/web-platform-tests/web-animations/timing-model/animations/current-time-expected.txt
LayoutTests/imported/w3c/web-platform-tests/web-animations/timing-model/animations/updating-the-finished-state-expected.txt
Source/WebCore/ChangeLog
Source/WebCore/animation/AnimationTimeline.cpp
Source/WebCore/animation/AnimationTimeline.h
Source/WebCore/animation/CSSTransition.cpp
Source/WebCore/animation/CSSTransition.h
Source/WebCore/animation/DeclarativeAnimation.cpp
Source/WebCore/animation/DeclarativeAnimation.h
Source/WebCore/animation/DocumentAnimationScheduler.cpp
Source/WebCore/animation/DocumentAnimationScheduler.h
Source/WebCore/animation/DocumentTimeline.cpp
Source/WebCore/animation/DocumentTimeline.h
Source/WebCore/animation/WebAnimation.cpp
Source/WebCore/animation/WebAnimation.h
Source/WebCore/animation/WebAnimation.idl
Source/WebCore/dom/Element.cpp

index a4f5e0f..eac110d 100644 (file)
@@ -1,3 +1,17 @@
+2018-10-28  Antoine Quint  <graouts@apple.com>
+
+        [Web Animations] Implement the update animations and send events procedure
+        https://bugs.webkit.org/show_bug.cgi?id=191013
+        <rdar://problem/45620495>
+
+        Reviewed by Dean Jackson.
+
+        Several tests that broke when turning Web Animations CSS Integration on by default are now passing. In the case of one test, we had to ensure
+        that the final animation frame had been committed before terminating the test or there would be a tiny image reference issue.
+
+        * TestExpectations:
+        * fast/layers/no-clipping-overflow-hidden-added-after-transform.html:
+
 2018-10-30  Youenn Fablet  <youenn@apple.com>
 
         LibWebRTCRtpReceiverBackend::getSynchronizationSources should use Vector::append
index 21e8f69..9cd7281 100644 (file)
@@ -2897,13 +2897,8 @@ webkit.org/b/190032 compositing/layer-creation/mismatched-transform-transition-o
 webkit.org/b/190032 compositing/layer-creation/scale-rotation-transition-overlap.html [ Failure ]
 webkit.org/b/190032 compositing/layer-creation/translate-scale-transition-overlap.html [ Failure ]
 webkit.org/b/190032 compositing/layer-creation/translate-transition-overlap.html [ Failure ]
-webkit.org/b/190032 compositing/visible-rect/animated-from-none.html [ Failure ]
-webkit.org/b/190032 fast/animation/css-animation-resuming-when-visible-with-style-change2.html [ Failure ]
 webkit.org/b/190032 imported/w3c/web-platform-tests/css/css-logical/animation-003.tentative.html [ Failure ]
 webkit.org/b/190032 imported/w3c/web-platform-tests/css/css-scoping/keyframes-001.html [ Failure ]
-webkit.org/b/190032 imported/w3c/web-platform-tests/web-animations/animation-model/keyframe-effects/effect-value-context.html [ Failure ]
-webkit.org/b/190032 imported/w3c/web-platform-tests/web-animations/interfaces/Animatable/animate.html [ Failure ]
-webkit.org/b/190032 imported/w3c/web-platform-tests/web-animations/timing-model/animations/current-time.html [ Failure ]
 
 # FIXME: Need to implement MediaRecorder dataavailable event to support these testcases
 fast/mediacapturefromelement/CanvasCaptureMediaStream-imagebitmaprenderingcontext.html [ Skip ]
index 393a768..8be0219 100644 (file)
@@ -32,7 +32,7 @@ div {
 <script>
     function transitionFinished() {
         if (window.testRunner)
-            window.testRunner.notifyDone();
+            requestAnimationFrame(() => window.testRunner.notifyDone());
     }
 
     if (!window.eventSender)
index 7dcabeb..c923c6e 100644 (file)
@@ -1,3 +1,15 @@
+2018-10-28  Antoine Quint  <graouts@apple.com>
+
+        [Web Animations] Implement the update animations and send events procedure
+        https://bugs.webkit.org/show_bug.cgi?id=191013
+        <rdar://problem/45620495>
+
+        Reviewed by Dean Jackson.
+
+        Progression in a couple of getAnimations() tests for CSS Animations.
+
+        * css-animations/test_document-get-animations-expected.txt:
+
 2018-10-26  Antoine Quint  <graouts@apple.com>
 
         [Web Animations] Rebase some flaky tests
index da150e2..c9d8abc 100644 (file)
@@ -4,8 +4,8 @@ PASS getAnimations for CSS Animations
 PASS Order of CSS Animations - within an element 
 FAIL Order of CSS Animations - across elements assert_equals: Order of second animation returned after tree surgery expected Element node <div style="animation: animLeft 100s"></div> but got Element node <div style="animation: animLeft 100s"></div>
 PASS Order of CSS Animations - across and within elements 
-FAIL Order of CSS Animations - markup-bound vs free animations assert_equals: getAnimations returns markup-bound and free animations expected 2 but got 1
-FAIL Order of CSS Animations - free animations assert_equals: getAnimations returns free animations expected 2 but got 0
+PASS Order of CSS Animations - markup-bound vs free animations 
+PASS Order of CSS Animations - free animations 
 FAIL Order of CSS Animations and CSS Transitions assert_equals: Transition comes first expected "[object CSSTransition]" but got "[object CSSAnimation]"
 PASS Finished but filling CSS Animations are returned 
 PASS Finished but not filling CSS Animations are not returned 
index 985c1fa..f2504d1 100644 (file)
@@ -1,3 +1,16 @@
+2018-10-28  Antoine Quint  <graouts@apple.com>
+
+        [Web Animations] Implement the update animations and send events procedure
+        https://bugs.webkit.org/show_bug.cgi?id=191013
+        <rdar://problem/45620495>
+
+        Reviewed by Dean Jackson.
+
+        Progressions in a couple of Web Animations Web Platform Tests.
+
+        * web-platform-tests/web-animations/timing-model/animations/current-time-expected.txt:
+        * web-platform-tests/web-animations/timing-model/animations/updating-the-finished-state-expected.txt:
+
 2018-10-29  Justin Michaud  <justin_michaud@apple.com>
 
         Revert r237347 registered custom properties... https://bugs.webkit.org/show_bug.cgi?id=190039
index 11249e7..d41a82e 100644 (file)
@@ -1,9 +1,7 @@
 
-Harness Error (TIMEOUT), message = null
-
 PASS The current time returns the hold time when set 
 PASS The current time is unresolved when there is no associated timeline (and no hold time is set) 
 PASS The current time is unresolved when the start time is unresolved (and no hold time is set) 
 PASS The current time is calculated from the timeline time, start time and playback rate 
-TIMEOUT The current time does not progress if playback rate is 0 Test timed out
+PASS The current time does not progress if playback rate is 0 
 
index be17758..efcd3b4 100644 (file)
@@ -10,19 +10,19 @@ PASS Updating the finished state when seeking a reversed animation exactly to ze
 PASS Updating the finished state when playing before end 
 PASS Updating the finished state when seeking before end 
 PASS Updating the finished state when seeking a reversed animation before end 
-TIMEOUT Updating the finished state when playback rate is zero and the current time is less than zero Test timed out
-NOTRUN Updating the finished state when playback rate is zero and the current time is less than end 
-NOTRUN Updating the finished state when playback rate is zero and the current time is greater than end 
-NOTRUN Updating the finished state when current time is unresolved 
+PASS Updating the finished state when playback rate is zero and the current time is less than zero 
+PASS Updating the finished state when playback rate is zero and the current time is less than end 
+PASS Updating the finished state when playback rate is zero and the current time is greater than end 
+PASS Updating the finished state when current time is unresolved 
 PASS Updating the finished state when there is a pending task 
-NOTRUN Updating the finished state when start time is unresolved and did seek = false 
+PASS Updating the finished state when start time is unresolved and did seek = false 
 PASS Updating the finished state when start time is unresolved and did seek = true 
-NOTRUN Finish notification steps don't run when the animation seeks to finish and then seeks back again 
-NOTRUN Finish notification steps run when the animation completes normally 
-NOTRUN Finish notification steps run when the animation seeks past finish 
-NOTRUN Finish notification steps run when the animation completes with .finish(), even if we then seek away 
-NOTRUN Animation finished promise is replaced after seeking back to start 
-NOTRUN Animation finished promise is replaced after replaying from start 
-PASS Animation finish event is fired again after seeking back to start 
-PASS Animation finish event is fired again after replaying from start 
+PASS Finish notification steps don't run when the animation seeks to finish and then seeks back again 
+PASS Finish notification steps run when the animation completes normally 
+PASS Finish notification steps run when the animation seeks past finish 
+PASS Finish notification steps run when the animation completes with .finish(), even if we then seek away 
+PASS Animation finished promise is replaced after seeking back to start 
+PASS Animation finished promise is replaced after replaying from start 
+TIMEOUT Animation finish event is fired again after seeking back to start Test timed out
+TIMEOUT Animation finish event is fired again after replaying from start Test timed out
 
index e8657f1..8110ab1 100644 (file)
@@ -1,3 +1,165 @@
+2018-10-28  Antoine Quint  <graouts@apple.com>
+
+        [Web Animations] Implement the update animations and send events procedure
+        https://bugs.webkit.org/show_bug.cgi?id=191013
+        <rdar://problem/45620495>
+
+        Reviewed by Dean Jackson.
+
+        While we implemented the various parts of what the Web Animations specification refers to as the "update animations and send events"
+        procedure, we did not implement it as one function and in the correct order, specifically updating animations and sending events in
+        two separate tasks. We now have a single method on DocumentTimeline which runs as the DisplayRefreshMonitor fires to update each
+        "relevant" animation with the current time, perform a microtask checkpoint and dispatch events.
+
+        Implementing this procedure allowed us to make several enhancements:
+
+        1. We introduce the concept of a "relevant" animation, which is essentially an animation that is either pending or playing. All animations
+        in a different state are no longer owned by the DocumentTimeline and can thus be destroyed if the developer doesn't hold references in JS.
+        Maintaining such a list guarantees that we're only updating animations that would have changed state since the last time the "update animations
+        and send events" procedure was run. Note that DeclarativeAnimation instances are also considered to be relevant if they have queued DOM events
+        to dispatch as they could otherwise be destroyed before they can fully dispatch them.
+
+        2. We no longer conflate the timing model and effects. Until now the way we would update animations was to go through all elements for which
+        we had a registered animation, invalidate their style and finally forcing a style update on the document. We had a separate data structure where
+        we help animations without targets so we update these as well in a separate pass, in order to make sure that promises and events would fire for
+        them as expected. We now let the "update animations and send events" procedure update the timing of all relevant animations and let individual
+        animation effects invalidate their style as needed, the document style invalidation happening naturally without DocumentTimeline forcing it. 
+
+        3. We use a single step to schedule the update of animations, which is to register for a display refresh monitor update provided a "relevant"
+        animation is known since the previous update. Until now we first had an "timing model invalidation" task scheduled upon any change of an animation's
+        timing model, which would then create a timer to the earliest moment any listed animation would require an update, finally registering a display
+        refresh monitor update, which used at least GenericTaskQueue<Timer> and potentially two, whereas we use none right now.
+
+        4. We allow for a display refresh monitor update to be canceled should the number of "relevant" animations since the last update goes back to 0.
+
+        To facilitate all of this, we have changed the m_animations ListHashSet to contain only the "relevant" animations, and no longer every animation created
+        that has this DocumentTimeline set as their "timeline" property. To keep this list current, every single change that changes a given animation's timing
+        ends up calling AnimationTimeline::animationTimingDidChange() passing the animation as the sole parameter and adding this animation to m_animations. We
+        immediately schedule a display refresh monitor update if one wasn't already scheduled. Then, when running the "update animations and send events"
+        procedure, we call a new WebAnimation::tick() method on each of those animations, which updates this animation's effect and relevance, using the newly
+        computed relevance to identify whether this animation should be kept in the m_animations ListHashSet.
+
+        This is only the first step towards a more efficient update and ownership model of animations by the document timeline since animations created as CSS
+        Animations and CSS Transitions are committed through CSS have dedicated data structures that are not updated in this particular patch, but this will be
+        addressed in a followup to keep this already significant patch smaller. Another issue that will be addressed later is the ability to not schedule display
+        refresh monitor udpates when only accelerated animations are running.
+
+        * animation/AnimationTimeline.cpp:
+        (WebCore::AnimationTimeline::animationTimingDidChange): Called by animations when any aspect of their timing model changes. The provided animation is then
+        added to the m_animations list unless its timeline is no longer this timeline.
+        (WebCore::AnimationTimeline::removeAnimation): Remove the provided animation from m_animations and remove any animation registered on the element-specific
+        animation lists if this animation has an effect with a target.
+        (WebCore::AnimationTimeline::animationWasAddedToElement): We no longer need to worry about the m_animationsWithoutTarget data structure since we removed it.
+        (WebCore::removeCSSTransitionFromMap): Fix a bug where we would remove any CSSTransition in the provided map that had a matching transition-property instead
+        of checking the CSSTransition registered for this transition-property was indeed the provided CSSTransition. The other code changes in this patch made this
+        code now cause regressions in the Web Platform Tests.
+        (WebCore::AnimationTimeline::animationWasRemovedFromElement): Stop updating m_animationsWithoutTarget since it no longer exists.
+        (WebCore::AnimationTimeline::elementWasRemoved):
+        (WebCore::AnimationTimeline::updateCSSAnimationsForElement): Fix a small error that caused a regression in the Web Platform Tests where we could attempt to
+        call setBackingAnimation() on a nullptr instead of a valid CSSAnimation.
+        (WebCore::AnimationTimeline::cancelOrRemoveDeclarativeAnimation):
+        (WebCore::AnimationTimeline::addAnimation): Deleted.
+        * animation/AnimationTimeline.h:
+        (WebCore::AnimationTimeline::hasElementAnimations const): Deleted.
+        (WebCore::AnimationTimeline:: const): Deleted.
+        (WebCore::AnimationTimeline::elementToAnimationsMap): Deleted.
+        (WebCore::AnimationTimeline::elementToCSSAnimationsMap): Deleted.
+        (WebCore::AnimationTimeline::elementToCSSTransitionsMap): Deleted.
+        * animation/CSSTransition.cpp:
+        (WebCore::CSSTransition::canBeListed const): Deleted.
+        * animation/CSSTransition.h:
+        * animation/DeclarativeAnimation.cpp:
+        (WebCore::DeclarativeAnimation::tick): Call the superclass's method and queue any necessary DOM events reflecting the timing model changes.
+        (WebCore::DeclarativeAnimation::needsTick const): Call the superclass's method and return true also if we have pending events since otherwise this animation
+        could be removed from m_animations on its AnimationTimeline and potentially destroyed before the GenericEventQueue had a chance to dispatch all events.
+        (WebCore::DeclarativeAnimation::startTime const): We removed the custom binding for this IDL property and renamed the method from bindingsStartTime to startTime.
+        (WebCore::DeclarativeAnimation::setStartTime): We removed the custom binding for this IDL property and renamed the method from setBindingsStartTime to setStartTime.
+        (WebCore::DeclarativeAnimation::bindingsStartTime const): Deleted.
+        (WebCore::DeclarativeAnimation::setBindingsStartTime): Deleted.
+        * animation/DeclarativeAnimation.h:
+        * animation/DocumentAnimationScheduler.cpp:
+        (WebCore::DocumentAnimationScheduler::unscheduleWebAnimationsResolution): Add a method to mark that we no longer need a display refresh monitor update for this
+        document's animation timeline. This is called when m_animations becomes empty.
+        * animation/DocumentAnimationScheduler.h:
+        * animation/DocumentTimeline.cpp:
+        (WebCore::DocumentTimeline::DocumentTimeline):
+        (WebCore::DocumentTimeline::detachFromDocument): Stop clearing two task queues and a timer that no longer exist and instead only clear the task queue to clear
+        the cached current time, which we queue any time we generate a new one (see DocumentTimeline::currentTime).
+        (WebCore::DocumentTimeline::getAnimations const): Use isRelevant() instead of canBeListed().
+        (WebCore::DocumentTimeline::updateThrottlingState):
+        (WebCore::DocumentTimeline::suspendAnimations):
+        (WebCore::DocumentTimeline::resumeAnimations):
+        (WebCore::DocumentTimeline::numberOfActiveAnimationsForTesting const):
+        (WebCore::DocumentTimeline::currentTime): Queue a task in the new m_currentTimeClearingTaskQueue task queue to clear the current time that we've generated and cached
+        in the next run loop (provided all pending JS execution has also completed). 
+        (WebCore::DocumentTimeline::maybeClearCachedCurrentTime):
+        (WebCore::DocumentTimeline::scheduleAnimationResolutionIfNeeded): Schedule a display refresh monitor update if we are not suspended and have "relevant" animations.
+        (WebCore::DocumentTimeline::animationTimingDidChange): Call scheduleAnimationResolutionIfNeeded() after calling the superclass's implementation.
+        (WebCore::DocumentTimeline::removeAnimation): Call unscheduleAnimationResolution() if the list of "relevant" animations is now empty.
+        (WebCore::DocumentTimeline::unscheduleAnimationResolution): Unschedule a pending display refresh monitor update.
+        (WebCore::DocumentTimeline::animationResolutionTimerFired):
+        (WebCore::DocumentTimeline::updateAnimationsAndSendEvents): Implement the "update animations and send events" procedure as specified by the Web Animations spec.
+        During this procedure, we call tick() on all animations listed in m_animations and create a list of animations to remove from that list if this animation is no
+        longer relevant following the call to tick().
+        (WebCore::DocumentTimeline::enqueueAnimationPlaybackEvent):
+        (WebCore::DocumentTimeline::timingModelDidChange): Deleted.
+        (WebCore::DocumentTimeline::scheduleInvalidationTaskIfNeeded): Deleted.
+        (WebCore::DocumentTimeline::performInvalidationTask): Deleted.
+        (WebCore::DocumentTimeline::updateAnimationSchedule): Deleted.
+        (WebCore::DocumentTimeline::animationScheduleTimerFired): Deleted.
+        (WebCore::DocumentTimeline::updateAnimations): Deleted.
+        (WebCore::compareAnimationPlaybackEvents): Deleted.
+        (WebCore::DocumentTimeline::performEventDispatchTask): Deleted.
+        * animation/DocumentTimeline.h:
+        * animation/WebAnimation.cpp: The majority of the changes to this class is that we call the new timingDidChange() method when any code that modifies the timing model
+        is run. We also remove methods to set the pending play and pause tasks as well as the animation's start time and hold time since any time we're changing these instance
+        variables, we later already have a call to update the timing model and we were doing more work than needed. As a result we no longer need an internal method to set the
+        start time and can stop requiring a custom IDL binding for the "startTime" property.
+        (WebCore::WebAnimation::effectTimingPropertiesDidChange):
+        (WebCore::WebAnimation::setEffect):
+        (WebCore::WebAnimation::setEffectInternal):
+        (WebCore::WebAnimation::setTimeline):
+        (WebCore::WebAnimation::setTimelineInternal):
+        (WebCore::WebAnimation::startTime const):
+        (WebCore::WebAnimation::setStartTime):
+        (WebCore::WebAnimation::silentlySetCurrentTime):
+        (WebCore::WebAnimation::setCurrentTime):
+        (WebCore::WebAnimation::setPlaybackRate):
+        (WebCore::WebAnimation::cancel):
+        (WebCore::WebAnimation::resetPendingTasks):
+        (WebCore::WebAnimation::finish):
+        (WebCore::WebAnimation::timingDidChange): New method called any time a timing property changed where we run the "update the finished state" procedure and notify the
+        animation's timeline that its timing changed so that it can be considered the next time the "update animations and send events" procedure runs.
+        (WebCore::WebAnimation::invalidateEffect):
+        (WebCore::WebAnimation::updateFinishedState): Update the animation's relevance after running the procedure as specified.
+        (WebCore::WebAnimation::play):
+        (WebCore::WebAnimation::runPendingPlayTask):
+        (WebCore::WebAnimation::pause):
+        (WebCore::WebAnimation::runPendingPauseTask):
+        (WebCore::WebAnimation::needsTick const):
+        (WebCore::WebAnimation::tick): New method called during the "update animations and send events" procedure where we run the "update the finished state" procedure and run
+        the pending play and pause tasks.
+        (WebCore::WebAnimation::resolve):
+        (WebCore::WebAnimation::updateRelevance):
+        (WebCore::WebAnimation::computeRelevance):
+        (WebCore::WebAnimation::timingModelDidChange): Deleted.
+        (WebCore::WebAnimation::setHoldTime): Deleted.
+        (WebCore::WebAnimation::bindingsStartTime const): Deleted.
+        (WebCore::WebAnimation::setBindingsStartTime): Deleted.
+        (WebCore::WebAnimation::setTimeToRunPendingPlayTask): Deleted.
+        (WebCore::WebAnimation::setTimeToRunPendingPauseTask): Deleted.
+        (WebCore::WebAnimation::updatePendingTasks): Deleted.
+        (WebCore::WebAnimation::timeToNextRequiredTick const): Deleted.
+        (WebCore::WebAnimation::runPendingTasks): Deleted.
+        (WebCore::WebAnimation::canBeListed const): Deleted.
+        * animation/WebAnimation.h:
+        (WebCore::WebAnimation::isRelevant const):
+        (WebCore::WebAnimation::hasPendingPlayTask const):
+        (WebCore::WebAnimation::isEffectInvalidationSuspended):
+        * animation/WebAnimation.idl:
+        * dom/Element.cpp:
+        (WebCore::Element::getAnimations): Use isRelevant() instead of canBeListed().
+
 2018-10-30  Youenn Fablet  <youenn@apple.com>
 
         LibWebRTCRtpReceiverBackend::getSynchronizationSources should use Vector::append
index 19c1001..61787b7 100644 (file)
@@ -55,18 +55,23 @@ AnimationTimeline::~AnimationTimeline()
 {
 }
 
-void AnimationTimeline::addAnimation(Ref<WebAnimation>&& animation)
+void AnimationTimeline::animationTimingDidChange(WebAnimation& animation)
 {
-    m_animationsWithoutTarget.add(animation.ptr());
-    m_animations.add(WTFMove(animation));
-    timingModelDidChange();
+    if (m_animations.add(&animation)) {
+        auto* timeline = animation.timeline();
+        if (timeline && timeline != this)
+            timeline->removeAnimation(animation);
+    }
 }
 
-void AnimationTimeline::removeAnimation(Ref<WebAnimation>&& animation)
+void AnimationTimeline::removeAnimation(WebAnimation& animation)
 {
-    m_animationsWithoutTarget.remove(animation.ptr());
-    m_animations.remove(WTFMove(animation));
-    timingModelDidChange();
+    ASSERT(!animation.timeline() || animation.timeline() == this);
+    m_animations.remove(&animation);
+    if (is<KeyframeEffectReadOnly>(animation.effect())) {
+        if (auto* target = downcast<KeyframeEffectReadOnly>(animation.effect())->target())
+            animationWasRemovedFromElement(animation, *target);
+    }
 }
 
 std::optional<double> AnimationTimeline::bindingsCurrentTime()
@@ -88,8 +93,6 @@ HashMap<Element*, ListHashSet<RefPtr<WebAnimation>>>& AnimationTimeline::relevan
 
 void AnimationTimeline::animationWasAddedToElement(WebAnimation& animation, Element& element)
 {
-    m_animationsWithoutTarget.remove(&animation);
-
     relevantMapForAnimation(animation).ensure(&element, [] {
         return ListHashSet<RefPtr<WebAnimation>> { };
     }).iterator->value.add(&animation);
@@ -102,7 +105,13 @@ static inline bool removeCSSTransitionFromMap(CSSTransition& transition, Element
         return false;
 
     auto& cssTransitionsByProperty = iterator->value;
-    cssTransitionsByProperty.remove(transition.property());
+
+    auto transitionIterator = cssTransitionsByProperty.find(transition.property());
+    if (transitionIterator == cssTransitionsByProperty.end() || transitionIterator->value != &transition)
+        return false;
+
+    cssTransitionsByProperty.remove(transitionIterator);
+
     if (cssTransitionsByProperty.isEmpty())
         map.remove(&element);
     return true;
@@ -110,9 +119,6 @@ static inline bool removeCSSTransitionFromMap(CSSTransition& transition, Element
 
 void AnimationTimeline::animationWasRemovedFromElement(WebAnimation& animation, Element& element)
 {
-    // This animation doesn't have a target for now.
-    m_animationsWithoutTarget.add(&animation);
-
     // First, we clear this animation from one of the m_elementToCSSAnimationsMap, m_elementToCSSTransitionsMap,
     // m_elementToAnimationsMap or m_elementToCompletedCSSTransitionByCSSPropertyID map, whichever is relevant to
     // this type of animation.
@@ -254,7 +260,8 @@ void AnimationTimeline::updateCSSAnimationsForElement(Element& element, const Re
                 // We've found the name of this animation in our list of previous animations, this means we've already
                 // created a CSSAnimation object for it and need to ensure that this CSSAnimation is backed by the current
                 // animation object for this animation name.
-                cssAnimationsByName.get(name)->setBackingAnimation(currentAnimation);
+                if (auto cssAnimation = cssAnimationsByName.get(name))
+                    cssAnimation->setBackingAnimation(currentAnimation);
             } else if (shouldConsiderAnimation(element, currentAnimation)) {
                 // Otherwise we are dealing with a new animation name and must create a CSSAnimation for it.
                 cssAnimationsByName.set(name, CSSAnimation::create(element, currentAnimation, currentStyle, afterChangeStyle));
@@ -469,7 +476,7 @@ void AnimationTimeline::cancelOrRemoveDeclarativeAnimation(RefPtr<DeclarativeAni
     ASSERT(animation);
     animation->cancel();
     animationWasRemovedFromElement(*animation, animation->target());
-    removeAnimation(animation.releaseNonNull());
+    removeAnimation(*animation);
 }
 
 } // namespace WebCore
index 3c7f40d..695891d 100644 (file)
@@ -47,8 +47,10 @@ class Element;
 class AnimationTimeline : public RefCounted<AnimationTimeline> {
 public:
     bool isDocumentTimeline() const { return m_classType == DocumentTimelineClass; }
-    void addAnimation(Ref<WebAnimation>&&);
-    void removeAnimation(Ref<WebAnimation>&&);
+
+    virtual void animationTimingDidChange(WebAnimation&);
+    virtual void removeAnimation(WebAnimation&);
+
     std::optional<double> bindingsCurrentTime();
     virtual std::optional<Seconds> currentTime() { return m_currentTime; }
 
@@ -77,12 +79,7 @@ protected:
 
     explicit AnimationTimeline(ClassType);
 
-    bool hasElementAnimations() const { return !m_elementToAnimationsMap.isEmpty() || !m_elementToCSSAnimationsMap.isEmpty() || !m_elementToCSSTransitionsMap.isEmpty(); }
-
-    const ListHashSet<WebAnimation*>& animationsWithoutTarget() const { return m_animationsWithoutTarget; }
-    const HashMap<Element*, ListHashSet<RefPtr<WebAnimation>>>& elementToAnimationsMap() { return m_elementToAnimationsMap; }
-    const HashMap<Element*, ListHashSet<RefPtr<WebAnimation>>>& elementToCSSAnimationsMap() { return m_elementToCSSAnimationsMap; }
-    const HashMap<Element*, ListHashSet<RefPtr<WebAnimation>>>& elementToCSSTransitionsMap() { return m_elementToCSSTransitionsMap; }
+    ListHashSet<RefPtr<WebAnimation>> m_animations;
 
 private:
     HashMap<Element*, ListHashSet<RefPtr<WebAnimation>>>& relevantMapForAnimation(WebAnimation&);
@@ -94,9 +91,6 @@ private:
     HashMap<Element*, ListHashSet<RefPtr<WebAnimation>>> m_elementToAnimationsMap;
     HashMap<Element*, ListHashSet<RefPtr<WebAnimation>>> m_elementToCSSAnimationsMap;
     HashMap<Element*, ListHashSet<RefPtr<WebAnimation>>> m_elementToCSSTransitionsMap;
-    ListHashSet<RefPtr<WebAnimation>> m_animations;
-
-    ListHashSet<WebAnimation*> m_animationsWithoutTarget;
     HashMap<Element*, HashMap<String, RefPtr<CSSAnimation>>> m_elementToCSSAnimationByName;
     HashMap<Element*, HashMap<CSSPropertyID, RefPtr<CSSTransition>>> m_elementToRunningCSSTransitionByCSSPropertyID;
     HashMap<Element*, HashMap<CSSPropertyID, RefPtr<CSSTransition>>> m_elementToCompletedCSSTransitionByCSSPropertyID;
index acdfa0c..d0b7d23 100644 (file)
@@ -74,15 +74,4 @@ void CSSTransition::setTimingProperties(Seconds delay, Seconds duration)
     unsuspendEffectInvalidation();
 }
 
-bool CSSTransition::canBeListed() const
-{
-    if (auto* transitionEffect = effect()) {
-        if (is<KeyframeEffectReadOnly>(transitionEffect)) {
-            if (!downcast<KeyframeEffectReadOnly>(effect())->hasBlendingKeyframes())
-                return false;
-        }
-    }
-    return WebAnimation::canBeListed();
-}
-
 } // namespace WebCore
index 3b9729b..dd78a7f 100644 (file)
@@ -49,7 +49,6 @@ public:
     const RenderStyle& reversingAdjustedStartStyle() const { return *m_reversingAdjustedStartStyle; }
     double reversingShorteningFactor() const { return m_reversingShorteningFactor; }
 
-    bool canBeListed() const final;
     void resolve(RenderStyle&) final;
 
 private:
index 52c9eaf..fe0d245 100644 (file)
@@ -49,6 +49,17 @@ DeclarativeAnimation::~DeclarativeAnimation()
 {
 }
 
+void DeclarativeAnimation::tick()
+{
+    WebAnimation::tick();
+    invalidateDOMEvents();
+}
+
+bool DeclarativeAnimation::needsTick() const
+{
+    return WebAnimation::needsTick() || m_eventQueue.hasPendingEvents();
+}
+
 void DeclarativeAnimation::remove()
 {
     m_eventQueue.close();
@@ -84,16 +95,16 @@ void DeclarativeAnimation::syncPropertiesWithBackingAnimation()
 {
 }
 
-std::optional<double> DeclarativeAnimation::bindingsStartTime() const
+std::optional<double> DeclarativeAnimation::startTime() const
 {
     flushPendingStyleChanges();
-    return WebAnimation::bindingsStartTime();
+    return WebAnimation::startTime();
 }
 
-void DeclarativeAnimation::setBindingsStartTime(std::optional<double> startTime)
+void DeclarativeAnimation::setStartTime(std::optional<double> startTime)
 {
     flushPendingStyleChanges();
-    return WebAnimation::setBindingsStartTime(startTime);
+    return WebAnimation::setStartTime(startTime);
 }
 
 std::optional<double> DeclarativeAnimation::bindingsCurrentTime() const
index 1540389..a728458 100644 (file)
@@ -45,10 +45,9 @@ public:
     Element& target() const { return m_target; }
     const Animation& backingAnimation() const { return m_backingAnimation; }
     void setBackingAnimation(const Animation&);
-    void invalidateDOMEvents(Seconds elapsedTime = 0_s);
 
-    std::optional<double> bindingsStartTime() const final;
-    void setBindingsStartTime(std::optional<double>) final;
+    std::optional<double> startTime() const final;
+    void setStartTime(std::optional<double>) final;
     std::optional<double> bindingsCurrentTime() const final;
     ExceptionOr<void> setBindingsCurrentTime(std::optional<double>) final;
     WebAnimation::PlayState bindingsPlayState() const final;
@@ -61,11 +60,15 @@ public:
     void setTimeline(RefPtr<AnimationTimeline>&&) final;
     void cancel() final;
 
+    bool needsTick() const override;
+    void tick() override;
+
 protected:
     DeclarativeAnimation(Element&, const Animation&);
 
     virtual void initialize(const Element&, const RenderStyle* oldStyle, const RenderStyle& newStyle);
     virtual void syncPropertiesWithBackingAnimation();
+    void invalidateDOMEvents(Seconds elapsedTime = 0_s);
 
 private:
     void flushPendingStyleChanges() const;
index 4f7dd03..ada330a 100644 (file)
@@ -64,6 +64,14 @@ bool DocumentAnimationScheduler::scheduleWebAnimationsResolution()
     return DisplayRefreshMonitorManager::sharedManager().scheduleAnimation(*this);
 }
 
+void DocumentAnimationScheduler::unscheduleWebAnimationsResolution()
+{
+    m_scheduledWebAnimationsResolution = false;
+
+    if (!m_scheduledScriptedAnimationResolution)
+        DisplayRefreshMonitorManager::sharedManager().unregisterClient(*this);
+}
+
 bool DocumentAnimationScheduler::scheduleScriptedAnimationResolution()
 {
     m_scheduledScriptedAnimationResolution = true;
index 258a692..98e88c5 100644 (file)
@@ -48,6 +48,7 @@ public:
     void windowScreenDidChange(PlatformDisplayID);
 
     bool scheduleWebAnimationsResolution();
+    void unscheduleWebAnimationsResolution();
     bool scheduleScriptedAnimationResolution();
 
     Seconds lastTimestamp() { return m_lastTimestamp; }
index 5bf10ba..97cc70a 100644 (file)
@@ -58,7 +58,6 @@ DocumentTimeline::DocumentTimeline(Document& document, Seconds originTime)
     : AnimationTimeline(DocumentTimelineClass)
     , m_document(&document)
     , m_originTime(originTime)
-    , m_animationScheduleTimer(*this, &DocumentTimeline::animationScheduleTimerFired)
 #if !USE(REQUEST_ANIMATION_FRAME_DISPLAY_MONITOR)
     , m_animationResolutionTimer(*this, &DocumentTimeline::animationResolutionTimerFired)
 #endif
@@ -71,15 +70,14 @@ DocumentTimeline::~DocumentTimeline() = default;
 
 void DocumentTimeline::detachFromDocument()
 {
-    m_invalidationTaskQueue.close();
-    m_eventDispatchTaskQueue.close();
-    m_animationScheduleTimer.stop();
+    m_currentTimeClearingTaskQueue.close();
     m_elementsWithRunningAcceleratedAnimations.clear();
 
-    auto& animationsToRemove = animations();
+    auto& animationsToRemove = m_animations;
     while (!animationsToRemove.isEmpty())
         animationsToRemove.first()->remove();
 
+    unscheduleAnimationResolution();
     m_document = nullptr;
 }
 
@@ -89,8 +87,8 @@ Vector<RefPtr<WebAnimation>> DocumentTimeline::getAnimations() const
 
     // FIXME: Filter and order the list as specified (webkit.org/b/179535).
     Vector<RefPtr<WebAnimation>> animations;
-    for (const auto& animation : this->animations()) {
-        if (animation->canBeListed() && is<KeyframeEffectReadOnly>(animation->effect())) {
+    for (const auto& animation : m_animations) {
+        if (animation->isRelevant() && is<KeyframeEffectReadOnly>(animation->effect())) {
             if (auto* target = downcast<KeyframeEffectReadOnly>(animation->effect())->target()) {
                 if (target->isDescendantOf(*m_document))
                     animations.append(animation);
@@ -102,8 +100,7 @@ Vector<RefPtr<WebAnimation>> DocumentTimeline::getAnimations() const
 
 void DocumentTimeline::updateThrottlingState()
 {
-    m_needsUpdateAnimationSchedule = false;
-    timingModelDidChange();
+    scheduleAnimationResolutionIfNeeded();
 }
 
 Seconds DocumentTimeline::animationInterval() const
@@ -118,16 +115,14 @@ void DocumentTimeline::suspendAnimations()
     if (animationsAreSuspended())
         return;
 
-    m_invalidationTaskQueue.cancelAllTasks();
-    if (m_animationScheduleTimer.isActive())
-        m_animationScheduleTimer.stop();
-
-    for (const auto& animation : animations())
+    for (const auto& animation : m_animations)
         animation->setSuspended(true);
 
     m_isSuspended = true;
 
     applyPendingAcceleratedAnimations();
+
+    unscheduleAnimationResolution();
 }
 
 void DocumentTimeline::resumeAnimations()
@@ -137,11 +132,10 @@ void DocumentTimeline::resumeAnimations()
 
     m_isSuspended = false;
 
-    for (const auto& animation : animations())
+    for (const auto& animation : m_animations)
         animation->setSuspended(false);
 
-    m_needsUpdateAnimationSchedule = false;
-    timingModelDidChange();
+    scheduleAnimationResolutionIfNeeded();
 }
 
 bool DocumentTimeline::animationsAreSuspended()
@@ -152,7 +146,7 @@ bool DocumentTimeline::animationsAreSuspended()
 unsigned DocumentTimeline::numberOfActiveAnimationsForTesting() const
 {
     unsigned count = 0;
-    for (const auto& animation : animations()) {
+    for (const auto& animation : m_animations) {
         if (!animation->isSuspended())
             ++count;
     }
@@ -196,8 +190,9 @@ std::optional<Seconds> DocumentTimeline::currentTime()
         // We want to be sure to keep this time cached until we've both finished running JS and finished updating
         // animations, so we schedule the invalidation task and register a whenIdle callback on the VM, which will
         // fire syncronously if no JS is running.
-        scheduleInvalidationTaskIfNeeded();
         m_waitingOnVMIdle = true;
+        if (!m_currentTimeClearingTaskQueue.hasPendingTasks())
+            m_currentTimeClearingTaskQueue.enqueueTask(std::bind(&DocumentTimeline::maybeClearCachedCurrentTime, this));
         m_document->vm().whenIdle([this, protectedThis = makeRefPtr(this)]() {
             m_waitingOnVMIdle = false;
             maybeClearCachedCurrentTime();
@@ -206,84 +201,34 @@ std::optional<Seconds> DocumentTimeline::currentTime()
     return m_cachedCurrentTime.value() - m_originTime;
 }
 
-void DocumentTimeline::timingModelDidChange()
-{
-    if (m_needsUpdateAnimationSchedule || m_isSuspended)
-        return;
-
-    m_needsUpdateAnimationSchedule = true;
-
-    // We know that we will resolve animations again, so we can cancel the timer right away.
-    if (m_animationScheduleTimer.isActive())
-        m_animationScheduleTimer.stop();
-
-    scheduleInvalidationTaskIfNeeded();
-}
-
-void DocumentTimeline::scheduleInvalidationTaskIfNeeded()
-{
-    if (m_invalidationTaskQueue.hasPendingTasks())
-        return;
-
-    m_invalidationTaskQueue.enqueueTask(std::bind(&DocumentTimeline::performInvalidationTask, this));
-}
-
-void DocumentTimeline::performInvalidationTask()
-{
-    // Now that the timing model has changed we can see if there are DOM events to dispatch for declarative animations.
-    if (!m_isSuspended) {
-        for (auto& animation : animations()) {
-            if (is<DeclarativeAnimation>(animation))
-                downcast<DeclarativeAnimation>(*animation).invalidateDOMEvents();
-        }
-    }
-
-    applyPendingAcceleratedAnimations();
-
-    updateAnimationSchedule();
-    maybeClearCachedCurrentTime();
-}
-
 void DocumentTimeline::maybeClearCachedCurrentTime()
 {
     // We want to make sure we only clear the cached current time if we're not currently running
     // JS or waiting on all current animation updating code to have completed. This is so that
     // we're guaranteed to have a consistent current time reported for all work happening in a given
     // JS frame or throughout updating animations in WebCore.
-    if (!m_waitingOnVMIdle && !m_invalidationTaskQueue.hasPendingTasks())
+    if (!m_waitingOnVMIdle && !m_currentTimeClearingTaskQueue.hasPendingTasks())
         m_cachedCurrentTime = std::nullopt;
 }
 
-void DocumentTimeline::updateAnimationSchedule()
+void DocumentTimeline::scheduleAnimationResolutionIfNeeded()
 {
-    if (!m_needsUpdateAnimationSchedule)
-        return;
-
-    m_needsUpdateAnimationSchedule = false;
-
-    if (!m_acceleratedAnimationsPendingRunningStateChange.isEmpty()) {
+    if (!m_isSuspended && !m_animations.isEmpty())
         scheduleAnimationResolution();
-        return;
-    }
-
-    Seconds scheduleDelay = Seconds::infinity();
-
-    for (const auto& animation : animations()) {
-        auto animationTimeToNextRequiredTick = animation->timeToNextRequiredTick();
-        if (animationTimeToNextRequiredTick < animationInterval()) {
-            scheduleAnimationResolution();
-            return;
-        }
-        scheduleDelay = std::min(scheduleDelay, animationTimeToNextRequiredTick);
-    }
+}
 
-    if (scheduleDelay < Seconds::infinity())
-        m_animationScheduleTimer.startOneShot(scheduleDelay);
+void DocumentTimeline::animationTimingDidChange(WebAnimation& animation)
+{
+    AnimationTimeline::animationTimingDidChange(animation);
+    scheduleAnimationResolutionIfNeeded();
 }
 
-void DocumentTimeline::animationScheduleTimerFired()
+void DocumentTimeline::removeAnimation(WebAnimation& animation)
 {
-    scheduleAnimationResolution();
+    AnimationTimeline::removeAnimation(animation);
+
+    if (m_animations.isEmpty())
+        unscheduleAnimationResolution();
 }
 
 void DocumentTimeline::scheduleAnimationResolution()
@@ -297,44 +242,81 @@ void DocumentTimeline::scheduleAnimationResolution()
 #endif
 }
 
+void DocumentTimeline::unscheduleAnimationResolution()
+{
+#if USE(REQUEST_ANIMATION_FRAME_DISPLAY_MONITOR)
+    m_document->animationScheduler().unscheduleWebAnimationsResolution();
+#else
+    // FIXME: We need to use the same logic as ScriptedAnimationController here,
+    // which will be addressed by the refactor tracked by webkit.org/b/179293.
+    m_animationResolutionTimer.stop();
+#endif
+}
+
 #if USE(REQUEST_ANIMATION_FRAME_DISPLAY_MONITOR)
 void DocumentTimeline::documentAnimationSchedulerDidFire()
 #else
 void DocumentTimeline::animationResolutionTimerFired()
 #endif
 {
-    updateAnimations();
+    updateAnimationsAndSendEvents();
 }
 
-void DocumentTimeline::updateAnimations()
+void DocumentTimeline::updateAnimationsAndSendEvents()
 {
     m_numberOfAnimationTimelineInvalidationsForTesting++;
 
-    for (const auto& animation : animations())
-        animation->runPendingTasks();
+    // https://drafts.csswg.org/web-animations/#update-animations-and-send-events
 
-    // Perform a microtask checkpoint such that all promises that may have resolved while
-    // running pending tasks can fire right away.
-    MicrotaskQueue::mainThreadQueue().performMicrotaskCheckpoint();
+    // 1. Update the current time of all timelines associated with doc passing now as the timestamp.
+
+    Vector<RefPtr<WebAnimation>> animationsToRemove;
+
+    for (auto& animation : m_animations) {
+        if (animation->timeline() != this) {
+            ASSERT(!animation->timeline());
+            animationsToRemove.append(animation);
+            continue;
+        }
+
+        // This will notify the animation that timing has changed and will call automatically
+        // schedule invalidation if required for this animation.
+        animation->tick();
 
-    // Let's first resolve any animation that does not have a target.
-    for (auto* animation : animationsWithoutTarget())
-        animation->resolve();
-
-    // For the rest of the animations, we will resolve them via TreeResolver::createAnimatedElementUpdate()
-    // by invalidating their target element's style.
-    if (m_document && hasElementAnimations()) {
-        for (const auto& elementToAnimationsMapItem : elementToAnimationsMap())
-            elementToAnimationsMapItem.key->invalidateStyleAndLayerComposition();
-        for (const auto& elementToCSSAnimationsMapItem : elementToCSSAnimationsMap())
-            elementToCSSAnimationsMapItem.key->invalidateStyleAndLayerComposition();
-        for (const auto& elementToCSSTransitionsMapItem : elementToCSSTransitionsMap())
-            elementToCSSTransitionsMapItem.key->invalidateStyleAndLayerComposition();
-        m_document->updateStyleIfNeeded();
+        if (!animation->isRelevant() && !animation->needsTick())
+            animationsToRemove.append(animation);
     }
 
-    // Time has advanced, the timing model requires invalidation now.
-    timingModelDidChange();
+    // 2. Perform a microtask checkpoint.
+    MicrotaskQueue::mainThreadQueue().performMicrotaskCheckpoint();
+
+    // 3. Let events to dispatch be a copy of doc's pending animation event queue.
+    // 4. Clear doc's pending animation event queue.
+    auto pendingAnimationEvents = WTFMove(m_pendingAnimationEvents);
+
+    // 5. Perform a stable sort of the animation events in events to dispatch as follows.
+    std::stable_sort(pendingAnimationEvents.begin(), pendingAnimationEvents.end(), [] (const Ref<AnimationPlaybackEvent>& lhs, const Ref<AnimationPlaybackEvent>& rhs) {
+        // 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
+        // and events whose scheduled event time is unresolved sort before events with a resolved scheduled event time.
+        // 2. Within events with equal scheduled event times, sort by their composite order. FIXME: We don't do this.
+        if (lhs->timelineTime() && !rhs->timelineTime())
+            return false;
+        if (!lhs->timelineTime() && rhs->timelineTime())
+            return true;
+        if (!lhs->timelineTime() && !rhs->timelineTime())
+            return true;
+        return lhs->timelineTime().value() < rhs->timelineTime().value();
+    });
+
+    // 6. Dispatch each of the events in events to dispatch at their corresponding target using the order established in the previous step.
+    for (auto& pendingEvent : pendingAnimationEvents)
+        pendingEvent->target()->dispatchEvent(pendingEvent);
+
+    // This will cancel any scheduled invalidation if we end up removing all animations.
+    for (auto& animation : animationsToRemove)
+        removeAnimation(*animation);
+
+    applyPendingAcceleratedAnimations();
 }
 
 bool DocumentTimeline::computeExtentOfAnimation(RenderElement& renderer, LayoutRect& bounds) const
@@ -503,34 +485,6 @@ bool DocumentTimeline::runningAnimationsForElementAreAllAccelerated(Element& ele
 void DocumentTimeline::enqueueAnimationPlaybackEvent(AnimationPlaybackEvent& event)
 {
     m_pendingAnimationEvents.append(event);
-
-    if (!m_eventDispatchTaskQueue.hasPendingTasks())
-        m_eventDispatchTaskQueue.enqueueTask(std::bind(&DocumentTimeline::performEventDispatchTask, this));
-}
-
-static inline bool compareAnimationPlaybackEvents(const Ref<WebCore::AnimationPlaybackEvent>& lhs, const Ref<WebCore::AnimationPlaybackEvent>& rhs)
-{
-    // Sort the events by their scheduled event time such that events that were scheduled to occur earlier, sort before events scheduled to occur later
-    // and events whose scheduled event time is unresolved sort before events with a resolved scheduled event time.
-    if (lhs->timelineTime() && !rhs->timelineTime())
-        return false;
-    if (!lhs->timelineTime() && rhs->timelineTime())
-        return true;
-    if (!lhs->timelineTime() && !rhs->timelineTime())
-        return true;
-    return lhs->timelineTime().value() < rhs->timelineTime().value();
-}
-
-void DocumentTimeline::performEventDispatchTask()
-{
-    if (m_pendingAnimationEvents.isEmpty())
-        return;
-
-    auto pendingAnimationEvents = WTFMove(m_pendingAnimationEvents);
-
-    std::stable_sort(pendingAnimationEvents.begin(), pendingAnimationEvents.end(), compareAnimationPlaybackEvents);
-    for (auto& pendingEvent : pendingAnimationEvents)
-        pendingEvent->target()->dispatchEvent(pendingEvent);
 }
 
 Vector<std::pair<String, double>> DocumentTimeline::acceleratedAnimationsForElement(Element& element) const
index 193adaf..f4074a8 100644 (file)
@@ -49,8 +49,8 @@ public:
 
     std::optional<Seconds> currentTime() override;
 
-    void timingModelDidChange() override;
-
+    void animationTimingDidChange(WebAnimation&) override;
+    void removeAnimation(WebAnimation&) override;
     void animationWasAddedToElement(WebAnimation&, Element&) final;
     void animationWasRemovedFromElement(WebAnimation&, Element&) final;
 
@@ -85,12 +85,13 @@ public:
 private:
     DocumentTimeline(Document&, Seconds);
 
+    void scheduleAnimationResolutionIfNeeded();
     void scheduleInvalidationTaskIfNeeded();
     void performInvalidationTask();
-    void updateAnimationSchedule();
     void animationScheduleTimerFired();
     void scheduleAnimationResolution();
-    void updateAnimations();
+    void unscheduleAnimationResolution();
+    void updateAnimationsAndSendEvents();
     void performEventDispatchTask();
     void maybeClearCachedCurrentTime();
     void updateListOfElementsWithRunningAcceleratedAnimationsForElement(Element&);
@@ -100,10 +101,7 @@ private:
     bool m_isSuspended { false };
     bool m_waitingOnVMIdle { false };
     std::optional<Seconds> m_cachedCurrentTime;
-    GenericTaskQueue<Timer> m_invalidationTaskQueue;
-    GenericTaskQueue<Timer> m_eventDispatchTaskQueue;
-    bool m_needsUpdateAnimationSchedule { false };
-    Timer m_animationScheduleTimer;
+    GenericTaskQueue<Timer> m_currentTimeClearingTaskQueue;
     HashSet<RefPtr<WebAnimation>> m_acceleratedAnimationsPendingRunningStateChange;
     Vector<Ref<AnimationPlaybackEvent>> m_pendingAnimationEvents;
     unsigned m_numberOfAnimationTimelineInvalidationsForTesting { 0 };
index 7c2a2c0..801c67a 100644 (file)
@@ -92,16 +92,7 @@ void WebAnimation::unsuspendEffectInvalidation()
 
 void WebAnimation::effectTimingPropertiesDidChange()
 {
-    updateFinishedState(DidSeek::No, SynchronouslyNotify::Yes);
-    timingModelDidChange();
-}
-
-void WebAnimation::timingModelDidChange()
-{
-    if (!isEffectInvalidationSuspended() && m_effect)
-        m_effect->invalidate();
-    if (m_timeline)
-        m_timeline->timingModelDidChange();
+    timingDidChange(DidSeek::No, SynchronouslyNotify::Yes);
 }
 
 void WebAnimation::setEffect(RefPtr<AnimationEffectReadOnly>&& newEffect)
@@ -122,11 +113,11 @@ void WebAnimation::setEffect(RefPtr<AnimationEffectReadOnly>&& newEffect)
 
     // 4. If animation has a pending pause task, reschedule that task to run as soon as animation is ready.
     if (hasPendingPauseTask())
-        setTimeToRunPendingPauseTask(TimeToRunPendingTask::WhenReady);
+        m_timeToRunPendingPauseTask = TimeToRunPendingTask::WhenReady;
 
     // 5. If animation has a pending play task, reschedule that task to run as soon as animation is ready to play new effect.
     if (hasPendingPlayTask())
-        setTimeToRunPendingPlayTask(TimeToRunPendingTask::WhenReady);
+        m_timeToRunPendingPlayTask = TimeToRunPendingTask::WhenReady;
 
     // 6. If new effect is not null and if new effect is the target effect of another animation, previous animation, run the
     // procedure to set the target effect of an animation (this procedure) on previous animation passing null as new effect.
@@ -138,15 +129,17 @@ void WebAnimation::setEffect(RefPtr<AnimationEffectReadOnly>&& newEffect)
     // while the effect was set via the API, the element still has a transition or animation set up and we must
     // not break the timeline-to-animation relationship.
 
+    invalidateEffect();
+
     // This object could be deleted after clearing the effect relationship.
     auto protectedThis = makeRef(*this);
     setEffectInternal(WTFMove(newEffect), isDeclarativeAnimation());
 
     // 8. Run the procedure to update an animation’s finished state for animation with the did seek flag set to false,
     // and the synchronously notify flag set to false.
-    updateFinishedState(DidSeek::No, SynchronouslyNotify::No);
+    timingDidChange(DidSeek::No, SynchronouslyNotify::No);
 
-    timingModelDidChange();
+    invalidateEffect();
 }
 
 void WebAnimation::setEffectInternal(RefPtr<AnimationEffectReadOnly>&& newEffect, bool doNotRemoveFromTimeline)
@@ -169,6 +162,7 @@ void WebAnimation::setEffectInternal(RefPtr<AnimationEffectReadOnly>&& newEffect
         oldEffect->setAnimation(nullptr);
         if (!doNotRemoveFromTimeline && m_timeline && previousTarget && previousTarget != newTarget)
             m_timeline->animationWasRemovedFromElement(*this, *previousTarget);
+        updateRelevance();
     }
 
     if (m_effect) {
@@ -189,7 +183,7 @@ void WebAnimation::setTimeline(RefPtr<AnimationTimeline>&& timeline)
 
     // 4. If the animation start time of animation is resolved, make animation’s hold time unresolved.
     if (m_startTime)
-        setHoldTime(std::nullopt);
+        m_holdTime = std::nullopt;
 
     if (is<KeyframeEffectReadOnly>(m_effect)) {
         auto* keyframeEffect = downcast<KeyframeEffectReadOnly>(m_effect.get());
@@ -211,11 +205,11 @@ void WebAnimation::setTimeline(RefPtr<AnimationTimeline>&& timeline)
 
     setSuspended(is<DocumentTimeline>(m_timeline) && downcast<DocumentTimeline>(*m_timeline).animationsAreSuspended());
 
-    updatePendingTasks();
-
     // 5. Run the procedure to update an animation’s finished state for animation with the did seek flag set to false,
     // and the synchronously notify flag set to false.
-    updateFinishedState(DidSeek::No, SynchronouslyNotify::No);
+    timingDidChange(DidSeek::No, SynchronouslyNotify::No);
+
+    invalidateEffect();
 }
 
 void WebAnimation::setTimelineInternal(RefPtr<AnimationTimeline>&& timeline)
@@ -227,9 +221,6 @@ void WebAnimation::setTimelineInternal(RefPtr<AnimationTimeline>&& timeline)
         m_timeline->removeAnimation(*this);
 
     m_timeline = WTFMove(timeline);
-
-    if (m_timeline)
-        m_timeline->addAnimation(*this);
 }
 
 void WebAnimation::effectTargetDidChange(Element* previousTarget, Element* newTarget)
@@ -244,23 +235,14 @@ void WebAnimation::effectTargetDidChange(Element* previousTarget, Element* newTa
         m_timeline->animationWasAddedToElement(*this, *newTarget);
 }
 
-void WebAnimation::setHoldTime(std::optional<Seconds> holdTime)
-{
-    if (m_holdTime == holdTime)
-        return;
-
-    m_holdTime = holdTime;
-    timingModelDidChange();
-}
-
-std::optional<double> WebAnimation::bindingsStartTime() const
+std::optional<double> WebAnimation::startTime() const
 {
     if (!m_startTime)
         return std::nullopt;
     return secondsToWebAnimationsAPITime(m_startTime.value());
 }
 
-void WebAnimation::setBindingsStartTime(std::optional<double> startTime)
+void WebAnimation::setStartTime(std::optional<double> startTime)
 {
     // 3.4.6 The procedure to set the start time of animation, animation, to new start time, is as follows:
     // https://drafts.csswg.org/web-animations/#setting-the-start-time-of-an-animation
@@ -278,51 +260,37 @@ void WebAnimation::setBindingsStartTime(std::optional<double> startTime)
 
     // 2. If timeline time is unresolved and new start time is resolved, make animation's hold time unresolved.
     if (!timelineTime && newStartTime)
-        setHoldTime(std::nullopt);
+        m_holdTime = std::nullopt;
 
     // 3. Let previous current time be animation's current time.
     auto previousCurrentTime = currentTime();
 
     // 4. Set animation's start time to new start time.
-    setStartTime(newStartTime);
+    m_startTime = newStartTime;
 
     // 5. Update animation's hold time based on the first matching condition from the following,
     if (newStartTime) {
         // If new start time is resolved,
         // If animation’s playback rate is not zero, make animation’s hold time unresolved.
         if (m_playbackRate)
-            setHoldTime(std::nullopt);
+            m_holdTime = std::nullopt;
     } else {
         // Otherwise (new start time is unresolved),
         // Set animation's hold time to previous current time even if previous current time is unresolved.
-        setHoldTime(previousCurrentTime);
+        m_holdTime = previousCurrentTime;
     }
 
     // 6. If animation has a pending play task or a pending pause task, cancel that task and resolve animation's current ready promise with animation.
     if (pending()) {
-        setTimeToRunPendingPauseTask(TimeToRunPendingTask::NotScheduled);
-        setTimeToRunPendingPlayTask(TimeToRunPendingTask::NotScheduled);
+        m_timeToRunPendingPauseTask = TimeToRunPendingTask::NotScheduled;
+        m_timeToRunPendingPlayTask = TimeToRunPendingTask::NotScheduled;
         m_readyPromise->resolve(*this);
     }
 
     // 7. Run the procedure to update an animation’s finished state for animation with the did seek flag set to true, and the synchronously notify flag set to false.
-    updateFinishedState(DidSeek::Yes, SynchronouslyNotify::No);
+    timingDidChange(DidSeek::Yes, SynchronouslyNotify::No);
 
-    timingModelDidChange();
-}
-
-std::optional<Seconds> WebAnimation::startTime() const
-{
-    return m_startTime;
-}
-
-void WebAnimation::setStartTime(std::optional<Seconds> newStartTime)
-{
-    if (m_startTime == newStartTime)
-        return;
-
-    m_startTime = newStartTime;
-    timingModelDidChange();
+    invalidateEffect();
 }
 
 std::optional<double> WebAnimation::bindingsCurrentTime() const
@@ -392,13 +360,13 @@ ExceptionOr<void> WebAnimation::silentlySetCurrentTime(std::optional<Seconds> se
     // Otherwise, set animation's start time to the result of evaluating timeline time - (seek time / playback rate)
     // where timeline time is the current time value of timeline associated with animation.
     if (m_holdTime || !m_startTime || !m_timeline || !m_timeline->currentTime() || !m_playbackRate)
-        setHoldTime(seekTime);
+        m_holdTime = seekTime;
     else
-        setStartTime(m_timeline->currentTime().value() - (seekTime.value() / m_playbackRate));
+        m_startTime = m_timeline->currentTime().value() - (seekTime.value() / m_playbackRate);
 
     // 3. If animation has no associated timeline or the associated timeline is inactive, make animation's start time unresolved.
     if (!m_timeline || !m_timeline->currentTime())
-        setStartTime(std::nullopt);
+        m_startTime = std::nullopt;
 
     // 4. Make animation's previous current time unresolved.
     m_previousCurrentTime = std::nullopt;
@@ -419,21 +387,23 @@ ExceptionOr<void> WebAnimation::setCurrentTime(std::optional<Seconds> seekTime)
     // 2. If animation has a pending pause task, synchronously complete the pause operation by performing the following steps:
     if (hasPendingPauseTask()) {
         // 1. Set animation's hold time to seek time.
-        setHoldTime(seekTime);
+        m_holdTime = seekTime;
         // 2. Make animation's start time unresolved.
-        setStartTime(std::nullopt);
+        m_startTime = std::nullopt;
         // 3. Cancel the pending pause task.
-        setTimeToRunPendingPauseTask(TimeToRunPendingTask::NotScheduled);
+        m_timeToRunPendingPauseTask = TimeToRunPendingTask::NotScheduled;
         // 4. Resolve animation's current ready promise with animation.
         m_readyPromise->resolve(*this);
     }
 
     // 3. Run the procedure to update an animation's finished state for animation with the did seek flag set to true, and the synchronously notify flag set to false.
-    updateFinishedState(DidSeek::Yes, SynchronouslyNotify::No);
+    timingDidChange(DidSeek::Yes, SynchronouslyNotify::No);
 
     if (m_effect)
         m_effect->animationDidSeek();
 
+    invalidateEffect();
+
     return { };
 }
 
@@ -466,6 +436,8 @@ void WebAnimation::setPlaybackRate(double newPlaybackRate, Silently silently)
         silentlySetCurrentTime(previousTime);
     else
         setCurrentTime(previousTime);
+
+    invalidateEffect();
 }
 
 auto WebAnimation::playState() const -> PlayState
@@ -510,6 +482,7 @@ Seconds WebAnimation::effectEndTime() const
 void WebAnimation::cancel()
 {
     cancel(Silently::No);
+    invalidateEffect();
 }
 
 void WebAnimation::cancel(Silently silently)
@@ -549,10 +522,14 @@ void WebAnimation::cancel(Silently silently)
     }
 
     // 2. Make animation's hold time unresolved.
-    setHoldTime(std::nullopt);
+    m_holdTime = std::nullopt;
 
     // 3. Make animation's start time unresolved.
-    setStartTime(std::nullopt);
+    m_startTime = std::nullopt;
+
+    timingDidChange(DidSeek::No, SynchronouslyNotify::No);
+
+    invalidateEffect();
 }
 
 void WebAnimation::enqueueAnimationPlaybackEvent(const AtomicString& type, std::optional<Seconds> currentTime, std::optional<Seconds> timelineTime)
@@ -586,11 +563,11 @@ void WebAnimation::resetPendingTasks(Silently silently)
 
     // 2. If animation has a pending play task, cancel that task.
     if (hasPendingPlayTask())
-        setTimeToRunPendingPlayTask(TimeToRunPendingTask::NotScheduled);
+        m_timeToRunPendingPlayTask = TimeToRunPendingTask::NotScheduled;
 
     // 3. If animation has a pending pause task, cancel that task.
     if (hasPendingPauseTask())
-        setTimeToRunPendingPauseTask(TimeToRunPendingTask::NotScheduled);
+        m_timeToRunPendingPauseTask = TimeToRunPendingTask::NotScheduled;
 
     // 4. Reject animation's current ready promise with a DOMException named "AbortError".
     if (silently == Silently::No)
@@ -623,30 +600,45 @@ ExceptionOr<void> WebAnimation::finish()
     // 4. If animation's start time is unresolved and animation has an associated active timeline, let the start time be the result of
     //    evaluating timeline time - (limit / playback rate) where timeline time is the current time value of the associated timeline.
     if (!m_startTime && m_timeline && m_timeline->currentTime())
-        setStartTime(m_timeline->currentTime().value() - (limit / m_playbackRate));
+        m_startTime = m_timeline->currentTime().value() - (limit / m_playbackRate);
 
     // 5. If there is a pending pause task and start time is resolved,
     if (hasPendingPauseTask() && m_startTime) {
         // 1. Let the hold time be unresolved.
-        setHoldTime(std::nullopt);
+        m_holdTime = std::nullopt;
         // 2. Cancel the pending pause task.
-        setTimeToRunPendingPauseTask(TimeToRunPendingTask::NotScheduled);
+        m_timeToRunPendingPauseTask = TimeToRunPendingTask::NotScheduled;
         // 3. Resolve the current ready promise of animation with animation.
         m_readyPromise->resolve(*this);
     }
 
     // 6. If there is a pending play task and start time is resolved, cancel that task and resolve the current ready promise of animation with animation.
     if (hasPendingPlayTask() && m_startTime) {
-        setTimeToRunPendingPlayTask(TimeToRunPendingTask::NotScheduled);
+        m_timeToRunPendingPlayTask = TimeToRunPendingTask::NotScheduled;
         m_readyPromise->resolve(*this);
     }
 
     // 7. Run the procedure to update an animation's finished state animation with the did seek flag set to true, and the synchronously notify flag set to true.
-    updateFinishedState(DidSeek::Yes, SynchronouslyNotify::Yes);
+    timingDidChange(DidSeek::Yes, SynchronouslyNotify::Yes);
+
+    invalidateEffect();
 
     return { };
 }
 
+void WebAnimation::timingDidChange(DidSeek didSeek, SynchronouslyNotify synchronouslyNotify)
+{
+    updateFinishedState(didSeek, synchronouslyNotify);
+    if (m_timeline)
+        m_timeline->animationTimingDidChange(*this);
+};
+
+void WebAnimation::invalidateEffect()
+{
+    if (!isEffectInvalidationSuspended() && m_effect)
+        m_effect->invalidate();
+}
+
 void WebAnimation::updateFinishedState(DidSeek didSeek, SynchronouslyNotify synchronouslyNotify)
 {
     // 3.4.14. Updating the finished state
@@ -667,31 +659,31 @@ void WebAnimation::updateFinishedState(DidSeek didSeek, SynchronouslyNotify sync
             // If animation playback rate > 0 and unconstrained current time is greater than or equal to target effect end,
             // If did seek is true, let the hold time be the value of unconstrained current time.
             if (didSeek == DidSeek::Yes)
-                setHoldTime(unconstrainedCurrentTime);
+                m_holdTime = unconstrainedCurrentTime;
             // If did seek is false, let the hold time be the maximum value of previous current time and target effect end. If the previous current time is unresolved, let the hold time be target effect end.
             else if (!m_previousCurrentTime)
-                setHoldTime(endTime);
+                m_holdTime = endTime;
             else
-                setHoldTime(std::max(m_previousCurrentTime.value(), endTime));
+                m_holdTime = std::max(m_previousCurrentTime.value(), endTime);
         } else if (m_playbackRate < 0 && unconstrainedCurrentTime <= 0_s) {
             // If animation playback rate < 0 and unconstrained current time is less than or equal to 0,
             // If did seek is true, let the hold time be the value of unconstrained current time.
             if (didSeek == DidSeek::Yes)
-                setHoldTime(unconstrainedCurrentTime);
+                m_holdTime = unconstrainedCurrentTime;
             // If did seek is false, let the hold time be the minimum value of previous current time and zero. If the previous current time is unresolved, let the hold time be zero.
             else if (!m_previousCurrentTime)
-                setHoldTime(0_s);
+                m_holdTime = 0_s;
             else
-                setHoldTime(std::min(m_previousCurrentTime.value(), 0_s));
+                m_holdTime = std::min(m_previousCurrentTime.value(), 0_s);
         } else if (m_playbackRate && m_timeline && m_timeline->currentTime()) {
             // If animation playback rate ≠ 0, and animation is associated with an active timeline,
             // Perform the following steps:
             // 1. If did seek is true and the hold time is resolved, let animation's start time be equal to the result of evaluating timeline time - (hold time / playback rate)
             //    where timeline time is the current time value of timeline associated with animation.
             if (didSeek == DidSeek::Yes && m_holdTime)
-                setStartTime(m_timeline->currentTime().value() - (m_holdTime.value() / m_playbackRate));
+                m_startTime = m_timeline->currentTime().value() - (m_holdTime.value() / m_playbackRate);
             // 2. Let the hold time be unresolved.
-            setHoldTime(std::nullopt);
+            m_holdTime = std::nullopt;
         }
     }
 
@@ -720,6 +712,8 @@ void WebAnimation::updateFinishedState(DidSeek didSeek, SynchronouslyNotify sync
     // finished promise to a new (pending) Promise object.
     if (!currentFinishedState && m_finishedPromise->isFulfilled())
         m_finishedPromise = makeUniqueRef<FinishedPromise>(*this, &WebAnimation::finishedPromiseResolve);
+
+    updateRelevance();
 }
 
 void WebAnimation::scheduleMicrotaskIfNeeded()
@@ -794,7 +788,7 @@ ExceptionOr<void> WebAnimation::play(AutoRewind autoRewind)
         //     - current time < zero, or
         //     - current time ≥ target effect end,
         // Set animation's hold time to zero.
-        setHoldTime(0_s);
+        m_holdTime = 0_s;
     } else if (m_playbackRate < 0 && autoRewind == AutoRewind::Yes && (!localTime || localTime.value() <= 0_s || localTime.value() > endTime)) {
         // If animation playback rate < 0, the auto-rewind flag is true and either animation's:
         //     - current time is unresolved, or
@@ -803,18 +797,18 @@ ExceptionOr<void> WebAnimation::play(AutoRewind autoRewind)
         // If target effect end is positive infinity, throw an InvalidStateError and abort these steps.
         if (endTime == Seconds::infinity())
             return Exception { InvalidStateError };
-        setHoldTime(endTime);
+        m_holdTime = endTime;
     } else if (!m_playbackRate && !localTime) {
         // If animation playback rate = 0 and animation's current time is unresolved,
         // Set animation's hold time to zero.
-        setHoldTime(0_s);
+        m_holdTime = 0_s;
     }
 
     // 4. If animation has a pending play task or a pending pause task,
     if (pending()) {
         // 1. Cancel that task.
-        setTimeToRunPendingPauseTask(TimeToRunPendingTask::NotScheduled);
-        setTimeToRunPendingPlayTask(TimeToRunPendingTask::NotScheduled);
+        m_timeToRunPendingPauseTask = TimeToRunPendingTask::NotScheduled;
+        m_timeToRunPendingPlayTask = TimeToRunPendingTask::NotScheduled;
         // 2. Set has pending ready promise to true.
         hasPendingReadyPromise = true;
     }
@@ -825,28 +819,21 @@ ExceptionOr<void> WebAnimation::play(AutoRewind autoRewind)
 
     // 6. If animation's hold time is resolved, let its start time be unresolved.
     if (m_holdTime)
-        setStartTime(std::nullopt);
+        m_startTime = std::nullopt;
 
     // 7. If has pending ready promise is false, let animation's current ready promise be a new (pending) Promise object.
     if (!hasPendingReadyPromise)
         m_readyPromise = makeUniqueRef<ReadyPromise>(*this, &WebAnimation::readyPromiseResolve);
 
     // 8. Schedule a task to run as soon as animation is ready.
-    setTimeToRunPendingPlayTask(TimeToRunPendingTask::WhenReady);
+    m_timeToRunPendingPlayTask = TimeToRunPendingTask::WhenReady;
 
     // 9. Run the procedure to update an animation's finished state for animation with the did seek flag set to false, and the synchronously notify flag set to false.
-    updateFinishedState(DidSeek::No, SynchronouslyNotify::No);
-
-    return { };
-}
+    timingDidChange(DidSeek::No, SynchronouslyNotify::No);
 
-void WebAnimation::setTimeToRunPendingPlayTask(TimeToRunPendingTask timeToRunPendingTask)
-{
-    if (m_timeToRunPendingPlayTask == timeToRunPendingTask)
-        return;
+    invalidateEffect();
 
-    m_timeToRunPendingPlayTask = timeToRunPendingTask;
-    updatePendingTasks();
+    return { };
 }
 
 void WebAnimation::runPendingPlayTask()
@@ -875,9 +862,9 @@ void WebAnimation::runPendingPlayTask()
             newStartTime -= m_holdTime.value() / m_playbackRate;
         // 2. If animation's playback rate is not 0, make animation's hold time unresolved.
         if (m_playbackRate)
-            setHoldTime(std::nullopt);
+            m_holdTime = std::nullopt;
         // 3. Set the animation start time of animation to new start time.
-        setStartTime(newStartTime);
+        m_startTime = newStartTime;
     }
 
     // 4. Resolve animation's current ready promise with animation.
@@ -885,7 +872,9 @@ void WebAnimation::runPendingPlayTask()
         m_readyPromise->resolve(*this);
 
     // 5. Run the procedure to update an animation's finished state for animation with the did seek flag set to false, and the synchronously notify flag set to false.
-    updateFinishedState(DidSeek::No, SynchronouslyNotify::No);
+    timingDidChange(DidSeek::No, SynchronouslyNotify::No);
+
+    invalidateEffect();
 }
 
 ExceptionOr<void> WebAnimation::pause()
@@ -907,13 +896,13 @@ ExceptionOr<void> WebAnimation::pause()
     if (!localTime) {
         if (m_playbackRate >= 0) {
             // If animation's playback rate is ≥ 0, let animation's hold time be zero.
-            setHoldTime(0_s);
+            m_holdTime = 0_s;
         } else if (effectEndTime() == Seconds::infinity()) {
             // Otherwise, if target effect end for animation is positive infinity, throw an InvalidStateError and abort these steps.
             return Exception { InvalidStateError };
         } else {
             // Otherwise, let animation's hold time be target effect end.
-            setHoldTime(effectEndTime());
+            m_holdTime = effectEndTime();
         }
     }
 
@@ -922,7 +911,7 @@ ExceptionOr<void> WebAnimation::pause()
 
     // 5. If animation has a pending play task, cancel that task and let has pending ready promise be true.
     if (hasPendingPlayTask()) {
-        setTimeToRunPendingPlayTask(TimeToRunPendingTask::NotScheduled);
+        m_timeToRunPendingPlayTask = TimeToRunPendingTask::NotScheduled;
         hasPendingReadyPromise = true;
     }
 
@@ -932,10 +921,12 @@ ExceptionOr<void> WebAnimation::pause()
 
     // 7. Schedule a task to be executed at the first possible moment after the user agent has performed any processing necessary
     //    to suspend the playback of animation's target effect, if any.
-    setTimeToRunPendingPauseTask(TimeToRunPendingTask::ASAP);
+    m_timeToRunPendingPauseTask = TimeToRunPendingTask::ASAP;
 
     // 8. Run the procedure to update an animation's finished state for animation with the did seek flag set to false, and the synchronously notify flag set to false.
-    updateFinishedState(DidSeek::No, SynchronouslyNotify::No);
+    timingDidChange(DidSeek::No, SynchronouslyNotify::No);
+
+    invalidateEffect();
 
     return { };
 }
@@ -972,15 +963,6 @@ ExceptionOr<void> WebAnimation::reverse()
     return { };
 }
 
-void WebAnimation::setTimeToRunPendingPauseTask(TimeToRunPendingTask timeToRunPendingTask)
-{
-    if (m_timeToRunPendingPauseTask == timeToRunPendingTask)
-        return;
-
-    m_timeToRunPendingPauseTask = timeToRunPendingTask;
-    updatePendingTasks();
-}
-
 void WebAnimation::runPendingPauseTask()
 {
     // 3.4.11. Pausing an animation, step 7.
@@ -1002,11 +984,11 @@ void WebAnimation::runPendingPauseTask()
         // Subsequently, the resulting readyTime value can be null. Unify behavior between C++17 and
         // C++14 builds (the latter using WTF's std::optional) and avoid null std::optional dereferencing
         // by defaulting to a Seconds(0) value. See https://bugs.webkit.org/show_bug.cgi?id=186189.
-        setHoldTime((readyTime.value_or(0_s) - animationStartTime.value()) * m_playbackRate);
+        m_holdTime = (readyTime.value_or(0_s) - animationStartTime.value()) * m_playbackRate;
     }
 
     // 3. Make animation's start time unresolved.
-    setStartTime(std::nullopt);
+    m_startTime = std::nullopt;
 
     // 4. Resolve animation's current ready promise with animation.
     if (!m_readyPromise->isFulfilled())
@@ -1014,62 +996,32 @@ void WebAnimation::runPendingPauseTask()
 
     // 5. Run the procedure to update an animation's finished state for animation with the did seek flag set to false, and the
     //    synchronously notify flag set to false.
-    updateFinishedState(DidSeek::No, SynchronouslyNotify::No);
-}
+    timingDidChange(DidSeek::No, SynchronouslyNotify::No);
 
-void WebAnimation::updatePendingTasks()
-{
-    timingModelDidChange();
+    invalidateEffect();
 }
 
-Seconds WebAnimation::timeToNextRequiredTick() const
+bool WebAnimation::needsTick() const
 {
-    // If we don't have a timeline, an effect, a start time or a playback rate other than 0,
-    // there is no value to apply so we don't need to schedule invalidation.
-    if (!m_timeline || !m_effect || !m_playbackRate)
-        return Seconds::infinity();
-
-    if (pending())
-        return 0_s;
-
-    if (!m_startTime)
-        return Seconds::infinity();
-
-    // If we're in or expected to be in the running state, we need to schedule invalidation as soon as possible.
-    if (hasPendingPlayTask() || playState() == PlayState::Running)
-        return 0_s;
-
-    if (auto animationCurrentTime = currentTime()) {
-        // If our current time is negative, we need to be scheduled to be resolved at the inverse
-        // of our current time, unless we fill backwards, in which case we want to invalidate as
-        // soon as possible.
-        auto localTime = animationCurrentTime.value();
-        if (localTime < 0_s)
-            return -localTime;
-    }
-
-    // In any other case, we're idle or already outside our active duration and have no need
-    // to schedule an invalidation.
-    return Seconds::infinity();
+    return pending() || playState() == PlayState::Running;
 }
 
-void WebAnimation::runPendingTasks()
+void WebAnimation::tick()
 {
+    updateFinishedState(DidSeek::No, SynchronouslyNotify::Yes);
+
+    // Run pending tasks, if any.
     if (hasPendingPauseTask())
         runPendingPauseTask();
-
     if (hasPendingPlayTask())
         runPendingPlayTask();
-}
 
-void WebAnimation::resolve()
-{
-    updateFinishedState(DidSeek::No, SynchronouslyNotify::Yes);
+    invalidateEffect();
 }
 
 void WebAnimation::resolve(RenderStyle& targetStyle)
 {
-    resolve();
+    timingDidChange(DidSeek::No, SynchronouslyNotify::Yes);
     if (m_effect)
         m_effect->apply(targetStyle);
 }
@@ -1123,7 +1075,12 @@ void WebAnimation::stop()
     removeAllEventListeners();
 }
 
-bool WebAnimation::canBeListed() const
+void WebAnimation::updateRelevance()
+{
+    m_isRelevant = computeRelevance();
+}
+
+bool WebAnimation::computeRelevance()
 {
     // To be listed in getAnimations() an animation needs a target effect which is current or in effect.
     if (!m_effect)
index c2b3b42..a4276c6 100644 (file)
@@ -57,8 +57,6 @@ public:
     virtual bool isCSSAnimation() const { return false; }
     virtual bool isCSSTransition() const { return false; }
 
-    virtual bool canBeListed() const;
-
     const String& id() const { return m_id; }
     void setId(const String& id) { m_id = id; }
 
@@ -67,9 +65,6 @@ public:
     AnimationTimeline* timeline() const { return m_timeline.get(); }
     virtual void setTimeline(RefPtr<AnimationTimeline>&&);
 
-    std::optional<Seconds> startTime() const;
-    void setStartTime(std::optional<Seconds>);
-
     std::optional<Seconds> currentTime() const;
     ExceptionOr<void> setCurrentTime(std::optional<Seconds>);
 
@@ -95,8 +90,8 @@ public:
     ExceptionOr<void> pause();
     ExceptionOr<void> reverse();
 
-    virtual std::optional<double> bindingsStartTime() const;
-    virtual void setBindingsStartTime(std::optional<double>);
+    virtual std::optional<double> startTime() const;
+    virtual void setStartTime(std::optional<double>);
     virtual std::optional<double> bindingsCurrentTime() const;
     virtual ExceptionOr<void> setBindingsCurrentTime(std::optional<double>);
     virtual PlayState bindingsPlayState() const { return playState(); }
@@ -106,15 +101,14 @@ public:
     virtual ExceptionOr<void> bindingsPlay() { return play(); }
     virtual ExceptionOr<void> bindingsPause() { return pause(); }
 
-    Seconds timeToNextRequiredTick() const;
-    void resolve();
+    virtual bool needsTick() const;
+    virtual void tick();
     virtual void resolve(RenderStyle&);
-    void runPendingTasks();
     void effectTargetDidChange(Element* previousTarget, Element* newTarget);
     void acceleratedStateDidChange();
     void applyPendingAcceleratedActions();
 
-    void timingModelDidChange();
+    bool isRelevant() const { return m_isRelevant; }
     void effectTimingPropertiesDidChange();
     void suspendEffectInvalidation();
     void unsuspendEffectInvalidation();
@@ -128,7 +122,6 @@ public:
 protected:
     explicit WebAnimation(Document&);
 
-    bool isEffectInvalidationSuspended() { return m_suspendCount; }
     void stop() override;
 
 private:
@@ -138,28 +131,29 @@ private:
     enum class AutoRewind { Yes, No };
     enum class TimeToRunPendingTask { NotScheduled, ASAP, WhenReady };
 
+    void timingDidChange(DidSeek, SynchronouslyNotify);
     void updateFinishedState(DidSeek, SynchronouslyNotify);
     void enqueueAnimationPlaybackEvent(const AtomicString&, std::optional<Seconds>, std::optional<Seconds>);
     Seconds effectEndTime() const;
     WebAnimation& readyPromiseResolve();
     WebAnimation& finishedPromiseResolve();
-    void setHoldTime(std::optional<Seconds>);
     std::optional<Seconds> currentTime(RespectHoldTime) const;
     ExceptionOr<void> silentlySetCurrentTime(std::optional<Seconds>);
     void finishNotificationSteps();
     void scheduleMicrotaskIfNeeded();
     void performMicrotask();
-    void setTimeToRunPendingPauseTask(TimeToRunPendingTask);
-    void setTimeToRunPendingPlayTask(TimeToRunPendingTask);
     bool hasPendingPauseTask() const { return m_timeToRunPendingPauseTask != TimeToRunPendingTask::NotScheduled; }
     bool hasPendingPlayTask() const { return m_timeToRunPendingPlayTask != TimeToRunPendingTask::NotScheduled; }
-    void updatePendingTasks();
     ExceptionOr<void> play(AutoRewind);
     void runPendingPauseTask();
     void runPendingPlayTask();
     void resetPendingTasks(Silently = Silently::No);
     void setEffectInternal(RefPtr<AnimationEffectReadOnly>&&, bool = false);
     void setTimelineInternal(RefPtr<AnimationTimeline>&&);
+    bool isEffectInvalidationSuspended() { return m_suspendCount; }
+    bool computeRelevance();
+    void updateRelevance();
+    void invalidateEffect();
 
     String m_id;
     RefPtr<AnimationEffectReadOnly> m_effect;
@@ -173,6 +167,7 @@ private:
     bool m_isSuspended { false };
     bool m_finishNotificationStepsMicrotaskPending;
     bool m_scheduledMicrotask;
+    bool m_isRelevant;
     UniqueRef<ReadyPromise> m_readyPromise;
     UniqueRef<FinishedPromise> m_finishedPromise;
     TimeToRunPendingTask m_timeToRunPendingPlayTask { TimeToRunPendingTask::NotScheduled };
index b845417..7ba0cfc 100644 (file)
@@ -40,7 +40,7 @@ enum AnimationPlayState {
     attribute DOMString id;
     attribute AnimationEffectReadOnly? effect;
     attribute AnimationTimeline? timeline;
-    [ImplementedAs=bindingsStartTime] attribute double? startTime;
+    attribute double? startTime;
     [MayThrowException, ImplementedAs=bindingsCurrentTime] attribute double? currentTime;
     attribute double playbackRate;
     [ImplementedAs=bindingsPlayState] readonly attribute AnimationPlayState playState;
index d2d5836..a4bb89f 100644 (file)
@@ -4031,7 +4031,7 @@ Vector<RefPtr<WebAnimation>> Element::getAnimations()
     Vector<RefPtr<WebAnimation>> animations;
     if (auto timeline = document().existingTimeline()) {
         for (auto& animation : timeline->animationsForElement(*this, AnimationTimeline::Ordering::Sorted)) {
-            if (animation->canBeListed())
+            if (animation->isRelevant())
                 animations.append(animation);
         }
     }