blob: 6e6e154f10a65bd22e180cf8aeba19bc40aa3f07 [file] [log] [blame]
// Copyright 2019 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 "components/paint_preview/renderer/paint_preview_recorder_utils.h"
#include <memory>
#include <string>
#include "base/containers/flat_map.h"
#include "base/containers/flat_set.h"
#include "base/files/file.h"
#include "base/files/scoped_temp_dir.h"
#include "base/memory/read_only_shared_memory_region.h"
#include "base/notreached.h"
#include "base/optional.h"
#include "base/unguessable_token.h"
#include "cc/paint/paint_canvas.h"
#include "cc/paint/paint_flags.h"
#include "cc/paint/paint_recorder.h"
#include "components/paint_preview/common/file_stream.h"
#include "components/paint_preview/common/mojom/paint_preview_recorder.mojom-shared.h"
#include "components/paint_preview/common/paint_preview_tracker.h"
#include "components/paint_preview/common/serialized_recording.h"
#include "components/paint_preview/common/test_utils.h"
#include "mojo/public/cpp/base/big_buffer.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/skia/include/core/SkFont.h"
#include "third_party/skia/include/core/SkRefCnt.h"
#include "third_party/skia/include/core/SkTextBlob.h"
#include "third_party/skia/include/core/SkTypeface.h"
namespace paint_preview {
namespace {
sk_sp<cc::PaintRecord> AddLink(const std::string& link, const SkRect& rect) {
cc::PaintRecorder link_recorder;
cc::PaintCanvas* link_canvas = link_recorder.beginRecording(
rect.x() + rect.width(), rect.y() + rect.height());
link_canvas->Annotate(cc::PaintCanvas::AnnotationType::URL, rect,
SkData::MakeWithCString(link.c_str()));
return link_recorder.finishRecordingAsPicture();
}
} // namespace
TEST(PaintPreviewRecorderUtilsTest, TestParseGlyphs) {
auto typeface = SkTypeface::MakeDefault();
SkFont font(typeface);
std::string unichars_1 = "abc";
std::string unichars_2 = "efg";
auto blob_1 = SkTextBlob::MakeFromString(unichars_1.c_str(), font);
auto blob_2 = SkTextBlob::MakeFromString(unichars_2.c_str(), font);
cc::PaintFlags flags;
cc::PaintRecorder outer_recorder;
cc::PaintCanvas* outer_canvas = outer_recorder.beginRecording(100, 100);
outer_canvas->drawTextBlob(blob_1, 10, 10, flags);
cc::PaintRecorder inner_recorder;
cc::PaintCanvas* inner_canvas = inner_recorder.beginRecording(50, 50);
inner_canvas->drawTextBlob(blob_2, 15, 20, flags);
outer_canvas->drawPicture(inner_recorder.finishRecordingAsPicture());
auto record = outer_recorder.finishRecordingAsPicture();
PaintPreviewTracker tracker(base::UnguessableToken::Create(),
base::UnguessableToken::Create(), true);
ParseGlyphsAndLinks(record.get(), &tracker);
auto* usage_map = tracker.GetTypefaceUsageMap();
EXPECT_TRUE(usage_map->count(typeface->uniqueID()));
EXPECT_TRUE(
(*usage_map)[typeface->uniqueID()]->IsSet(typeface->unicharToGlyph('a')));
EXPECT_TRUE(
(*usage_map)[typeface->uniqueID()]->IsSet(typeface->unicharToGlyph('b')));
EXPECT_TRUE(
(*usage_map)[typeface->uniqueID()]->IsSet(typeface->unicharToGlyph('c')));
EXPECT_TRUE(
(*usage_map)[typeface->uniqueID()]->IsSet(typeface->unicharToGlyph('e')));
EXPECT_TRUE(
(*usage_map)[typeface->uniqueID()]->IsSet(typeface->unicharToGlyph('f')));
EXPECT_TRUE(
(*usage_map)[typeface->uniqueID()]->IsSet(typeface->unicharToGlyph('g')));
}
TEST(PaintPreviewRecorderUtilsTest, TestParseLinks) {
cc::PaintFlags flags;
cc::PaintRecorder outer_recorder;
cc::PaintCanvas* outer_canvas = outer_recorder.beginRecording(500, 500);
outer_canvas->save();
outer_canvas->translate(10, 20);
std::string link_1 = "http://www.foo.com/";
SkRect rect_1 = SkRect::MakeXYWH(10, 20, 30, 40);
outer_canvas->drawPicture(AddLink(link_1, rect_1));
outer_canvas->restore();
outer_canvas->save();
outer_canvas->concat(SkMatrix::Translate(40, 50));
outer_canvas->scale(2, 4);
std::string link_2 = "http://www.bar.com/";
SkRect rect_2 = SkRect::MakeXYWH(1, 2, 3, 4);
outer_canvas->drawPicture(AddLink(link_2, rect_2));
cc::PaintRecorder inner_recorder;
cc::PaintCanvas* inner_canvas = inner_recorder.beginRecording(500, 500);
inner_canvas->rotate(20);
std::string link_3 = "http://www.baz.com/";
SkRect rect_3 = SkRect::MakeXYWH(5, 7, 9, 13);
inner_canvas->drawPicture(AddLink(link_3, rect_3));
outer_canvas->drawPicture(inner_recorder.finishRecordingAsPicture());
outer_canvas->restore();
outer_canvas->save();
outer_canvas->translate(20, 50);
outer_canvas->setMatrix(SkMatrix::Translate(10, 30));
std::string link_4 = "http://www.example.com/";
SkRect rect_4 = SkRect::MakeXYWH(10, 30, 40, 50);
outer_canvas->drawPicture(AddLink(link_4, rect_4));
outer_canvas->restore();
outer_canvas->saveLayer(&rect_1, nullptr);
outer_canvas->saveLayerAlpha(&rect_1, 8);
outer_canvas->restoreToCount(1);
auto record = outer_recorder.finishRecordingAsPicture();
PaintPreviewTracker tracker(base::UnguessableToken::Create(),
base::UnguessableToken::Create(), true);
ParseGlyphsAndLinks(record.get(), &tracker);
std::vector<mojom::LinkDataPtr> links;
tracker.MoveLinks(&links);
ASSERT_EQ(links.size(), 4U);
EXPECT_EQ(links[0]->url, link_1);
EXPECT_EQ(links[0]->rect.x(), rect_1.x() + 10);
EXPECT_EQ(links[0]->rect.y(), rect_1.y() + 20);
EXPECT_EQ(links[0]->rect.width(), rect_1.width());
EXPECT_EQ(links[0]->rect.height(), rect_1.height());
EXPECT_EQ(links[1]->url, link_2);
EXPECT_EQ(links[1]->rect.x(), rect_2.x() * 2 + 40);
EXPECT_EQ(links[1]->rect.y(), rect_2.y() * 4 + 50);
EXPECT_EQ(links[1]->rect.width(), rect_2.width() * 2);
EXPECT_EQ(links[1]->rect.height(), rect_2.height() * 4);
EXPECT_EQ(links[2]->url, link_3);
EXPECT_EQ(links[2]->rect.x(), 35);
EXPECT_EQ(links[2]->rect.y(), 83);
EXPECT_EQ(links[2]->rect.width(), 25);
EXPECT_EQ(links[2]->rect.height(), 61);
EXPECT_EQ(links[3]->url, link_4);
EXPECT_EQ(links[3]->rect.x(), rect_4.x() + 10);
EXPECT_EQ(links[3]->rect.y(), rect_4.y() + 30);
EXPECT_EQ(links[3]->rect.width(), rect_4.width());
EXPECT_EQ(links[3]->rect.height(), rect_4.height());
}
TEST(PaintPreviewRecorderUtilsTest, TestTransformSubframeRects) {
PaintPreviewTracker tracker(base::UnguessableToken::Create(),
base::UnguessableToken::Create(), true);
gfx::Rect rect(20, 30, 40, 50);
auto subframe_token = base::UnguessableToken::Create();
int old_id = tracker.CreateContentForRemoteFrame(rect, subframe_token);
cc::PaintFlags flags;
cc::PaintRecorder recorder;
cc::PaintCanvas* canvas = recorder.beginRecording(500, 500);
canvas->save();
canvas->translate(10, 20);
canvas->recordCustomData(old_id);
canvas->restore();
auto record = recorder.finishRecordingAsPicture();
auto map = tracker.GetSubframePicsForTesting();
auto it = map.find(old_id);
ASSERT_NE(it, map.end());
auto old_cull_rect = it->second->cullRect();
EXPECT_EQ(rect.x(), old_cull_rect.x());
EXPECT_EQ(rect.y(), old_cull_rect.y());
EXPECT_EQ(rect.width(), old_cull_rect.width());
EXPECT_EQ(rect.height(), old_cull_rect.height());
ParseGlyphsAndLinks(record.get(), &tracker);
map = tracker.GetSubframePicsForTesting();
ASSERT_EQ(map.size(), 1U);
// Iterate over the one element since we don't know the key.
for (const auto& pair : map) {
auto old_cull_rect = pair.second->cullRect();
EXPECT_EQ(rect.x() + 10, old_cull_rect.x());
EXPECT_EQ(rect.y() + 20, old_cull_rect.y());
EXPECT_EQ(rect.width(), old_cull_rect.width());
EXPECT_EQ(rect.height(), old_cull_rect.height());
}
}
class PaintPreviewRecorderUtilsSerializeAsSkPictureTest
: public testing::TestWithParam<RecordingPersistence> {
public:
PaintPreviewRecorderUtilsSerializeAsSkPictureTest()
: tracker(base::UnguessableToken::Create(),
base::UnguessableToken::Create(),
true),
dimensions(100, 100),
recorder() {}
~PaintPreviewRecorderUtilsSerializeAsSkPictureTest() override = default;
protected:
void SetUp() override {
canvas = recorder.beginRecording(dimensions.width(), dimensions.width());
cc::PaintFlags flags;
canvas->drawRect(SkRect::MakeWH(dimensions.width(), dimensions.height()),
flags);
}
base::Optional<SerializedRecording> SerializeAsSkPicture(
base::Optional<size_t> max_capture_size,
size_t* serialized_size) {
auto skp = PaintRecordToSkPicture(recorder.finishRecordingAsPicture(),
&tracker, dimensions);
if (!skp)
return base::nullopt;
canvas = nullptr;
switch (GetParam()) {
case RecordingPersistence::kFileSystem: {
base::ScopedTempDir temp_dir;
if (!temp_dir.CreateUniqueTempDir())
return base::nullopt;
base::FilePath file_path = temp_dir.GetPath().AppendASCII("test_file");
base::File write_file(
file_path, base::File::FLAG_CREATE_ALWAYS | base::File::FLAG_WRITE);
if (!RecordToFile(std::move(write_file), skp, &tracker,
max_capture_size, serialized_size))
return base::nullopt;
return {SerializedRecording(file_path)};
} break;
case RecordingPersistence::kMemoryBuffer: {
base::Optional<mojo_base::BigBuffer> buffer =
RecordToBuffer(skp, &tracker, max_capture_size, serialized_size);
if (!buffer.has_value())
return base::nullopt;
return {SerializedRecording(std::move(buffer.value()))};
} break;
}
NOTREACHED();
return base::nullopt;
}
PaintPreviewTracker tracker;
gfx::Rect dimensions;
cc::PaintRecorder recorder;
// Valid after SetUp() until SerializeAsSkPicture() is called.
cc::PaintCanvas* canvas{};
};
TEST_P(PaintPreviewRecorderUtilsSerializeAsSkPictureTest, Roundtrip) {
base::flat_set<uint32_t> ctx;
uint32_t content_id = tracker.CreateContentForRemoteFrame(
gfx::Rect(10, 10), base::UnguessableToken::Create());
canvas->recordCustomData(content_id);
ctx.insert(content_id);
content_id = tracker.CreateContentForRemoteFrame(
gfx::Rect(20, 20), base::UnguessableToken::Create());
canvas->recordCustomData(content_id);
ctx.insert(content_id);
size_t out_size = 0;
auto recording = SerializeAsSkPicture(base::nullopt, &out_size);
ASSERT_TRUE(recording.has_value());
base::Optional<SkpResult> result = std::move(recording.value()).Deserialize();
ASSERT_TRUE(result.has_value());
for (auto& content_id : ctx) {
EXPECT_TRUE(result->ctx.contains(content_id));
result->ctx.erase(content_id);
}
EXPECT_TRUE(result->ctx.empty());
}
TEST_P(PaintPreviewRecorderUtilsSerializeAsSkPictureTest, FailIfExceedMaxSize) {
size_t out_size = 2;
auto recording = SerializeAsSkPicture({1}, &out_size);
EXPECT_FALSE(recording.has_value());
EXPECT_LE(out_size, 1U);
}
INSTANTIATE_TEST_SUITE_P(All,
PaintPreviewRecorderUtilsSerializeAsSkPictureTest,
testing::Values(RecordingPersistence::kFileSystem,
RecordingPersistence::kMemoryBuffer),
PersistenceParamToString);
TEST(PaintPreviewRecorderUtilsTest, TestBuildResponse) {
auto token = base::UnguessableToken::Create();
auto embedding_token = base::UnguessableToken::Create();
PaintPreviewTracker tracker(token, embedding_token, true);
tracker.AnnotateLink(GURL("www.google.com"), SkRect::MakeXYWH(1, 2, 3, 4));
tracker.AnnotateLink(GURL("www.chromium.org"),
SkRect::MakeXYWH(10, 20, 10, 20));
tracker.CreateContentForRemoteFrame(gfx::Rect(1, 1, 1, 1),
base::UnguessableToken::Create());
tracker.CreateContentForRemoteFrame(gfx::Rect(1, 2, 4, 8),
base::UnguessableToken::Create());
auto response = mojom::PaintPreviewCaptureResponse::New();
BuildResponse(&tracker, response.get());
EXPECT_EQ(response->embedding_token, embedding_token);
EXPECT_EQ(response->links.size(), 2U);
EXPECT_THAT(response->links[0]->url, GURL("www.google.com"));
EXPECT_THAT(response->links[0]->rect, gfx::Rect(1, 2, 3, 4));
EXPECT_THAT(response->links[1]->url, GURL("www.chromium.org"));
EXPECT_THAT(response->links[1]->rect, gfx::Rect(10, 20, 10, 20));
auto* content_map = tracker.GetPictureSerializationContext();
for (const auto& id_pair : response->content_id_to_embedding_token) {
auto it = content_map->find(id_pair.first);
EXPECT_NE(it, content_map->end());
EXPECT_EQ(id_pair.first, it->first);
EXPECT_EQ(id_pair.second, it->second);
}
}
} // namespace paint_preview