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
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
+<!DOCTYPE html>
<title>Test that new animations do not run while we are suspended</title>
<style>
#box {
-<!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>
-<!DOCTYPE html>
+<!DOCTYPE html><!-- webkit-test-runner [ enableCSSAnimationsAndCSSTransitionsBackedByWebAnimations=true ] -->
<html>
<head>
<style>
-<!DOCTYPE html>
+<!DOCTYPE html><!-- webkit-test-runner [ enableCSSAnimationsAndCSSTransitionsBackedByWebAnimations=true ] -->
<html>
<head>
<style>
-<!DOCTYPE html>
+<!DOCTYPE html><!-- webkit-test-runner [ enableCSSAnimationsAndCSSTransitionsBackedByWebAnimations=true ] -->
<html>
<head>
<style>
+<!DOCTYPE html>
<title>Test that newly created transitions do not run while we are suspended</title>
<style>
#box {
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
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) {
void DocumentTimeline::timingModelDidChange()
{
- if (m_needsUpdateAnimationSchedule)
+ if (m_needsUpdateAnimationSchedule || m_isSuspended)
return;
m_needsUpdateAnimationSchedule = true;
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);
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;
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,
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))
void timingModelDidChange();
void suspendEffectInvalidation();
void unsuspendEffectInvalidation();
+ void setSuspended(bool);
+ bool isSuspended() const { return m_isSuspended; }
String description();
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;
// 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);
}
}
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);
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();
#include "DOMWindow.h"
#include "Document.h"
#include "DocumentLoader.h"
+#include "DocumentTimeline.h"
#include "Frame.h"
#include "FrameLoader.h"
#include "FrameLoaderClient.h"
#include "Logging.h"
#include "Page.h"
#include "PageCache.h"
+#include "RuntimeEnabledFeatures.h"
#include "SVGDocumentExtensions.h"
#include "ScriptController.h"
#include "SerializedScriptValue.h"
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);
#include "Chrome.h"
#include "ChromeClient.h"
#include "DOMWindow.h"
+#include "DocumentTimeline.h"
#include "DocumentType.h"
#include "Editing.h"
#include "Editor.h"
{
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();
}
}
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);
}
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);
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();
+ }
}
}
unsigned Internals::numberOfActiveAnimations() const
{
+ if (RuntimeEnabledFeatures::sharedFeatures().cssAnimationsAndCSSTransitionsBackedByWebAnimationsEnabled())
+ return frame()->document()->timeline().numberOfActiveAnimationsForTesting();
return frame()->animation().numberOfActiveAnimations(frame()->document());
}
if (!document || !document->frame())
return Exception { InvalidAccessError };
+ if (RuntimeEnabledFeatures::sharedFeatures().cssAnimationsAndCSSTransitionsBackedByWebAnimationsEnabled())
+ return document->timeline().animationsAreSuspended();
return document->frame()->animation().animationsAreSuspendedForDocument(document);
}
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 { };
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 { };