blob: 796327ac53291aa8f5731d349e235c9f9469b618 [file] [log] [blame]
// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/paint_preview/browser/paint_preview_client.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/files/scoped_temp_dir.h"
#include "base/functional/callback.h"
#include "base/notreached.h"
#include "base/strings/strcat.h"
#include "base/strings/utf_string_conversions.h"
#include "base/test/protobuf_matchers.h"
#include "base/test/test_future.h"
#include "base/unguessable_token.h"
#include "base/version.h"
#include "build/build_config.h"
#include "components/paint_preview/common/capture_result.h"
#include "components/paint_preview/common/file_stream.h"
#include "components/paint_preview/common/mock_paint_preview_recorder.h"
#include "components/paint_preview/common/mojom/paint_preview_recorder.mojom-data-view.h"
#include "components/paint_preview/common/mojom/paint_preview_recorder.mojom-forward.h"
#include "components/paint_preview/common/mojom/paint_preview_recorder.mojom.h"
#include "components/paint_preview/common/proto/paint_preview.pb.h"
#include "components/paint_preview/common/redaction_params.h"
#include "components/paint_preview/common/serialized_recording.h"
#include "components/paint_preview/common/test_utils.h"
#include "components/paint_preview/common/version.h"
#include "components/version_info/version_info.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/render_process_host.h"
#include "content/public/browser/web_contents.h"
#include "content/public/test/navigation_simulator.h"
#include "content/public/test/test_renderer_host.h"
#include "content/public/test/test_utils.h"
#include "mojo/public/cpp/base/big_buffer.h"
#include "mojo/public/cpp/bindings/associated_receiver.h"
#include "mojo/public/cpp/bindings/struct_ptr.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/common/associated_interfaces/associated_interface_provider.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/SkSurfaceProps.h"
namespace paint_preview {
using base::test::EqualsProto;
using testing::AnyOf;
using testing::Key;
using testing::UnorderedElementsAre;
using CaptureResultFuture =
base::test::TestFuture<base::UnguessableToken,
mojom::PaintPreviewStatus,
std::unique_ptr<CaptureResult>>;
namespace {
const std::string_view kMainFrameUrl = "https://www.chromium.org";
const std::string_view kOtherOriginUrl = "https://chromium.org";
// Convert |params| to the mojo::PaintPreviewServiceParams format. NOTE: this
// does not set the file parameter as the file is created in the client
// internals and should be treated as an opaque file (with an unknown path) in
// the render frame's service.
mojom::PaintPreviewCaptureParamsPtr ToMojoParams(
const PaintPreviewClient::PaintPreviewParams& params) {
mojom::PaintPreviewCaptureParamsPtr params_ptr =
mojom::PaintPreviewCaptureParams::New();
params_ptr->persistence = params.persistence;
params_ptr->guid = params.inner.get_document_guid();
params_ptr->is_main_frame = params.inner.is_main_frame;
params_ptr->geometry_metadata_params = mojom::GeometryMetadataParams::New();
params_ptr->geometry_metadata_params->clip_rect = params.inner.clip_rect;
params_ptr->skip_accelerated_content = params.inner.skip_accelerated_content;
return params_ptr;
}
base::FilePath ParseStringFilePath(std::string_view raw_path) {
return base::FilePath(
#if BUILDFLAG(IS_WIN)
base::UTF8ToWide(raw_path)
#else
raw_path
#endif
);
}
void VerifyFilePath(std::string_view raw_path,
const base::Location& location = FROM_HERE) {
base::ScopedAllowBlockingForTesting scope;
EXPECT_TRUE(base::PathExists(ParseStringFilePath(raw_path)))
<< "Expected at " << location.ToString();
}
base::UnguessableToken DeserializeFrameToken(
const PaintPreviewFrameProto& frame) {
auto token = base::UnguessableToken::Deserialize(frame.embedding_token_high(),
frame.embedding_token_low())
.value();
CHECK_NE(token, base::UnguessableToken::Null());
return token;
}
sk_sp<SkPicture> ReadPictureFromFile(std::string_view raw_path) {
base::ScopedAllowBlockingForTesting scope;
FileRStream rstream(
base::File(ParseStringFilePath(raw_path),
base::File::FLAG_OPEN_ALWAYS | base::File::FLAG_READ));
return SkPicture::MakeFromStream(&rstream, nullptr);
}
sk_sp<SkPicture> ReadPictureFromBuffer(mojo_base::BigBuffer buffer) {
base::ScopedAllowBlockingForTesting scope;
SkMemoryStream stream(buffer.data(), buffer.size(),
/*copyData=*/false);
return SkPicture::MakeFromStream(&stream, nullptr);
}
SkBitmap MakeBitmapFromPicture(sk_sp<SkPicture> pic) {
SkBitmap bitmap;
CHECK(bitmap.tryAllocN32Pixels(pic->cullRect().width(),
pic->cullRect().height()));
SkCanvas canvas(bitmap, SkSurfaceProps{});
canvas.drawPicture(pic);
return bitmap;
}
} // namespace
class PaintPreviewClientRenderViewHostTestBase
: public content::RenderViewHostTestHarness {
public:
PaintPreviewClientRenderViewHostTestBase() = default;
PaintPreviewClientRenderViewHostTestBase(
const PaintPreviewClientRenderViewHostTestBase&) = delete;
PaintPreviewClientRenderViewHostTestBase& operator=(
const PaintPreviewClientRenderViewHostTestBase&) = delete;
protected:
void SetUp() override {
ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
RenderViewHostTestHarness::SetUp();
content::RenderFrameHostTester::For(main_rfh())
->InitializeRenderFrameIfNeeded();
content::NavigationSimulator::NavigateAndCommitFromBrowser(
web_contents(), GURL(kMainFrameUrl));
}
void TearDown() override {
#if BUILDFLAG(IS_POSIX)
base::SetPosixFilePermissions(temp_dir_.GetPath(), 0777);
#endif
content::RenderViewHostTestHarness::TearDown();
}
void OverrideInterface(content::RenderFrameHost* rfh,
MockPaintPreviewRecorder* service) {
ASSERT_TRUE(rfh);
blink::AssociatedInterfaceProvider* remote_interfaces =
rfh->GetRemoteAssociatedInterfaces();
remote_interfaces->OverrideBinderForTesting(
mojom::PaintPreviewRecorder::Name_,
base::BindRepeating(&MockPaintPreviewRecorder::BindRequest,
base::Unretained(service)));
}
content::RenderFrameHost* AddChildRFH(content::RenderFrameHost* parent,
std::string_view origin) {
content::RenderFrameHost* result =
content::RenderFrameHostTester::For(parent)->AppendChild("");
content::RenderFrameHostTester::For(result)
->InitializeRenderFrameIfNeeded();
SimulateNavigation(&result, GURL(origin));
return result;
}
void SimulateNavigation(content::RenderFrameHost** rfh, const GURL& url) {
auto navigation_simulator =
content::NavigationSimulator::CreateRendererInitiated(url, *rfh);
navigation_simulator->Commit();
*rfh = navigation_simulator->GetFinalRenderFrameHost();
}
base::ScopedTempDir temp_dir_;
private:
};
class PaintPreviewClientRenderViewHostTest
: public PaintPreviewClientRenderViewHostTestBase,
public testing::WithParamInterface<RecordingPersistence> {
public:
mojo::StructPtr<mojom::PaintPreviewCaptureResponse>
NewMockPaintPreviewCaptureResponse() {
auto response = mojom::PaintPreviewCaptureResponse::New();
response->geometry_metadata = mojom::GeometryMetadataResponse::New();
if (GetParam() == RecordingPersistence::kMemoryBuffer) {
response->skp = {mojo_base::BigBuffer()};
}
return response;
}
private:
};
TEST_P(PaintPreviewClientRenderViewHostTest, CaptureMainFrameMock) {
PaintPreviewClient::PaintPreviewParams params(GetParam());
params.root_dir = temp_dir_.GetPath();
params.inner.is_main_frame = true;
content::RenderFrameHost* rfh = main_rfh();
GURL expected_url = rfh->GetLastCommittedURL();
auto response = NewMockPaintPreviewCaptureResponse();
response->embedding_token = std::nullopt;
response->geometry_metadata->scroll_offsets = gfx::Point(5, 10);
response->geometry_metadata->frame_offsets = gfx::Point(20, 30);
PaintPreviewProto expected_proto;
auto* metadata = expected_proto.mutable_metadata();
metadata->set_url(expected_url.spec());
metadata->set_version(kPaintPreviewVersion);
auto* chromeVersion = metadata->mutable_chrome_version();
const auto& current_chrome_version = version_info::GetVersion();
chromeVersion->set_major(current_chrome_version.components()[0]);
chromeVersion->set_minor(current_chrome_version.components()[1]);
chromeVersion->set_build(current_chrome_version.components()[2]);
chromeVersion->set_patch(current_chrome_version.components()[3]);
PaintPreviewFrameProto* main_frame = expected_proto.mutable_root_frame();
main_frame->set_is_main_frame(true);
main_frame->set_scroll_offset_x(5);
main_frame->set_scroll_offset_y(10);
main_frame->set_frame_offset_x(20);
main_frame->set_frame_offset_y(30);
MockPaintPreviewRecorder service;
service.SetExpectedParams(ToMojoParams(params));
service.SetResponse(std::move(response));
OverrideInterface(rfh, &service);
PaintPreviewClient::CreateForWebContents(web_contents());
auto* client = PaintPreviewClient::FromWebContents(web_contents());
ASSERT_NE(client, nullptr);
CaptureResultFuture main_capture_future;
client->CapturePaintPreview(params.Clone(), rfh,
main_capture_future.GetCallback());
auto [returned_guid, status, result] = main_capture_future.Take();
EXPECT_EQ(returned_guid, params.inner.get_document_guid());
EXPECT_EQ(status, mojom::PaintPreviewStatus::kOk);
auto token = DeserializeFrameToken(result->proto.root_frame());
// The token for the main frame is set internally since the render frame
// host won't have one. To simplify the proto comparison using
// EqualsProto copy the generated one into |expected_proto|.
main_frame->set_embedding_token_low(token.GetLowForSerialization());
main_frame->set_embedding_token_high(token.GetHighForSerialization());
if (GetParam() == RecordingPersistence::kFileSystem) {
main_frame->set_file_path(
temp_dir_.GetPath()
.AppendASCII(base::StrCat({token.ToString(), ".skp"}))
.AsUTF8Unsafe());
}
EXPECT_THAT(result->proto, EqualsProto(expected_proto));
switch (GetParam()) {
case RecordingPersistence::kFileSystem:
VerifyFilePath(result->proto.root_frame().file_path());
break;
case RecordingPersistence::kMemoryBuffer: {
EXPECT_EQ(result->serialized_skps.size(), 1u);
EXPECT_TRUE(result->serialized_skps.contains(token));
} break;
default:
NOTREACHED();
}
}
TEST_P(PaintPreviewClientRenderViewHostTest, CaptureFailureMock) {
PaintPreviewClient::PaintPreviewParams params(GetParam());
params.root_dir = temp_dir_.GetPath();
params.inner.is_main_frame = true;
MockPaintPreviewRecorder recorder;
recorder.SetExpectedParams(ToMojoParams(params));
recorder.SetResponse(
base::unexpected(mojom::PaintPreviewStatus::kCaptureFailed));
OverrideInterface(main_rfh(), &recorder);
PaintPreviewClient::CreateForWebContents(web_contents());
auto* client = PaintPreviewClient::FromWebContents(web_contents());
ASSERT_NE(client, nullptr);
CaptureResultFuture main_capture_future;
client->CapturePaintPreview(params.Clone(), main_rfh(),
main_capture_future.GetCallback());
auto [returned_guid, status, result] = main_capture_future.Take();
EXPECT_EQ(returned_guid, params.inner.get_document_guid());
EXPECT_EQ(status, mojom::PaintPreviewStatus::kFailed);
content::RunAllTasksUntilIdle();
EXPECT_TRUE(base::IsDirectoryEmpty(temp_dir_.GetPath()));
}
// We can only mutate file permissions on POSIX systems.
#if BUILDFLAG(IS_POSIX)
TEST_F(PaintPreviewClientRenderViewHostTestBase, SubframeFileCreationFails) {
auto* subframe = AddChildRFH(main_rfh(), "https://example.test");
ASSERT_TRUE(subframe);
PaintPreviewClient::PaintPreviewParams params(
RecordingPersistence::kFileSystem);
params.root_dir = temp_dir_.GetPath();
params.inner.is_main_frame = true;
MockPaintPreviewRecorder recorder;
recorder.SetExpectedParams(ToMojoParams(params));
recorder.SetResponse(
base::unexpected(mojom::PaintPreviewStatus::kCaptureFailed));
OverrideInterface(main_rfh(), &recorder);
PaintPreviewClient::CreateForWebContents(web_contents());
auto* client = PaintPreviewClient::FromWebContents(web_contents());
ASSERT_NE(client, nullptr);
base::test::TestFuture<void> recorder_received_request_future;
recorder.SetReceivedRequestClosure(
recorder_received_request_future.GetCallback());
CaptureResultFuture main_capture_future;
client->CapturePaintPreview(params.Clone(), main_rfh(),
main_capture_future.GetCallback());
ASSERT_TRUE(recorder_received_request_future.Wait());
// Before sending the main frame response, make the directory read-only to
// cause a file creation error on subsequent writes, and issue a reentrant
// call for a subframe, to see if the main frame's control flow mistakenly
// invokes the callback even after the subframe failed the whole capture.
base::SetPosixFilePermissions(temp_dir_.GetPath(), 0555);
client->CaptureSubframePaintPreview(params.inner.get_document_guid(),
gfx::Rect(), subframe);
recorder.SendResponse();
auto [returned_guid, status, result] = main_capture_future.Take();
EXPECT_EQ(returned_guid, params.inner.get_document_guid());
// The precise status from the client depends on thread scheduling.
EXPECT_THAT(status, AnyOf(mojom::PaintPreviewStatus::kFileCreationError,
mojom::PaintPreviewStatus::kFailed));
EXPECT_EQ(result, nullptr);
}
#endif
TEST_P(PaintPreviewClientRenderViewHostTest, RenderFrameDeletedNotCapturing) {
// Test that a deleting a render frame doesn't cause any problems if not
// capturing.
PaintPreviewClient::CreateForWebContents(web_contents());
auto* client = PaintPreviewClient::FromWebContents(web_contents());
ASSERT_NE(client, nullptr);
client->RenderFrameDeleted(main_rfh());
}
TEST_P(PaintPreviewClientRenderViewHostTest, RenderFrameDeletedDuringCapture) {
PaintPreviewClient::PaintPreviewParams params(GetParam());
params.root_dir = temp_dir_.GetPath();
params.inner.is_main_frame = true;
params.inner.skip_accelerated_content = true;
content::RenderFrameHost* rfh = main_rfh();
auto response = NewMockPaintPreviewCaptureResponse();
response->geometry_metadata = mojom::GeometryMetadataResponse::New();
response->embedding_token = std::nullopt;
MockPaintPreviewRecorder service;
service.SetExpectedParams(ToMojoParams(params));
service.SetResponse(std::move(response));
OverrideInterface(rfh, &service);
PaintPreviewClient::CreateForWebContents(web_contents());
auto* client = PaintPreviewClient::FromWebContents(web_contents());
ASSERT_NE(client, nullptr);
base::test::TestFuture<void> service_received_request_future;
service.SetReceivedRequestClosure(
service_received_request_future.GetCallback());
CaptureResultFuture main_capture_future;
client->CapturePaintPreview(params.Clone(), rfh,
main_capture_future.GetCallback());
ASSERT_TRUE(service_received_request_future.Wait());
client->RenderFrameDeleted(rfh);
service.SendResponse();
auto [guid, status, result] = main_capture_future.Take();
EXPECT_EQ(status, mojom::PaintPreviewStatus::kFailed);
EXPECT_EQ(result, nullptr);
}
INSTANTIATE_TEST_SUITE_P(
All,
PaintPreviewClientRenderViewHostTest,
testing::Values(RecordingPersistence::kFileSystem,
RecordingPersistence::kMemoryBuffer),
[](const testing::TestParamInfo<RecordingPersistence>& info) {
return std::string(PersistenceToString(info.param));
});
enum class ResponseOrdering { kMainFrameThenSubframe, kSubframeThenMainFrame };
std::string_view OrderingToString(ResponseOrdering ordering) {
switch (ordering) {
case ResponseOrdering::kMainFrameThenSubframe:
return "kMainFrameThenSubframe";
case ResponseOrdering::kSubframeThenMainFrame:
return "kSubframeThenMainFrame";
}
NOTREACHED();
}
class PaintPreviewClientRenderViewHostResponseOrderingTest
: public PaintPreviewClientRenderViewHostTestBase,
public testing::WithParamInterface<
std::tuple<RecordingPersistence, ResponseOrdering>> {
public:
mojo::StructPtr<mojom::PaintPreviewCaptureResponse>
NewMockPaintPreviewCaptureResponse() {
auto response = mojom::PaintPreviewCaptureResponse::New();
response->geometry_metadata = mojom::GeometryMetadataResponse::New();
if (persistence() == RecordingPersistence::kMemoryBuffer) {
response->skp = {mojo_base::BigBuffer()};
}
return response;
}
RecordingPersistence persistence() const { return std::get<0>(GetParam()); }
ResponseOrdering ordering() const { return std::get<1>(GetParam()); }
private:
};
TEST_P(PaintPreviewClientRenderViewHostResponseOrderingTest,
CaptureMainFrameWithSubframe) {
content::RenderFrameHost* rfh = main_rfh();
content::RenderFrameHost* subframe =
AddChildRFH(rfh, "https://www.chromium.org");
GURL expected_url = rfh->GetLastCommittedURL();
PaintPreviewClient::PaintPreviewParams main_frame_params(persistence());
main_frame_params.root_dir = temp_dir_.GetPath();
main_frame_params.inner.is_main_frame = true;
auto main_frame_response = NewMockPaintPreviewCaptureResponse();
main_frame_response->embedding_token = std::nullopt;
main_frame_response->geometry_metadata->scroll_offsets = gfx::Point(5, 10);
main_frame_response->geometry_metadata->frame_offsets = gfx::Point(20, 30);
auto subframe_response = NewMockPaintPreviewCaptureResponse();
subframe_response->embedding_token = std::nullopt;
subframe_response->geometry_metadata->scroll_offsets = gfx::Point(5, 10);
subframe_response->geometry_metadata->frame_offsets = gfx::Point(20, 30);
MockPaintPreviewRecorder main_frame_recorder;
main_frame_recorder.SetExpectedParams(ToMojoParams(main_frame_params));
main_frame_recorder.SetResponse(std::move(main_frame_response));
base::test::TestFuture<void> main_frame_req;
main_frame_recorder.SetReceivedRequestClosure(main_frame_req.GetCallback());
OverrideInterface(rfh, &main_frame_recorder);
const gfx::Rect subframe_rect(0, 0, 100, 200);
PaintPreviewClient::PaintPreviewParams expected_subframe_params =
PaintPreviewClient::PaintPreviewParams::CreateForTesting(
persistence(), main_frame_params.inner.get_document_guid());
expected_subframe_params.root_dir = temp_dir_.GetPath();
expected_subframe_params.inner.is_main_frame = false;
expected_subframe_params.inner.clip_rect = subframe_rect;
MockPaintPreviewRecorder subframe_recorder;
subframe_recorder.SetExpectedParams(ToMojoParams(expected_subframe_params));
subframe_recorder.SetResponse(std::move(subframe_response));
base::test::TestFuture<void> subframe_req;
subframe_recorder.SetReceivedRequestClosure(subframe_req.GetCallback());
OverrideInterface(subframe, &subframe_recorder);
PaintPreviewClient::CreateForWebContents(web_contents());
auto* client = PaintPreviewClient::FromWebContents(web_contents());
ASSERT_NE(client, nullptr);
CaptureResultFuture main_capture_future;
client->CapturePaintPreview(main_frame_params.Clone(), rfh,
main_capture_future.GetCallback());
ASSERT_TRUE(main_frame_req.Wait());
client->CaptureSubframePaintPreview(
main_frame_params.inner.get_document_guid(), subframe_rect, subframe);
ASSERT_TRUE(subframe_req.Wait());
switch (ordering()) {
case ResponseOrdering::kMainFrameThenSubframe:
main_frame_recorder.SendResponse();
subframe_recorder.SendResponse();
break;
case ResponseOrdering::kSubframeThenMainFrame:
subframe_recorder.SendResponse();
main_frame_recorder.SendResponse();
break;
}
auto [main_guid, main_status, result] = main_capture_future.Take();
EXPECT_EQ(main_guid, main_frame_params.inner.get_document_guid());
EXPECT_EQ(main_status, mojom::PaintPreviewStatus::kOk);
auto token = DeserializeFrameToken(result->proto.root_frame());
switch (persistence()) {
case RecordingPersistence::kFileSystem:
VerifyFilePath(result->proto.root_frame().file_path());
ASSERT_EQ(result->proto.subframes().size(), 1);
VerifyFilePath(result->proto.subframes()[0].file_path());
break;
case RecordingPersistence::kMemoryBuffer:
EXPECT_EQ(result->serialized_skps.size(), 2u);
EXPECT_TRUE(result->serialized_skps.contains(token));
break;
default:
NOTREACHED();
}
}
TEST_P(PaintPreviewClientRenderViewHostResponseOrderingTest,
CaptureMainFrameWithSubframe_RedactedIframe) {
content::RenderFrameHost* rfh = main_rfh();
content::RenderFrameHost* subframe =
AddChildRFH(rfh, "https://www.chromium.org");
GURL expected_url = rfh->GetLastCommittedURL();
PaintPreviewClient::PaintPreviewParams main_frame_params(persistence());
main_frame_params.root_dir = temp_dir_.GetPath();
main_frame_params.inner.is_main_frame = true;
main_frame_params.inner.redaction_params =
RedactionParams({url::Origin::Create(GURL(kOtherOriginUrl))}, {});
auto main_frame_response = NewMockPaintPreviewCaptureResponse();
main_frame_response->embedding_token = std::nullopt;
main_frame_response->geometry_metadata->scroll_offsets = gfx::Point(0, 0);
main_frame_response->geometry_metadata->frame_offsets = gfx::Point(0, 0);
auto subframe_response = mojom::GeometryMetadataResponse::New();
subframe_response->scroll_offsets = gfx::Point(0, 0);
subframe_response->frame_offsets = gfx::Point(0, 0);
MockPaintPreviewRecorder main_frame_recorder;
main_frame_recorder.SetExpectedParams(ToMojoParams(main_frame_params));
main_frame_recorder.SetResponse(std::move(main_frame_response));
base::test::TestFuture<void> main_frame_req;
main_frame_recorder.SetReceivedRequestClosure(main_frame_req.GetCallback());
OverrideInterface(rfh, &main_frame_recorder);
const gfx::Rect subframe_rect(0, 0, 100, 200);
MockPaintPreviewRecorder subframe_recorder;
auto subframe_geo_params = mojom::GeometryMetadataParams::New();
subframe_geo_params->clip_rect = subframe_rect;
subframe_recorder.SetExpectedGeometryParams(subframe_geo_params.Clone());
subframe_recorder.SetGeometryResponse(subframe_response.Clone());
base::test::TestFuture<void> subframe_req;
subframe_recorder.SetReceivedRequestClosure(subframe_req.GetCallback());
OverrideInterface(subframe, &subframe_recorder);
PaintPreviewClient::CreateForWebContents(web_contents());
auto* client = PaintPreviewClient::FromWebContents(web_contents());
ASSERT_NE(client, nullptr);
CaptureResultFuture main_capture_future;
client->CapturePaintPreview(main_frame_params.Clone(), rfh,
main_capture_future.GetCallback());
ASSERT_TRUE(main_frame_req.Wait());
client->CaptureSubframePaintPreview(
main_frame_params.inner.get_document_guid(), subframe_rect, subframe);
ASSERT_TRUE(subframe_req.Wait());
switch (ordering()) {
case ResponseOrdering::kMainFrameThenSubframe:
main_frame_recorder.SendResponse();
subframe_recorder.SendGeometryResponse();
break;
case ResponseOrdering::kSubframeThenMainFrame:
subframe_recorder.SendGeometryResponse();
main_frame_recorder.SendResponse();
break;
}
auto [main_guid, main_status, result] = main_capture_future.Take();
EXPECT_EQ(main_guid, main_frame_params.inner.get_document_guid());
EXPECT_EQ(main_status, mojom::PaintPreviewStatus::kOk);
sk_sp<SkPicture> skp;
switch (persistence()) {
case RecordingPersistence::kFileSystem:
VerifyFilePath(result->proto.root_frame().file_path());
ASSERT_EQ(result->proto.subframes().size(), 1);
VerifyFilePath(result->proto.subframes(0).file_path());
skp = ReadPictureFromFile(result->proto.subframes(0).file_path());
break;
case RecordingPersistence::kMemoryBuffer: {
auto subframe_token = DeserializeFrameToken(result->proto.subframes(0));
ASSERT_THAT(result->serialized_skps,
UnorderedElementsAre(
Key(DeserializeFrameToken(result->proto.root_frame())),
Key(subframe_token)));
skp = ReadPictureFromBuffer(
std::move(result->serialized_skps.at(subframe_token)));
break;
}
}
SkBitmap subframe_bitmap = MakeBitmapFromPicture(skp);
EXPECT_EQ(subframe_bitmap.width(), subframe_rect.width());
EXPECT_EQ(subframe_bitmap.height(), subframe_rect.height());
EXPECT_EQ(subframe_bitmap.getColor(0, 0), SK_ColorBLACK);
EXPECT_EQ(subframe_bitmap.getColor(subframe_bitmap.width() - 1,
subframe_bitmap.height() - 1),
SK_ColorBLACK);
}
INSTANTIATE_TEST_SUITE_P(
,
PaintPreviewClientRenderViewHostResponseOrderingTest,
testing::Combine(testing::Values(RecordingPersistence::kFileSystem,
RecordingPersistence::kMemoryBuffer),
testing::Values(ResponseOrdering::kMainFrameThenSubframe,
ResponseOrdering::kSubframeThenMainFrame)),
[](const testing::TestParamInfo<
std::tuple<paint_preview::RecordingPersistence, ResponseOrdering>>&
param) {
return base::StrCat({
PersistenceToString(std::get<0>(param.param)),
"_",
OrderingToString(std::get<1>(param.param)),
});
});
} // namespace paint_preview