// Copyright 2022 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 "pdf/paint_manager.h" #include #include "base/files/file_path.h" #include "base/run_loop.h" #include "base/strings/string_piece.h" #include "cc/test/pixel_comparator.h" #include "cc/test/pixel_test_utils.h" #include "pdf/paint_ready_rect.h" #include "pdf/test/test_helpers.h" #include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h" #include "third_party/skia/include/core/SkBitmap.h" #include "third_party/skia/include/core/SkCanvas.h" #include "third_party/skia/include/core/SkColor.h" #include "third_party/skia/include/core/SkImage.h" #include "third_party/skia/include/core/SkRect.h" #include "third_party/skia/include/core/SkRefCnt.h" #include "third_party/skia/include/core/SkSurface.h" #include "ui/gfx/geometry/rect.h" #include "ui/gfx/geometry/size.h" #include "ui/gfx/geometry/skia_conversions.h" namespace chrome_pdf { namespace { using ::testing::_; using ::testing::NiceMock; base::FilePath GetTestDataFilePath(base::StringPiece filename) { return base::FilePath(FILE_PATH_LITERAL("paint_manager")) .AppendASCII(filename); } class FakeClient : public PaintManager::Client { public: MOCK_METHOD(void, InvalidatePluginContainer, (), (override)); MOCK_METHOD(void, OnPaint, (const std::vector& paint_rects, std::vector& ready, std::vector& pending), (override)); MOCK_METHOD(void, UpdateSnapshot, (sk_sp snapshot), (override)); MOCK_METHOD(void, UpdateScale, (float scale), (override)); MOCK_METHOD(void, UpdateLayerTransform, (float scale, const gfx::Vector2dF& translate), (override)); }; class PaintManagerTest : public testing::Test { protected: void WaitForOnPaint() { base::RunLoop run_loop; EXPECT_CALL(client_, OnPaint).WillOnce([&run_loop] { run_loop.Quit(); }); run_loop.Run(); } sk_sp WaitForFlush( const std::vector& expected_paint_rects, std::vector fake_ready, std::vector fake_pending) { EXPECT_CALL(client_, OnPaint(expected_paint_rects, _, _)) .WillOnce([&fake_ready, &fake_pending]( const std::vector& paint_rects, std::vector& ready, std::vector& pending) { ready = std::move(fake_ready); pending = std::move(fake_pending); }); sk_sp saved_snapshot; base::RunLoop run_loop; EXPECT_CALL(client_, UpdateSnapshot) .WillOnce([&saved_snapshot, &run_loop](sk_sp snapshot) { saved_snapshot = std::move(snapshot); run_loop.Quit(); }); run_loop.Run(); return saved_snapshot; } void TestPaintImage(const gfx::Size& plugin_size, const gfx::Size& source_size, const gfx::Rect& paint_rect, const gfx::Rect& overlapped_rect) { // Paint `paint_rect` from `source_size` image over a magenta background. paint_manager_.SetSize(plugin_size, 1.0f); sk_sp snapshot = WaitForFlush( /*expected_paint_rects=*/{{gfx::Rect(plugin_size)}}, /*fake_ready=*/ { {gfx::Rect(plugin_size), CreateSkiaImageForTesting(plugin_size, SK_ColorMAGENTA)}, {paint_rect, CreateSkiaImageForTesting(source_size, SK_ColorRED)}, }, /*fake_pending=*/{}); ASSERT_TRUE(snapshot); // Check if snapshot has `overlapped_rect` painted red. snapshot = snapshot->makeSubset( SkIRect::MakeWH(plugin_size.width(), plugin_size.height())); ASSERT_TRUE(snapshot); SkBitmap snapshot_bitmap; ASSERT_TRUE(snapshot->asLegacyBitmap(&snapshot_bitmap)); sk_sp expected_surface = CreateSkiaSurfaceForTesting(plugin_size, SK_ColorMAGENTA); expected_surface->getCanvas()->clipIRect( gfx::RectToSkIRect(overlapped_rect)); expected_surface->getCanvas()->clear(SK_ColorRED); SkBitmap expected_bitmap; ASSERT_TRUE(expected_surface->makeImageSnapshot()->asLegacyBitmap( &expected_bitmap)); EXPECT_TRUE( cc::MatchesBitmap(snapshot_bitmap, expected_bitmap, cc::ExactPixelComparator(/*discard_alpha=*/false))); } void TestScroll(const gfx::Vector2d& scroll_amount, const gfx::Rect& expected_paint_rect, base::StringPiece expected_png) { // Paint non-uniform initial image. gfx::Size plugin_size = paint_manager_.GetEffectiveSize(); ASSERT_GE(plugin_size.width(), 4); ASSERT_GE(plugin_size.height(), 4); sk_sp initial_surface = CreateSkiaSurfaceForTesting(plugin_size, SK_ColorRED); initial_surface->getCanvas()->clipIRect(SkIRect::MakeLTRB( 1, 1, plugin_size.width() - 1, plugin_size.height() - 2)); initial_surface->getCanvas()->clear(SK_ColorGREEN); paint_manager_.Invalidate(); ASSERT_TRUE(WaitForFlush( /*expected_paint_rects=*/{gfx::Rect(plugin_size)}, /*fake_ready=*/ {{gfx::Rect(plugin_size), initial_surface->makeImageSnapshot()}}, /*fake_pending=*/{})); // Scroll by `scroll_amount`, painting `expected_paint_rect` magenta. paint_manager_.ScrollRect(gfx::Rect(plugin_size), scroll_amount); sk_sp snapshot = WaitForFlush( /*expected_paint_rects=*/{expected_paint_rect}, /*fake_ready=*/ {{expected_paint_rect, CreateSkiaImageForTesting(plugin_size, SK_ColorMAGENTA)}}, /*fake_pending=*/{}); ASSERT_TRUE(snapshot); // Compare snapshot to `expected_png`. snapshot = snapshot->makeSubset( SkIRect::MakeWH(plugin_size.width(), plugin_size.height())); ASSERT_TRUE(snapshot); EXPECT_TRUE( MatchesPngFile(snapshot.get(), GetTestDataFilePath(expected_png))); } NiceMock client_; PaintManager paint_manager_{&client_}; }; TEST_F(PaintManagerTest, GetNewContextSizeWhenGrowingBelowMaximum) { EXPECT_EQ(gfx::Size(450, 350), PaintManager::GetNewContextSize({450, 350}, {450, 349})); EXPECT_EQ(gfx::Size(450, 350), PaintManager::GetNewContextSize({450, 350}, {449, 350})); } TEST_F(PaintManagerTest, GetNewContextSizeWhenGrowingAboveMaximum) { EXPECT_EQ(gfx::Size(501, 400), PaintManager::GetNewContextSize({450, 350}, {451, 350})); EXPECT_EQ(gfx::Size(500, 401), PaintManager::GetNewContextSize({450, 350}, {450, 351})); } TEST_F(PaintManagerTest, GetNewContextSizeWhenShrinkingAboveMinimum) { EXPECT_EQ(gfx::Size(450, 350), PaintManager::GetNewContextSize({450, 350}, {350, 251})); EXPECT_EQ(gfx::Size(450, 350), PaintManager::GetNewContextSize({450, 350}, {351, 250})); } TEST_F(PaintManagerTest, GetNewContextSizeWhenShrinkingBelowMinimum) { EXPECT_EQ(gfx::Size(399, 300), PaintManager::GetNewContextSize({450, 350}, {349, 250})); EXPECT_EQ(gfx::Size(400, 299), PaintManager::GetNewContextSize({450, 350}, {350, 249})); } TEST_F(PaintManagerTest, Create) { EXPECT_EQ(gfx::Size(0, 0), paint_manager_.GetEffectiveSize()); EXPECT_EQ(1.0f, paint_manager_.GetEffectiveDeviceScale()); } TEST_F(PaintManagerTest, SetSizeWithoutPaint) { EXPECT_CALL(client_, InvalidatePluginContainer).Times(0); paint_manager_.SetSize({400, 300}, 2.0f); EXPECT_EQ(gfx::Size(400, 300), paint_manager_.GetEffectiveSize()); EXPECT_EQ(2.0f, paint_manager_.GetEffectiveDeviceScale()); } TEST_F(PaintManagerTest, SetSizeWithPaint) { paint_manager_.SetSize({400, 300}, 2.0f); EXPECT_CALL(client_, InvalidatePluginContainer); EXPECT_CALL(client_, UpdateScale(0.5f)); WaitForOnPaint(); } TEST_F(PaintManagerTest, SetTransformWithoutSurface) { EXPECT_CALL(client_, UpdateLayerTransform).Times(0); paint_manager_.SetTransform(0.25f, {150, 50}, {-4, 8}, /*schedule_flush=*/true); } TEST_F(PaintManagerTest, SetTransformWithSurface) { paint_manager_.SetSize({400, 300}, 2.0f); WaitForOnPaint(); EXPECT_CALL(client_, UpdateLayerTransform(0.25f, gfx::Vector2dF(116.5f, 29.5f))); paint_manager_.SetTransform(0.25f, {150, 50}, {-4, 8}, /*schedule_flush=*/true); WaitForOnPaint(); } TEST_F(PaintManagerTest, ClearTransform) { paint_manager_.SetSize({400, 300}, 2.0f); WaitForOnPaint(); EXPECT_CALL(client_, UpdateLayerTransform(1.0f, gfx::Vector2dF())); paint_manager_.ClearTransform(); } TEST_F(PaintManagerTest, DoPaintFirst) { paint_manager_.SetSize({400, 300}, 2.0f); sk_sp snapshot = WaitForFlush(/*expected_paint_rects=*/{{0, 0, 400, 300}}, /*fake_ready=*/ {{{25, 50, 200, 100}, CreateSkiaImageForTesting({200, 100}, SK_ColorGRAY)}}, /*fake_pending=*/{}); EXPECT_TRUE(MatchesPngFile(snapshot.get(), GetTestDataFilePath("do_paint_first.png"))); } TEST_F(PaintManagerTest, PaintImage) { // Painted area is within the plugin area and the source image. TestPaintImage(/*plugin_size=*/{20, 20}, /*source_size=*/{15, 15}, /*paint_rect=*/{0, 0, 10, 10}, /*overlapped_rect=*/{0, 0, 10, 10}); // Painted area straddles the plugin area and the source image. TestPaintImage(/*plugin_size=*/{50, 30}, /*source_size=*/{30, 50}, /*paint_rect=*/{10, 10, 30, 30}, /*overlapped_rect=*/{10, 10, 20, 20}); // Painted area is outside the plugin area. TestPaintImage(/*plugin_size=*/{10, 10}, /*source_size=*/{30, 30}, /*paint_rect=*/{10, 10, 10, 10}, /*overlapped_rect=*/{0, 0, 0, 0}); // Painted area is outside the source image. TestPaintImage(/*plugin_size=*/{15, 15}, /*source_size=*/{5, 5}, /*paint_rect=*/{10, 10, 5, 5}, /*overlapped_rect=*/{0, 0, 0, 0}); } TEST_F(PaintManagerTest, Scroll) { paint_manager_.SetSize({4, 5}, 1.0f); TestScroll(/*scroll_amount=*/{1, 0}, /*expected_paint_rect=*/{0, 0, 1, 5}, "scroll_right.png"); TestScroll(/*scroll_amount=*/{-2, 0}, /*expected_paint_rect=*/{2, 0, 2, 5}, "scroll_left.png"); TestScroll(/*scroll_amount=*/{0, 3}, /*expected_paint_rect=*/{0, 0, 4, 3}, "scroll_down.png"); TestScroll(/*scroll_amount=*/{0, -3}, /*expected_paint_rect=*/{0, 2, 4, 3}, "scroll_up.png"); } TEST_F(PaintManagerTest, ScrollIgnored) { paint_manager_.SetSize({4, 5}, 1.0f); // Scroll to the edge of the plugin area. TestScroll(/*scroll_amount=*/{4, 0}, /*expected_paint_rect=*/{0, 0, 4, 5}, "scroll_ignored.png"); TestScroll(/*scroll_amount=*/{-4, 0}, /*expected_paint_rect=*/{0, 0, 4, 5}, "scroll_ignored.png"); TestScroll(/*scroll_amount=*/{0, 5}, /*expected_paint_rect=*/{0, 0, 4, 5}, "scroll_ignored.png"); TestScroll(/*scroll_amount=*/{0, -5}, /*expected_paint_rect=*/{0, 0, 4, 5}, "scroll_ignored.png"); // Scroll outside of the plugin area. TestScroll(/*scroll_amount=*/{5, 0}, /*expected_paint_rect=*/{0, 0, 4, 5}, "scroll_ignored.png"); TestScroll(/*scroll_amount=*/{-7, 0}, /*expected_paint_rect=*/{0, 0, 4, 5}, "scroll_ignored.png"); TestScroll(/*scroll_amount=*/{0, 8}, /*expected_paint_rect=*/{0, 0, 4, 5}, "scroll_ignored.png"); TestScroll(/*scroll_amount=*/{0, -9}, /*expected_paint_rect=*/{0, 0, 4, 5}, "scroll_ignored.png"); } } // namespace } // namespace chrome_pdf