[Web Animations] Schedule animations registered on the document timeline
authorgraouts@webkit.org <graouts@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Sun, 5 Nov 2017 22:34:10 +0000 (22:34 +0000)
committergraouts@webkit.org <graouts@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Sun, 5 Nov 2017 22:34:10 +0000 (22:34 +0000)
https://bugs.webkit.org/show_bug.cgi?id=179236
<rdar://problem/35332669>

Reviewed by Dean Jackson.

Source/WebCore:

We now schedule animations contained in the document timeline using a three-step approach.

1. Each time an object that is part of the timing model changes one of its timing properties, we call
   animationTimingModelDidChange() on the document timeline. This schedules performInvalidationTask()
   to be called when the current run loop completes, such that we invalidate the timing model just once
   per run loop.

2. Once performInvalidationTask() is called, the timing model is invalidated in updateAnimationSchedule().
   We iterate over the registered animations on the timineline and identify the shortest interval between
   the current time and the next moment one of the animations requires a tick to update its value. If we
   find a value below 15ms, we schedule animations to be resolved with scheduleAnimationResolution() right
   away. If the value is above 15ms, and not inifinity, we schedule a one-shot timer for that interval to
   call scheduleAnimationResolution().

3. Once scheduleAnimationResolution() is called, we call scheduleAnimation() on the shared DisplayRefreshMonitorManager
   to be notified when the next display refresh occurs to actually resolve animations with resolveAnimations().

Note that, in this patch, resolveAnimations() does nothing, we will add support for interpolating values in
a future patch.

Another important thing to note is that every time the document timeline's current time is requested, we cache
it for the duration of the run loop such that the timing model always uses the same value during a given run loop.

Finally, to support tests where we check the state of the timing model by manually advancing time, we expose a
new pause() method on AnimationTimeline for tests to call to avoid the timeline to self-advance.

* animation/AnimationTimeline.cpp:
(WebCore::AnimationTimeline::addAnimation): Mark that the timing model changed as a result of adding an animation.
(WebCore::AnimationTimeline::removeAnimation): Mark that the timing model changed as a result of removing an animation.
(WebCore::AnimationTimeline::bindingsCurrentTime): Update the method signature to no longer be const and call into
currentTime() instead of reading directly from the m_currentTime member variable since a subclass, like DocumentTimeline,
may have a custom currentTime() implementation.
(WebCore::AnimationTimeline::setCurrentTime): Mark that the timing model changed as a result of the timeline current time
changing.
(WebCore::AnimationTimeline::bindingsCurrentTime const): Deleted.
* animation/AnimationTimeline.h:
(WebCore::AnimationTimeline::currentTime): Change both methods signatures to no longer be const so that DocumentTimeline's
implementation of currentTime() may cache the current time in a member variable, enqueuing a callback when the run loop
completes for this member variable to be reset, and updating some states.
(WebCore::AnimationTimeline::pause): To be implemented by subclasses.
(WebCore::AnimationTimeline::animationTimingModelDidChange): Add a new virtual method to indicate that the timing model
needs invalidating.
(WebCore::AnimationTimeline::animations const): Add an accessor to allow animations to be accessed by a subclass.
* animation/DocumentTimeline.cpp:
(WebCore::DocumentTimeline::create):
(WebCore::DocumentTimeline::DocumentTimeline): Update the constructor signature to receive a Document and a PlatformDisplayID
since we need a reference to the Document to get at the nowTime() and a PlatformDisplayID to create the DisplayRefreshMonitor.
(WebCore::DocumentTimeline::~DocumentTimeline): Close the task queue when the timeline gets destroyed.
(WebCore::DocumentTimeline::currentTime): If we don't have a current cahed current time, compute one and schedule
the invalidation task if needed so that we may reset the cached value as the run loop completes.
(WebCore::DocumentTimeline::pause): Allows the timeline not to self-advance, for testing purposes only.
(WebCore::DocumentTimeline::animationTimingModelDidChange): If we haven't already done so, mark that we need to update our
animation schedule in the invalidation task and schedule that task if not scheduled yet.
(WebCore::DocumentTimeline::scheduleInvalidationTaskIfNeeded): Schedule the invalidation task to run as the run loop completes
if we haven't already done so.
(WebCore::DocumentTimeline::performInvalidationTask): Update the animation schedule if needed and reset the cached current
time value.
(WebCore::DocumentTimeline::updateAnimationSchedule): Iterate over registed animations and find the shortest interval until
one of them needs to update their animation. If the shortest interval is below 15ms, schedule the animation resolution right
away. If the shortest inverval is finite and above 15ms, then schedule a one-shot timer for that interval to perform the
animation resolution then.
(WebCore::DocumentTimeline::animationScheduleTimerFired): The one-shot timer to perform the animation resolution has fired,
we call scheduleAnimationResolution().
(WebCore::DocumentTimeline::scheduleAnimationResolution): We call scheduleAnimation() on the shared DisplayRefreshMonitorManager
so that we may resolve animations on the next display refresh, or start a timer if the DisplayRefreshMonitorManager is not available.
(WebCore::DocumentTimeline::displayRefreshFired): The display is about to refresh, we call resolveAnimations().
(WebCore::DocumentTimeline::animationResolutionTimerFired): The fallback animation resolution timer has fired, we call resolveAnimations().
(WebCore::DocumentTimeline::resolveAnimations): Currently do nothing, this is where we'll iterate over registered animations to
update them with the current time.
(WebCore::DocumentTimeline::windowScreenDidChange): Notify the shared DisplayRefreshMonitorManager that the PlatformDisplayID
changed.
(WebCore::DocumentTimeline::createDisplayRefreshMonitor const): Provide a DisplayRefreshMonitor as part of the
DisplayRefreshMonitorClient protocol.
* animation/DocumentTimeline.h:
* animation/WebAnimation.cpp:
(WebCore::WebAnimation::create): Remove extra white space.
(WebCore::WebAnimation::setStartTime): Mark that the timing model changed as a result of changing this animation's start time.
(WebCore::WebAnimation::timeToNextRequiredTick const): Compute the interval until the next time we need to resolve this animation.
If the provided current time is before this animation's start time, compute the delay until the start time. If the current time
is after the animation's start time but before the animation's end time, indicate that we want to resolve the animation again
right away and return 0ms. In any other case, return an infinite interval to indicate that we don't need to be refreshed after
the provided time.
* animation/WebAnimation.h:
* dom/Document.cpp:
(WebCore::Document::windowScreenDidChange): Notify the document timeline that the PlatformDisplayID changed.
(WebCore::Document::timeline): Provide the Document and the PlatformDisplayID to the DocumentTimeline.
* testing/Internals.cpp:
(WebCore::Internals::pauseTimeline):
* testing/Internals.h:
* testing/Internals.idl:

LayoutTests:

Adopt the new internals.pauseTimeline() method to ensure that the existing
tests do not have a self-advancing timeline since we're interested in checking
the timing model state based on manually setting the timeline current time.

Also update some WPT expectations with some progressions.

* TestExpectations: Mark two tests as flaky due to the sample time being logged
in the failure.
* http/wpt/web-animations/interfaces/AnimationTimeline/document-timeline-expected.txt:
* http/wpt/web-animations/timing-model/animations/current-time-expected.txt:
* http/wpt/web-animations/timing-model/animations/set-the-animation-start-time-expected.txt:
* http/wpt/wk-web-animations/timing-model/animation-creation-basic.html:
* http/wpt/wk-web-animations/timing-model/animation-current-time.html:
* http/wpt/wk-web-animations/timing-model/animation-effect-timing.html:
* http/wpt/wk-web-animations/timing-model/animation-effect.html:
* http/wpt/wk-web-animations/timing-model/animation-interface-effect-property.html:
* http/wpt/wk-web-animations/timing-model/animation-interface-start-time-property.html:
* http/wpt/wk-web-animations/timing-model/animation-playback-rate.html:
* http/wpt/wk-web-animations/timing-model/document-timeline.html:
* http/wpt/wk-web-animations/timing-model/keyframe-effect-interface-timing-duration.html:
* http/wpt/wk-web-animations/timing-model/keyframe-effect.html:
* http/wpt/wk-web-animations/timing-model/timeline-current-time.html:

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

27 files changed:
LayoutTests/ChangeLog
LayoutTests/TestExpectations
LayoutTests/http/wpt/web-animations/interfaces/AnimationTimeline/document-timeline-expected.txt
LayoutTests/http/wpt/web-animations/timing-model/animations/current-time-expected.txt
LayoutTests/http/wpt/web-animations/timing-model/animations/set-the-animation-start-time-expected.txt
LayoutTests/http/wpt/wk-web-animations/timing-model/animation-creation-basic.html
LayoutTests/http/wpt/wk-web-animations/timing-model/animation-current-time.html
LayoutTests/http/wpt/wk-web-animations/timing-model/animation-effect-timing.html
LayoutTests/http/wpt/wk-web-animations/timing-model/animation-effect.html
LayoutTests/http/wpt/wk-web-animations/timing-model/animation-interface-effect-property.html
LayoutTests/http/wpt/wk-web-animations/timing-model/animation-interface-start-time-property.html
LayoutTests/http/wpt/wk-web-animations/timing-model/animation-playback-rate.html
LayoutTests/http/wpt/wk-web-animations/timing-model/document-timeline.html
LayoutTests/http/wpt/wk-web-animations/timing-model/keyframe-effect-interface-timing-duration.html
LayoutTests/http/wpt/wk-web-animations/timing-model/keyframe-effect.html
LayoutTests/http/wpt/wk-web-animations/timing-model/timeline-current-time.html
Source/WebCore/ChangeLog
Source/WebCore/animation/AnimationTimeline.cpp
Source/WebCore/animation/AnimationTimeline.h
Source/WebCore/animation/DocumentTimeline.cpp
Source/WebCore/animation/DocumentTimeline.h
Source/WebCore/animation/WebAnimation.cpp
Source/WebCore/animation/WebAnimation.h
Source/WebCore/dom/Document.cpp
Source/WebCore/testing/Internals.cpp
Source/WebCore/testing/Internals.h
Source/WebCore/testing/Internals.idl

index aa6d6c1..ef870a5 100644 (file)
@@ -1,3 +1,34 @@
+2017-11-05  Antoine Quint  <graouts@apple.com>
+
+        [Web Animations] Schedule animations registered on the document timeline
+        https://bugs.webkit.org/show_bug.cgi?id=179236
+        <rdar://problem/35332669>
+
+        Reviewed by Dean Jackson.
+
+        Adopt the new internals.pauseTimeline() method to ensure that the existing
+        tests do not have a self-advancing timeline since we're interested in checking
+        the timing model state based on manually setting the timeline current time.
+
+        Also update some WPT expectations with some progressions.
+
+        * TestExpectations: Mark two tests as flaky due to the sample time being logged
+        in the failure.
+        * http/wpt/web-animations/interfaces/AnimationTimeline/document-timeline-expected.txt:
+        * http/wpt/web-animations/timing-model/animations/current-time-expected.txt:
+        * http/wpt/web-animations/timing-model/animations/set-the-animation-start-time-expected.txt:
+        * http/wpt/wk-web-animations/timing-model/animation-creation-basic.html:
+        * http/wpt/wk-web-animations/timing-model/animation-current-time.html:
+        * http/wpt/wk-web-animations/timing-model/animation-effect-timing.html:
+        * http/wpt/wk-web-animations/timing-model/animation-effect.html:
+        * http/wpt/wk-web-animations/timing-model/animation-interface-effect-property.html:
+        * http/wpt/wk-web-animations/timing-model/animation-interface-start-time-property.html:
+        * http/wpt/wk-web-animations/timing-model/animation-playback-rate.html:
+        * http/wpt/wk-web-animations/timing-model/document-timeline.html:
+        * http/wpt/wk-web-animations/timing-model/keyframe-effect-interface-timing-duration.html:
+        * http/wpt/wk-web-animations/timing-model/keyframe-effect.html:
+        * http/wpt/wk-web-animations/timing-model/timeline-current-time.html:
+
 2017-11-05  Per Arne Vollan  <pvollan@apple.com>
 
         Mark http/tests/security/xss-DENIED-xsl-external-entity.xml as a flaky failure on Windows.
index ee9aab4..d9b99d8 100644 (file)
@@ -1582,3 +1582,6 @@ webkit.org/b/177799 accessibility/table-detection.html [ Pass Failure ]
 webkit.org/b/177997 webgl/1.0.2/conformance/textures/copy-tex-image-2d-formats.html [ Pass Failure ]
 
 webkit.org/b/179069 imported/w3c/web-platform-tests/html/semantics/embedded-content/the-iframe-element/sandbox_032.htm [ Pass Failure ]
+
+webkit.org/b/179287 http/wpt/web-animations/interfaces/AnimationTimeline/document-timeline.html [ Pass Failure ]
+webkit.org/b/179287 http/wpt/web-animations/timing-model/animations/set-the-animation-start-time.html [ Pass Failure ]
index 7f740cb..f18dfab 100644 (file)
@@ -1,7 +1,7 @@
 
 PASS document.timeline identity tests 
-FAIL document.timeline.currentTime value tests assert_true: document.timeline.currentTime is positive expected true got false
-FAIL document.timeline.currentTime liveness tests assert_greater_than: document.timeline.currentTime increases between script blocks expected a number but got a "object"
-FAIL iframe document.timeline.currentTime liveness tests assert_greater_than: iframe document.timeline.currentTime increases between script blocks expected a number but got a "object"
-FAIL document.timeline.currentTime time should be the same for all RAF callbacks in a frame assert_greater_than_equal: currentTime should have progressed expected a number but got a "object"
+FAIL document.timeline.currentTime value tests assert_equals: document.timeline.currentTime matches requestAnimationFrame time expected 552.3000000000001 but got 0.5538000000000001
+PASS document.timeline.currentTime liveness tests 
+PASS iframe document.timeline.currentTime liveness tests 
+PASS document.timeline.currentTime time should be the same for all RAF callbacks in a frame 
+
index 9fa9f3d..8852257 100644 (file)
@@ -2,6 +2,6 @@
 FAIL The current time returns the hold time when set animation.play is not a function. (In 'animation.play()', 'animation.play' is undefined)
 FAIL The current time is unresolved when there is no associated timeline (and no hold time is set) undefined is not an object (evaluating 'animation.ready.then')
 PASS The current time is unresolved when the start time is unresolved (and no hold time is set) 
-FAIL The current time is calculated from the timeline time, start time and playback rate assert_approx_equals: Animation has a unresolved start time expected a number but got a "object"
+PASS The current time is calculated from the timeline time, start time and playback rate 
 FAIL The current time does not progress if playback rate is 0 createDiv(t).animate is not a function. (In 'createDiv(t).animate(null, 100 * MS_PER_SEC)', 'createDiv(t).animate' is undefined)
 
index 09bee90..b6f5eae 100644 (file)
@@ -1,7 +1,7 @@
 
-FAIL Setting the start time of an animation without an active timeline assert_equals: Setting the current time succeeds expected (number) 1000 but got (object) null
-FAIL Setting an unresolved start time an animation without an active timeline does not clear the current time assert_equals: Setting the current time succeeds expected (number) 1000 but got (object) null
-FAIL Setting the start time clears the hold time assert_equals: The current time is calculated from the hold time expected (number) 1000 but got (object) null
+FAIL Setting the start time of an animation without an active timeline assert_equals: Start time remains null after setting current time expected (object) null but got (number) -999.896
+FAIL Setting an unresolved start time an animation without an active timeline does not clear the current time assert_equals: Start time remains null after setting current time expected (object) null but got (number) -999.896
+FAIL Setting the start time clears the hold time assert_equals: Animation reports it is running after setting a resolved start time expected (string) "running" but got (undefined) undefined
 FAIL Setting an unresolved start time sets the hold time assert_equals: expected (string) "running" but got (undefined) undefined
 FAIL Setting the start time resolves a pending ready promise undefined is not an object (evaluating 'animation.ready.then')
 FAIL Setting the start time resolves a pending pause task undefined is not an object (evaluating 'animation.ready.then')
index 2fe7f30..55f5ea0 100644 (file)
@@ -8,6 +8,8 @@
 <script>
 'use strict';
 
+internals.pauseTimeline(document.timeline);
+
 test(t => {
     assert_own_property(window, "Animation");
 }, "The Animation interface is defined.");
index 87be122..2089a1a 100644 (file)
@@ -8,6 +8,8 @@
 <script>
 'use strict';
 
+internals.pauseTimeline(document.timeline);
+
 test(t => {
     const animation = new Animation;
     assert_equals(animation.currentTime, null);
index 2ccff92..e1e129c 100644 (file)
@@ -8,6 +8,8 @@
 <script>
 'use strict';
 
+internals.pauseTimeline(document.timeline);
+
 test(t => {
     assert_own_property(window, "AnimationEffectTiming");
 }, "The AnimationEffectTiming interface is defined.");
index 77d4efc..566671a 100644 (file)
@@ -8,6 +8,8 @@
 <script>
 'use strict';
 
+internals.pauseTimeline(document.timeline);
+
 test(t => {
     assert_own_property(window, "AnimationEffect");
 }, "The AnimationEffect interface is defined.");
index 9ef715a..8e2c1c8 100644 (file)
@@ -8,6 +8,8 @@
 <script>
 'use strict';
 
+internals.pauseTimeline(document.timeline);
+
 test(t => {
     assert_own_property(window, "DocumentTimeline");
 }, "The DocumentTimeline interface is defined.");
index 386000c..88b6845 100644 (file)
@@ -8,6 +8,8 @@
 <script>
 'use strict';
 
+internals.pauseTimeline(document.timeline);
+
 test(t => {
     const keyframeEffect = new KeyframeEffect(document.body);
     assert_inherits(keyframeEffect, "timing");
index 9c7ab5e..ce0c6f7 100644 (file)
@@ -8,6 +8,8 @@
 <script>
 'use strict';
 
+internals.pauseTimeline(document.timeline);
+
 test(t => {
     assert_own_property(window, "KeyframeEffect");
 }, "The KeyframeEffect interface is defined.");
index 2095938..7b5d61a 100644 (file)
@@ -8,6 +8,8 @@
 <script>
 'use strict';
 
+internals.pauseTimeline(document.timeline);
+
 test(t => {
     assert_equals(document.timeline.currentTime, null);
 }, 'The document timeline currentTime is null by default.');
index ae31b08..03eb4ac 100644 (file)
@@ -1,3 +1,102 @@
+2017-11-05  Antoine Quint  <graouts@apple.com>
+
+        [Web Animations] Schedule animations registered on the document timeline
+        https://bugs.webkit.org/show_bug.cgi?id=179236
+        <rdar://problem/35332669>
+
+        Reviewed by Dean Jackson.
+
+        We now schedule animations contained in the document timeline using a three-step approach.
+
+        1. Each time an object that is part of the timing model changes one of its timing properties, we call
+           animationTimingModelDidChange() on the document timeline. This schedules performInvalidationTask()
+           to be called when the current run loop completes, such that we invalidate the timing model just once
+           per run loop.
+
+        2. Once performInvalidationTask() is called, the timing model is invalidated in updateAnimationSchedule().
+           We iterate over the registered animations on the timineline and identify the shortest interval between
+           the current time and the next moment one of the animations requires a tick to update its value. If we
+           find a value below 15ms, we schedule animations to be resolved with scheduleAnimationResolution() right
+           away. If the value is above 15ms, and not inifinity, we schedule a one-shot timer for that interval to
+           call scheduleAnimationResolution().
+
+        3. Once scheduleAnimationResolution() is called, we call scheduleAnimation() on the shared DisplayRefreshMonitorManager
+           to be notified when the next display refresh occurs to actually resolve animations with resolveAnimations().
+
+        Note that, in this patch, resolveAnimations() does nothing, we will add support for interpolating values in
+        a future patch.
+
+        Another important thing to note is that every time the document timeline's current time is requested, we cache
+        it for the duration of the run loop such that the timing model always uses the same value during a given run loop.
+
+        Finally, to support tests where we check the state of the timing model by manually advancing time, we expose a
+        new pause() method on AnimationTimeline for tests to call to avoid the timeline to self-advance.
+
+        * animation/AnimationTimeline.cpp:
+        (WebCore::AnimationTimeline::addAnimation): Mark that the timing model changed as a result of adding an animation.
+        (WebCore::AnimationTimeline::removeAnimation): Mark that the timing model changed as a result of removing an animation.
+        (WebCore::AnimationTimeline::bindingsCurrentTime): Update the method signature to no longer be const and call into
+        currentTime() instead of reading directly from the m_currentTime member variable since a subclass, like DocumentTimeline,
+        may have a custom currentTime() implementation.
+        (WebCore::AnimationTimeline::setCurrentTime): Mark that the timing model changed as a result of the timeline current time
+        changing.
+        (WebCore::AnimationTimeline::bindingsCurrentTime const): Deleted.
+        * animation/AnimationTimeline.h:
+        (WebCore::AnimationTimeline::currentTime): Change both methods signatures to no longer be const so that DocumentTimeline's
+        implementation of currentTime() may cache the current time in a member variable, enqueuing a callback when the run loop
+        completes for this member variable to be reset, and updating some states.
+        (WebCore::AnimationTimeline::pause): To be implemented by subclasses.
+        (WebCore::AnimationTimeline::animationTimingModelDidChange): Add a new virtual method to indicate that the timing model
+        needs invalidating.
+        (WebCore::AnimationTimeline::animations const): Add an accessor to allow animations to be accessed by a subclass.
+        * animation/DocumentTimeline.cpp:
+        (WebCore::DocumentTimeline::create):
+        (WebCore::DocumentTimeline::DocumentTimeline): Update the constructor signature to receive a Document and a PlatformDisplayID
+        since we need a reference to the Document to get at the nowTime() and a PlatformDisplayID to create the DisplayRefreshMonitor.
+        (WebCore::DocumentTimeline::~DocumentTimeline): Close the task queue when the timeline gets destroyed.
+        (WebCore::DocumentTimeline::currentTime): If we don't have a current cahed current time, compute one and schedule
+        the invalidation task if needed so that we may reset the cached value as the run loop completes.
+        (WebCore::DocumentTimeline::pause): Allows the timeline not to self-advance, for testing purposes only.
+        (WebCore::DocumentTimeline::animationTimingModelDidChange): If we haven't already done so, mark that we need to update our
+        animation schedule in the invalidation task and schedule that task if not scheduled yet.
+        (WebCore::DocumentTimeline::scheduleInvalidationTaskIfNeeded): Schedule the invalidation task to run as the run loop completes
+        if we haven't already done so.
+        (WebCore::DocumentTimeline::performInvalidationTask): Update the animation schedule if needed and reset the cached current
+        time value.
+        (WebCore::DocumentTimeline::updateAnimationSchedule): Iterate over registed animations and find the shortest interval until
+        one of them needs to update their animation. If the shortest interval is below 15ms, schedule the animation resolution right
+        away. If the shortest inverval is finite and above 15ms, then schedule a one-shot timer for that interval to perform the
+        animation resolution then. 
+        (WebCore::DocumentTimeline::animationScheduleTimerFired): The one-shot timer to perform the animation resolution has fired,
+        we call scheduleAnimationResolution().
+        (WebCore::DocumentTimeline::scheduleAnimationResolution): We call scheduleAnimation() on the shared DisplayRefreshMonitorManager
+        so that we may resolve animations on the next display refresh, or start a timer if the DisplayRefreshMonitorManager is not available.
+        (WebCore::DocumentTimeline::displayRefreshFired): The display is about to refresh, we call resolveAnimations().
+        (WebCore::DocumentTimeline::animationResolutionTimerFired): The fallback animation resolution timer has fired, we call resolveAnimations().
+        (WebCore::DocumentTimeline::resolveAnimations): Currently do nothing, this is where we'll iterate over registered animations to
+        update them with the current time.
+        (WebCore::DocumentTimeline::windowScreenDidChange): Notify the shared DisplayRefreshMonitorManager that the PlatformDisplayID
+        changed.
+        (WebCore::DocumentTimeline::createDisplayRefreshMonitor const): Provide a DisplayRefreshMonitor as part of the
+        DisplayRefreshMonitorClient protocol. 
+        * animation/DocumentTimeline.h:
+        * animation/WebAnimation.cpp:
+        (WebCore::WebAnimation::create): Remove extra white space.
+        (WebCore::WebAnimation::setStartTime): Mark that the timing model changed as a result of changing this animation's start time.
+        (WebCore::WebAnimation::timeToNextRequiredTick const): Compute the interval until the next time we need to resolve this animation.
+        If the provided current time is before this animation's start time, compute the delay until the start time. If the current time
+        is after the animation's start time but before the animation's end time, indicate that we want to resolve the animation again
+        right away and return 0ms. In any other case, return an infinite interval to indicate that we don't need to be refreshed after
+        the provided time.
+        * animation/WebAnimation.h:
+        * dom/Document.cpp:
+        (WebCore::Document::windowScreenDidChange): Notify the document timeline that the PlatformDisplayID changed.
+        (WebCore::Document::timeline): Provide the Document and the PlatformDisplayID to the DocumentTimeline.
+        * testing/Internals.cpp:
+        (WebCore::Internals::pauseTimeline):
+        * testing/Internals.h:
+        * testing/Internals.idl:
+
 2017-11-05  Chris Dumez  <cdumez@apple.com>
 
         Implement ServiceWorkerRegistration.update()
index 77378e4..a66bf3f 100644 (file)
@@ -45,23 +45,27 @@ AnimationTimeline::~AnimationTimeline()
 void AnimationTimeline::addAnimation(Ref<WebAnimation>&& animation)
 {
     m_animations.add(WTFMove(animation));
+    animationTimingModelDidChange();
 }
 
 void AnimationTimeline::removeAnimation(Ref<WebAnimation>&& animation)
 {
     m_animations.remove(WTFMove(animation));
+    animationTimingModelDidChange();
 }
 
-std::optional<double> AnimationTimeline::bindingsCurrentTime() const
+std::optional<double> AnimationTimeline::bindingsCurrentTime()
 {
-    if (!m_currentTime)
+    auto time = currentTime();
+    if (!time)
         return std::nullopt;
-    return m_currentTime->value();
+    return time->value();
 }
 
 void AnimationTimeline::setCurrentTime(Seconds currentTime)
 {
     m_currentTime = currentTime;
+    animationTimingModelDidChange();
 }
 
 String AnimationTimeline::description()
index aabd8e9..0b72857 100644 (file)
@@ -43,10 +43,13 @@ public:
     bool isDocumentTimeline() const { return m_classType == DocumentTimelineClass; }
     void addAnimation(Ref<WebAnimation>&&);
     void removeAnimation(Ref<WebAnimation>&&);
-    std::optional<double> bindingsCurrentTime() const;
-    std::optional<Seconds> currentTime() const { return m_currentTime; }
+    std::optional<double> bindingsCurrentTime();
+    virtual std::optional<Seconds> currentTime() { return m_currentTime; }
     WEBCORE_EXPORT void setCurrentTime(Seconds);
     WEBCORE_EXPORT String description();
+    WEBCORE_EXPORT virtual void pause() { };
+
+    virtual void animationTimingModelDidChange() { };
 
     virtual ~AnimationTimeline();
 
@@ -57,6 +60,8 @@ protected:
 
     ClassType classType() const { return m_classType; }
 
+    HashSet<RefPtr<WebAnimation>> animations() const { return m_animations; }
+
     explicit AnimationTimeline(ClassType);
 
 private:
index 7c70a58..900f8a5 100644 (file)
 #include "config.h"
 #include "DocumentTimeline.h"
 
+#include "Chrome.h"
+#include "ChromeClient.h"
+#include "DOMWindow.h"
+#include "DisplayRefreshMonitor.h"
+#include "DisplayRefreshMonitorManager.h"
+#include "Document.h"
+#include "Page.h"
+
+static const Seconds animationInterval { 15_ms };
+
 namespace WebCore {
 
-Ref<DocumentTimeline> DocumentTimeline::create()
+Ref<DocumentTimeline> DocumentTimeline::create(Document& document, PlatformDisplayID displayID)
 {
-    return adoptRef(*new DocumentTimeline());
+    return adoptRef(*new DocumentTimeline(document, displayID));
 }
 
-DocumentTimeline::DocumentTimeline()
+DocumentTimeline::DocumentTimeline(Document& document, PlatformDisplayID displayID)
     : AnimationTimeline(DocumentTimelineClass)
+    , m_document(document)
+    , m_animationScheduleTimer(*this, &DocumentTimeline::animationScheduleTimerFired)
+#if !USE(REQUEST_ANIMATION_FRAME_DISPLAY_MONITOR)
+    , m_animationResolutionTimer(*this, &DocumentTimeline::animationResolutionTimerFired)
+#endif
+{
+    windowScreenDidChange(displayID);
+}
+
+DocumentTimeline::~DocumentTimeline()
+{
+    m_invalidationTaskQueue.close();
+}
+
+std::optional<Seconds> DocumentTimeline::currentTime()
+{
+    if (m_paused)
+        return AnimationTimeline::currentTime();
+
+    if (!m_cachedCurrentTime) {
+        m_cachedCurrentTime = Seconds(m_document->domWindow()->nowTimestamp());
+        scheduleInvalidationTaskIfNeeded();
+    }
+    return m_cachedCurrentTime;
+}
+
+void DocumentTimeline::pause()
+{
+    m_paused = true;
+}
+
+void DocumentTimeline::animationTimingModelDidChange()
+{
+    if (m_needsUpdateAnimationSchedule)
+        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()
+{
+    updateAnimationSchedule();
+    m_cachedCurrentTime = std::nullopt;
+}
+
+void DocumentTimeline::updateAnimationSchedule()
+{
+    if (!m_needsUpdateAnimationSchedule)
+        return;
+
+    m_needsUpdateAnimationSchedule = false;
+
+    Seconds now = currentTime().value();
+    Seconds scheduleDelay = Seconds::infinity();
+
+    for (const auto& animation : animations()) {
+        auto animationTimeToNextRequiredTick = animation->timeToNextRequiredTick(now);
+        if (animationTimeToNextRequiredTick < animationInterval) {
+            scheduleAnimationResolution();
+            return;
+        }
+        scheduleDelay = std::min(scheduleDelay, animationTimeToNextRequiredTick);
+    }
+
+    if (scheduleDelay < Seconds::infinity())
+        m_animationScheduleTimer.startOneShot(scheduleDelay);
+}
+
+void DocumentTimeline::animationScheduleTimerFired()
+{
+    scheduleAnimationResolution();
+}
+
+void DocumentTimeline::scheduleAnimationResolution()
+{
+#if USE(REQUEST_ANIMATION_FRAME_DISPLAY_MONITOR)
+    DisplayRefreshMonitorManager::sharedManager().scheduleAnimation(*this);
+#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.startOneShot(animationInterval);
+#endif
+}
+
+#if USE(REQUEST_ANIMATION_FRAME_DISPLAY_MONITOR)
+void DocumentTimeline::displayRefreshFired()
+#else
+void DocumentTimeline::animationResolutionTimerFired()
+#endif
 {
+    resolveAnimations();
+}
+
+void DocumentTimeline::resolveAnimations()
+{
+    // Time has advanced, the timing model requires invalidation now.
+    animationTimingModelDidChange();
+}
+
+void DocumentTimeline::windowScreenDidChange(PlatformDisplayID displayID)
+{
+#if USE(REQUEST_ANIMATION_FRAME_DISPLAY_MONITOR)
+    DisplayRefreshMonitorManager::sharedManager().windowScreenDidChange(displayID, *this);
+#else
+    UNUSED_PARAM(displayID);
+#endif
+}
+
+#if USE(REQUEST_ANIMATION_FRAME_DISPLAY_MONITOR)
+RefPtr<DisplayRefreshMonitor> DocumentTimeline::createDisplayRefreshMonitor(PlatformDisplayID displayID) const
+{
+    if (!m_document->page())
+        return nullptr;
+
+    if (auto monitor = m_document->page()->chrome().client().createDisplayRefreshMonitor(displayID))
+        return monitor;
+
+    return DisplayRefreshMonitor::createDefaultDisplayRefreshMonitor(displayID);
 }
+#endif
 
 } // namespace WebCore
index e0d9d2e..3866010 100644 (file)
 #pragma once
 
 #include "AnimationTimeline.h"
+#include "GenericTaskQueue.h"
+#include "PlatformScreen.h"
+#include "Timer.h"
 #include <wtf/Ref.h>
 
+#if USE(REQUEST_ANIMATION_FRAME_DISPLAY_MONITOR)
+#include "DisplayRefreshMonitorClient.h"
+#endif
+
 namespace WebCore {
 
-class DocumentTimeline final : public AnimationTimeline {
+class DocumentTimeline final : public AnimationTimeline
+#if USE(REQUEST_ANIMATION_FRAME_DISPLAY_MONITOR)
+    , public DisplayRefreshMonitorClient
+#endif
+{
 public:
-    static Ref<DocumentTimeline> create();
-    ~DocumentTimeline() { }
+    static Ref<DocumentTimeline> create(Document&, PlatformDisplayID);
+    ~DocumentTimeline();
+
+    std::optional<Seconds> currentTime() override;
+    void pause() override;
+
+    void animationTimingModelDidChange() override;
+    void windowScreenDidChange(PlatformDisplayID);
 
 private:
-    DocumentTimeline();
+    DocumentTimeline(Document&, PlatformDisplayID);
+
+    void scheduleInvalidationTaskIfNeeded();
+    void performInvalidationTask();
+    void updateAnimationSchedule();
+    void animationScheduleTimerFired();
+    void scheduleAnimationResolution();
+    void resolveAnimations();
+
+    Ref<Document> m_document;
+    bool m_paused { false };
+    std::optional<Seconds> m_cachedCurrentTime;
+    GenericTaskQueue<Timer> m_invalidationTaskQueue;
+    bool m_needsUpdateAnimationSchedule { false };
+    Timer m_animationScheduleTimer;
 
+#if USE(REQUEST_ANIMATION_FRAME_DISPLAY_MONITOR)
+    // Override for DisplayRefreshMonitorClient
+    void displayRefreshFired() override;
+    RefPtr<DisplayRefreshMonitor> createDisplayRefreshMonitor(PlatformDisplayID) const override;
+#else
+    void animationResolutionTimerFired();
+    Timer m_animationResolutionTimer;
+#endif
 };
 
 } // namespace WebCore
index 6f5c8ea..8f2daa8 100644 (file)
@@ -42,7 +42,7 @@ Ref<WebAnimation> WebAnimation::create(Document& document, AnimationEffect* effe
     // FIXME: the spec mandates distinguishing between an omitted timeline parameter
     // and an explicit null or undefined value (webkit.org/b/179065).
     result->setTimeline(timeline ? timeline : &document.timeline());
-    
+
     return result;
 }
 
@@ -107,6 +107,9 @@ void WebAnimation::setStartTime(std::optional<Seconds> startTime)
         return;
 
     m_startTime = startTime;
+    
+    if (m_timeline)
+        m_timeline->animationTimingModelDidChange();
 }
 
 std::optional<double> WebAnimation::bindingsCurrentTime() const
@@ -172,6 +175,29 @@ void WebAnimation::setPlaybackRate(double newPlaybackRate)
         setCurrentTime(previousTime);
 }
 
+Seconds WebAnimation::timeToNextRequiredTick(Seconds timelineTime) const
+{
+    if (!m_timeline || !m_startTime || !m_effect || !m_playbackRate)
+        return Seconds::infinity();
+
+    auto startTime = m_startTime.value();
+    auto endTime = startTime + (m_effect->timing()->duration() / m_playbackRate);
+
+    // If we haven't started yet, return the interval until our active start time.
+    auto activeStartTime = std::min(startTime, endTime);
+    if (timelineTime <= activeStartTime)
+        return activeStartTime - timelineTime;
+
+    // If we're in the middle of our active duration, we want to be called as soon as possible.
+    auto activeEndTime = std::max(startTime, endTime);
+    if (timelineTime <= activeEndTime)
+        return 0_ms;
+
+    // If none of the previous cases match, then we're already past our active duration
+    // and do not need scheduling.
+    return Seconds::infinity();
+}
+
 String WebAnimation::description()
 {
     return "Animation";
index 647746a..b4b5246 100644 (file)
@@ -62,6 +62,8 @@ public:
     double playbackRate() const { return m_playbackRate; }
     void setPlaybackRate(double);
 
+    Seconds timeToNextRequiredTick(Seconds) const;
+
     String description();
 
 private:
index f56393c..a55bd9b 100644 (file)
@@ -5815,6 +5815,9 @@ void Document::windowScreenDidChange(PlatformDisplayID displayID)
     if (m_scriptedAnimationController)
         m_scriptedAnimationController->windowScreenDidChange(displayID);
 
+    if (m_timeline)
+        m_timeline->windowScreenDidChange(displayID);
+
     if (RenderView* view = renderView()) {
         if (view->usesCompositing())
             view->compositor().windowScreenDidChange(displayID);
@@ -7451,7 +7454,8 @@ void Document::setConsoleMessageListener(RefPtr<StringCallback>&& listener)
 DocumentTimeline& Document::timeline()
 {
     if (!m_timeline)
-        m_timeline = DocumentTimeline::create();
+        m_timeline = DocumentTimeline::create(*this, page() ? page()->chrome().displayID() : 0);
+
     return *m_timeline;
 }
 
index 6d1cf58..7267235 100644 (file)
@@ -4284,6 +4284,11 @@ String Internals::timelineDescription(AnimationTimeline& timeline)
     return timeline.description();
 }
 
+void Internals::pauseTimeline(AnimationTimeline& timeline)
+{
+    timeline.pause();
+}
+
 void Internals::setTimelineCurrentTime(AnimationTimeline& timeline, double currentTime)
 {
     timeline.setCurrentTime(Seconds(currentTime));
index c8ac91a..9230801 100644 (file)
@@ -628,6 +628,7 @@ public:
 #endif
 
     String timelineDescription(AnimationTimeline&);
+    void pauseTimeline(AnimationTimeline&);
     void setTimelineCurrentTime(AnimationTimeline&, double);
 
 private:
index f196a7d..a5f4b0a 100644 (file)
@@ -567,6 +567,7 @@ enum EventThrottlingBehavior {
     boolean hasServiceWorkerRegisteredForOrigin(DOMString origin);
 
     [EnabledAtRuntime=WebAnimations] DOMString timelineDescription(AnimationTimeline timeline);
+    [EnabledAtRuntime=WebAnimations] void pauseTimeline(AnimationTimeline timeline);
     [EnabledAtRuntime=WebAnimations] void setTimelineCurrentTime(AnimationTimeline timeline, double currentTime);
     [Conditional=APPLE_PAY] readonly attribute MockPaymentCoordinator mockPaymentCoordinator;
 };