Commit b56e2510 authored by Fredrik Söderquist's avatar Fredrik Söderquist Committed by Commit Bot

Suspend SVG image animations when the page is hidden

Check Document::hidden() in LayoutObject::WillRenderImage,
thus pausing animations on pages that are not visible.
To resume animations, we may need to explicitly resume the
timeline, so a hook is added in Page::SetVisibilityState to
allow calling StartAnimation() on the relevant SVGImages.

Bug: 856082
Cq-Include-Trybots: luci.chromium.try:linux_layout_tests_slimming_paint_v2;master.tryserver.blink:linux_trusty_blink_rel
Change-Id: I4a0b008730ec3e6c2af26e15d52eebb26020ad32
Reviewed-on: https://chromium-review.googlesource.com/1115130Reviewed-by: default avatarChris Harrelson <chrishtr@chromium.org>
Commit-Queue: Fredrik Söderquist <fs@opera.com>
Cr-Commit-Position: refs/heads/master@{#571543}
parent cb5559bc
...@@ -3518,6 +3518,10 @@ bool LayoutObject::WillRenderImage() { ...@@ -3518,6 +3518,10 @@ bool LayoutObject::WillRenderImage() {
if (GetDocument().IsContextPaused()) if (GetDocument().IsContextPaused())
return false; return false;
// Suspend animations when the page is not visible.
if (GetDocument().hidden())
return false;
// If we're not in a window (i.e., we're dormant from being in a background // If we're not in a window (i.e., we're dormant from being in a background
// tab) then we don't want to render either. // tab) then we don't want to render either.
return GetDocument().View()->IsVisible(); return GetDocument().View()->IsVisible();
......
...@@ -66,6 +66,7 @@ ...@@ -66,6 +66,7 @@
#include "third_party/blink/renderer/core/paint/paint_layer.h" #include "third_party/blink/renderer/core/paint/paint_layer.h"
#include "third_party/blink/renderer/core/paint/paint_layer_scrollable_area.h" #include "third_party/blink/renderer/core/paint/paint_layer_scrollable_area.h"
#include "third_party/blink/renderer/core/probe/core_probes.h" #include "third_party/blink/renderer/core/probe/core_probes.h"
#include "third_party/blink/renderer/core/svg/graphics/svg_image_chrome_client.h"
#include "third_party/blink/renderer/platform/graphics/graphics_layer.h" #include "third_party/blink/renderer/platform/graphics/graphics_layer.h"
#include "third_party/blink/renderer/platform/loader/fetch/resource_fetcher.h" #include "third_party/blink/renderer/platform/loader/fetch/resource_fetcher.h"
#include "third_party/blink/renderer/platform/plugins/plugin_data.h" #include "third_party/blink/renderer/platform/plugins/plugin_data.h"
...@@ -345,6 +346,14 @@ void Page::ResetPluginData() { ...@@ -345,6 +346,14 @@ void Page::ResetPluginData() {
} }
} }
static void RestoreSVGImageAnimations() {
for (const Page* page : AllPages()) {
if (auto* svg_image_chrome_client =
ToSVGImageChromeClientOrNull(page->GetChromeClient()))
svg_image_chrome_client->RestoreAnimationIfNeeded();
}
}
void Page::SetValidationMessageClient(ValidationMessageClient* client) { void Page::SetValidationMessageClient(ValidationMessageClient* client) {
validation_message_client_ = client; validation_message_client_ = client;
} }
...@@ -456,11 +465,15 @@ void Page::SetVisibilityState(mojom::PageVisibilityState visibility_state, ...@@ -456,11 +465,15 @@ void Page::SetVisibilityState(mojom::PageVisibilityState visibility_state,
return; return;
visibility_state_ = visibility_state; visibility_state_ = visibility_state;
if (!is_initial_state) if (is_initial_state)
NotifyPageVisibilityChanged(); return;
NotifyPageVisibilityChanged();
if (!is_initial_state && main_frame_) if (main_frame_) {
if (IsPageVisible())
RestoreSVGImageAnimations();
main_frame_->DidChangeVisibilityState(); main_frame_->DidChangeVisibilityState();
}
} }
mojom::PageVisibilityState Page::VisibilityState() const { mojom::PageVisibilityState Page::VisibilityState() const {
......
...@@ -593,6 +593,17 @@ void SVGImage::ResetAnimation() { ...@@ -593,6 +593,17 @@ void SVGImage::ResetAnimation() {
ScheduleTimelineRewind(); ScheduleTimelineRewind();
} }
void SVGImage::RestoreAnimation() {
// If the image has no animations then do nothing.
if (!MaybeAnimated())
return;
// If there are no clients, or no client is going to render, then do nothing.
ImageObserver* image_observer = GetImageObserver();
if (!image_observer || image_observer->ShouldPauseAnimation(this))
return;
StartAnimation();
}
bool SVGImage::MaybeAnimated() { bool SVGImage::MaybeAnimated() {
SVGSVGElement* root_element = SvgRootElement(page_.Get()); SVGSVGElement* root_element = SvgRootElement(page_.Get());
if (!root_element) if (!root_element)
......
...@@ -73,6 +73,7 @@ class CORE_EXPORT SVGImage final : public Image { ...@@ -73,6 +73,7 @@ class CORE_EXPORT SVGImage final : public Image {
void StartAnimation() override; void StartAnimation() override;
void ResetAnimation() override; void ResetAnimation() override;
void RestoreAnimation();
PaintImage::CompletionState completion_state() const { PaintImage::CompletionState completion_state() const {
return load_state_ == LoadState::kLoadCompleted return load_state_ == LoadState::kLoadCompleted
......
...@@ -91,6 +91,13 @@ void SVGImageChromeClient::ResumeAnimation() { ...@@ -91,6 +91,13 @@ void SVGImageChromeClient::ResumeAnimation() {
ScheduleAnimation(nullptr); ScheduleAnimation(nullptr);
} }
void SVGImageChromeClient::RestoreAnimationIfNeeded() {
// If the timeline is not suspended we needn't attempt to restore.
if (!IsSuspended())
return;
image_->RestoreAnimation();
}
void SVGImageChromeClient::ScheduleAnimation(const LocalFrameView*) { void SVGImageChromeClient::ScheduleAnimation(const LocalFrameView*) {
// Because a single SVGImage can be shared by multiple pages, we can't key // Because a single SVGImage can be shared by multiple pages, we can't key
// our svg image layout on the page's real animation frame. Therefore, we // our svg image layout on the page's real animation frame. Therefore, we
...@@ -105,7 +112,7 @@ void SVGImageChromeClient::ScheduleAnimation(const LocalFrameView*) { ...@@ -105,7 +112,7 @@ void SVGImageChromeClient::ScheduleAnimation(const LocalFrameView*) {
// stringent. // stringent.
double fire_time = 0; double fire_time = 0;
if (image_->MaybeAnimated()) { if (image_->MaybeAnimated()) {
if (timeline_state_ >= kSuspended) if (IsSuspended())
return; return;
fire_time = kAnimationFrameDelay; fire_time = kAnimationFrameDelay;
} }
......
...@@ -49,6 +49,8 @@ class CORE_EXPORT SVGImageChromeClient final : public EmptyChromeClient { ...@@ -49,6 +49,8 @@ class CORE_EXPORT SVGImageChromeClient final : public EmptyChromeClient {
void SuspendAnimation(); void SuspendAnimation();
void ResumeAnimation(); void ResumeAnimation();
void RestoreAnimationIfNeeded();
bool IsSuspended() const { return timeline_state_ >= kSuspended; } bool IsSuspended() const { return timeline_state_ >= kSuspended; }
private: private:
...@@ -59,6 +61,7 @@ class CORE_EXPORT SVGImageChromeClient final : public EmptyChromeClient { ...@@ -59,6 +61,7 @@ class CORE_EXPORT SVGImageChromeClient final : public EmptyChromeClient {
void ScheduleAnimation(const LocalFrameView*) override; void ScheduleAnimation(const LocalFrameView*) override;
void SetTimer(std::unique_ptr<TimerBase>); void SetTimer(std::unique_ptr<TimerBase>);
TimerBase* GetTimerForTesting() const { return animation_timer_.get(); }
void AnimationTimerFired(TimerBase*); void AnimationTimerFired(TimerBase*);
SVGImage* image_; SVGImage* image_;
...@@ -71,6 +74,8 @@ class CORE_EXPORT SVGImageChromeClient final : public EmptyChromeClient { ...@@ -71,6 +74,8 @@ class CORE_EXPORT SVGImageChromeClient final : public EmptyChromeClient {
FRIEND_TEST_ALL_PREFIXES(SVGImageTest, TimelineSuspendAndResume); FRIEND_TEST_ALL_PREFIXES(SVGImageTest, TimelineSuspendAndResume);
FRIEND_TEST_ALL_PREFIXES(SVGImageTest, ResetAnimation); FRIEND_TEST_ALL_PREFIXES(SVGImageTest, ResetAnimation);
FRIEND_TEST_ALL_PREFIXES(SVGImagePageVisibilityTest,
PageVisibilityHiddenToVisible);
}; };
DEFINE_TYPE_CASTS(SVGImageChromeClient, DEFINE_TYPE_CASTS(SVGImageChromeClient,
......
...@@ -11,9 +11,12 @@ ...@@ -11,9 +11,12 @@
#include "third_party/blink/public/platform/scheduler/test/renderer_scheduler_test_support.h" #include "third_party/blink/public/platform/scheduler/test/renderer_scheduler_test_support.h"
#include "third_party/blink/renderer/core/frame/local_frame_view.h" #include "third_party/blink/renderer/core/frame/local_frame_view.h"
#include "third_party/blink/renderer/core/frame/visual_viewport.h" #include "third_party/blink/renderer/core/frame/visual_viewport.h"
#include "third_party/blink/renderer/core/html/html_image_element.h"
#include "third_party/blink/renderer/core/layout/layout_view.h" #include "third_party/blink/renderer/core/layout/layout_view.h"
#include "third_party/blink/renderer/core/paint/paint_layer.h" #include "third_party/blink/renderer/core/paint/paint_layer.h"
#include "third_party/blink/renderer/core/svg/graphics/svg_image_chrome_client.h" #include "third_party/blink/renderer/core/svg/graphics/svg_image_chrome_client.h"
#include "third_party/blink/renderer/core/testing/sim/sim_request.h"
#include "third_party/blink/renderer/core/testing/sim/sim_test.h"
#include "third_party/blink/renderer/platform/geometry/float_rect.h" #include "third_party/blink/renderer/platform/geometry/float_rect.h"
#include "third_party/blink/renderer/platform/graphics/paint/paint_canvas.h" #include "third_party/blink/renderer/platform/graphics/paint/paint_canvas.h"
#include "third_party/blink/renderer/platform/graphics/paint/paint_flags.h" #include "third_party/blink/renderer/platform/graphics/paint/paint_flags.h"
...@@ -194,4 +197,56 @@ TEST_F(SVGImageTest, SetSizeOnVisualViewport) { ...@@ -194,4 +197,56 @@ TEST_F(SVGImageTest, SetSizeOnVisualViewport) {
GetImage().GetPageForTesting()->GetVisualViewport().Size()); GetImage().GetPageForTesting()->GetVisualViewport().Size());
} }
class SVGImagePageVisibilityTest : public SimTest {};
TEST_F(SVGImagePageVisibilityTest, PageVisibilityHiddenToVisible) {
SimRequest main_resource("https://example.com/", "text/html");
SimRequest image_resource("https://example.com/image.svg", "image/svg+xml");
LoadURL("https://example.com/");
main_resource.Complete("<img src='image.svg' width='100' id='image'>");
image_resource.Complete(kAnimatedDocument);
Compositor().BeginFrame();
Element* element = GetDocument().getElementById("image");
ASSERT_TRUE(IsHTMLImageElement(element));
ImageResourceContent* image_content =
ToHTMLImageElement(*element).CachedImage();
ASSERT_TRUE(image_content);
ASSERT_TRUE(image_content->IsLoaded());
ASSERT_TRUE(image_content->HasImage());
Image* image = image_content->GetImage();
ASSERT_TRUE(image->IsSVGImage());
SVGImageChromeClient& svg_image_chrome_client =
ToSVGImage(*image).ChromeClientForTesting();
TimerBase* timer = svg_image_chrome_client.GetTimerForTesting();
// Wait for the next animation frame to be triggered, and then trigger a new
// frame. The image animation timeline should be running.
test::RunDelayedTasks(TimeDelta::FromMilliseconds(1) +
TimeDelta::FromSecondsD(timer->NextFireInterval()));
Compositor().BeginFrame();
EXPECT_FALSE(svg_image_chrome_client.IsSuspended());
// Set page visibility to 'hidden', and then wait for the animation timer to
// fire. This should suspend the image animation. (Suspend the image's
// animation timeline.)
WebView().SetVisibilityState(mojom::PageVisibilityState::kHidden, false);
test::RunDelayedTasks(TimeDelta::FromMilliseconds(1) +
TimeDelta::FromSecondsD(timer->NextFireInterval()));
EXPECT_TRUE(svg_image_chrome_client.IsSuspended());
// Set page visibility to 'visible' - this should schedule a new animation
// frame and resume the image animation.
WebView().SetVisibilityState(mojom::PageVisibilityState::kVisible, false);
test::RunDelayedTasks(TimeDelta::FromMilliseconds(1) +
TimeDelta::FromSecondsD(timer->NextFireInterval()));
Compositor().BeginFrame();
EXPECT_FALSE(svg_image_chrome_client.IsSuspended());
}
} // namespace blink } // namespace blink
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment