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( ...@@ -111,8 +111,9 @@ void PaintPreviewDemoService::OnCaptured(
FinishedSuccessfullyCallback callback, FinishedSuccessfullyCallback callback,
const DirectoryKey& key, const DirectoryKey& key,
PaintPreviewBaseService::CaptureStatus status, PaintPreviewBaseService::CaptureStatus status,
std::unique_ptr<PaintPreviewProto> proto) { std::unique_ptr<CaptureResult> result) {
if (status != PaintPreviewBaseService::CaptureStatus::kOk || !proto) { if (status != PaintPreviewBaseService::CaptureStatus::kOk ||
!result->capture_success) {
std::move(callback).Run(false); std::move(callback).Run(false);
return; return;
} }
...@@ -120,7 +121,7 @@ void PaintPreviewDemoService::OnCaptured( ...@@ -120,7 +121,7 @@ void PaintPreviewDemoService::OnCaptured(
GetTaskRunner()->PostTaskAndReplyWithResult( GetTaskRunner()->PostTaskAndReplyWithResult(
FROM_HERE, FROM_HERE,
base::BindOnce(&FileManager::SerializePaintPreviewProto, GetFileManager(), base::BindOnce(&FileManager::SerializePaintPreviewProto, GetFileManager(),
key, *proto, true), key, result->proto, true),
base::BindOnce(&PaintPreviewDemoService::OnSerializationFinished, base::BindOnce(&PaintPreviewDemoService::OnSerializationFinished,
weak_ptr_factory_.GetWeakPtr(), std::move(callback))); weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
} }
......
...@@ -60,7 +60,7 @@ class PaintPreviewDemoService : public PaintPreviewBaseService { ...@@ -60,7 +60,7 @@ class PaintPreviewDemoService : public PaintPreviewBaseService {
void OnCaptured(FinishedSuccessfullyCallback callback, void OnCaptured(FinishedSuccessfullyCallback callback,
const DirectoryKey& key, const DirectoryKey& key,
PaintPreviewBaseService::CaptureStatus status, PaintPreviewBaseService::CaptureStatus status,
std::unique_ptr<PaintPreviewProto> proto); std::unique_ptr<CaptureResult> result);
void OnSerializationFinished(FinishedSuccessfullyCallback callback, void OnSerializationFinished(FinishedSuccessfullyCallback callback,
bool success); bool success);
......
...@@ -239,14 +239,15 @@ void PaintPreviewTabService::OnCaptured( ...@@ -239,14 +239,15 @@ void PaintPreviewTabService::OnCaptured(
int frame_tree_node_id, int frame_tree_node_id,
FinishedCallback callback, FinishedCallback callback,
PaintPreviewBaseService::CaptureStatus status, PaintPreviewBaseService::CaptureStatus status,
std::unique_ptr<PaintPreviewProto> proto) { std::unique_ptr<CaptureResult> result) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
auto* web_contents = auto* web_contents =
content::WebContents::FromFrameTreeNodeId(frame_tree_node_id); content::WebContents::FromFrameTreeNodeId(frame_tree_node_id);
if (web_contents) if (web_contents)
web_contents->DecrementCapturerCount(true); web_contents->DecrementCapturerCount(true);
if (status != PaintPreviewBaseService::CaptureStatus::kOk || !proto) { if (status != PaintPreviewBaseService::CaptureStatus::kOk ||
!result->capture_success) {
std::move(callback).Run(Status::kCaptureFailed); std::move(callback).Run(Status::kCaptureFailed);
return; return;
} }
...@@ -254,7 +255,7 @@ void PaintPreviewTabService::OnCaptured( ...@@ -254,7 +255,7 @@ void PaintPreviewTabService::OnCaptured(
GetTaskRunner()->PostTaskAndReplyWithResult( GetTaskRunner()->PostTaskAndReplyWithResult(
FROM_HERE, FROM_HERE,
base::BindOnce(&FileManager::SerializePaintPreviewProto, GetFileManager(), base::BindOnce(&FileManager::SerializePaintPreviewProto, GetFileManager(),
key, *proto, true), key, result->proto, true),
base::BindOnce(&PaintPreviewTabService::OnFinished, base::BindOnce(&PaintPreviewTabService::OnFinished,
weak_ptr_factory_.GetWeakPtr(), tab_id, weak_ptr_factory_.GetWeakPtr(), tab_id,
std::move(callback))); std::move(callback)));
......
...@@ -113,7 +113,7 @@ class PaintPreviewTabService : public PaintPreviewBaseService { ...@@ -113,7 +113,7 @@ class PaintPreviewTabService : public PaintPreviewBaseService {
int frame_tree_node_id, int frame_tree_node_id,
FinishedCallback callback, FinishedCallback callback,
PaintPreviewBaseService::CaptureStatus status, PaintPreviewBaseService::CaptureStatus status,
std::unique_ptr<PaintPreviewProto>); std::unique_ptr<CaptureResult> result);
void OnFinished(int tab_id, FinishedCallback callback, bool success); void OnFinished(int tab_id, FinishedCallback callback, bool success);
......
...@@ -2839,6 +2839,7 @@ if (!is_android) { ...@@ -2839,6 +2839,7 @@ if (!is_android) {
"//components/paint_preview/browser", "//components/paint_preview/browser",
"//components/paint_preview/browser:test_support", "//components/paint_preview/browser:test_support",
"//components/paint_preview/common", "//components/paint_preview/common",
"//components/paint_preview/common:test_utils",
"//components/services/paint_preview_compositor/public/mojom", "//components/services/paint_preview_compositor/public/mojom",
] ]
} }
......
...@@ -78,23 +78,24 @@ void PaintPreviewBaseService::CapturePaintPreview( ...@@ -78,23 +78,24 @@ void PaintPreviewBaseService::CapturePaintPreview(
OnCapturedCallback callback) { OnCapturedCallback callback) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI); DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
if (policy_ && !policy_->SupportedForContents(web_contents)) { if (policy_ && !policy_->SupportedForContents(web_contents)) {
std::move(callback).Run(kContentUnsupported, nullptr); std::move(callback).Run(kContentUnsupported, {});
return; return;
} }
PaintPreviewClient::CreateForWebContents(web_contents); // Is a singleton. PaintPreviewClient::CreateForWebContents(web_contents); // Is a singleton.
auto* client = PaintPreviewClient::FromWebContents(web_contents); auto* client = PaintPreviewClient::FromWebContents(web_contents);
if (!client) { if (!client) {
std::move(callback).Run(kClientCreationFailed, nullptr); std::move(callback).Run(kClientCreationFailed, {});
return; return;
} }
PaintPreviewClient::PaintPreviewParams params; PaintPreviewClient::PaintPreviewParams params(
params.document_guid = base::UnguessableToken::Create(); mojom::Persistence::kFileSystem);
params.clip_rect = clip_rect;
params.is_main_frame = (render_frame_host == web_contents->GetMainFrame());
params.root_dir = root_dir; 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. // 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 // Although, it is harder to get this right in the client due to its
...@@ -142,7 +143,7 @@ void PaintPreviewBaseService::OnCaptured( ...@@ -142,7 +143,7 @@ void PaintPreviewBaseService::OnCaptured(
OnCapturedCallback callback, OnCapturedCallback callback,
base::UnguessableToken guid, base::UnguessableToken guid,
mojom::PaintPreviewStatus status, mojom::PaintPreviewStatus status,
std::unique_ptr<PaintPreviewProto> proto) { std::unique_ptr<CaptureResult> result) {
auto* web_contents = auto* web_contents =
content::WebContents::FromFrameTreeNodeId(frame_tree_node_id); content::WebContents::FromFrameTreeNodeId(frame_tree_node_id);
if (web_contents) if (web_contents)
...@@ -150,15 +151,15 @@ void PaintPreviewBaseService::OnCaptured( ...@@ -150,15 +151,15 @@ void PaintPreviewBaseService::OnCaptured(
if (!(status == mojom::PaintPreviewStatus::kOk || if (!(status == mojom::PaintPreviewStatus::kOk ||
status == mojom::PaintPreviewStatus::kPartialSuccess) || status == mojom::PaintPreviewStatus::kPartialSuccess) ||
!proto) { !result->capture_success) {
DVLOG(1) << "ERROR: Paint Preview failed to capture for document " DVLOG(1) << "ERROR: Paint Preview failed to capture for document "
<< guid.ToString() << " with error " << status; << guid.ToString() << " with error " << status;
std::move(callback).Run(kCaptureFailed, nullptr); std::move(callback).Run(kCaptureFailed, {});
return; return;
} }
base::UmaHistogramTimes("Browser.PaintPreview.Capture.TotalCaptureDuration", base::UmaHistogramTimes("Browser.PaintPreview.Capture.TotalCaptureDuration",
base::TimeTicks::Now() - start_time); base::TimeTicks::Now() - start_time);
std::move(callback).Run(kOk, std::move(proto)); std::move(callback).Run(kOk, std::move(result));
} }
} // namespace paint_preview } // namespace paint_preview
...@@ -20,6 +20,7 @@ ...@@ -20,6 +20,7 @@
#include "components/keyed_service/core/keyed_service.h" #include "components/keyed_service/core/keyed_service.h"
#include "components/paint_preview/browser/file_manager.h" #include "components/paint_preview/browser/file_manager.h"
#include "components/paint_preview/browser/paint_preview_policy.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/file_utils.h"
#include "components/paint_preview/common/mojom/paint_preview_recorder.mojom.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/proto/paint_preview.pb.h"
...@@ -49,8 +50,7 @@ class PaintPreviewBaseService : public KeyedService { ...@@ -49,8 +50,7 @@ class PaintPreviewBaseService : public KeyedService {
}; };
using OnCapturedCallback = using OnCapturedCallback =
base::OnceCallback<void(CaptureStatus, base::OnceCallback<void(CaptureStatus, std::unique_ptr<CaptureResult>)>;
std::unique_ptr<PaintPreviewProto>)>;
using OnReadProtoCallback = using OnReadProtoCallback =
base::OnceCallback<void(std::unique_ptr<PaintPreviewProto>)>; base::OnceCallback<void(std::unique_ptr<PaintPreviewProto>)>;
...@@ -136,7 +136,7 @@ class PaintPreviewBaseService : public KeyedService { ...@@ -136,7 +136,7 @@ class PaintPreviewBaseService : public KeyedService {
OnCapturedCallback callback, OnCapturedCallback callback,
base::UnguessableToken guid, base::UnguessableToken guid,
mojom::PaintPreviewStatus status, mojom::PaintPreviewStatus status,
std::unique_ptr<PaintPreviewProto> proto); std::unique_ptr<CaptureResult> result);
std::unique_ptr<PaintPreviewPolicy> policy_ = nullptr; std::unique_ptr<PaintPreviewPolicy> policy_ = nullptr;
scoped_refptr<base::SequencedTaskRunner> task_runner_; scoped_refptr<base::SequencedTaskRunner> task_runner_;
......
...@@ -195,22 +195,22 @@ TEST_F(PaintPreviewBaseServiceTest, CaptureMainFrame) { ...@@ -195,22 +195,22 @@ TEST_F(PaintPreviewBaseServiceTest, CaptureMainFrame) {
PaintPreviewBaseService::CaptureStatus expected_status, PaintPreviewBaseService::CaptureStatus expected_status,
const base::FilePath& expected_path, const base::FilePath& expected_path,
PaintPreviewBaseService::CaptureStatus status, PaintPreviewBaseService::CaptureStatus status,
std::unique_ptr<PaintPreviewProto> proto) { std::unique_ptr<CaptureResult> result) {
EXPECT_EQ(status, expected_status); EXPECT_EQ(status, expected_status);
EXPECT_TRUE(proto->has_root_frame()); EXPECT_TRUE(result->proto.has_root_frame());
EXPECT_EQ(proto->subframes_size(), 0); EXPECT_EQ(result->proto.subframes_size(), 0);
EXPECT_TRUE(proto->root_frame().is_main_frame()); EXPECT_TRUE(result->proto.root_frame().is_main_frame());
auto token = base::UnguessableToken::Deserialize( auto token = base::UnguessableToken::Deserialize(
proto->root_frame().embedding_token_high(), result->proto.root_frame().embedding_token_high(),
proto->root_frame().embedding_token_low()); result->proto.root_frame().embedding_token_low());
#if defined(OS_WIN) #if defined(OS_WIN)
base::FilePath path = base::FilePath( base::FilePath path = base::FilePath(
base::UTF8ToUTF16(proto->root_frame().file_path())); base::UTF8ToUTF16(result->proto.root_frame().file_path()));
base::FilePath name( base::FilePath name(
base::UTF8ToUTF16(base::StrCat({token.ToString(), ".skp"}))); base::UTF8ToUTF16(base::StrCat({token.ToString(), ".skp"})));
#else #else
base::FilePath path = 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"})); base::FilePath name(base::StrCat({token.ToString(), ".skp"}));
#endif #endif
EXPECT_EQ(path.DirName(), expected_path); EXPECT_EQ(path.DirName(), expected_path);
...@@ -248,9 +248,9 @@ TEST_F(PaintPreviewBaseServiceTest, CaptureFailed) { ...@@ -248,9 +248,9 @@ TEST_F(PaintPreviewBaseServiceTest, CaptureFailed) {
[](base::OnceClosure quit_closure, [](base::OnceClosure quit_closure,
PaintPreviewBaseService::CaptureStatus expected_status, PaintPreviewBaseService::CaptureStatus expected_status,
PaintPreviewBaseService::CaptureStatus status, PaintPreviewBaseService::CaptureStatus status,
std::unique_ptr<PaintPreviewProto> proto) { std::unique_ptr<CaptureResult> result) {
EXPECT_EQ(status, expected_status); EXPECT_EQ(status, expected_status);
EXPECT_EQ(proto, nullptr); EXPECT_EQ(result, nullptr);
std::move(quit_closure).Run(); std::move(quit_closure).Run();
}, },
loop.QuitClosure(), loop.QuitClosure(),
...@@ -283,9 +283,9 @@ TEST_F(PaintPreviewBaseServiceTest, CaptureDisallowed) { ...@@ -283,9 +283,9 @@ TEST_F(PaintPreviewBaseServiceTest, CaptureDisallowed) {
[](base::OnceClosure quit_closure, [](base::OnceClosure quit_closure,
PaintPreviewBaseService::CaptureStatus expected_status, PaintPreviewBaseService::CaptureStatus expected_status,
PaintPreviewBaseService::CaptureStatus status, PaintPreviewBaseService::CaptureStatus status,
std::unique_ptr<PaintPreviewProto> proto) { std::unique_ptr<CaptureResult> result) {
EXPECT_EQ(status, expected_status); EXPECT_EQ(status, expected_status);
EXPECT_EQ(proto, nullptr); EXPECT_EQ(result, nullptr);
std::move(quit_closure).Run(); std::move(quit_closure).Run();
}, },
loop.QuitClosure(), loop.QuitClosure(),
......
...@@ -14,10 +14,13 @@ ...@@ -14,10 +14,13 @@
#include "base/containers/flat_set.h" #include "base/containers/flat_set.h"
#include "base/files/file_path.h" #include "base/files/file_path.h"
#include "base/unguessable_token.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/mojom/paint_preview_recorder.mojom.h"
#include "components/paint_preview/common/proto/paint_preview.pb.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_observer.h"
#include "content/public/browser/web_contents_user_data.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 "mojo/public/cpp/bindings/associated_remote.h"
#include "ui/gfx/geometry/rect.h" #include "ui/gfx/geometry/rect.h"
#include "url/gurl.h" #include "url/gurl.h"
...@@ -37,33 +40,22 @@ class PaintPreviewClient ...@@ -37,33 +40,22 @@ class PaintPreviewClient
using PaintPreviewCallback = using PaintPreviewCallback =
base::OnceCallback<void(base::UnguessableToken, base::OnceCallback<void(base::UnguessableToken,
mojom::PaintPreviewStatus, mojom::PaintPreviewStatus,
std::unique_ptr<PaintPreviewProto>)>; std::unique_ptr<CaptureResult>)>;
// Augmented version of mojom::PaintPreviewServiceParams. // Augmented version of mojom::PaintPreviewServiceParams.
struct PaintPreviewParams { struct PaintPreviewParams {
PaintPreviewParams(); explicit PaintPreviewParams(mojom::Persistence persistence);
~PaintPreviewParams(); ~PaintPreviewParams();
// The document GUID for this capture. // Indicates where the PaintPreviewRecorder should store its intermediate
base::UnguessableToken document_guid; // artifacts.
mojom::Persistence persistence;
// The root directory in which to store paint_previews. This should be // The root directory in which to store paint_previews. This should be
// a subdirectory inside the active user profile's directory. // a subdirectory inside the active user profile's directory.
base::FilePath root_dir; base::FilePath root_dir;
// The rect to which to clip the capture to. RecordingParams inner;
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;
}; };
~PaintPreviewClient() override; ~PaintPreviewClient() override;
...@@ -95,20 +87,21 @@ class PaintPreviewClient ...@@ -95,20 +87,21 @@ class PaintPreviewClient
// Internal Storage Classes ------------------------------------------------- // Internal Storage Classes -------------------------------------------------
// Representation of data for capturing a paint preview. // Ephemeral state for a document being captured. This will be accumulated to
struct PaintPreviewData { // as the capture progresses and results in a |CaptureResult|.
struct InProgressDocumentCaptureState {
public: public:
PaintPreviewData(); InProgressDocumentCaptureState();
~PaintPreviewData(); ~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::FilePath root_dir;
base::UnguessableToken root_frame_token; base::UnguessableToken root_frame_token;
// URL of the root frame.
GURL root_url;
// UKM Source ID of the WebContent. // UKM Source ID of the WebContent.
ukm::SourceId source_id; ukm::SourceId source_id;
...@@ -127,45 +120,51 @@ class PaintPreviewClient ...@@ -127,45 +120,51 @@ class PaintPreviewClient
// All the render frames that are allowed to be captured. // All the render frames that are allowed to be captured.
base::flat_set<base::UnguessableToken> accepted_tokens; base::flat_set<base::UnguessableToken> accepted_tokens;
// Data proto that is returned via callback. // If |Persistence::kMemoryBuffer|, this will contain the successful
std::unique_ptr<PaintPreviewProto> proto; // 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; // Indicates that at least one subframe finished unsuccessfully.
PaintPreviewData(PaintPreviewData&& other) noexcept; bool had_error = false;
private: // Indicates that at least one subframe finished successfully.
PaintPreviewData(const PaintPreviewData&) = delete; bool had_success = false;
PaintPreviewData& operator=(const PaintPreviewData&) = delete;
};
struct CreateResult { // Indicates if we should clean up files associated with awaiting frames on
public: // destruction
CreateResult(base::File file, base::File::Error error); bool should_clean_up_files = false;
~CreateResult();
CreateResult(CreateResult&& other);
CreateResult& operator=(CreateResult&& other);
base::File file; // Generates a file path based off |root_dir| and |frame_guid|. Will be in
base::File::Error error; // the form "{hexadecimal}.skp".
base::FilePath FilePathForFrame(const base::UnguessableToken& frame_guid);
private: // Record a successful recording into this capture state.
CreateResult(const CreateResult&) = delete; void RecordSuccessfulFrame(const base::UnguessableToken& frame_guid,
CreateResult& operator=(const CreateResult&) = delete; 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( private:
const PaintPreviewParams& params, InProgressDocumentCaptureState(const InProgressDocumentCaptureState&) =
base::File file); delete;
InProgressDocumentCaptureState& operator=(
const InProgressDocumentCaptureState&) = delete;
};
// Sets up for a capture of a frame on |render_frame_host| according to // Sets up for a capture of a frame on |render_frame_host| according to
// |params|. // |params|.
void CapturePaintPreviewInternal(const PaintPreviewParams& params, void CapturePaintPreviewInternal(const RecordingParams& params,
content::RenderFrameHost* render_frame_host); content::RenderFrameHost* render_frame_host);
// Initiates capture via the PaintPreviewRecorder associated with // Initiates capture via the PaintPreviewRecorder associated with
...@@ -173,19 +172,17 @@ class PaintPreviewClient ...@@ -173,19 +172,17 @@ class PaintPreviewClient
// is the GUID associated with the frame. |path| is file path associated with // 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). // the File stored in |result| (base::File isn't aware of its file path).
void RequestCaptureOnUIThread( void RequestCaptureOnUIThread(
const PaintPreviewParams& params,
const base::UnguessableToken& frame_guid, const base::UnguessableToken& frame_guid,
const RecordingParams& params,
const content::GlobalFrameRoutingId& render_frame_id, const content::GlobalFrameRoutingId& render_frame_id,
const base::FilePath& path, mojom::PaintPreviewStatus status,
CreateResult result); mojom::PaintPreviewCaptureParamsPtr capture_params);
// Handles recording the frame and updating client state when capture is // Handles recording the frame and updating client state when capture is
// complete. // complete.
void OnPaintPreviewCapturedCallback( void OnPaintPreviewCapturedCallback(
const base::UnguessableToken& guid,
const base::UnguessableToken& frame_guid, const base::UnguessableToken& frame_guid,
bool is_main_frame, const RecordingParams& params,
const base::FilePath& filename,
const content::GlobalFrameRoutingId& render_frame_id, const content::GlobalFrameRoutingId& render_frame_id,
mojom::PaintPreviewStatus status, mojom::PaintPreviewStatus status,
mojom::PaintPreviewCaptureResponsePtr response); mojom::PaintPreviewCaptureResponsePtr response);
...@@ -195,17 +192,9 @@ class PaintPreviewClient ...@@ -195,17 +192,9 @@ class PaintPreviewClient
void MarkFrameAsProcessed(base::UnguessableToken guid, void MarkFrameAsProcessed(base::UnguessableToken guid,
const base::UnguessableToken& frame_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. // 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 ------------------------------------------------------------------ // Storage ------------------------------------------------------------------
...@@ -218,8 +207,11 @@ class PaintPreviewClient ...@@ -218,8 +207,11 @@ class PaintPreviewClient
base::flat_map<base::UnguessableToken, base::flat_set<base::UnguessableToken>> base::flat_map<base::UnguessableToken, base::flat_set<base::UnguessableToken>>
pending_previews_on_subframe_; pending_previews_on_subframe_;
// Maps a document GUID to its data. // Maps a document GUID to its capture state while it is in-progress. Entries
base::flat_map<base::UnguessableToken, PaintPreviewData> all_document_data_; // 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}; base::WeakPtrFactory<PaintPreviewClient> weak_ptr_factory_{this};
......
...@@ -7,6 +7,8 @@ import("//testing/test.gni") ...@@ -7,6 +7,8 @@ import("//testing/test.gni")
if (!is_ios) { if (!is_ios) {
static_library("common") { static_library("common") {
sources = [ sources = [
"capture_result.cc",
"capture_result.h",
"file_stream.cc", "file_stream.cc",
"file_stream.h", "file_stream.h",
"file_utils.cc", "file_utils.cc",
...@@ -37,9 +39,13 @@ if (!is_ios) { ...@@ -37,9 +39,13 @@ if (!is_ios) {
source_set("test_utils") { source_set("test_utils") {
testonly = true testonly = true
sources = [ "test_utils.h" ] sources = [
"test_utils.cc",
"test_utils.h",
]
deps = [ deps = [
"//components/paint_preview/common/mojom",
"//testing/gmock", "//testing/gmock",
"//testing/gtest", "//testing/gtest",
] ]
...@@ -58,6 +64,7 @@ if (!is_ios) { ...@@ -58,6 +64,7 @@ if (!is_ios) {
deps = [ deps = [
":common", ":common",
":test_utils",
"//base", "//base",
"//base/test:test_support", "//base/test:test_support",
"//skia", "//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 @@ ...@@ -4,6 +4,7 @@
module paint_preview.mojom; module paint_preview.mojom;
import "mojo/public/mojom/base/big_buffer.mojom";
import "mojo/public/mojom/base/file.mojom"; import "mojo/public/mojom/base/file.mojom";
import "mojo/public/mojom/base/time.mojom"; import "mojo/public/mojom/base/time.mojom";
import "mojo/public/mojom/base/unguessable_token.mojom"; import "mojo/public/mojom/base/unguessable_token.mojom";
...@@ -36,7 +37,24 @@ enum PaintPreviewStatus { ...@@ -36,7 +37,24 @@ enum PaintPreviewStatus {
kFailed, 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 { 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). // GUID for the Paint Preview (used to associate subframes to main frame).
mojo_base.mojom.UnguessableToken guid; mojo_base.mojom.UnguessableToken guid;
...@@ -52,7 +70,9 @@ struct PaintPreviewCaptureParams { ...@@ -52,7 +70,9 @@ struct PaintPreviewCaptureParams {
// File to write the SkPicture to (write-only). A separate file should be // File to write the SkPicture to (write-only). A separate file should be
// created for each RenderFrame. // 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 // The maximum allowed size of a capture that can be produced. A value of
// 0 means the size is unrestricted. // 0 means the size is unrestricted.
...@@ -85,6 +105,15 @@ struct PaintPreviewCaptureResponse { ...@@ -85,6 +105,15 @@ struct PaintPreviewCaptureResponse {
// Scroll offsets of the frame at capture time. // Scroll offsets of the frame at capture time.
gfx.mojom.Size scroll_offsets; 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 // Service for capturing a paint preview of a RenderFrame's contents. This
......
...@@ -44,6 +44,8 @@ message PaintPreviewFrameProto { ...@@ -44,6 +44,8 @@ message PaintPreviewFrameProto {
required bool is_main_frame = 3; required bool is_main_frame = 3;
// The file path to the serialized Skia Picture. // The file path to the serialized Skia Picture.
// null if the persistence type of the |PaintPreviewCaptureParams| is
// |Persistence::MemoryBuffer|.
optional string file_path = 4; optional string file_path = 4;
// A list of links within the frame. // 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 @@ ...@@ -5,6 +5,7 @@
#ifndef COMPONENTS_PAINT_PREVIEW_COMMON_TEST_UTILS_H_ #ifndef COMPONENTS_PAINT_PREVIEW_COMMON_TEST_UTILS_H_
#define 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" #include "testing/gmock/include/gmock/gmock.h"
MATCHER_P(EqualsProto, message, "") { MATCHER_P(EqualsProto, message, "") {
...@@ -14,4 +15,9 @@ MATCHER_P(EqualsProto, message, "") { ...@@ -14,4 +15,9 @@ MATCHER_P(EqualsProto, message, "") {
return expected_serialized == actual_serialized; 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_ #endif // COMPONENTS_PAINT_PREVIEW_COMMON_TEST_UTILS_H_
...@@ -38,6 +38,7 @@ if (!is_ios) { ...@@ -38,6 +38,7 @@ if (!is_ios) {
"//base", "//base",
"//base/test:test_support", "//base/test:test_support",
"//cc/paint", "//cc/paint",
"//components/paint_preview/common:test_utils",
"//testing/gmock", "//testing/gmock",
"//testing/gtest", "//testing/gtest",
] ]
......
...@@ -18,7 +18,6 @@ ...@@ -18,7 +18,6 @@
#include "cc/paint/paint_recorder.h" #include "cc/paint/paint_recorder.h"
#include "components/paint_preview/renderer/paint_preview_recorder_utils.h" #include "components/paint_preview/renderer/paint_preview_recorder_utils.h"
#include "content/public/renderer/render_frame.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/common/associated_interfaces/associated_interface_registry.h"
#include "third_party/blink/public/web/web_local_frame.h" #include "third_party/blink/public/web/web_local_frame.h"
...@@ -46,6 +45,7 @@ FinishedRecording FinishRecording( ...@@ -46,6 +45,7 @@ FinishedRecording FinishRecording(
sk_sp<const cc::PaintRecord> recording, sk_sp<const cc::PaintRecord> recording,
const gfx::Rect& bounds, const gfx::Rect& bounds,
std::unique_ptr<PaintPreviewTracker> tracker, std::unique_ptr<PaintPreviewTracker> tracker,
mojom::Persistence persistence,
base::File skp_file, base::File skp_file,
size_t max_capture_size, size_t max_capture_size,
mojom::PaintPreviewCaptureResponsePtr response) { mojom::PaintPreviewCaptureResponsePtr response) {
...@@ -61,9 +61,24 @@ FinishedRecording FinishRecording( ...@@ -61,9 +61,24 @@ FinishedRecording FinishRecording(
ParseGlyphs(recording.get(), tracker.get()); ParseGlyphs(recording.get(), tracker.get());
TRACE_EVENT_END0("paint_preview", "ParseGlyphs"); TRACE_EVENT_END0("paint_preview", "ParseGlyphs");
size_t serialized_size = 0; size_t serialized_size = 0;
if (!SerializeAsSkPicture(recording, tracker.get(), bounds,
std::move(skp_file), max_capture_size, bool success = false;
&serialized_size)) { 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; out.status = mojom::PaintPreviewStatus::kCaptureFailed;
return out; return out;
} }
...@@ -215,7 +230,8 @@ void PaintPreviewRecorderImpl::CapturePaintPreviewInternal( ...@@ -215,7 +230,8 @@ void PaintPreviewRecorderImpl::CapturePaintPreviewInternal(
// image. // image.
FinishedRecording recording = FinishRecording( FinishedRecording recording = FinishRecording(
recorder.finishRecordingAsPicture(), bounds, std::move(tracker), 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)); std::move(callback).Run(recording.status, std::move(recording.response));
} }
......
...@@ -33,12 +33,8 @@ void ParseGlyphs(const cc::PaintOpBuffer* buffer, ...@@ -33,12 +33,8 @@ void ParseGlyphs(const cc::PaintOpBuffer* buffer,
bool SerializeAsSkPicture(sk_sp<const cc::PaintRecord> record, bool SerializeAsSkPicture(sk_sp<const cc::PaintRecord> record,
PaintPreviewTracker* tracker, PaintPreviewTracker* tracker,
const gfx::Rect& dimensions, const gfx::Rect& dimensions,
base::File file, SkWStream* out_stream) {
size_t max_size,
size_t* serialized_size) {
TRACE_EVENT0("paint_preview", "SerializeAsSkPicture"); TRACE_EVENT0("paint_preview", "SerializeAsSkPicture");
if (!file.IsValid())
return false;
// base::Unretained is safe as |tracker| outlives the usage of // base::Unretained is safe as |tracker| outlives the usage of
// |custom_callback|. // |custom_callback|.
...@@ -54,13 +50,10 @@ bool SerializeAsSkPicture(sk_sp<const cc::PaintRecord> record, ...@@ -54,13 +50,10 @@ bool SerializeAsSkPicture(sk_sp<const cc::PaintRecord> record,
TypefaceSerializationContext typeface_context(tracker->GetTypefaceUsageMap()); TypefaceSerializationContext typeface_context(tracker->GetTypefaceUsageMap());
auto serial_procs = MakeSerialProcs(tracker->GetPictureSerializationContext(), auto serial_procs = MakeSerialProcs(tracker->GetPictureSerializationContext(),
&typeface_context); &typeface_context);
FileWStream stream(std::move(file), max_size);
skp->serialize(&stream, &serial_procs); skp->serialize(out_stream, &serial_procs);
stream.flush(); out_stream->flush();
stream.Close(); return true;
DCHECK(serialized_size);
*serialized_size = stream.ActualBytesWritten();
return !stream.DidWriteFail();
} }
void BuildResponse(PaintPreviewTracker* tracker, void BuildResponse(PaintPreviewTracker* tracker,
...@@ -79,4 +72,43 @@ void BuildResponse(PaintPreviewTracker* tracker, ...@@ -79,4 +72,43 @@ void BuildResponse(PaintPreviewTracker* tracker,
tracker->MoveLinks(&response->links); 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 } // namespace paint_preview
...@@ -8,7 +8,9 @@ ...@@ -8,7 +8,9 @@
#include "base/files/file.h" #include "base/files/file.h"
#include "cc/paint/paint_record.h" #include "cc/paint/paint_record.h"
#include "components/paint_preview/common/mojom/paint_preview_recorder.mojom-forward.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/SkRefCnt.h"
#include "third_party/skia/include/core/SkStream.h"
#include "ui/gfx/geometry/rect.h" #include "ui/gfx/geometry/rect.h"
// These utilities are used by the PaintPreviewRecorderImpl. They are separate // These utilities are used by the PaintPreviewRecorderImpl. They are separate
...@@ -23,17 +25,12 @@ class PaintPreviewTracker; ...@@ -23,17 +25,12 @@ class PaintPreviewTracker;
// them to |tracker|. // them to |tracker|.
void ParseGlyphs(const cc::PaintOpBuffer* buffer, PaintPreviewTracker* tracker); void ParseGlyphs(const cc::PaintOpBuffer* buffer, PaintPreviewTracker* tracker);
// Serializes |record| to |file| as an SkPicture of size |dimensions|. |tracker| // Serializes |record| to |out_stream| as an SkPicture of size |dimensions|.
// supplies metadata required during serialization. |max_size| is a limit on the // |tracker| supplies metadata required during serialization.
// 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|.
bool SerializeAsSkPicture(sk_sp<const cc::PaintRecord> record, bool SerializeAsSkPicture(sk_sp<const cc::PaintRecord> record,
PaintPreviewTracker* tracker, PaintPreviewTracker* tracker,
const gfx::Rect& dimensions, const gfx::Rect& dimensions,
base::File file, SkWStream* out_stream);
size_t max_size,
size_t* serialized_size);
// Builds a mojom::PaintPreviewCaptureResponse |response| using the data // Builds a mojom::PaintPreviewCaptureResponse |response| using the data
// contained in |tracker|. // contained in |tracker|.
...@@ -41,6 +38,36 @@ bool SerializeAsSkPicture(sk_sp<const cc::PaintRecord> record, ...@@ -41,6 +38,36 @@ bool SerializeAsSkPicture(sk_sp<const cc::PaintRecord> record,
void BuildResponse(PaintPreviewTracker* tracker, void BuildResponse(PaintPreviewTracker* tracker,
mojom::PaintPreviewCaptureResponse* response); 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 } // namespace paint_preview
#endif // COMPONENTS_PAINT_PREVIEW_RENDERER_PAINT_PREVIEW_RECORDER_UTILS_H_ #endif // COMPONENTS_PAINT_PREVIEW_RENDERER_PAINT_PREVIEW_RECORDER_UTILS_H_
...@@ -4,6 +4,7 @@ ...@@ -4,6 +4,7 @@
#include "components/paint_preview/renderer/paint_preview_recorder_utils.h" #include "components/paint_preview/renderer/paint_preview_recorder_utils.h"
#include <memory>
#include <string> #include <string>
#include "base/containers/flat_map.h" #include "base/containers/flat_map.h"
...@@ -11,13 +12,18 @@ ...@@ -11,13 +12,18 @@
#include "base/files/file.h" #include "base/files/file.h"
#include "base/files/scoped_temp_dir.h" #include "base/files/scoped_temp_dir.h"
#include "base/memory/read_only_shared_memory_region.h" #include "base/memory/read_only_shared_memory_region.h"
#include "base/notreached.h"
#include "base/optional.h"
#include "base/unguessable_token.h" #include "base/unguessable_token.h"
#include "cc/paint/paint_canvas.h" #include "cc/paint/paint_canvas.h"
#include "cc/paint/paint_flags.h" #include "cc/paint/paint_flags.h"
#include "cc/paint/paint_recorder.h" #include "cc/paint/paint_recorder.h"
#include "components/paint_preview/common/file_stream.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/paint_preview_tracker.h"
#include "components/paint_preview/common/serial_utils.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/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h" #include "testing/gtest/include/gtest/gtest.h"
#include "third_party/skia/include/core/SkFont.h" #include "third_party/skia/include/core/SkFont.h"
...@@ -64,18 +70,80 @@ TEST(PaintPreviewRecorderUtilsTest, TestParseGlyphs) { ...@@ -64,18 +70,80 @@ TEST(PaintPreviewRecorderUtilsTest, TestParseGlyphs) {
(*usage_map)[typeface->uniqueID()]->IsSet(typeface->unicharToGlyph('g'))); (*usage_map)[typeface->uniqueID()]->IsSet(typeface->unicharToGlyph('g')));
} }
TEST(PaintPreviewRecorderUtilsTest, TestSerializeAsSkPicture) { class PaintPreviewRecorderUtilsSerializeAsSkPictureTest
PaintPreviewTracker tracker(base::UnguessableToken::Create(), : public testing::TestWithParam<mojom::Persistence> {
base::UnguessableToken::Create(), true); 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::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; base::flat_set<uint32_t> ctx;
uint32_t content_id = tracker.CreateContentForRemoteFrame( uint32_t content_id = tracker.CreateContentForRemoteFrame(
gfx::Rect(10, 10), base::UnguessableToken::Create()); gfx::Rect(10, 10), base::UnguessableToken::Create());
...@@ -86,20 +154,10 @@ TEST(PaintPreviewRecorderUtilsTest, TestSerializeAsSkPicture) { ...@@ -86,20 +154,10 @@ TEST(PaintPreviewRecorderUtilsTest, TestSerializeAsSkPicture) {
canvas->recordCustomData(content_id); canvas->recordCustomData(content_id);
ctx.insert(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; size_t out_size = 0;
EXPECT_TRUE(SerializeAsSkPicture(record, &tracker, dimensions, auto rstream = SerializeAsSkPicture(base::nullopt, &out_size);
std::move(write_file), 0, &out_size)); ASSERT_TRUE(rstream.has_value());
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));
SkDeserialProcs procs; SkDeserialProcs procs;
procs.fPictureProc = [](const void* data, size_t length, void* ctx) { procs.fPictureProc = [](const void* data, size_t length, void* ctx) {
uint32_t content_id; uint32_t content_id;
...@@ -112,35 +170,23 @@ TEST(PaintPreviewRecorderUtilsTest, TestSerializeAsSkPicture) { ...@@ -112,35 +170,23 @@ TEST(PaintPreviewRecorderUtilsTest, TestSerializeAsSkPicture) {
return MakeEmptyPicture(); return MakeEmptyPicture();
}; };
procs.fPictureCtx = &ctx; procs.fPictureCtx = &ctx;
SkPicture::MakeFromStream(&rstream, &procs); SkPicture::MakeFromStream(rstream.value().get(), &procs);
EXPECT_TRUE(ctx.empty()); EXPECT_TRUE(ctx.empty());
} }
TEST(PaintPreviewRecorderUtilsTest, TestSerializeAsSkPictureFail) { TEST_P(PaintPreviewRecorderUtilsSerializeAsSkPictureTest, FailIfExceedMaxSize) {
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();
size_t out_size = 2; size_t out_size = 2;
EXPECT_FALSE(SerializeAsSkPicture(record, &tracker, dimensions, auto rstream = SerializeAsSkPicture({1}, &out_size);
std::move(write_file), 1, &out_size)); EXPECT_FALSE(rstream.has_value());
EXPECT_LE(out_size, 1U); EXPECT_LE(out_size, 1U);
} }
INSTANTIATE_TEST_SUITE_P(All,
PaintPreviewRecorderUtilsSerializeAsSkPictureTest,
testing::Values(mojom::Persistence::kFileSystem,
mojom::Persistence::kMemoryBuffer),
PersistenceParamToString);
TEST(PaintPreviewRecorderUtilsTest, TestBuildResponse) { TEST(PaintPreviewRecorderUtilsTest, TestBuildResponse) {
auto token = base::UnguessableToken::Create(); auto token = base::UnguessableToken::Create();
auto embedding_token = base::UnguessableToken::Create(); auto embedding_token = base::UnguessableToken::Create();
......
...@@ -55,6 +55,7 @@ source_set("unit_tests") { ...@@ -55,6 +55,7 @@ source_set("unit_tests") {
"//base", "//base",
"//base/test:test_support", "//base/test:test_support",
"//components/paint_preview/common", "//components/paint_preview/common",
"//components/paint_preview/common:test_utils",
"//components/paint_preview/common/proto", "//components/paint_preview/common/proto",
"//skia", "//skia",
"//testing/gmock", "//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