Title: [238128] trunk
Revision
238128
Author
grao...@webkit.org
Date
2018-11-13 02:22:56 -0800 (Tue, 13 Nov 2018)

Log Message

[Web Animations] Don't schedule animation frames or update style while an accelerated animation is running
https://bugs.webkit.org/show_bug.cgi?id=191542
<rdar://problem/45356027>

Reviewed by Simon Fraser.

Source/WebCore:

Test: animations/no-style-recalc-during-accelerated-animation.html

In order to be more power-efficient, we stop scheduling calls to updateAnimationsAndSendEvents() when running only accelerated
animations. To do that, we prevent scheduling further animation resolution if we're in the process of updating animations, and
when we are done, call the new DocumentTimeline::scheduleNextTick() method that will check whether we have only accelerated
animations running, and in that case check which of those animations needs an update the soonest and starts a timer scheduled
for that time when we'll schedule animation resolution.

By default, animations compute the time until their natural completion but in the case of CSS Animations, we want to make sure
we also update animations in-flight to dispatch "animationiteration" events.

* animation/AnimationEffect.h: Make the simpleIterationProgress() public so it can be called by WebAnimation::timeToNextTick().
* animation/DocumentTimeline.cpp:
(WebCore::DocumentTimeline::DocumentTimeline): Create the m_tickScheduleTimer and set it up to call scheduleAnimationResolutionIfNeeded().
(WebCore::DocumentTimeline::suspendAnimations): If we don't already have a cached current time, cache the current time.
(WebCore::DocumentTimeline::resumeAnimations): Reset the cached current time to ensure we'll get a fresh one when updating animations next.
(WebCore::DocumentTimeline::liveCurrentTime const): Factor the code to compute the current time out of currentTime() so that we can
cache the current time in suspendAnimations() without also automatically clearing the current time.
(WebCore::DocumentTimeline::currentTime): Use liveCurrentTime() and cacheCurrentTime() since much of the code from this function has been
factored out into those. Additionally, we were failing to clear the current time if called inside an animation frame, which we now do correctly
by virtue of using cacheCurrentTime(). This fixes some flakiness.
(WebCore::DocumentTimeline::cacheCurrentTime): Factor the code to cache the current time out of currentTime().
(WebCore::DocumentTimeline::maybeClearCachedCurrentTime): No need to clear the current time if we get suspended.
(WebCore::DocumentTimeline::scheduleAnimationResolutionIfNeeded): Prevent scheduling an animation update if we're in the middle of one already,
scheduleNextTick() will be called after animations are updated to see if we should schedule an animation update instead.
(WebCore::DocumentTimeline::unscheduleAnimationResolution): Cancel the m_tickScheduleTimer if we need to unschedule animation resolution.
(WebCore::DocumentTimeline::animationResolutionTimerFired): Factor the call to applyPendingAcceleratedAnimations() out of updateAnimationsAndSendEvents()
and call scheduleNextTick().
(WebCore::DocumentTimeline::updateAnimationsAndSendEvents): Set the new m_isUpdatingAnimations member variable to true while this function is running.
(WebCore::DocumentTimeline::scheduleNextTick): Schedule an animation update immediately if we have any relevant animation that is not accelerated.
Otherwise, iterate through all animations to figure out the earliest moment at which we need to update animations.
(WebCore::DocumentTimeline::updateListOfElementsWithRunningAcceleratedAnimationsForElement): Use the new WebAnimation::isRunningAccelerated() function.
* animation/DocumentTimeline.h:
* animation/WebAnimation.cpp:
(WebCore::WebAnimation::isRunningAccelerated const): Since we end up checking if an animation is running with an accelerated effect, we introduce a new
function to get that information directly through the WebAnimation object without bothering about its effect.
(WebCore::WebAnimation::resolve): We should only call updateFinishedState() here since timingDidChange() would also notify the timeline about a potential
change in relevance, which is not necessary and which would schedule an animation frame even for animations that are accelerated.
(WebCore::WebAnimation::timeToNextTick const): Compute the time until our animation completion or, in the case of CSS animations, the next iteration.
* animation/WebAnimation.h:

LayoutTests:

Add a test that checks that we make only minimal style updates and still dispatch events while an accelerated animation is running.

* animations/no-style-recalc-during-accelerated-animation-expected.txt: Added.
* animations/no-style-recalc-during-accelerated-animation.html: Added.
* fast/layers/no-clipping-overflow-hidden-added-after-transform-expected.html:
* fast/layers/no-clipping-overflow-hidden-added-after-transform.html: Change the colors to avoid a tiny ImageOnlyFailure.
* platform/win/TestExpectations: Mark some regressions tracked by webkit.org/b/191584.

Modified Paths

Added Paths

Diff

Modified: trunk/LayoutTests/ChangeLog (238127 => 238128)


--- trunk/LayoutTests/ChangeLog	2018-11-13 10:05:16 UTC (rev 238127)
+++ trunk/LayoutTests/ChangeLog	2018-11-13 10:22:56 UTC (rev 238128)
@@ -1,3 +1,19 @@
+2018-11-12  Antoine Quint  <grao...@apple.com>
+
+        [Web Animations] Don't schedule animation frames or update style while an accelerated animation is running
+        https://bugs.webkit.org/show_bug.cgi?id=191542
+        <rdar://problem/45356027>
+
+        Reviewed by Simon Fraser.
+
+        Add a test that checks that we make only minimal style updates and still dispatch events while an accelerated animation is running.
+
+        * animations/no-style-recalc-during-accelerated-animation-expected.txt: Added.
+        * animations/no-style-recalc-during-accelerated-animation.html: Added.
+        * fast/layers/no-clipping-overflow-hidden-added-after-transform-expected.html:
+        * fast/layers/no-clipping-overflow-hidden-added-after-transform.html: Change the colors to avoid a tiny ImageOnlyFailure.
+        * platform/win/TestExpectations: Mark some regressions tracked by webkit.org/b/191584.
+
 2018-11-12  Darshan Kadu  <darsh7...@gmail.com>
 
         Implement Cache API support for WPE/GTK

Added: trunk/LayoutTests/animations/no-style-recalc-during-accelerated-animation-expected.txt (0 => 238128)


--- trunk/LayoutTests/animations/no-style-recalc-during-accelerated-animation-expected.txt	                        (rev 0)
+++ trunk/LayoutTests/animations/no-style-recalc-during-accelerated-animation-expected.txt	2018-11-13 10:22:56 UTC (rev 238128)
@@ -0,0 +1,2 @@
+Got iteration event.
+PASS: saw two or fewer style recalcs during the animation.

Added: trunk/LayoutTests/animations/no-style-recalc-during-accelerated-animation.html (0 => 238128)


--- trunk/LayoutTests/animations/no-style-recalc-during-accelerated-animation.html	                        (rev 0)
+++ trunk/LayoutTests/animations/no-style-recalc-during-accelerated-animation.html	2018-11-13 10:22:56 UTC (rev 238128)
@@ -0,0 +1,54 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <style>
+        #box {
+            width: 100px;
+            height: 100px;
+            background-color: blue;
+        }
+
+        .animating {
+            animation: move 150ms linear 2;
+        }
+
+        @keyframes move {
+            from { transform: translateX(0); }
+            to   { transform: translateX(500px); }
+        }
+    </style>
+    <script>
+
+        if (window.testRunner) {
+            testRunner.dumpAsText();
+            testRunner.waitUntilDone();
+        }
+
+        window.addEventListener("load", () => {
+            if (!window.internals || !window.testRunner)
+                return;
+
+            const box = document.getElementById("box");
+            const result = document.getElementById("result");
+
+            box.addEventListener("animationstart", () => internals.startTrackingStyleRecalcs());
+            box.addEventListener("animationiteration", () => result.innerText = "Got iteration event.\n");
+            box.addEventListener("animationend", () => {
+                const numRecalcs = internals.styleRecalcCount();
+                if (numRecalcs > 2)
+                    result.innerText += "FAIL: saw " + numRecalcs + " style recalcs during the animation, should only see two."
+                else 
+                    result.innerText += "PASS: saw two or fewer style recalcs during the animation."
+
+                if (window.testRunner)
+                    testRunner.notifyDone();
+            });
+            box.classList.add("animating");
+        });
+    </script>
+</head>
+<body>
+<div id="box"></div>
+<p id="result"></p>
+</body>
+</html>

Modified: trunk/LayoutTests/fast/layers/no-clipping-overflow-hidden-added-after-transform-expected.html (238127 => 238128)


--- trunk/LayoutTests/fast/layers/no-clipping-overflow-hidden-added-after-transform-expected.html	2018-11-13 10:05:16 UTC (rev 238127)
+++ trunk/LayoutTests/fast/layers/no-clipping-overflow-hidden-added-after-transform-expected.html	2018-11-13 10:22:56 UTC (rev 238128)
@@ -9,12 +9,12 @@
 
 #overflowHidden {
     overflow: hidden;
-    background: purple;
+    background: white;
 }
 
 #transformed {
     -webkit-transform: rotate(45deg) translate3d(0, 0, 0);
-    background: green;
+    background: black;
 }
 </style>
 </head>

Modified: trunk/LayoutTests/fast/layers/no-clipping-overflow-hidden-added-after-transform.html (238127 => 238128)


--- trunk/LayoutTests/fast/layers/no-clipping-overflow-hidden-added-after-transform.html	2018-11-13 10:05:16 UTC (rev 238127)
+++ trunk/LayoutTests/fast/layers/no-clipping-overflow-hidden-added-after-transform.html	2018-11-13 10:22:56 UTC (rev 238128)
@@ -9,13 +9,13 @@
 
 #overflowHidden {
     overflow: hidden;
-    background: purple;
+    background: white;
 }
 
 #transformed {
     -webkit-transform: rotate(0deg) translate3d(0, 0, 0);
     -webkit-transition: -webkit-transform linear 1ms;
-    background: green;
+    background: black;
 }
 
 #transformed:hover {
@@ -32,7 +32,7 @@
 <script>
     function transitionFinished() {
         if (window.testRunner)
-            requestAnimationFrame(() => window.testRunner.notifyDone());
+            window.testRunner.notifyDone();
     }
 
     if (!window.eventSender)

Modified: trunk/LayoutTests/platform/win/TestExpectations (238127 => 238128)


--- trunk/LayoutTests/platform/win/TestExpectations	2018-11-13 10:05:16 UTC (rev 238127)
+++ trunk/LayoutTests/platform/win/TestExpectations	2018-11-13 10:22:56 UTC (rev 238128)
@@ -4251,3 +4251,14 @@
 webkit.org/b/191368 fast/text/stroking-decorations.html [ Crash ]
 webkit.org/b/191368 imported/blink/fast/text/international/complex-text-trailing-space.html [ Crash ]
 webkit.org/b/191368 imported/blink/fast/text/sub-pixel/complex-text-preferred-width.html [ Crash ]
+
+webkit.org/b/191584 animations/animation-direction-normal.html [ Failure ]
+webkit.org/b/191584 animations/animation-direction-reverse.html [ Failure ]
+webkit.org/b/191584 animations/dynamic-stylesheet-loading.html [ Failure ]
+webkit.org/b/191584 animations/play-state-paused.html [ Failure ]
+webkit.org/b/191584 animations/transform-non-accelerated.html [ Failure ]
+webkit.org/b/191584 transitions/start-transform-transition.html [ Failure ]
+webkit.org/b/191584 http/wpt/css/css-animations/set-animation-play-state-to-paused-001.html [ ImageOnlyFailure ]
+webkit.org/b/191584 webanimations/accelerated-animation-with-delay.html [ ImageOnlyFailure ]
+webkit.org/b/191584 webanimations/accelerated-transition-by-removing-property.html [ ImageOnlyFailure ]
+webkit.org/b/191584 fast/animation/css-animation-resuming-when-visible-with-style-change.html [ Timeout ]

Modified: trunk/Source/WebCore/ChangeLog (238127 => 238128)


--- trunk/Source/WebCore/ChangeLog	2018-11-13 10:05:16 UTC (rev 238127)
+++ trunk/Source/WebCore/ChangeLog	2018-11-13 10:22:56 UTC (rev 238128)
@@ -1,3 +1,52 @@
+2018-11-12  Antoine Quint  <grao...@apple.com>
+
+        [Web Animations] Don't schedule animation frames or update style while an accelerated animation is running
+        https://bugs.webkit.org/show_bug.cgi?id=191542
+        <rdar://problem/45356027>
+
+        Reviewed by Simon Fraser.
+
+        Test: animations/no-style-recalc-during-accelerated-animation.html
+
+        In order to be more power-efficient, we stop scheduling calls to updateAnimationsAndSendEvents() when running only accelerated
+        animations. To do that, we prevent scheduling further animation resolution if we're in the process of updating animations, and
+        when we are done, call the new DocumentTimeline::scheduleNextTick() method that will check whether we have only accelerated
+        animations running, and in that case check which of those animations needs an update the soonest and starts a timer scheduled
+        for that time when we'll schedule animation resolution.
+
+        By default, animations compute the time until their natural completion but in the case of CSS Animations, we want to make sure
+        we also update animations in-flight to dispatch "animationiteration" events.
+
+        * animation/AnimationEffect.h: Make the simpleIterationProgress() public so it can be called by WebAnimation::timeToNextTick().
+        * animation/DocumentTimeline.cpp:
+        (WebCore::DocumentTimeline::DocumentTimeline): Create the m_tickScheduleTimer and set it up to call scheduleAnimationResolutionIfNeeded().
+        (WebCore::DocumentTimeline::suspendAnimations): If we don't already have a cached current time, cache the current time.
+        (WebCore::DocumentTimeline::resumeAnimations): Reset the cached current time to ensure we'll get a fresh one when updating animations next.
+        (WebCore::DocumentTimeline::liveCurrentTime const): Factor the code to compute the current time out of currentTime() so that we can
+        cache the current time in suspendAnimations() without also automatically clearing the current time.
+        (WebCore::DocumentTimeline::currentTime): Use liveCurrentTime() and cacheCurrentTime() since much of the code from this function has been
+        factored out into those. Additionally, we were failing to clear the current time if called inside an animation frame, which we now do correctly
+        by virtue of using cacheCurrentTime(). This fixes some flakiness.
+        (WebCore::DocumentTimeline::cacheCurrentTime): Factor the code to cache the current time out of currentTime(). 
+        (WebCore::DocumentTimeline::maybeClearCachedCurrentTime): No need to clear the current time if we get suspended.
+        (WebCore::DocumentTimeline::scheduleAnimationResolutionIfNeeded): Prevent scheduling an animation update if we're in the middle of one already,
+        scheduleNextTick() will be called after animations are updated to see if we should schedule an animation update instead.
+        (WebCore::DocumentTimeline::unscheduleAnimationResolution): Cancel the m_tickScheduleTimer if we need to unschedule animation resolution.
+        (WebCore::DocumentTimeline::animationResolutionTimerFired): Factor the call to applyPendingAcceleratedAnimations() out of updateAnimationsAndSendEvents()
+        and call scheduleNextTick().
+        (WebCore::DocumentTimeline::updateAnimationsAndSendEvents): Set the new m_isUpdatingAnimations member variable to true while this function is running.
+        (WebCore::DocumentTimeline::scheduleNextTick): Schedule an animation update immediately if we have any relevant animation that is not accelerated.
+        Otherwise, iterate through all animations to figure out the earliest moment at which we need to update animations.
+        (WebCore::DocumentTimeline::updateListOfElementsWithRunningAcceleratedAnimationsForElement): Use the new WebAnimation::isRunningAccelerated() function.
+        * animation/DocumentTimeline.h:
+        * animation/WebAnimation.cpp:
+        (WebCore::WebAnimation::isRunningAccelerated const): Since we end up checking if an animation is running with an accelerated effect, we introduce a new
+        function to get that information directly through the WebAnimation object without bothering about its effect.
+        (WebCore::WebAnimation::resolve): We should only call updateFinishedState() here since timingDidChange() would also notify the timeline about a potential
+        change in relevance, which is not necessary and which would schedule an animation frame even for animations that are accelerated.
+        (WebCore::WebAnimation::timeToNextTick const): Compute the time until our animation completion or, in the case of CSS animations, the next iteration.
+        * animation/WebAnimation.h:
+
 2018-11-13  Miguel Gomez  <mago...@igalia.com>
 
         [GTK][WPE] Incorrect tile coverage when resizing a layer out of the visible area

Modified: trunk/Source/WebCore/animation/AnimationEffect.h (238127 => 238128)


--- trunk/Source/WebCore/animation/AnimationEffect.h	2018-11-13 10:05:16 UTC (rev 238127)
+++ trunk/Source/WebCore/animation/AnimationEffect.h	2018-11-13 10:22:56 UTC (rev 238128)
@@ -90,6 +90,7 @@
     std::optional<Seconds> localTime() const;
     std::optional<Seconds> activeTime() const;
     Seconds endTime() const;
+    std::optional<double> simpleIterationProgress() const;
     std::optional<double> iterationProgress() const;
     std::optional<double> currentIteration() const;
     Seconds activeDuration() const;
@@ -104,7 +105,6 @@
     enum class ComputedDirection { Forwards, Reverse };
 
     std::optional<double> overallProgress() const;
-    std::optional<double> simpleIterationProgress() const;
     AnimationEffect::ComputedDirection currentDirection() const;
     std::optional<double> directedProgress() const;
     std::optional<double> transformedProgress() const;

Modified: trunk/Source/WebCore/animation/DocumentTimeline.cpp (238127 => 238128)


--- trunk/Source/WebCore/animation/DocumentTimeline.cpp	2018-11-13 10:05:16 UTC (rev 238127)
+++ trunk/Source/WebCore/animation/DocumentTimeline.cpp	2018-11-13 10:22:56 UTC (rev 238128)
@@ -63,6 +63,7 @@
     : AnimationTimeline(DocumentTimelineClass)
     , m_document(&document)
     , m_originTime(originTime)
+    , m_tickScheduleTimer(*this, &DocumentTimeline::scheduleAnimationResolutionIfNeeded)
 #if !USE(REQUEST_ANIMATION_FRAME_DISPLAY_MONITOR)
     , m_animationResolutionTimer(*this, &DocumentTimeline::animationResolutionTimerFired)
 #endif
@@ -208,6 +209,9 @@
     if (animationsAreSuspended())
         return;
 
+    if (!m_cachedCurrentTime)
+        m_cachedCurrentTime = liveCurrentTime();
+
     for (const auto& animation : m_animations)
         animation->setSuspended(true);
 
@@ -223,6 +227,8 @@
     if (!animationsAreSuspended())
         return;
 
+    m_cachedCurrentTime = std::nullopt;
+
     m_isSuspended = false;
 
     for (const auto& animation : m_animations)
@@ -246,6 +252,15 @@
     return count;
 }
 
+Seconds DocumentTimeline::liveCurrentTime() const
+{
+#if USE(REQUEST_ANIMATION_FRAME_DISPLAY_MONITOR)
+    return m_document->animationScheduler().lastTimestamp();
+#else
+    return Seconds(m_document->domWindow()->nowTimestamp());
+#endif
+}
+
 std::optional<Seconds> DocumentTimeline::currentTime()
 {
     if (!m_document || !m_document->domWindow())
@@ -259,12 +274,14 @@
         }
     }
 
+    auto currentTime = liveCurrentTime();
+
 #if USE(REQUEST_ANIMATION_FRAME_DISPLAY_MONITOR)
     // If we're in the middle of firing a frame, either due to a requestAnimationFrame callback
     // or scheduling an animation update, we want to ensure we use the same time we're using as
     // the timestamp for requestAnimationFrame() callbacks.
     if (m_document->animationScheduler().isFiring())
-        m_cachedCurrentTime = m_document->animationScheduler().lastTimestamp();
+        cacheCurrentTime(currentTime);
 #endif
 
     if (!m_cachedCurrentTime) {
@@ -273,27 +290,32 @@
         // be since the last time a frame fired by increment of our update interval. This way code using something
         // like setTimeout() or handling events will get a time that's only updating at around 60fps, or less if
         // we're throttled.
-        auto lastAnimationSchedulerTimestamp = m_document->animationScheduler().lastTimestamp();
+        auto lastAnimationSchedulerTimestamp = currentTime;
         auto delta = Seconds(m_document->domWindow()->nowTimestamp()) - lastAnimationSchedulerTimestamp;
         int frames = std::floor(delta.seconds() / animationInterval().seconds());
-        m_cachedCurrentTime = lastAnimationSchedulerTimestamp + Seconds(frames * animationInterval().seconds());
+        cacheCurrentTime(lastAnimationSchedulerTimestamp + Seconds(frames * animationInterval().seconds()));
 #else
-        m_cachedCurrentTime = Seconds(m_document->domWindow()->nowTimestamp());
+        cacheCurrentTime(currentTime);
 #endif
-        // 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.
-        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();
-        });
     }
     return m_cachedCurrentTime.value() - m_originTime;
 }
 
+void DocumentTimeline::cacheCurrentTime(Seconds newCurrentTime)
+{
+    m_cachedCurrentTime = newCurrentTime;
+    // 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.
+    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();
+    });
+}
+
 void DocumentTimeline::maybeClearCachedCurrentTime()
 {
     // We want to make sure we only clear the cached current time if we're not currently running
@@ -300,13 +322,13 @@
     // 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_currentTimeClearingTaskQueue.hasPendingTasks())
+    if (!m_isSuspended && !m_waitingOnVMIdle && !m_currentTimeClearingTaskQueue.hasPendingTasks())
         m_cachedCurrentTime = std::nullopt;
 }
 
 void DocumentTimeline::scheduleAnimationResolutionIfNeeded()
 {
-    if (!m_isSuspended && !m_animations.isEmpty())
+    if (!m_isUpdatingAnimations && !m_isSuspended && !m_animations.isEmpty())
         scheduleAnimationResolution();
 }
 
@@ -337,6 +359,7 @@
 
 void DocumentTimeline::unscheduleAnimationResolution()
 {
+    m_tickScheduleTimer.stop();
 #if USE(REQUEST_ANIMATION_FRAME_DISPLAY_MONITOR)
     m_document->animationScheduler().unscheduleWebAnimationsResolution();
 #else
@@ -353,6 +376,8 @@
 #endif
 {
     updateAnimationsAndSendEvents();
+    applyPendingAcceleratedAnimations();
+    scheduleNextTick();
 }
 
 void DocumentTimeline::updateAnimationsAndSendEvents()
@@ -359,6 +384,8 @@
 {
     m_numberOfAnimationTimelineInvalidationsForTesting++;
 
+    m_isUpdatingAnimations = true;
+
     // https://drafts.csswg.org/web-animations/#update-animations-and-send-events
 
     // 1. Update the current time of all timelines associated with doc passing now as the timestamp.
@@ -422,7 +449,7 @@
     for (auto& completedTransition : completedTransitions)
         transitionDidComplete(completedTransition);
 
-    applyPendingAcceleratedAnimations();
+    m_isUpdatingAnimations = false;
 }
 
 void DocumentTimeline::transitionDidComplete(RefPtr<CSSTransition> transition)
@@ -438,6 +465,34 @@
     }
 }
 
+void DocumentTimeline::scheduleNextTick()
+{
+    // There is no tick to schedule if we don't have any relevant animations.
+    if (m_animations.isEmpty())
+        return;
+
+    for (const auto& animation : m_animations) {
+        if (!animation->isRunningAccelerated()) {
+            scheduleAnimationResolutionIfNeeded();
+            return;
+        }
+    }
+
+    Seconds scheduleDelay = Seconds::infinity();
+
+    for (const auto& animation : m_animations) {
+        auto animationTimeToNextRequiredTick = animation->timeToNextTick();
+        if (animationTimeToNextRequiredTick < animationInterval()) {
+            scheduleAnimationResolutionIfNeeded();
+            return;
+        }
+        scheduleDelay = std::min(scheduleDelay, animationTimeToNextRequiredTick);
+    }
+
+    if (scheduleDelay < Seconds::infinity())
+        m_tickScheduleTimer.startOneShot(scheduleDelay);
+}
+
 bool DocumentTimeline::computeExtentOfAnimation(RenderElement& renderer, LayoutRect& bounds) const
 {
     if (!renderer.element())
@@ -540,7 +595,7 @@
     auto animations = animationsForElement(element);
     bool runningAnimationsForElementAreAllAccelerated = !animations.isEmpty();
     for (const auto& animation : animations) {
-        if (is<KeyframeEffect>(animation->effect()) && !downcast<KeyframeEffect>(animation->effect())->isRunningAccelerated()) {
+        if (!animation->isRunningAccelerated()) {
             runningAnimationsForElementAreAllAccelerated = false;
             break;
         }

Modified: trunk/Source/WebCore/animation/DocumentTimeline.h (238127 => 238128)


--- trunk/Source/WebCore/animation/DocumentTimeline.h	2018-11-13 10:05:16 UTC (rev 238127)
+++ trunk/Source/WebCore/animation/DocumentTimeline.h	2018-11-13 10:22:56 UTC (rev 238128)
@@ -85,6 +85,8 @@
 private:
     DocumentTimeline(Document&, Seconds);
 
+    Seconds liveCurrentTime() const;
+    void cacheCurrentTime(Seconds);
     void scheduleAnimationResolutionIfNeeded();
     void scheduleInvalidationTaskIfNeeded();
     void performInvalidationTask();
@@ -96,11 +98,13 @@
     void maybeClearCachedCurrentTime();
     void updateListOfElementsWithRunningAcceleratedAnimationsForElement(Element&);
     void transitionDidComplete(RefPtr<CSSTransition>);
+    void scheduleNextTick();
 
     RefPtr<Document> m_document;
     Seconds m_originTime;
     bool m_isSuspended { false };
     bool m_waitingOnVMIdle { false };
+    bool m_isUpdatingAnimations { false };
     std::optional<Seconds> m_cachedCurrentTime;
     GenericTaskQueue<Timer> m_currentTimeClearingTaskQueue;
     HashSet<RefPtr<WebAnimation>> m_acceleratedAnimationsPendingRunningStateChange;
@@ -107,6 +111,7 @@
     Vector<Ref<AnimationPlaybackEvent>> m_pendingAnimationEvents;
     unsigned m_numberOfAnimationTimelineInvalidationsForTesting { 0 };
     HashSet<Element*> m_elementsWithRunningAcceleratedAnimations;
+    Timer m_tickScheduleTimer;
 
 #if !USE(REQUEST_ANIMATION_FRAME_DISPLAY_MONITOR)
     void animationResolutionTimerFired();

Modified: trunk/Source/WebCore/animation/WebAnimation.cpp (238127 => 238128)


--- trunk/Source/WebCore/animation/WebAnimation.cpp	2018-11-13 10:05:16 UTC (rev 238127)
+++ trunk/Source/WebCore/animation/WebAnimation.cpp	2018-11-13 10:22:56 UTC (rev 238128)
@@ -1094,6 +1094,11 @@
     invalidateEffect();
 }
 
+bool WebAnimation::isRunningAccelerated() const
+{
+    return is<KeyframeEffect>(m_effect) && downcast<KeyframeEffect>(*m_effect).isRunningAccelerated();
+}
+
 bool WebAnimation::needsTick() const
 {
     return pending() || playState() == PlayState::Running;
@@ -1114,7 +1119,7 @@
 
 void WebAnimation::resolve(RenderStyle& targetStyle)
 {
-    timingDidChange(DidSeek::No, SynchronouslyNotify::Yes);
+    updateFinishedState(DidSeek::No, SynchronouslyNotify::Yes);
     if (m_effect)
         m_effect->apply(targetStyle);
 }
@@ -1194,4 +1199,40 @@
     return phase == AnimationEffect::Phase::Before || (phase == AnimationEffect::Phase::Active && playState() != PlayState::Finished);
 }
 
+Seconds WebAnimation::timeToNextTick() const
+{
+    ASSERT(isRunningAccelerated());
+
+    if (pending())
+        return 0_s;
+
+    // If we're not running, there's no telling when we'll end.
+    if (playState() != PlayState::Running)
+        return Seconds::infinity();
+
+    // CSS Animations dispatch events for each iteration, so compute the time until
+    // the end of this iteration. Any other animation only cares about remaning total time.
+    if (isCSSAnimation()) {
+        // If we're actively running, we need the time until the next iteration.
+        if (auto iterationProgress = effect()->simpleIterationProgress())
+            return effect()->iterationDuration() * (1 - iterationProgress.value());
+
+        // Otherwise we're probably in the before phase waiting to reach our start time.
+        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;
+            if (localTime < effect()->delay())
+                return effect()->delay() - localTime;
+        }
+    } else if (auto animationCurrentTime = currentTime())
+        return effect()->endTime() - animationCurrentTime.value();
+
+    ASSERT_NOT_REACHED();
+    return Seconds::infinity();
+}
+
 } // namespace WebCore

Modified: trunk/Source/WebCore/animation/WebAnimation.h (238127 => 238128)


--- trunk/Source/WebCore/animation/WebAnimation.h	2018-11-13 10:05:16 UTC (rev 238127)
+++ trunk/Source/WebCore/animation/WebAnimation.h	2018-11-13 10:22:56 UTC (rev 238128)
@@ -104,11 +104,13 @@
 
     virtual bool needsTick() const;
     virtual void tick();
+    Seconds timeToNextTick() const;
     virtual void resolve(RenderStyle&);
     void effectTargetDidChange(Element* previousTarget, Element* newTarget);
     void acceleratedStateDidChange();
     void applyPendingAcceleratedActions();
 
+    bool isRunningAccelerated() const;
     bool isRelevant() const { return m_isRelevant; }
     void effectTimingDidChange();
     void suspendEffectInvalidation();
_______________________________________________
webkit-changes mailing list
webkit-changes@lists.webkit.org
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to