[Web Animations] Suspend animations when required
authorgraouts@webkit.org <graouts@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Thu, 12 Apr 2018 17:37:55 +0000 (17:37 +0000)
committergraouts@webkit.org <graouts@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Thu, 12 Apr 2018 17:37:55 +0000 (17:37 +0000)
https://bugs.webkit.org/show_bug.cgi?id=184541

Reviewed by Jon Lee.

Source/WebCore:

Animations managed by CSSAnimationController get suspended under a number of scenarios, we now add the possibility
to suspend animations on a DocumentTimeline as well such that Web Animations and CSS Animations and CSS Transitions
implemented as Web Animations get suspended under the same conditions as well. We also update the implementation for
Internals::numberOfActiveAnimations() such that tests checking that animations get suspended pass.

* animation/DocumentTimeline.cpp:
(WebCore::DocumentTimeline::suspendAnimations): When asked to be suspended, the DocumentTimeline cancels pending
invalidation tasks and updates all of the animations it manages, including those running on the compositor.
(WebCore::DocumentTimeline::resumeAnimations): When asked to be resumed, the DocumentTimeline resumes animations
it manages and rewinds its invalidation timer.
(WebCore::DocumentTimeline::animationsAreSuspended):
(WebCore::DocumentTimeline::numberOfActiveAnimationsForTesting const): Called by Internals::numberOfActiveAnimations(),
this returns the number of animations managed by this timeline that are not suspended.
(WebCore::DocumentTimeline::currentTime):
(WebCore::DocumentTimeline::timingModelDidChange): Ensure the invalidation timer is not rewound if the timeline
is suspended.
* animation/DocumentTimeline.h:
* animation/WebAnimation.cpp:
(WebCore::WebAnimation::setTimeline): When moving to a new timeline, ensure we match the new timeline's animation state.
(WebCore::WebAnimation::setSuspended): Toggle the accelerated running state of any backing hardware animations when
the suspension state of an animation changes.
* animation/WebAnimation.h:
(WebCore::WebAnimation::isSuspended const):
* dom/Document.cpp:
(WebCore::Document::didBecomeCurrentDocumentInFrame):
(WebCore::Document::resume):
* dom/Document.h:
* history/CachedFrame.cpp:
(WebCore::CachedFrameBase::restore):
* page/Frame.cpp:
(WebCore::Frame::clearTimers):
* page/Page.cpp:
(WebCore::Page::setIsVisibleInternal):
(WebCore::Page::hiddenPageCSSAnimationSuspensionStateChanged):
* testing/Internals.cpp:
(WebCore::Internals::numberOfActiveAnimations const):
(WebCore::Internals::animationsAreSuspended const):
(WebCore::Internals::suspendAnimations const):
(WebCore::Internals::resumeAnimations const):

LayoutTests:

Mark more tests as passing when the CSS Animations and CSS Transitions as Web Animations flag is on.

* animations/animation-controller-drt-api.html:
* animations/animation-followed-by-transition.html:
* fast/animation/css-animation-resuming-when-visible-with-style-change.html:
* fast/animation/css-animation-resuming-when-visible.html:

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

18 files changed:
LayoutTests/ChangeLog
LayoutTests/animations/added-while-suspended.html
LayoutTests/animations/animation-controller-drt-api.html
LayoutTests/animations/animation-followed-by-transition.html
LayoutTests/fast/animation/css-animation-resuming-when-visible-with-style-change.html
LayoutTests/fast/animation/css-animation-resuming-when-visible.html
LayoutTests/transitions/created-while-suspended.html
Source/WebCore/ChangeLog
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/dom/Document.h
Source/WebCore/history/CachedFrame.cpp
Source/WebCore/page/Frame.cpp
Source/WebCore/page/Page.cpp
Source/WebCore/testing/Internals.cpp

index ac14573..4d57a91 100644 (file)
@@ -1,5 +1,19 @@
 2018-04-12  Antoine Quint  <graouts@apple.com>
 
+        [Web Animations] Suspend animations when required
+        https://bugs.webkit.org/show_bug.cgi?id=184541
+
+        Reviewed by Jon Lee.
+
+        Mark more tests as passing when the CSS Animations and CSS Transitions as Web Animations flag is on.
+
+        * animations/animation-controller-drt-api.html:
+        * animations/animation-followed-by-transition.html:
+        * fast/animation/css-animation-resuming-when-visible-with-style-change.html:
+        * fast/animation/css-animation-resuming-when-visible.html:
+
+2018-04-12  Antoine Quint  <graouts@apple.com>
+
         [Web Animations] Throttle animations when lowPowerMode is on
         https://bugs.webkit.org/show_bug.cgi?id=184540
 
index 3e71f51..e0d4b0e 100644 (file)
@@ -1,3 +1,4 @@
+<!DOCTYPE html>
 <title>Test that new animations do not run while we are suspended</title>
 <style>
 #box {
index 65374d4..4f6542b 100644 (file)
@@ -1,5 +1,4 @@
-<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
-   "http://www.w3.org/TR/html4/loose.dtd">
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"><!-- webkit-test-runner [ enableCSSAnimationsAndCSSTransitionsBackedByWebAnimations=true ] -->
 
 <html lang="en">
 <head>
index fe1f238..7166418 100644 (file)
@@ -1,4 +1,4 @@
-<!DOCTYPE html>
+<!DOCTYPE html><!-- webkit-test-runner [ enableCSSAnimationsAndCSSTransitionsBackedByWebAnimations=true ] -->
 <html>
 <head>
     <style>
index 330bf84..e411b73 100644 (file)
@@ -1,4 +1,4 @@
-<!DOCTYPE html>
+<!DOCTYPE html><!-- webkit-test-runner [ enableCSSAnimationsAndCSSTransitionsBackedByWebAnimations=true ] -->
 <html>
 <head>
 <style>
index cf24107..f9bd0f9 100644 (file)
@@ -1,3 +1,4 @@
+<!DOCTYPE html>
 <title>Test that newly created transitions do not run while we are suspended</title>
 <style>
 #box {
index 04079ec..de406e2 100644 (file)
@@ -1,5 +1,52 @@
 2018-04-12  Antoine Quint  <graouts@apple.com>
 
+        [Web Animations] Suspend animations when required
+        https://bugs.webkit.org/show_bug.cgi?id=184541
+
+        Reviewed by Jon Lee.
+
+        Animations managed by CSSAnimationController get suspended under a number of scenarios, we now add the possibility
+        to suspend animations on a DocumentTimeline as well such that Web Animations and CSS Animations and CSS Transitions
+        implemented as Web Animations get suspended under the same conditions as well. We also update the implementation for
+        Internals::numberOfActiveAnimations() such that tests checking that animations get suspended pass.
+
+        * animation/DocumentTimeline.cpp:
+        (WebCore::DocumentTimeline::suspendAnimations): When asked to be suspended, the DocumentTimeline cancels pending
+        invalidation tasks and updates all of the animations it manages, including those running on the compositor.
+        (WebCore::DocumentTimeline::resumeAnimations): When asked to be resumed, the DocumentTimeline resumes animations
+        it manages and rewinds its invalidation timer.
+        (WebCore::DocumentTimeline::animationsAreSuspended):
+        (WebCore::DocumentTimeline::numberOfActiveAnimationsForTesting const): Called by Internals::numberOfActiveAnimations(),
+        this returns the number of animations managed by this timeline that are not suspended.
+        (WebCore::DocumentTimeline::currentTime):
+        (WebCore::DocumentTimeline::timingModelDidChange): Ensure the invalidation timer is not rewound if the timeline
+        is suspended.
+        * animation/DocumentTimeline.h:
+        * animation/WebAnimation.cpp:
+        (WebCore::WebAnimation::setTimeline): When moving to a new timeline, ensure we match the new timeline's animation state.
+        (WebCore::WebAnimation::setSuspended): Toggle the accelerated running state of any backing hardware animations when
+        the suspension state of an animation changes.
+        * animation/WebAnimation.h:
+        (WebCore::WebAnimation::isSuspended const):
+        * dom/Document.cpp:
+        (WebCore::Document::didBecomeCurrentDocumentInFrame):
+        (WebCore::Document::resume):
+        * dom/Document.h:
+        * history/CachedFrame.cpp:
+        (WebCore::CachedFrameBase::restore):
+        * page/Frame.cpp:
+        (WebCore::Frame::clearTimers):
+        * page/Page.cpp:
+        (WebCore::Page::setIsVisibleInternal):
+        (WebCore::Page::hiddenPageCSSAnimationSuspensionStateChanged):
+        * testing/Internals.cpp:
+        (WebCore::Internals::numberOfActiveAnimations const):
+        (WebCore::Internals::animationsAreSuspended const):
+        (WebCore::Internals::suspendAnimations const):
+        (WebCore::Internals::resumeAnimations const):
+
+2018-04-12  Antoine Quint  <graouts@apple.com>
+
         [Web Animations] Throttle animations when lowPowerMode is on
         https://bugs.webkit.org/show_bug.cgi?id=184540
 
index d89489b..ea74b7f 100644 (file)
@@ -83,9 +83,55 @@ Seconds DocumentTimeline::animationInterval() const
     return m_document->page()->isLowPowerModeEnabled() ? throttledAnimationInterval : defaultAnimationInterval;
 }
 
+void DocumentTimeline::suspendAnimations()
+{
+    if (animationsAreSuspended())
+        return;
+
+    m_isSuspended = true;
+
+    m_invalidationTaskQueue.cancelAllTasks();
+    if (m_animationScheduleTimer.isActive())
+        m_animationScheduleTimer.stop();
+
+    for (const auto& animation : animations())
+        animation->setSuspended(true);
+
+    applyPendingAcceleratedAnimations();
+}
+
+void DocumentTimeline::resumeAnimations()
+{
+    if (!animationsAreSuspended())
+        return;
+
+    m_isSuspended = false;
+
+    for (const auto& animation : animations())
+        animation->setSuspended(false);
+
+    m_needsUpdateAnimationSchedule = false;
+    timingModelDidChange();
+}
+
+bool DocumentTimeline::animationsAreSuspended()
+{
+    return m_isSuspended;
+}
+
+unsigned DocumentTimeline::numberOfActiveAnimationsForTesting() const
+{
+    unsigned count = 0;
+    for (const auto& animation : animations()) {
+        if (!animation->isSuspended())
+            ++count;
+    }
+    return count;
+}
+
 std::optional<Seconds> DocumentTimeline::currentTime()
 {
-    if (m_paused || !m_document || !m_document->domWindow())
+    if (m_paused || m_isSuspended || !m_document || !m_document->domWindow())
         return AnimationTimeline::currentTime();
 
     if (!m_cachedCurrentTime) {
@@ -102,7 +148,7 @@ void DocumentTimeline::pause()
 
 void DocumentTimeline::timingModelDidChange()
 {
-    if (m_needsUpdateAnimationSchedule)
+    if (m_needsUpdateAnimationSchedule || m_isSuspended)
         return;
 
     m_needsUpdateAnimationSchedule = true;
index 953639a..1ba448b 100644 (file)
@@ -73,6 +73,10 @@ public:
 
     void updateThrottlingState();
     WEBCORE_EXPORT Seconds animationInterval() const;
+    WEBCORE_EXPORT void suspendAnimations();
+    WEBCORE_EXPORT void resumeAnimations();
+    WEBCORE_EXPORT bool animationsAreSuspended();
+    WEBCORE_EXPORT unsigned numberOfActiveAnimationsForTesting() const;
 
 private:
     DocumentTimeline(Document&, PlatformDisplayID);
@@ -87,6 +91,7 @@ private:
 
     RefPtr<Document> m_document;
     bool m_paused { false };
+    bool m_isSuspended { false };
     std::optional<Seconds> m_cachedCurrentTime;
     GenericTaskQueue<Timer> m_invalidationTaskQueue;
     GenericTaskQueue<Timer> m_eventDispatchTaskQueue;
index 5de6e94..4e3af24 100644 (file)
@@ -177,6 +177,8 @@ void WebAnimation::setTimeline(RefPtr<AnimationTimeline>&& timeline)
 
     m_timeline = WTFMove(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,
@@ -1017,6 +1019,21 @@ void WebAnimation::resolve(RenderStyle& targetStyle)
         m_effect->apply(targetStyle);
 }
 
+void WebAnimation::setSuspended(bool isSuspended)
+{
+    if (m_isSuspended == isSuspended)
+        return;
+
+    m_isSuspended = isSuspended;
+
+    if (!is<KeyframeEffectReadOnly>(m_effect))
+        return;
+
+    auto& keyframeEffect = downcast<KeyframeEffectReadOnly>(*m_effect);
+    if (keyframeEffect.isRunningAccelerated() && playState() == PlayState::Running)
+        keyframeEffect.animationPlayStateDidChange(isSuspended ? PlayState::Paused : PlayState::Running);
+}
+
 void WebAnimation::acceleratedStateDidChange()
 {
     if (is<DocumentTimeline>(m_timeline))
index 253284a..3ab9976 100644 (file)
@@ -106,6 +106,8 @@ public:
     void timingModelDidChange();
     void suspendEffectInvalidation();
     void unsuspendEffectInvalidation();
+    void setSuspended(bool);
+    bool isSuspended() const { return m_isSuspended; }
 
     String description();
 
@@ -154,6 +156,7 @@ private:
     int m_suspendCount { 0 };
     double m_playbackRate { 1 };
     bool m_isStopped { false };
+    bool m_isSuspended { false };
     bool m_finishNotificationStepsMicrotaskPending;
     bool m_scheduledMicrotask;
     UniqueRef<ReadyPromise> m_readyPromise;
index 69b6ca6..ef2628c 100644 (file)
@@ -2259,11 +2259,17 @@ void Document::didBecomeCurrentDocumentInFrame()
     // be out of sync if the DOM suspension state changed while the document was not in the frame (possibly in the
     // page cache, or simply newly created).
     if (m_frame->activeDOMObjectsAndAnimationsSuspended()) {
-        m_frame->animation().suspendAnimationsForDocument(this);
+        if (RuntimeEnabledFeatures::sharedFeatures().cssAnimationsAndCSSTransitionsBackedByWebAnimationsEnabled())
+            timeline().suspendAnimations();
+        else
+            m_frame->animation().suspendAnimationsForDocument(this);
         suspendScheduledTasks(ActiveDOMObject::PageWillBeSuspended);
     } else {
         resumeScheduledTasks(ActiveDOMObject::PageWillBeSuspended);
-        m_frame->animation().resumeAnimationsForDocument(this);
+        if (RuntimeEnabledFeatures::sharedFeatures().cssAnimationsAndCSSTransitionsBackedByWebAnimationsEnabled())
+            timeline().resumeAnimations();
+        else
+            m_frame->animation().resumeAnimationsForDocument(this);
     }
 }
 
@@ -4923,7 +4929,11 @@ void Document::resume(ActiveDOMObject::ReasonForSuspension reason)
 
     ASSERT(m_frame);
     m_frame->loader().client().dispatchDidBecomeFrameset(isFrameSet());
-    m_frame->animation().resumeAnimationsForDocument(this);
+
+    if (RuntimeEnabledFeatures::sharedFeatures().cssAnimationsAndCSSTransitionsBackedByWebAnimationsEnabled())
+        timeline().resumeAnimations();
+    else
+        m_frame->animation().resumeAnimationsForDocument(this);
 
     resumeScheduledTasks(reason);
 
index bd72e58..a5ad510 100644 (file)
@@ -1392,7 +1392,7 @@ public:
 
     WEBCORE_EXPORT void setConsoleMessageListener(RefPtr<StringCallback>&&); // For testing.
 
-    DocumentTimeline& timeline();
+    WEBCORE_EXPORT DocumentTimeline& timeline();
     DocumentTimeline* existingTimeline() const { return m_timeline.get(); }
     Vector<RefPtr<WebAnimation>> getAnimations();
         
index e6fe563..5b20dde 100644 (file)
@@ -32,6 +32,7 @@
 #include "DOMWindow.h"
 #include "Document.h"
 #include "DocumentLoader.h"
+#include "DocumentTimeline.h"
 #include "Frame.h"
 #include "FrameLoader.h"
 #include "FrameLoaderClient.h"
@@ -39,6 +40,7 @@
 #include "Logging.h"
 #include "Page.h"
 #include "PageCache.h"
+#include "RuntimeEnabledFeatures.h"
 #include "SVGDocumentExtensions.h"
 #include "ScriptController.h"
 #include "SerializedScriptValue.h"
@@ -96,7 +98,10 @@ void CachedFrameBase::restore()
     if (m_document->svgExtensions())
         m_document->accessSVGExtensions().unpauseAnimations();
 
-    frame.animation().resumeAnimationsForDocument(m_document.get());
+    if (RuntimeEnabledFeatures::sharedFeatures().cssAnimationsAndCSSTransitionsBackedByWebAnimationsEnabled())
+        m_document->timeline().resumeAnimations();
+    else
+        frame.animation().resumeAnimationsForDocument(m_document.get());
 
     m_document->resume(ActiveDOMObject::PageCache);
 
index 2ee375d..29ba80c 100644 (file)
@@ -40,6 +40,7 @@
 #include "Chrome.h"
 #include "ChromeClient.h"
 #include "DOMWindow.h"
+#include "DocumentTimeline.h"
 #include "DocumentType.h"
 #include "Editing.h"
 #include "Editor.h"
@@ -777,7 +778,10 @@ void Frame::clearTimers(FrameView *view, Document *document)
 {
     if (view) {
         view->layoutContext().unscheduleLayout();
-        view->frame().animation().suspendAnimationsForDocument(document);
+        if (RuntimeEnabledFeatures::sharedFeatures().cssAnimationsAndCSSTransitionsBackedByWebAnimationsEnabled())
+            document->timeline().suspendAnimations();
+        else
+            view->frame().animation().suspendAnimationsForDocument(document);
         view->frame().eventHandler().stopAutoscrollTimer();
     }
 }
index 2acb198..c5000e7 100644 (file)
@@ -1637,8 +1637,14 @@ void Page::setIsVisibleInternal(bool isVisible)
         if (FrameView* view = mainFrame().view())
             view->show();
 
-        if (m_settings->hiddenPageCSSAnimationSuspensionEnabled())
-            mainFrame().animation().resumeAnimations();
+        if (m_settings->hiddenPageCSSAnimationSuspensionEnabled()) {
+            if (RuntimeEnabledFeatures::sharedFeatures().cssAnimationsAndCSSTransitionsBackedByWebAnimationsEnabled()) {
+                forEachDocument([&] (Document& document) {
+                    document.timeline().resumeAnimations();
+                });
+            } else
+                mainFrame().animation().resumeAnimations();
+        }
 
         setSVGAnimationsState(*this, SVGAnimationsState::Resumed);
 
@@ -1651,8 +1657,14 @@ void Page::setIsVisibleInternal(bool isVisible)
     }
 
     if (!isVisible) {
-        if (m_settings->hiddenPageCSSAnimationSuspensionEnabled())
-            mainFrame().animation().suspendAnimations();
+        if (m_settings->hiddenPageCSSAnimationSuspensionEnabled()) {
+            if (RuntimeEnabledFeatures::sharedFeatures().cssAnimationsAndCSSTransitionsBackedByWebAnimationsEnabled()) {
+                forEachDocument([&] (Document& document) {
+                    document.timeline().suspendAnimations();
+                });
+            } else
+                mainFrame().animation().suspendAnimations();
+        }
 
         setSVGAnimationsState(*this, SVGAnimationsState::Paused);
 
@@ -1998,10 +2010,19 @@ void Page::resetSeenMediaEngines()
 void Page::hiddenPageCSSAnimationSuspensionStateChanged()
 {
     if (!isVisible()) {
-        if (m_settings->hiddenPageCSSAnimationSuspensionEnabled())
-            mainFrame().animation().suspendAnimations();
-        else
-            mainFrame().animation().resumeAnimations();
+        if (RuntimeEnabledFeatures::sharedFeatures().cssAnimationsAndCSSTransitionsBackedByWebAnimationsEnabled()) {
+            forEachDocument([&] (Document& document) {
+                if (m_settings->hiddenPageCSSAnimationSuspensionEnabled())
+                    document.timeline().suspendAnimations();
+                else
+                    document.timeline().resumeAnimations();
+            });
+        } else {
+            if (m_settings->hiddenPageCSSAnimationSuspensionEnabled())
+                mainFrame().animation().suspendAnimations();
+            else
+                mainFrame().animation().resumeAnimations();
+        }
     }
 }
 
index 4e11434..b583ac8 100644 (file)
@@ -915,6 +915,8 @@ ExceptionOr<unsigned> Internals::lastSpatialNavigationCandidateCount() const
 
 unsigned Internals::numberOfActiveAnimations() const
 {
+    if (RuntimeEnabledFeatures::sharedFeatures().cssAnimationsAndCSSTransitionsBackedByWebAnimationsEnabled())
+        return frame()->document()->timeline().numberOfActiveAnimationsForTesting();
     return frame()->animation().numberOfActiveAnimations(frame()->document());
 }
 
@@ -924,6 +926,8 @@ ExceptionOr<bool> Internals::animationsAreSuspended() const
     if (!document || !document->frame())
         return Exception { InvalidAccessError };
 
+    if (RuntimeEnabledFeatures::sharedFeatures().cssAnimationsAndCSSTransitionsBackedByWebAnimationsEnabled())
+        return document->timeline().animationsAreSuspended();
     return document->frame()->animation().animationsAreSuspendedForDocument(document);
 }
 
@@ -950,11 +954,19 @@ ExceptionOr<void> Internals::suspendAnimations() const
     if (!document || !document->frame())
         return Exception { InvalidAccessError };
 
-    document->frame()->animation().suspendAnimationsForDocument(document);
+    if (RuntimeEnabledFeatures::sharedFeatures().cssAnimationsAndCSSTransitionsBackedByWebAnimationsEnabled()) {
+        document->timeline().suspendAnimations();
+        for (Frame* frame = document->frame(); frame; frame = frame->tree().traverseNext()) {
+            if (Document* document = frame->document())
+                document->timeline().suspendAnimations();
+        }
+    } else {
+        document->frame()->animation().suspendAnimationsForDocument(document);
 
-    for (Frame* frame = document->frame(); frame; frame = frame->tree().traverseNext()) {
-        if (Document* document = frame->document())
-            frame->animation().suspendAnimationsForDocument(document);
+        for (Frame* frame = document->frame(); frame; frame = frame->tree().traverseNext()) {
+            if (Document* document = frame->document())
+                frame->animation().suspendAnimationsForDocument(document);
+        }
     }
 
     return { };
@@ -966,11 +978,19 @@ ExceptionOr<void> Internals::resumeAnimations() const
     if (!document || !document->frame())
         return Exception { InvalidAccessError };
 
-    document->frame()->animation().resumeAnimationsForDocument(document);
+    if (RuntimeEnabledFeatures::sharedFeatures().cssAnimationsAndCSSTransitionsBackedByWebAnimationsEnabled()) {
+        document->timeline().resumeAnimations();
+        for (Frame* frame = document->frame(); frame; frame = frame->tree().traverseNext()) {
+            if (Document* document = frame->document())
+                document->timeline().resumeAnimations();
+        }
+    } else {
+        document->frame()->animation().resumeAnimationsForDocument(document);
 
-    for (Frame* frame = document->frame(); frame; frame = frame->tree().traverseNext()) {
-        if (Document* document = frame->document())
-            frame->animation().resumeAnimationsForDocument(document);
+        for (Frame* frame = document->frame(); frame; frame = frame->tree().traverseNext()) {
+            if (Document* document = frame->document())
+                frame->animation().resumeAnimationsForDocument(document);
+        }
     }
 
     return { };