#include "pdf/paint_manager.h"
#include <string_view>
#include <utility>
#include "base/files/file_path.h"
#include "base/run_loop.h"
#include "base/test/metrics/histogram_tester.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/SkCPURecorder.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;
constexpr char kRenderAndPaintTimeMetric[] = "PDF.RenderAndPaintTime";
constexpr char kRenderPaintAndFlushTimeMetric[] = "PDF.RenderPaintAndFlushTime";
base::FilePath GetTestDataFilePath(std::string_view 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<gfx::Rect>& paint_rects,
std::vector<PaintReadyRect>& ready,
std::vector<gfx::Rect>& pending),
(override));
MOCK_METHOD(void, UpdateSnapshot, (sk_sp<SkImage> 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<SkImage> WaitForFlush(
const std::vector<gfx::Rect>& expected_paint_rects,
std::vector<PaintReadyRect> fake_ready,
std::vector<gfx::Rect> fake_pending) {
EXPECT_CALL(client_, OnPaint(expected_paint_rects, _, _))
.WillOnce([&fake_ready, &fake_pending](
const std::vector<gfx::Rect>& paint_rects,
std::vector<PaintReadyRect>& ready,
std::vector<gfx::Rect>& pending) {
ready = std::move(fake_ready);
pending = std::move(fake_pending);
});
sk_sp<SkImage> saved_snapshot;
base::RunLoop run_loop;
EXPECT_CALL(client_, UpdateSnapshot)
.WillOnce([&saved_snapshot, &run_loop](sk_sp<SkImage> snapshot) {
saved_snapshot = std::move(snapshot);
run_loop.Quit();
});
run_loop.Run();
return saved_snapshot;
}
SkBitmap GeneratePaintImageExpectation(const gfx::Size& plugin_size,
const gfx::Rect& paint_rect,
const gfx::Rect& overlapped_rect) {
sk_sp<SkSurface> surface =
CreateSkiaSurfaceForTesting(plugin_size, SK_ColorMAGENTA);
SkCanvas* canvas = surface->getCanvas();
canvas->clipIRect(gfx::RectToSkIRect(paint_rect));
canvas->clear(SK_ColorWHITE);
canvas->clipIRect(gfx::RectToSkIRect(overlapped_rect));
canvas->clear(SK_ColorRED);
SkBitmap bitmap;
EXPECT_TRUE(surface->makeImageSnapshot()->asLegacyBitmap(&bitmap));
return bitmap;
}
void TestPaintImage(const gfx::Size& plugin_size,
const gfx::Size& source_size,
const gfx::Rect& paint_rect,
const gfx::Rect& overlapped_rect) {
paint_manager_.SetSize(plugin_size, 1.0f);
sk_sp<SkImage> snapshot = WaitForFlush(
{{gfx::Rect(plugin_size)}},
{
{gfx::Rect(plugin_size),
CreateSkiaImageForTesting(plugin_size, SK_ColorMAGENTA)},
{paint_rect, CreateSkiaImageForTesting(source_size, SK_ColorRED)},
},
{});
ASSERT_TRUE(snapshot);
snapshot = snapshot->makeSubset(
skcpu::Recorder::TODO(),
SkIRect::MakeWH(plugin_size.width(), plugin_size.height()), {});
ASSERT_TRUE(snapshot);
SkBitmap snapshot_bitmap;
ASSERT_TRUE(snapshot->asLegacyBitmap(&snapshot_bitmap));
SkBitmap expected_bitmap =
GeneratePaintImageExpectation(plugin_size, paint_rect, overlapped_rect);
EXPECT_TRUE(cc::MatchesBitmap(snapshot_bitmap, expected_bitmap,
cc::ExactPixelComparator()));
}
void TestScroll(const gfx::Vector2d& scroll_amount,
const gfx::Rect& expected_paint_rect,
std::string_view expected_png) {
gfx::Size plugin_size = paint_manager_.GetEffectiveSize();
ASSERT_GE(plugin_size.width(), 4);
ASSERT_GE(plugin_size.height(), 4);
sk_sp<SkSurface> 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(
{gfx::Rect(plugin_size)},
{{gfx::Rect(plugin_size), initial_surface->makeImageSnapshot()}},
{}));
paint_manager_.ScrollRect(gfx::Rect(plugin_size), scroll_amount);
sk_sp<SkImage> snapshot = WaitForFlush(
{expected_paint_rect},
{{expected_paint_rect,
CreateSkiaImageForTesting(plugin_size, SK_ColorMAGENTA)}},
{});
ASSERT_TRUE(snapshot);
snapshot = snapshot->makeSubset(
skcpu::Recorder::TODO(),
SkIRect::MakeWH(plugin_size.width(), plugin_size.height()), {});
ASSERT_TRUE(snapshot);
EXPECT_TRUE(MatchesPngFile(*snapshot, GetTestDataFilePath(expected_png)));
}
NiceMock<FakeClient> 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},
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},
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) {
base::HistogramTester histograms;
paint_manager_.SetSize({400, 300}, 2.0f);
sk_sp<SkImage> snapshot =
WaitForFlush({{0, 0, 400, 300}},
{{{25, 50, 200, 100},
CreateSkiaImageForTesting({200, 100}, SK_ColorGRAY)}},
{});
ASSERT_TRUE(snapshot);
EXPECT_TRUE(
MatchesPngFile(*snapshot, GetTestDataFilePath("do_paint_first.png")));
histograms.ExpectTotalCount(kRenderAndPaintTimeMetric, 1);
histograms.ExpectTotalCount(kRenderPaintAndFlushTimeMetric, 1);
}
TEST_F(PaintManagerTest, PaintImage) {
base::HistogramTester histograms;
TestPaintImage({20, 20}, {15, 15},
{0, 0, 10, 10},
{0, 0, 10, 10});
TestPaintImage({50, 30}, {30, 50},
{10, 10, 30, 30},
{10, 10, 20, 20});
TestPaintImage({10, 10}, {30, 30},
{10, 10, 10, 10},
{0, 0, 0, 0});
TestPaintImage({15, 15}, {5, 5},
{10, 10, 5, 5},
{0, 0, 0, 0});
histograms.ExpectTotalCount(kRenderAndPaintTimeMetric, 4);
histograms.ExpectTotalCount(kRenderPaintAndFlushTimeMetric, 4);
}
TEST_F(PaintManagerTest, Scroll) {
base::HistogramTester histograms;
paint_manager_.SetSize({4, 5}, 1.0f);
TestScroll({1, 0}, {0, 0, 1, 5},
"scroll_right.png");
TestScroll({-2, 0}, {2, 0, 2, 5},
"scroll_left.png");
TestScroll({0, 3}, {0, 0, 4, 3},
"scroll_down.png");
TestScroll({0, -3}, {0, 2, 4, 3},
"scroll_up.png");
histograms.ExpectTotalCount(kRenderAndPaintTimeMetric, 8);
histograms.ExpectTotalCount(kRenderPaintAndFlushTimeMetric, 8);
}
TEST_F(PaintManagerTest, ScrollIgnored) {
base::HistogramTester histograms;
paint_manager_.SetSize({4, 5}, 1.0f);
TestScroll({4, 0}, {0, 0, 4, 5},
"scroll_ignored.png");
TestScroll({-4, 0}, {0, 0, 4, 5},
"scroll_ignored.png");
TestScroll({0, 5}, {0, 0, 4, 5},
"scroll_ignored.png");
TestScroll({0, -5}, {0, 0, 4, 5},
"scroll_ignored.png");
TestScroll({5, 0}, {0, 0, 4, 5},
"scroll_ignored.png");
TestScroll({-7, 0}, {0, 0, 4, 5},
"scroll_ignored.png");
TestScroll({0, 8}, {0, 0, 4, 5},
"scroll_ignored.png");
TestScroll({0, -9}, {0, 0, 4, 5},
"scroll_ignored.png");
histograms.ExpectTotalCount(kRenderAndPaintTimeMetric, 16);
histograms.ExpectTotalCount(kRenderPaintAndFlushTimeMetric, 16);
}
}
}