Commit 3a5ea04e authored by Michael Tang's avatar Michael Tang Committed by Commit Bot

Allow PaintPreviewCapture to be parameterized over where to store its intermediate artifacts.

Allows for subframe recordings to be saved to either the disk or memory buffers.

Change-Id: I0630babd421716ed9bf354e22a3b8eca8ce46932
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2274166Reviewed-by: default avatarCalder Kitagawa <ckitagawa@chromium.org>
Reviewed-by: default avatarKen Buchanan <kenrb@chromium.org>
Commit-Queue: Ken Buchanan <kenrb@chromium.org>
Cr-Commit-Position: refs/heads/master@{#791529}
parent 9382efe1
......@@ -111,8 +111,9 @@ void PaintPreviewDemoService::OnCaptured(
FinishedSuccessfullyCallback callback,
const DirectoryKey& key,
PaintPreviewBaseService::CaptureStatus status,
std::unique_ptr<PaintPreviewProto> proto) {
if (status != PaintPreviewBaseService::CaptureStatus::kOk || !proto) {
std::unique_ptr<CaptureResult> result) {
if (status != PaintPreviewBaseService::CaptureStatus::kOk ||
!result->capture_success) {
std::move(callback).Run(false);
return;
}
......@@ -120,7 +121,7 @@ void PaintPreviewDemoService::OnCaptured(
GetTaskRunner()->PostTaskAndReplyWithResult(
FROM_HERE,
base::BindOnce(&FileManager::SerializePaintPreviewProto, GetFileManager(),
key, *proto, true),
key, result->proto, true),
base::BindOnce(&PaintPreviewDemoService::OnSerializationFinished,
weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
}
......
......@@ -60,7 +60,7 @@ class PaintPreviewDemoService : public PaintPreviewBaseService {
void OnCaptured(FinishedSuccessfullyCallback callback,
const DirectoryKey& key,
PaintPreviewBaseService::CaptureStatus status,
std::unique_ptr<PaintPreviewProto> proto);
std::unique_ptr<CaptureResult> result);
void OnSerializationFinished(FinishedSuccessfullyCallback callback,
bool success);
......
......@@ -239,14 +239,15 @@ void PaintPreviewTabService::OnCaptured(
int frame_tree_node_id,
FinishedCallback callback,
PaintPreviewBaseService::CaptureStatus status,
std::unique_ptr<PaintPreviewProto> proto) {
std::unique_ptr<CaptureResult> result) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
auto* web_contents =
content::WebContents::FromFrameTreeNodeId(frame_tree_node_id);
if (web_contents)
web_contents->DecrementCapturerCount(true);
if (status != PaintPreviewBaseService::CaptureStatus::kOk || !proto) {
if (status != PaintPreviewBaseService::CaptureStatus::kOk ||
!result->capture_success) {
std::move(callback).Run(Status::kCaptureFailed);
return;
}
......@@ -254,7 +255,7 @@ void PaintPreviewTabService::OnCaptured(
GetTaskRunner()->PostTaskAndReplyWithResult(
FROM_HERE,
base::BindOnce(&FileManager::SerializePaintPreviewProto, GetFileManager(),
key, *proto, true),
key, result->proto, true),
base::BindOnce(&PaintPreviewTabService::OnFinished,
weak_ptr_factory_.GetWeakPtr(), tab_id,
std::move(callback)));
......
......@@ -113,7 +113,7 @@ class PaintPreviewTabService : public PaintPreviewBaseService {
int frame_tree_node_id,
FinishedCallback callback,
PaintPreviewBaseService::CaptureStatus status,
std::unique_ptr<PaintPreviewProto>);
std::unique_ptr<CaptureResult> result);
void OnFinished(int tab_id, FinishedCallback callback, bool success);
......
......@@ -2839,6 +2839,7 @@ if (!is_android) {
"//components/paint_preview/browser",
"//components/paint_preview/browser:test_support",
"//components/paint_preview/common",
"//components/paint_preview/common:test_utils",
"//components/services/paint_preview_compositor/public/mojom",
]
}
......
......@@ -78,23 +78,24 @@ void PaintPreviewBaseService::CapturePaintPreview(
OnCapturedCallback callback) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
if (policy_ && !policy_->SupportedForContents(web_contents)) {
std::move(callback).Run(kContentUnsupported, nullptr);
std::move(callback).Run(kContentUnsupported, {});
return;
}
PaintPreviewClient::CreateForWebContents(web_contents); // Is a singleton.
auto* client = PaintPreviewClient::FromWebContents(web_contents);
if (!client) {
std::move(callback).Run(kClientCreationFailed, nullptr);
std::move(callback).Run(kClientCreationFailed, {});
return;
}
PaintPreviewClient::PaintPreviewParams params;
params.document_guid = base::UnguessableToken::Create();
params.clip_rect = clip_rect;
params.is_main_frame = (render_frame_host == web_contents->GetMainFrame());
PaintPreviewClient::PaintPreviewParams params(
mojom::Persistence::kFileSystem);
params.root_dir = root_dir;
params.max_per_capture_size = max_per_capture_size;
params.inner.clip_rect = clip_rect;
params.inner.is_main_frame =
(render_frame_host == web_contents->GetMainFrame());
params.inner.max_per_capture_size = max_per_capture_size;
// TODO(crbug/1064253): Consider moving to client so that this always happens.
// Although, it is harder to get this right in the client due to its
......@@ -142,7 +143,7 @@ void PaintPreviewBaseService::OnCaptured(
OnCapturedCallback callback,
base::UnguessableToken guid,
mojom::PaintPreviewStatus status,
std::unique_ptr<PaintPreviewProto> proto) {
std::unique_ptr<CaptureResult> result) {
auto* web_contents =
content::WebContents::FromFrameTreeNodeId(frame_tree_node_id);
if (web_contents)
......@@ -150,15 +151,15 @@ void PaintPreviewBaseService::OnCaptured(
if (!(status == mojom::PaintPreviewStatus::kOk ||
status == mojom::PaintPreviewStatus::kPartialSuccess) ||
!proto) {
!result->capture_success) {
DVLOG(1) << "ERROR: Paint Preview failed to capture for document "
<< guid.ToString() << " with error " << status;
std::move(callback).Run(kCaptureFailed, nullptr);
std::move(callback).Run(kCaptureFailed, {});
return;
}
base::UmaHistogramTimes("Browser.PaintPreview.Capture.TotalCaptureDuration",
base::TimeTicks::Now() - start_time);
std::move(callback).Run(kOk, std::move(proto));
std::move(callback).Run(kOk, std::move(result));
}
} // namespace paint_preview
......@@ -20,6 +20,7 @@
#include "components/keyed_service/core/keyed_service.h"
#include "components/paint_preview/browser/file_manager.h"
#include "components/paint_preview/browser/paint_preview_policy.h"
#include "components/paint_preview/common/capture_result.h"
#include "components/paint_preview/common/file_utils.h"
#include "components/paint_preview/common/mojom/paint_preview_recorder.mojom.h"
#include "components/paint_preview/common/proto/paint_preview.pb.h"
......@@ -49,8 +50,7 @@ class PaintPreviewBaseService : public KeyedService {
};
using OnCapturedCallback =
base::OnceCallback<void(CaptureStatus,
std::unique_ptr<PaintPreviewProto>)>;
base::OnceCallback<void(CaptureStatus, std::unique_ptr<CaptureResult>)>;
using OnReadProtoCallback =
base::OnceCallback<void(std::unique_ptr<PaintPreviewProto>)>;
......@@ -136,7 +136,7 @@ class PaintPreviewBaseService : public KeyedService {
OnCapturedCallback callback,
base::UnguessableToken guid,
mojom::PaintPreviewStatus status,
std::unique_ptr<PaintPreviewProto> proto);
std::unique_ptr<CaptureResult> result);
std::unique_ptr<PaintPreviewPolicy> policy_ = nullptr;
scoped_refptr<base::SequencedTaskRunner> task_runner_;
......
......@@ -195,22 +195,22 @@ TEST_F(PaintPreviewBaseServiceTest, CaptureMainFrame) {
PaintPreviewBaseService::CaptureStatus expected_status,
const base::FilePath& expected_path,
PaintPreviewBaseService::CaptureStatus status,
std::unique_ptr<PaintPreviewProto> proto) {
std::unique_ptr<CaptureResult> result) {
EXPECT_EQ(status, expected_status);
EXPECT_TRUE(proto->has_root_frame());
EXPECT_EQ(proto->subframes_size(), 0);
EXPECT_TRUE(proto->root_frame().is_main_frame());
EXPECT_TRUE(result->proto.has_root_frame());
EXPECT_EQ(result->proto.subframes_size(), 0);
EXPECT_TRUE(result->proto.root_frame().is_main_frame());
auto token = base::UnguessableToken::Deserialize(
proto->root_frame().embedding_token_high(),
proto->root_frame().embedding_token_low());
result->proto.root_frame().embedding_token_high(),
result->proto.root_frame().embedding_token_low());
#if defined(OS_WIN)
base::FilePath path = base::FilePath(
base::UTF8ToUTF16(proto->root_frame().file_path()));
base::UTF8ToUTF16(result->proto.root_frame().file_path()));
base::FilePath name(
base::UTF8ToUTF16(base::StrCat({token.ToString(), ".skp"})));
#else
base::FilePath path =
base::FilePath(proto->root_frame().file_path());
base::FilePath(result->proto.root_frame().file_path());
base::FilePath name(base::StrCat({token.ToString(), ".skp"}));
#endif
EXPECT_EQ(path.DirName(), expected_path);
......@@ -248,9 +248,9 @@ TEST_F(PaintPreviewBaseServiceTest, CaptureFailed) {
[](base::OnceClosure quit_closure,
PaintPreviewBaseService::CaptureStatus expected_status,
PaintPreviewBaseService::CaptureStatus status,
std::unique_ptr<PaintPreviewProto> proto) {
std::unique_ptr<CaptureResult> result) {
EXPECT_EQ(status, expected_status);
EXPECT_EQ(proto, nullptr);
EXPECT_EQ(result, nullptr);
std::move(quit_closure).Run();
},
loop.QuitClosure(),
......@@ -283,9 +283,9 @@ TEST_F(PaintPreviewBaseServiceTest, CaptureDisallowed) {
[](base::OnceClosure quit_closure,
PaintPreviewBaseService::CaptureStatus expected_status,
PaintPreviewBaseService::CaptureStatus status,
std::unique_ptr<PaintPreviewProto> proto) {
std::unique_ptr<CaptureResult> result) {
EXPECT_EQ(status, expected_status);
EXPECT_EQ(proto, nullptr);
EXPECT_EQ(result, nullptr);
std::move(quit_closure).Run();
},
loop.QuitClosure(),
......
......@@ -14,10 +14,13 @@
#include "base/containers/flat_set.h"
#include "base/files/file_path.h"
#include "base/unguessable_token.h"
#include "components/paint_preview/common/capture_result.h"
#include "components/paint_preview/common/mojom/paint_preview_recorder.mojom-shared.h"
#include "components/paint_preview/common/mojom/paint_preview_recorder.mojom.h"
#include "components/paint_preview/common/proto/paint_preview.pb.h"
#include "content/public/browser/web_contents_observer.h"
#include "content/public/browser/web_contents_user_data.h"
#include "mojo/public/cpp/base/big_buffer.h"
#include "mojo/public/cpp/bindings/associated_remote.h"
#include "ui/gfx/geometry/rect.h"
#include "url/gurl.h"
......@@ -37,33 +40,22 @@ class PaintPreviewClient
using PaintPreviewCallback =
base::OnceCallback<void(base::UnguessableToken,
mojom::PaintPreviewStatus,
std::unique_ptr<PaintPreviewProto>)>;
std::unique_ptr<CaptureResult>)>;
// Augmented version of mojom::PaintPreviewServiceParams.
struct PaintPreviewParams {
PaintPreviewParams();
explicit PaintPreviewParams(mojom::Persistence persistence);
~PaintPreviewParams();
// The document GUID for this capture.
base::UnguessableToken document_guid;
// Indicates where the PaintPreviewRecorder should store its intermediate
// artifacts.
mojom::Persistence persistence;
// The root directory in which to store paint_previews. This should be
// a subdirectory inside the active user profile's directory.
base::FilePath root_dir;
// The rect to which to clip the capture to.
gfx::Rect clip_rect;
// Whether the capture is for the main frame or an OOP subframe.
bool is_main_frame;
// The maximum capture size allowed per SkPicture captured. A size of 0 is
// unlimited.
// TODO(crbug/1071446): Ideally, this would cap the total size rather than
// being a per SkPicture limit. However, that is non-trivial due to the
// async ordering of captures from different frames making it hard to keep
// track of available headroom at the time of each capture triggering.
size_t max_per_capture_size;
RecordingParams inner;
};
~PaintPreviewClient() override;
......@@ -95,20 +87,21 @@ class PaintPreviewClient
// Internal Storage Classes -------------------------------------------------
// Representation of data for capturing a paint preview.
struct PaintPreviewData {
// Ephemeral state for a document being captured. This will be accumulated to
// as the capture progresses and results in a |CaptureResult|.
struct InProgressDocumentCaptureState {
public:
PaintPreviewData();
~PaintPreviewData();
InProgressDocumentCaptureState();
~InProgressDocumentCaptureState();
mojom::Persistence persistence;
// Root directory to store artifacts to.
// If |Persistence::kFileSystem|, the root directory to store artifacts to.
// Ignored if |Persistence::kMemoryBuffer|.
base::FilePath root_dir;
base::UnguessableToken root_frame_token;
// URL of the root frame.
GURL root_url;
// UKM Source ID of the WebContent.
ukm::SourceId source_id;
......@@ -127,45 +120,51 @@ class PaintPreviewClient
// All the render frames that are allowed to be captured.
base::flat_set<base::UnguessableToken> accepted_tokens;
// Data proto that is returned via callback.
std::unique_ptr<PaintPreviewProto> proto;
// If |Persistence::kMemoryBuffer|, this will contain the successful
// recordings. Empty if |Persistence::FileSystem|
base::flat_map<base::UnguessableToken, mojo_base::BigBuffer>
serialized_skps;
bool had_error = false;
PaintPreviewProto proto;
PaintPreviewData& operator=(PaintPreviewData&& other) noexcept;
PaintPreviewData(PaintPreviewData&& other) noexcept;
// Indicates that at least one subframe finished unsuccessfully.
bool had_error = false;
private:
PaintPreviewData(const PaintPreviewData&) = delete;
PaintPreviewData& operator=(const PaintPreviewData&) = delete;
};
// Indicates that at least one subframe finished successfully.
bool had_success = false;
struct CreateResult {
public:
CreateResult(base::File file, base::File::Error error);
~CreateResult();
CreateResult(CreateResult&& other);
CreateResult& operator=(CreateResult&& other);
// Indicates if we should clean up files associated with awaiting frames on
// destruction
bool should_clean_up_files = false;
base::File file;
base::File::Error error;
// Generates a file path based off |root_dir| and |frame_guid|. Will be in
// the form "{hexadecimal}.skp".
base::FilePath FilePathForFrame(const base::UnguessableToken& frame_guid);
private:
CreateResult(const CreateResult&) = delete;
CreateResult& operator=(const CreateResult&) = delete;
};
// Record a successful recording into this capture state.
void RecordSuccessfulFrame(const base::UnguessableToken& frame_guid,
bool is_main_frame,
mojom::PaintPreviewCaptureResponsePtr response);
// Helpers -------------------------------------------------------------------
// Convert this capture state into a form that can be returned to the
// original paint preview capture request.
std::unique_ptr<CaptureResult> IntoCaptureResult() &&;
static CreateResult CreateFileHandle(const base::FilePath& path);
InProgressDocumentCaptureState& operator=(
InProgressDocumentCaptureState&& other) noexcept;
InProgressDocumentCaptureState(
InProgressDocumentCaptureState&& other) noexcept;
mojom::PaintPreviewCaptureParamsPtr CreateMojoParams(
const PaintPreviewParams& params,
base::File file);
private:
InProgressDocumentCaptureState(const InProgressDocumentCaptureState&) =
delete;
InProgressDocumentCaptureState& operator=(
const InProgressDocumentCaptureState&) = delete;
};
// Sets up for a capture of a frame on |render_frame_host| according to
// |params|.
void CapturePaintPreviewInternal(const PaintPreviewParams& params,
void CapturePaintPreviewInternal(const RecordingParams& params,
content::RenderFrameHost* render_frame_host);
// Initiates capture via the PaintPreviewRecorder associated with
......@@ -173,19 +172,17 @@ class PaintPreviewClient
// is the GUID associated with the frame. |path| is file path associated with
// the File stored in |result| (base::File isn't aware of its file path).
void RequestCaptureOnUIThread(
const PaintPreviewParams& params,
const base::UnguessableToken& frame_guid,
const RecordingParams& params,
const content::GlobalFrameRoutingId& render_frame_id,
const base::FilePath& path,
CreateResult result);
mojom::PaintPreviewStatus status,
mojom::PaintPreviewCaptureParamsPtr capture_params);
// Handles recording the frame and updating client state when capture is
// complete.
void OnPaintPreviewCapturedCallback(
const base::UnguessableToken& guid,
const base::UnguessableToken& frame_guid,
bool is_main_frame,
const base::FilePath& filename,
const RecordingParams& params,
const content::GlobalFrameRoutingId& render_frame_id,
mojom::PaintPreviewStatus status,
mojom::PaintPreviewCaptureResponsePtr response);
......@@ -195,17 +192,9 @@ class PaintPreviewClient
void MarkFrameAsProcessed(base::UnguessableToken guid,
const base::UnguessableToken& frame_guid);
// Records the data from a processed frame if it was captured successfully.
mojom::PaintPreviewStatus RecordFrame(
const base::UnguessableToken& guid,
const base::UnguessableToken& frame_guid,
bool is_main_frame,
const base::FilePath& filename,
const content::GlobalFrameRoutingId& render_frame_id,
mojom::PaintPreviewCaptureResponsePtr response);
// Handles finishing the capture once all frames are received.
void OnFinished(base::UnguessableToken guid, PaintPreviewData* document_data);
void OnFinished(base::UnguessableToken guid,
InProgressDocumentCaptureState* document_data);
// Storage ------------------------------------------------------------------
......@@ -218,8 +207,11 @@ class PaintPreviewClient
base::flat_map<base::UnguessableToken, base::flat_set<base::UnguessableToken>>
pending_previews_on_subframe_;
// Maps a document GUID to its data.
base::flat_map<base::UnguessableToken, PaintPreviewData> all_document_data_;
// Maps a document GUID to its capture state while it is in-progress. Entries
// in this map should be cleaned up when a capture completes (either
// successfully or not).
base::flat_map<base::UnguessableToken, InProgressDocumentCaptureState>
all_document_data_;
base::WeakPtrFactory<PaintPreviewClient> weak_ptr_factory_{this};
......
......@@ -7,6 +7,8 @@ import("//testing/test.gni")
if (!is_ios) {
static_library("common") {
sources = [
"capture_result.cc",
"capture_result.h",
"file_stream.cc",
"file_stream.h",
"file_utils.cc",
......@@ -37,9 +39,13 @@ if (!is_ios) {
source_set("test_utils") {
testonly = true
sources = [ "test_utils.h" ]
sources = [
"test_utils.cc",
"test_utils.h",
]
deps = [
"//components/paint_preview/common/mojom",
"//testing/gmock",
"//testing/gtest",
]
......@@ -58,6 +64,7 @@ if (!is_ios) {
deps = [
":common",
":test_utils",
"//base",
"//base/test:test_support",
"//skia",
......
// Copyright 2020 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/common/capture_result.h"
namespace paint_preview {
RecordingParams::RecordingParams(const base::UnguessableToken& document_guid)
: document_guid(document_guid),
is_main_frame(false),
max_per_capture_size(0) {}
CaptureResult::CaptureResult(mojom::Persistence persistence)
: persistence(persistence) {}
CaptureResult::~CaptureResult() = default;
CaptureResult::CaptureResult(CaptureResult&&) = default;
CaptureResult& CaptureResult::operator=(CaptureResult&&) = default;
} // namespace paint_preview
// Copyright 2020 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.
#ifndef COMPONENTS_PAINT_PREVIEW_COMMON_CAPTURE_RESULT_H_
#define COMPONENTS_PAINT_PREVIEW_COMMON_CAPTURE_RESULT_H_
#include "base/containers/flat_map.h"
#include "base/unguessable_token.h"
#include "components/paint_preview/common/mojom/paint_preview_recorder.mojom-forward.h"
#include "components/paint_preview/common/proto/paint_preview.pb.h"
#include "mojo/public/cpp/base/big_buffer.h"
#include "ui/gfx/geometry/rect.h"
namespace paint_preview {
// A subset of PaintPreviewCaptureParams that will be filled in by
// PaintPreviewClient. This type mainly exists to aggregate related parameters.
struct RecordingParams {
explicit RecordingParams(const base::UnguessableToken& document_guid);
// The document GUID for this capture.
const base::UnguessableToken document_guid;
// The rect to which to clip the capture to.
gfx::Rect clip_rect;
// Whether the capture is for the main frame or an OOP subframe.
bool is_main_frame;
// The maximum capture size allowed per SkPicture captured. A size of 0 is
// unlimited.
// TODO(crbug/1071446): Ideally, this would cap the total size rather than
// being a per SkPicture limit. However, that is non-trivial due to the
// async ordering of captures from different frames making it hard to keep
// track of available headroom at the time of each capture triggering.
size_t max_per_capture_size;
};
// The result of a capture of a WebContents, which may contain recordings of
// multiple subframes.
struct CaptureResult {
public:
explicit CaptureResult(mojom::Persistence persistence);
~CaptureResult();
CaptureResult(CaptureResult&&);
CaptureResult& operator=(CaptureResult&&);
// Will match the |persistence| in the original capture request.
mojom::Persistence persistence;
PaintPreviewProto proto = {};
// Maps frame embedding tokens to buffers containing the serialized
// recordings. See |PaintPreviewCaptureResponse::skp| for information on how
// to intepret these buffers. Empty if |Persistence::FileSystem|.
base::flat_map<base::UnguessableToken, mojo_base::BigBuffer> serialized_skps =
{};
// Indicates that at least one subframe finished successfully.
bool capture_success = false;
};
} // namespace paint_preview
#endif
......@@ -4,6 +4,7 @@
module paint_preview.mojom;
import "mojo/public/mojom/base/big_buffer.mojom";
import "mojo/public/mojom/base/file.mojom";
import "mojo/public/mojom/base/time.mojom";
import "mojo/public/mojom/base/unguessable_token.mojom";
......@@ -36,7 +37,24 @@ enum PaintPreviewStatus {
kFailed,
};
// Enumation of strategies to store artifacts of an operation.
enum Persistence {
// Store artifacts in the file system. This strategy can be robust to browser
// restarts, since the lifetime of the files can outlive the browser process.
kFileSystem,
// Store artifacts in memory buffers returned from or passed to capture or
// compositing methods. On low-memory devices, there is a higher risk of
// out-of-memory issues.
kMemoryBuffer,
};
// The collection of parameters needed for a recording of a render frame.
struct PaintPreviewCaptureParams {
// The strategy for where to store the serialized SkPictures resulting from
// this capture.
Persistence persistence;
// GUID for the Paint Preview (used to associate subframes to main frame).
mojo_base.mojom.UnguessableToken guid;
......@@ -52,7 +70,9 @@ struct PaintPreviewCaptureParams {
// File to write the SkPicture to (write-only). A separate file should be
// created for each RenderFrame.
mojo_base.mojom.File file;
//
// null if not |Persistence::FileSystem|.
mojo_base.mojom.File? file;
// The maximum allowed size of a capture that can be produced. A value of
// 0 means the size is unrestricted.
......@@ -85,6 +105,15 @@ struct PaintPreviewCaptureResponse {
// Scroll offsets of the frame at capture time.
gfx.mojom.Size scroll_offsets;
// The serialized skia picture. It should be deserialized with the
// |SkDeserialProcs| that correspond with the |SkSerialProcs| it was
// serialized with.
//
// It is not safe to deserialize this in the browser process.
//
// null if not |Persistence::MemoryBuffer|.
mojo_base.mojom.BigBuffer? skp;
};
// Service for capturing a paint preview of a RenderFrame's contents. This
......
......@@ -44,6 +44,8 @@ message PaintPreviewFrameProto {
required bool is_main_frame = 3;
// The file path to the serialized Skia Picture.
// null if the persistence type of the |PaintPreviewCaptureParams| is
// |Persistence::MemoryBuffer|.
optional string file_path = 4;
// A list of links within the frame.
......
// Copyright 2020 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/common/test_utils.h"
std::string PersistenceParamToString(
const ::testing::TestParamInfo<paint_preview::mojom::Persistence>&
persistence) {
switch (persistence.param) {
case paint_preview::mojom::Persistence::kFileSystem:
return "FileSystem";
case paint_preview::mojom::Persistence::kMemoryBuffer:
return "MemoryBuffer";
}
}
......@@ -5,6 +5,7 @@
#ifndef COMPONENTS_PAINT_PREVIEW_COMMON_TEST_UTILS_H_
#define COMPONENTS_PAINT_PREVIEW_COMMON_TEST_UTILS_H_
#include "components/paint_preview/common/mojom/paint_preview_recorder.mojom-shared.h"
#include "testing/gmock/include/gmock/gmock.h"
MATCHER_P(EqualsProto, message, "") {
......@@ -14,4 +15,9 @@ MATCHER_P(EqualsProto, message, "") {
return expected_serialized == actual_serialized;
}
// Allow |mojom::Persistence| to be stringified in gtest.
std::string PersistenceParamToString(
const ::testing::TestParamInfo<paint_preview::mojom::Persistence>&
persistence);
#endif // COMPONENTS_PAINT_PREVIEW_COMMON_TEST_UTILS_H_
......@@ -38,6 +38,7 @@ if (!is_ios) {
"//base",
"//base/test:test_support",
"//cc/paint",
"//components/paint_preview/common:test_utils",
"//testing/gmock",
"//testing/gtest",
]
......
......@@ -18,7 +18,6 @@
#include "cc/paint/paint_recorder.h"
#include "components/paint_preview/renderer/paint_preview_recorder_utils.h"
#include "content/public/renderer/render_frame.h"
#include "mojo/public/cpp/base/shared_memory_utils.h"
#include "third_party/blink/public/common/associated_interfaces/associated_interface_registry.h"
#include "third_party/blink/public/web/web_local_frame.h"
......@@ -46,6 +45,7 @@ FinishedRecording FinishRecording(
sk_sp<const cc::PaintRecord> recording,
const gfx::Rect& bounds,
std::unique_ptr<PaintPreviewTracker> tracker,
mojom::Persistence persistence,
base::File skp_file,
size_t max_capture_size,
mojom::PaintPreviewCaptureResponsePtr response) {
......@@ -61,9 +61,24 @@ FinishedRecording FinishRecording(
ParseGlyphs(recording.get(), tracker.get());
TRACE_EVENT_END0("paint_preview", "ParseGlyphs");
size_t serialized_size = 0;
if (!SerializeAsSkPicture(recording, tracker.get(), bounds,
std::move(skp_file), max_capture_size,
&serialized_size)) {
bool success = false;
switch (persistence) {
case mojom::Persistence::kFileSystem:
success = SerializeAsSkPictureToFile(recording, bounds, tracker.get(),
std::move(skp_file),
max_capture_size, &serialized_size);
break;
case mojom::Persistence::kMemoryBuffer:
mojo_base::BigBuffer buffer;
success = SerializeAsSkPictureToMemoryBuffer(
recording, bounds, tracker.get(), &buffer, max_capture_size,
&serialized_size);
out.response->skp.emplace(std::move(buffer));
break;
}
if (!success) {
out.status = mojom::PaintPreviewStatus::kCaptureFailed;
return out;
}
......@@ -215,7 +230,8 @@ void PaintPreviewRecorderImpl::CapturePaintPreviewInternal(
// image.
FinishedRecording recording = FinishRecording(
recorder.finishRecordingAsPicture(), bounds, std::move(tracker),
std::move(params->file), params->max_capture_size, std::move(response));
params->persistence, std::move(params->file), params->max_capture_size,
std::move(response));
std::move(callback).Run(recording.status, std::move(recording.response));
}
......
......@@ -33,12 +33,8 @@ void ParseGlyphs(const cc::PaintOpBuffer* buffer,
bool SerializeAsSkPicture(sk_sp<const cc::PaintRecord> record,
PaintPreviewTracker* tracker,
const gfx::Rect& dimensions,
base::File file,
size_t max_size,
size_t* serialized_size) {
SkWStream* out_stream) {
TRACE_EVENT0("paint_preview", "SerializeAsSkPicture");
if (!file.IsValid())
return false;
// base::Unretained is safe as |tracker| outlives the usage of
// |custom_callback|.
......@@ -54,13 +50,10 @@ bool SerializeAsSkPicture(sk_sp<const cc::PaintRecord> record,
TypefaceSerializationContext typeface_context(tracker->GetTypefaceUsageMap());
auto serial_procs = MakeSerialProcs(tracker->GetPictureSerializationContext(),
&typeface_context);
FileWStream stream(std::move(file), max_size);
skp->serialize(&stream, &serial_procs);
stream.flush();
stream.Close();
DCHECK(serialized_size);
*serialized_size = stream.ActualBytesWritten();
return !stream.DidWriteFail();
skp->serialize(out_stream, &serial_procs);
out_stream->flush();
return true;
}
void BuildResponse(PaintPreviewTracker* tracker,
......@@ -79,4 +72,43 @@ void BuildResponse(PaintPreviewTracker* tracker,
tracker->MoveLinks(&response->links);
}
bool SerializeAsSkPictureToFile(sk_sp<const cc::PaintRecord> recording,
const gfx::Rect& bounds,
PaintPreviewTracker* tracker,
base::File file,
size_t max_capture_size,
size_t* serialized_size) {
if (!file.IsValid())
return false;
FileWStream file_stream(std::move(file), max_capture_size);
if (!SerializeAsSkPicture(recording, tracker, bounds, &file_stream))
return false;
file_stream.Close();
*serialized_size = file_stream.ActualBytesWritten();
return !file_stream.DidWriteFail();
}
bool SerializeAsSkPictureToMemoryBuffer(sk_sp<const cc::PaintRecord> recording,
const gfx::Rect& bounds,
PaintPreviewTracker* tracker,
mojo_base::BigBuffer* buffer,
size_t max_capture_size,
size_t* serialized_size) {
SkDynamicMemoryWStream memory_stream;
if (!SerializeAsSkPicture(recording, tracker, bounds, &memory_stream))
return false;
// |0| indicates "no size limit".
if (max_capture_size == 0)
max_capture_size = SIZE_MAX;
sk_sp<SkData> data = memory_stream.detachAsData();
*serialized_size = std::min(data->size(), max_capture_size);
*buffer = mojo_base::BigBuffer(
base::span<const uint8_t>(data->bytes(), *serialized_size));
return data->size() <= max_capture_size;
}
} // namespace paint_preview
......@@ -8,7 +8,9 @@
#include "base/files/file.h"
#include "cc/paint/paint_record.h"
#include "components/paint_preview/common/mojom/paint_preview_recorder.mojom-forward.h"
#include "mojo/public/cpp/base/big_buffer.h"
#include "third_party/skia/include/core/SkRefCnt.h"
#include "third_party/skia/include/core/SkStream.h"
#include "ui/gfx/geometry/rect.h"
// These utilities are used by the PaintPreviewRecorderImpl. They are separate
......@@ -23,17 +25,12 @@ class PaintPreviewTracker;
// them to |tracker|.
void ParseGlyphs(const cc::PaintOpBuffer* buffer, PaintPreviewTracker* tracker);
// Serializes |record| to |file| as an SkPicture of size |dimensions|. |tracker|
// supplies metadata required during serialization. |max_size| is a limit on the
// total serialized size although 0 means the size is unrestricted. If
// |max_size| is exceeded the serialization will fail. The size of the
// serialized output is set as |serialized_size|.
// Serializes |record| to |out_stream| as an SkPicture of size |dimensions|.
// |tracker| supplies metadata required during serialization.
bool SerializeAsSkPicture(sk_sp<const cc::PaintRecord> record,
PaintPreviewTracker* tracker,
const gfx::Rect& dimensions,
base::File file,
size_t max_size,
size_t* serialized_size);
SkWStream* out_stream);
// Builds a mojom::PaintPreviewCaptureResponse |response| using the data
// contained in |tracker|.
......@@ -41,6 +38,36 @@ bool SerializeAsSkPicture(sk_sp<const cc::PaintRecord> record,
void BuildResponse(PaintPreviewTracker* tracker,
mojom::PaintPreviewCaptureResponse* response);
// Utility function that wraps |SerializeAsSkPicture| to serialize and write
// |recording| to |file|.
//
// |max_size| is a limit on the total serialized size although 0 means the size
// is unrestricted. If |max_size| is exceeded the serialization will fail.
// |serialized_size| will contain the size of the serialized output.
//
// Returns |true| on success.
bool SerializeAsSkPictureToFile(sk_sp<const cc::PaintRecord> recording,
const gfx::Rect& bounds,
PaintPreviewTracker* tracker,
base::File file,
size_t max_capture_size,
size_t* serialized_size);
// Utility function that wraps |SerializeAsSkPicture| to serialize and write
// |recording| to |buffer|.
//
// |max_size| is a limit on the total serialized size although 0 means the size
// is unrestricted. If |max_size| is exceeded the serialization will fail.
// |serialized_size| will contain the size of the serialized output.
//
// Returns |true| on success.
bool SerializeAsSkPictureToMemoryBuffer(sk_sp<const cc::PaintRecord> recording,
const gfx::Rect& bounds,
PaintPreviewTracker* tracker,
mojo_base::BigBuffer* buffer,
size_t max_capture_size,
size_t* serialized_size);
} // namespace paint_preview
#endif // COMPONENTS_PAINT_PREVIEW_RENDERER_PAINT_PREVIEW_RECORDER_UTILS_H_
......@@ -4,6 +4,7 @@
#include "components/paint_preview/renderer/paint_preview_recorder_utils.h"
#include <memory>
#include <string>
#include "base/containers/flat_map.h"
......@@ -11,13 +12,18 @@
#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/serial_utils.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"
......@@ -64,18 +70,80 @@ TEST(PaintPreviewRecorderUtilsTest, TestParseGlyphs) {
(*usage_map)[typeface->uniqueID()]->IsSet(typeface->unicharToGlyph('g')));
}
TEST(PaintPreviewRecorderUtilsTest, TestSerializeAsSkPicture) {
PaintPreviewTracker tracker(base::UnguessableToken::Create(),
base::UnguessableToken::Create(), true);
class PaintPreviewRecorderUtilsSerializeAsSkPictureTest
: public testing::TestWithParam<mojom::Persistence> {
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<std::unique_ptr<SkStream>> SerializeAsSkPicture(
base::Optional<size_t> max_capture_size,
size_t* serialized_size) {
auto record = recorder.finishRecordingAsPicture();
canvas = nullptr;
switch (GetParam()) {
case mojom::Persistence::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 (!SerializeAsSkPictureToFile(
record, dimensions, &tracker, std::move(write_file),
max_capture_size.value_or(0), serialized_size))
return base::nullopt;
base::File read_file(file_path, base::File::FLAG_OPEN |
base::File::FLAG_READ |
base::File::FLAG_EXCLUSIVE_READ);
return {std::make_unique<FileRStream>(std::move(read_file))};
} break;
case mojom::Persistence::kMemoryBuffer: {
mojo_base::BigBuffer memory_buffer;
if (!SerializeAsSkPictureToMemoryBuffer(
record, dimensions, &tracker, &memory_buffer,
max_capture_size.value_or(0), serialized_size))
return base::nullopt;
bool copy_data = true;
return {std::make_unique<SkMemoryStream>(
memory_buffer.data(), memory_buffer.size(), copy_data)};
} break;
}
NOTREACHED();
return base::nullopt;
}
gfx::Rect dimensions(100, 100);
PaintPreviewTracker tracker;
gfx::Rect dimensions;
cc::PaintRecorder recorder;
cc::PaintCanvas* canvas =
recorder.beginRecording(dimensions.width(), dimensions.width());
cc::PaintFlags flags;
canvas->drawRect(SkRect::MakeWH(dimensions.width(), dimensions.height()),
flags);
// 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());
......@@ -86,20 +154,10 @@ TEST(PaintPreviewRecorderUtilsTest, TestSerializeAsSkPicture) {
canvas->recordCustomData(content_id);
ctx.insert(content_id);
base::ScopedTempDir temp_dir;
ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
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);
auto record = recorder.finishRecordingAsPicture();
size_t out_size = 0;
EXPECT_TRUE(SerializeAsSkPicture(record, &tracker, dimensions,
std::move(write_file), 0, &out_size));
base::File read_file(file_path, base::File::FLAG_OPEN |
base::File::FLAG_READ |
base::File::FLAG_EXCLUSIVE_READ);
FileRStream rstream(std::move(read_file));
auto rstream = SerializeAsSkPicture(base::nullopt, &out_size);
ASSERT_TRUE(rstream.has_value());
SkDeserialProcs procs;
procs.fPictureProc = [](const void* data, size_t length, void* ctx) {
uint32_t content_id;
......@@ -112,35 +170,23 @@ TEST(PaintPreviewRecorderUtilsTest, TestSerializeAsSkPicture) {
return MakeEmptyPicture();
};
procs.fPictureCtx = &ctx;
SkPicture::MakeFromStream(&rstream, &procs);
SkPicture::MakeFromStream(rstream.value().get(), &procs);
EXPECT_TRUE(ctx.empty());
}
TEST(PaintPreviewRecorderUtilsTest, TestSerializeAsSkPictureFail) {
PaintPreviewTracker tracker(base::UnguessableToken::Create(),
base::UnguessableToken::Create(), true);
gfx::Rect dimensions(100, 100);
cc::PaintRecorder recorder;
cc::PaintCanvas* canvas =
recorder.beginRecording(dimensions.width(), dimensions.width());
cc::PaintFlags flags;
canvas->drawRect(SkRect::MakeWH(dimensions.width(), dimensions.height()),
flags);
base::ScopedTempDir temp_dir;
ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
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);
auto record = recorder.finishRecordingAsPicture();
TEST_P(PaintPreviewRecorderUtilsSerializeAsSkPictureTest, FailIfExceedMaxSize) {
size_t out_size = 2;
EXPECT_FALSE(SerializeAsSkPicture(record, &tracker, dimensions,
std::move(write_file), 1, &out_size));
auto rstream = SerializeAsSkPicture({1}, &out_size);
EXPECT_FALSE(rstream.has_value());
EXPECT_LE(out_size, 1U);
}
INSTANTIATE_TEST_SUITE_P(All,
PaintPreviewRecorderUtilsSerializeAsSkPictureTest,
testing::Values(mojom::Persistence::kFileSystem,
mojom::Persistence::kMemoryBuffer),
PersistenceParamToString);
TEST(PaintPreviewRecorderUtilsTest, TestBuildResponse) {
auto token = base::UnguessableToken::Create();
auto embedding_token = base::UnguessableToken::Create();
......
......@@ -55,6 +55,7 @@ source_set("unit_tests") {
"//base",
"//base/test:test_support",
"//components/paint_preview/common",
"//components/paint_preview/common:test_utils",
"//components/paint_preview/common/proto",
"//skia",
"//testing/gmock",
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment