blob: b9bc178682c6b9d67b73852058604706358a1227 [file] [log] [blame]
// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "third_party/blink/renderer/core/svg/graphics/svg_image.h"
#include <memory>
#include "base/memory/ptr_util.h"
#include "testing/gtest/include/gtest/gtest.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/visual_viewport.h"
#include "third_party/blink/renderer/core/html/html_image_element.h"
#include "third_party/blink/renderer/core/layout/jank_tracker.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/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/graphics/paint/paint_canvas.h"
#include "third_party/blink/renderer/platform/graphics/paint/paint_flags.h"
#include "third_party/blink/renderer/platform/shared_buffer.h"
#include "third_party/blink/renderer/platform/testing/unit_test_helpers.h"
#include "third_party/blink/renderer/platform/timer.h"
#include "third_party/skia/include/core/SkCanvas.h"
#include "third_party/skia/include/utils/SkNullCanvas.h"
namespace blink {
class SVGImageTest : public testing::Test {
public:
SVGImage& GetImage() { return *image_; }
void Load(const char* data, bool should_pause) {
observer_ = MakeGarbageCollected<PauseControlImageObserver>(should_pause);
image_ = SVGImage::Create(observer_);
image_->SetData(SharedBuffer::Create(data, strlen(data)), true);
}
void PumpFrame() {
Image* image = image_.get();
std::unique_ptr<SkCanvas> null_canvas = SkMakeNullCanvas();
SkiaPaintCanvas canvas(null_canvas.get());
PaintFlags flags;
FloatRect dummy_rect(0, 0, 100, 100);
image->Draw(&canvas, flags, dummy_rect, dummy_rect,
kDoNotRespectImageOrientation,
Image::kDoNotClampImageToSourceRect, Image::kSyncDecode);
}
private:
class PauseControlImageObserver
: public GarbageCollectedFinalized<PauseControlImageObserver>,
public ImageObserver {
USING_GARBAGE_COLLECTED_MIXIN(PauseControlImageObserver);
public:
PauseControlImageObserver(bool should_pause)
: should_pause_(should_pause) {}
void DecodedSizeChangedTo(const Image*, size_t new_size) override {}
bool ShouldPauseAnimation(const Image*) override { return should_pause_; }
void Changed(const Image*) override {}
void AsyncLoadCompleted(const blink::Image*) override {}
void Trace(blink::Visitor* visitor) override {
ImageObserver::Trace(visitor);
}
private:
bool should_pause_;
};
Persistent<PauseControlImageObserver> observer_;
scoped_refptr<SVGImage> image_;
};
const char kAnimatedDocument[] =
"<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'>"
"<style>"
"@keyframes rot {"
" from { transform: rotate(0deg); } to { transform: rotate(-360deg); }"
"}"
".spinner {"
" transform-origin: 50%% 50%%;"
" animation-name: rot;"
" animation-duration: 4s;"
" animation-iteration-count: infinite;"
" animation-timing-function: linear;"
"}"
"</style>"
"<path class='spinner' fill='none' d='M 8,1.125 A 6.875,6.875 0 1 1 "
"1.125,8' stroke-width='2' stroke='blue'/>"
"</svg>";
TEST_F(SVGImageTest, TimelineSuspendAndResume) {
const bool kShouldPause = true;
Load(kAnimatedDocument, kShouldPause);
SVGImageChromeClient& chrome_client = GetImage().ChromeClientForTesting();
TaskRunnerTimer<SVGImageChromeClient>* timer =
new TaskRunnerTimer<SVGImageChromeClient>(
scheduler::GetSingleThreadTaskRunnerForTesting(), &chrome_client,
&SVGImageChromeClient::AnimationTimerFired);
chrome_client.SetTimer(base::WrapUnique(timer));
// Simulate a draw. Cause a frame (timer) to be scheduled.
PumpFrame();
EXPECT_TRUE(GetImage().MaybeAnimated());
EXPECT_TRUE(timer->IsActive());
// Fire the timer/trigger a frame update. Since the observer always returns
// true for shouldPauseAnimation, this will result in the timeline being
// suspended.
test::RunDelayedTasks(TimeDelta::FromMilliseconds(1) +
timer->NextFireInterval());
EXPECT_TRUE(chrome_client.IsSuspended());
EXPECT_FALSE(timer->IsActive());
// Simulate a draw. This should resume the animation again.
PumpFrame();
EXPECT_TRUE(timer->IsActive());
EXPECT_FALSE(chrome_client.IsSuspended());
}
TEST_F(SVGImageTest, ResetAnimation) {
const bool kShouldPause = false;
Load(kAnimatedDocument, kShouldPause);
SVGImageChromeClient& chrome_client = GetImage().ChromeClientForTesting();
TaskRunnerTimer<SVGImageChromeClient>* timer =
new TaskRunnerTimer<SVGImageChromeClient>(
scheduler::GetSingleThreadTaskRunnerForTesting(), &chrome_client,
&SVGImageChromeClient::AnimationTimerFired);
chrome_client.SetTimer(base::WrapUnique(timer));
// Simulate a draw. Cause a frame (timer) to be scheduled.
PumpFrame();
EXPECT_TRUE(GetImage().MaybeAnimated());
EXPECT_TRUE(timer->IsActive());
// Reset the animation. This will suspend the timeline but not cancel the
// timer.
GetImage().ResetAnimation();
EXPECT_TRUE(chrome_client.IsSuspended());
EXPECT_TRUE(timer->IsActive());
// Fire the timer/trigger a frame update. The timeline will remain
// suspended and no frame will be scheduled.
test::RunDelayedTasks(TimeDelta::FromMillisecondsD(1) +
timer->NextFireInterval());
EXPECT_TRUE(chrome_client.IsSuspended());
EXPECT_FALSE(timer->IsActive());
// Simulate a draw. This should resume the animation again.
PumpFrame();
EXPECT_FALSE(chrome_client.IsSuspended());
EXPECT_TRUE(timer->IsActive());
}
TEST_F(SVGImageTest, SupportsSubsequenceCaching) {
const bool kShouldPause = true;
Load(kAnimatedDocument, kShouldPause);
PumpFrame();
LocalFrame* local_frame =
ToLocalFrame(GetImage().GetPageForTesting()->MainFrame());
EXPECT_TRUE(local_frame->GetDocument()->IsSVGDocument());
LayoutObject* svg_root = local_frame->View()->GetLayoutView()->FirstChild();
EXPECT_TRUE(svg_root->IsSVGRoot());
EXPECT_TRUE(
ToLayoutBoxModelObject(svg_root)->Layer()->SupportsSubsequenceCaching());
}
TEST_F(SVGImageTest, JankTrackerDisabled) {
const bool kDontPause = false;
Load("<svg xmlns='http://www.w3.org/2000/svg'></svg>", kDontPause);
LocalFrame* local_frame =
ToLocalFrame(GetImage().GetPageForTesting()->MainFrame());
EXPECT_TRUE(local_frame->GetDocument()->IsSVGDocument());
auto& jank_tracker = local_frame->View()->GetJankTracker();
EXPECT_FALSE(jank_tracker.IsActive());
}
TEST_F(SVGImageTest, SetSizeOnVisualViewport) {
const bool kDontPause = false;
Load(
"<svg xmlns='http://www.w3.org/2000/svg'>"
" <rect id='green' width='100%' height='100%' fill='green' />"
"</svg>",
kDontPause);
PumpFrame();
LocalFrame* local_frame =
ToLocalFrame(GetImage().GetPageForTesting()->MainFrame());
ASSERT_FALSE(local_frame->View()->Size().IsEmpty());
EXPECT_EQ(local_frame->View()->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();
test::RunPendingTasks();
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) +
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().SetIsHidden(/*is_hidden=*/true, /*initial_state=*/false);
test::RunDelayedTasks(TimeDelta::FromMilliseconds(1) +
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().SetIsHidden(/*is_hidden=*/false, /*initial_state=*/false);
test::RunDelayedTasks(TimeDelta::FromMilliseconds(1) +
timer->NextFireInterval());
Compositor().BeginFrame();
EXPECT_FALSE(svg_image_chrome_client.IsSuspended());
}
} // namespace blink