Commit 66cdca60 authored by ckitagawa's avatar ckitagawa Committed by Commit Bot

[Paint Preview] Paint Preview Recorder

Renderer side paint preview implementation. This CL creates a mojo
service on a RenderFrameObserver that facilitates most of the paint
preview capture management.

Flow:
- Browser calls this service to request paint preview capture of current
  frame (future CL 1)
- PaintPreviewServiceImpl sets up for capture in Blink (this CL)
- Blink handles the capture via a flow similar to printing (future CL 2)
  - This will include invoking a request to the browser to forward
    requests to OOP subframes.
- PaintPreviewServiceImpl does post-processing and marshals a response
  (this CL)
  - Two artifacts are produced:
    - A SkPicture is written to the file handle provided by the browser
    - A proto containing metadata for the frame is sent via shared
      memory to the browser.
- Browser does some minor modifications to the proto then either stores
  the proto to disk or forwards the proto and SkPicture files to a
  utility process for playback. The browser will retain the links from
  the proto for hit testing. (future CLs)

Part of landing:
https://chromium-review.googlesource.com/c/chromium/src/+/1786583

Internal Doc: go/fdt-design

Bug: 994833
Change-Id: I88ffb89d92dfcb78b61a91a21df2b842c713f8b0
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1824669Reviewed-by: default avatarScott Violet <sky@chromium.org>
Reviewed-by: default avatarKhushal <khushalsagar@chromium.org>
Reviewed-by: default avatarJochen Eisinger <jochen@chromium.org>
Reviewed-by: default avatarKen Buchanan <kenrb@chromium.org>
Reviewed-by: default avatarIan Vollick <vollick@chromium.org>
Commit-Queue: Calder Kitagawa <ckitagawa@chromium.org>
Cr-Commit-Position: refs/heads/master@{#706443}
parent bed41c7e
......@@ -243,6 +243,7 @@ test("components_unittests") {
"//components/page_load_metrics/renderer:unit_tests",
"//components/paint_preview/browser:unit_tests",
"//components/paint_preview/common:unit_tests",
"//components/paint_preview/renderer:unit_tests",
"//components/password_manager/content/browser:unit_tests",
"//components/payments/content:unit_tests",
"//components/payments/content/utility:unit_tests",
......@@ -510,6 +511,7 @@ if (!is_ios && !is_fuchsia) {
"dom_distiller/content/browser/distiller_page_web_contents_browsertest.cc",
"dom_distiller/content/browser/test/dom_distiller_js_browsertest.cc",
"offline_pages/content/renovations/test/page_renovator_browsertest.cc",
"paint_preview/renderer/paint_preview_recorder_browsertest.cc",
"security_state/content/content_utils_browsertest.cc",
"ukm/content/source_url_recorder_browsertest.cc",
]
......@@ -536,6 +538,7 @@ if (!is_ios && !is_fuchsia) {
"//components/dom_distiller/core:test_support",
"//components/offline_pages/content/renovations",
"//components/offline_pages/core/renovations",
"//components/paint_preview/renderer",
"//components/password_manager/content/browser",
"//components/security_state/content",
"//components/security_state/core",
......
include_rules = [
"+mojo/public/cpp",
"+third_party/harfbuzz-ng/src/src",
"+third_party/skia/include/core",
"+ui/gfx/geometry",
......
......@@ -10,9 +10,9 @@ low-cost alternative to a tab in various contexts.
## Why //components?
This directory facilitates sharing code between Blink and the browser
process. This has the additional benefit of keeping most of the code
centralized to this directory. Parts of the code are consumed in;
This directory facilitates sharing code between Blink and the browser process.
This has the additional benefit of keeping most of the code centralized to this
directory. Parts of the code are consumed in;
* `//cc`
* `//chrome`
......@@ -24,4 +24,6 @@ so it is incompatible with iOS.
## Directory Structure (WIP)
* `browser/` - Code related to managing and requesting paint previews.
* `common/` - Shared code; mojo, protos, and C++ code.
* `renderer/` - Code related to capturing paint previews within the renderer.
......@@ -31,6 +31,19 @@ if (!is_ios) {
]
}
source_set("test_utils") {
testonly = true
sources = [
"test_utils.h",
]
deps = [
"//testing/gmock",
"//testing/gtest",
]
}
source_set("unit_tests") {
testonly = true
......
# Copyright 2019 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
import("//mojo/public/tools/bindings/mojom.gni")
mojom("mojom") {
sources = [
"paint_preview_recorder.mojom",
]
public_deps = [
"//components/discardable_memory/public/mojom",
"//mojo/public/mojom/base",
"//ui/gfx/geometry/mojom",
]
}
per-file *.mojom=set noparent
per-file *.mojom=file://ipc/SECURITY_OWNERS
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
module paint_preview.mojom;
import "components/discardable_memory/public/mojom/discardable_shared_memory_manager.mojom";
import "mojo/public/mojom/base/file.mojom";
import "mojo/public/mojom/base/shared_memory.mojom";
import "mojo/public/mojom/base/unguessable_token.mojom";
import "ui/gfx/geometry/mojom/geometry.mojom";
// Status codes for the PaintPreviewRecorder.
enum PaintPreviewStatus {
// Everything worked as intended.
kOk,
// The service was already performing a capture of the frame.
kAlreadyCapturing,
// Capturing the SkPicture for the frame failed or the file provided was bad.
kCaptureFailed,
// Serializing the proto to shared memory failed. Either the data was invalid,
// or the memory could not be allocated.
kProtoSerializationFailed,
};
struct PaintPreviewCaptureParams {
// GUID for the Paint Preview (used to associate subframes to main frame).
mojo_base.mojom.UnguessableToken guid;
// Clip rect for the capture. An empty |clip_rect| will be treated as
// unclipped and will default to the frame (document) size.
gfx.mojom.Rect clip_rect;
// Used to identify if the capture request is for the main frame.
bool is_main_frame;
// File to write the SkPicture to (write-only). A separate file should be
// created for each RenderFrame.
mojo_base.mojom.File file;
};
// Service for capturing a paint preview of a RenderFrame's contents. This
// includes both the visual contents (as an SkPicture) and hyperlinks
// for the frame.
interface PaintPreviewRecorder {
// Captures a paint preview of the RenderFrame that receives the request.
//
// This interface is used for both the main frame and sub frames.
// Out-of-process subframes are handled by making requests back to the browser
// via the RenderFrameProxy. The browser handles dispatching these requests to
// the correct RenderFrame and aggregating all the outputs.
//
// Returns a status. If |status| == kOk then proto contains a serialized
// PaintPreviewFrameProto.
CapturePaintPreview(PaintPreviewCaptureParams params) =>
(PaintPreviewStatus status,
mojo_base.mojom.ReadOnlySharedMemoryRegion? proto);
};
......@@ -44,7 +44,10 @@ void RectToRectProto(RectProto* rect_proto, const gfx::Rect& rect) {
} // namespace
PaintPreviewTracker::PaintPreviewTracker() = default;
PaintPreviewTracker::PaintPreviewTracker(const base::UnguessableToken& guid,
int routing_id,
bool is_main_frame)
: guid_(guid), routing_id_(routing_id), is_main_frame_(is_main_frame) {}
PaintPreviewTracker::~PaintPreviewTracker() = default;
uint32_t PaintPreviewTracker::CreateContentForRemoteFrame(const gfx::Rect& rect,
......
......@@ -23,17 +23,22 @@
namespace paint_preview {
// Tracks metadata for a Paint Preview.
// Tracks metadata for a Paint Preview. Contains all the data required to
// produce a PaintPreviewFrameProto.
class PaintPreviewTracker {
public:
PaintPreviewTracker();
PaintPreviewTracker(const base::UnguessableToken& guid,
int routing_id,
bool is_main_frame);
~PaintPreviewTracker();
// Data Collection ----------------------------------------------------------
// Getters ------------------------------------------------------------------
// Use base::UnguessableToken as a GUID that identifies the paint preview.
void SetGuid(base::UnguessableToken guid) { guid_ = guid; }
base::UnguessableToken Guid() const { return guid_; }
int RoutingId() const { return routing_id_; }
bool IsMainFrame() const { return is_main_frame_; }
// Data Collection ----------------------------------------------------------
// Creates a placeholder SkPicture for an OOP subframe located at |rect|
// mapped to the |routing_id| of OOP RenderFrame. Returns the content id of
......@@ -56,6 +61,7 @@ class PaintPreviewTracker {
void CustomDataToSkPictureCallback(SkCanvas* canvas, uint32_t content_id);
// Expose internal maps for use in MakeSerialProcs().
// NOTE: Cannot be const due to how SkPicture procs work.
PictureSerializationContext* GetPictureSerializationContext() {
return &content_id_to_proxy_id_;
}
......@@ -65,7 +71,10 @@ class PaintPreviewTracker {
const std::vector<LinkDataProto>& GetLinks() const { return links_; }
private:
base::UnguessableToken guid_;
const base::UnguessableToken guid_;
const int routing_id_;
const bool is_main_frame_;
std::vector<LinkDataProto> links_;
PictureSerializationContext content_id_to_proxy_id_;
TypefaceUsageMap typeface_glyph_usage_;
......
......@@ -18,6 +18,8 @@ namespace paint_preview {
namespace {
constexpr int32_t kRoutingId = 1;
struct TestContext {
const gfx::Rect* rect;
bool was_called;
......@@ -25,15 +27,17 @@ struct TestContext {
} // namespace
TEST(PaintPreviewTrackerTest, TestGuid) {
TEST(PaintPreviewTrackerTest, TestGetters) {
auto token = base::UnguessableToken::Create();
PaintPreviewTracker tracker;
tracker.SetGuid(token);
PaintPreviewTracker tracker(token, kRoutingId, true);
EXPECT_EQ(tracker.Guid(), token);
EXPECT_EQ(tracker.RoutingId(), kRoutingId);
EXPECT_TRUE(tracker.IsMainFrame());
}
TEST(PaintPreviewTrackerTest, TestRemoteFramePlaceholderPicture) {
PaintPreviewTracker tracker;
PaintPreviewTracker tracker(base::UnguessableToken::Create(), kRoutingId,
true);
const int kRoutingId = 50;
gfx::Rect rect(50, 40, 30, 20);
uint32_t content_id = tracker.CreateContentForRemoteFrame(rect, kRoutingId);
......@@ -53,7 +57,8 @@ TEST(PaintPreviewTrackerTest, TestRemoteFramePlaceholderPicture) {
}
TEST(PaintPreviewTrackerTest, TestGlyphRunList) {
PaintPreviewTracker tracker;
PaintPreviewTracker tracker(base::UnguessableToken::Create(), kRoutingId,
true);
std::string unichars = "abc";
auto typeface = SkTypeface::MakeDefault();
SkFont font(typeface);
......@@ -70,7 +75,8 @@ TEST(PaintPreviewTrackerTest, TestGlyphRunList) {
}
TEST(PaintPreviewTrackerTest, TestAnnotateLinks) {
PaintPreviewTracker tracker;
PaintPreviewTracker tracker(base::UnguessableToken::Create(), kRoutingId,
true);
const std::string url_1 = "https://www.chromium.org";
const gfx::Rect rect_1(10, 20, 30, 40);
tracker.AnnotateLink(url_1, rect_1);
......
......@@ -33,7 +33,7 @@ message PaintPreviewFrameProto {
// Originates in renderer as Routing ID.
// Converted to (Process ID || Routing ID) once processed in browser.
required int64 id = 3;
required uint64 id = 3;
// Boolean indicating if the frame is the main frame.
required bool is_main_frame = 4;
......
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef COMPONENTS_PAINT_PREVIEW_COMMON_TEST_UTILS_H_
#define COMPONENTS_PAINT_PREVIEW_COMMON_TEST_UTILS_H_
#include "testing/gmock/include/gmock/gmock.h"
MATCHER_P(EqualsProto, message, "") {
std::string expected_serialized, actual_serialized;
message.SerializeToString(&expected_serialized);
arg.SerializeToString(&actual_serialized);
return expected_serialized == actual_serialized;
}
#endif // COMPONENTS_PAINT_PREVIEW_COMMON_TEST_UTILS_H_
# Copyright 2019 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
import("//testing/test.gni")
if (!is_ios) {
static_library("renderer") {
sources = [
"paint_preview_recorder_impl.cc",
"paint_preview_recorder_impl.h",
"paint_preview_recorder_utils.cc",
"paint_preview_recorder_utils.h",
]
deps = [
"//base",
"//cc/paint",
"//content/public/renderer",
"//mojo/public/cpp/base",
"//third_party/blink/public:blink_headers",
"//third_party/blink/public/common",
]
public_deps = [
"//components/paint_preview/common",
"//components/paint_preview/common/mojom",
"//components/paint_preview/common/proto",
]
}
source_set("unit_tests") {
testonly = true
sources = [
"paint_preview_recorder_utils_unittest.cc",
]
deps = [
":renderer",
"//base",
"//base/test:test_support",
"//cc/paint",
"//components/paint_preview/common:test_utils",
"//testing/gmock",
"//testing/gtest",
]
}
test("paint_preview_renderer_unit_tests") {
deps = [
":unit_tests",
"//base",
"//base/test:test_support",
"//components/test:run_all_unittests",
]
}
}
include_rules = [
"+cc/paint",
"+content/public/renderer",
"+content/public/test",
"+third_party/blink/public",
]
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/paint_preview/renderer/paint_preview_recorder_impl.h"
#include "base/files/file.h"
#include "base/files/file_path.h"
#include "base/files/scoped_temp_dir.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/renderer/render_frame.h"
#include "content/public/renderer/render_view.h"
#include "content/public/test/render_view_test.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace paint_preview {
namespace {
// Checks that |status| == |expected_status| and loads |region| into |proto| if
// |expected_status| == kOk. If |expected_status| != kOk |proto| can safely be
// nullptr.
void OnCaptureFinished(mojom::PaintPreviewStatus expected_status,
PaintPreviewFrameProto* proto,
mojom::PaintPreviewStatus status,
base::ReadOnlySharedMemoryRegion region) {
EXPECT_EQ(status, expected_status);
if (expected_status == mojom::PaintPreviewStatus::kOk) {
EXPECT_TRUE(region.IsValid());
auto mapping = region.Map();
EXPECT_TRUE(proto->ParseFromArray(mapping.memory(), mapping.size()));
}
}
} // namespace
class PaintPreviewRecorderRenderViewTest : public content::RenderViewTest {
public:
PaintPreviewRecorderRenderViewTest() {}
~PaintPreviewRecorderRenderViewTest() override {}
void SetUp() override {
ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
RenderViewTest::SetUp();
}
content::RenderFrame* GetFrame() { return view_->GetMainRenderFrame(); }
base::FilePath MakeTestFilePath(const std::string& filename) {
return temp_dir_.GetPath().AppendASCII(filename);
}
private:
base::ScopedTempDir temp_dir_;
};
TEST_F(PaintPreviewRecorderRenderViewTest, TestCaptureMainFrame) {
LoadHTML(
"<body style='min-height:1000px;'>"
" <div style='width: 100px; height: 100px; "
" background-color: #000000'>&nbsp;</div>"
" <p><a href='https://www.foo.com'>Foo</a></p>"
"</body>");
base::FilePath skp_path = MakeTestFilePath("test.skp");
mojom::PaintPreviewCaptureParamsPtr params =
mojom::PaintPreviewCaptureParams::New();
auto token = base::UnguessableToken::Create();
params->guid = token;
params->clip_rect = gfx::Rect();
params->is_main_frame = true;
base::File skp_file(skp_path,
base::File::FLAG_CREATE_ALWAYS | base::File::FLAG_WRITE);
params->file = std::move(skp_file);
PaintPreviewFrameProto out_proto;
content::RenderFrame* frame = GetFrame();
int routing_id = frame->GetRoutingID();
PaintPreviewRecorderImpl paint_preview_recorder(frame);
paint_preview_recorder.CapturePaintPreview(
std::move(params),
base::BindOnce(&OnCaptureFinished, mojom::PaintPreviewStatus::kOk,
&out_proto));
// Here id() is just the routing ID.
EXPECT_EQ(static_cast<int>(out_proto.id()), routing_id);
EXPECT_TRUE(out_proto.is_main_frame());
EXPECT_EQ(out_proto.unguessable_token_low(), token.GetLowForSerialization());
EXPECT_EQ(out_proto.unguessable_token_high(),
token.GetHighForSerialization());
EXPECT_EQ(out_proto.content_id_proxy_id_map_size(), 0);
// NOTE: should be non-zero once the Blink implementation is hooked up.
EXPECT_EQ(out_proto.links_size(), 0);
}
TEST_F(PaintPreviewRecorderRenderViewTest, TestCaptureInvalidFile) {
LoadHTML("<body></body>");
mojom::PaintPreviewCaptureParamsPtr params =
mojom::PaintPreviewCaptureParams::New();
auto token = base::UnguessableToken::Create();
params->guid = token;
params->clip_rect = gfx::Rect();
params->is_main_frame = true;
base::File skp_file; // Invalid file.
params->file = std::move(skp_file);
content::RenderFrame* frame = GetFrame();
PaintPreviewRecorderImpl paint_preview_recorder(frame);
paint_preview_recorder.CapturePaintPreview(
std::move(params),
base::BindOnce(&OnCaptureFinished,
mojom::PaintPreviewStatus::kCaptureFailed, nullptr));
}
} // namespace paint_preview
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/paint_preview/renderer/paint_preview_recorder_impl.h"
#include <utility>
#include "base/auto_reset.h"
#include "base/memory/shared_memory.h"
#include "base/task_runner.h"
#include "cc/paint/paint_record.h"
#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"
namespace paint_preview {
namespace {
mojom::PaintPreviewStatus FinishRecording(
sk_sp<const cc::PaintRecord> recording,
const gfx::Rect& bounds,
PaintPreviewTracker* tracker,
base::File skp_file,
base::ReadOnlySharedMemoryRegion* region) {
ParseGlyphs(recording.get(), tracker);
if (!SerializeAsSkPicture(recording, tracker, bounds, std::move(skp_file)))
return mojom::PaintPreviewStatus::kCaptureFailed;
if (!BuildAndSerializeProto(tracker, region))
return mojom::PaintPreviewStatus::kProtoSerializationFailed;
return mojom::PaintPreviewStatus::kOk;
}
} // namespace
PaintPreviewRecorderImpl::PaintPreviewRecorderImpl(
content::RenderFrame* render_frame)
: content::RenderFrameObserver(render_frame),
is_painting_preview_(false),
is_main_frame_(render_frame->IsMainFrame()),
routing_id_(render_frame->GetRoutingID()) {
render_frame->GetAssociatedInterfaceRegistry()->AddInterface(
base::BindRepeating(&PaintPreviewRecorderImpl::BindPaintPreviewRecorder,
weak_ptr_factory_.GetWeakPtr()));
}
PaintPreviewRecorderImpl::~PaintPreviewRecorderImpl() = default;
void PaintPreviewRecorderImpl::CapturePaintPreview(
mojom::PaintPreviewCaptureParamsPtr params,
CapturePaintPreviewCallback callback) {
mojom::PaintPreviewStatus status = mojom::PaintPreviewStatus::kOk;
base::ReadOnlySharedMemoryRegion region;
// This should not be called recursively or multiple times while unfinished
// (Blink can only run one capture per RenderFrame at a time).
DCHECK(!is_painting_preview_);
// DCHECK, but fallback safely as it is difficult to reason about whether this
// might happen due to it being tied to a RenderFrame rather than
// RenderWidget and we don't want to crash the renderer as this is
// recoverable.
if (is_painting_preview_) {
status = mojom::PaintPreviewStatus::kAlreadyCapturing;
std::move(callback).Run(status, std::move(region));
return;
}
base::AutoReset<bool>(&is_painting_preview_, true);
CapturePaintPreviewInternal(params, &region, &status);
std::move(callback).Run(status, std::move(region));
}
void PaintPreviewRecorderImpl::OnDestruct() {
paint_preview_recorder_receiver_.reset();
base::ThreadTaskRunnerHandle::Get()->DeleteSoon(FROM_HERE, this);
}
void PaintPreviewRecorderImpl::BindPaintPreviewRecorder(
mojo::PendingAssociatedReceiver<mojom::PaintPreviewRecorder> receiver) {
paint_preview_recorder_receiver_.Bind(std::move(receiver));
}
void PaintPreviewRecorderImpl::CapturePaintPreviewInternal(
const mojom::PaintPreviewCaptureParamsPtr& params,
base::ReadOnlySharedMemoryRegion* region,
mojom::PaintPreviewStatus* status) {
blink::WebLocalFrame* frame = render_frame()->GetWebFrame();
// Warm up paint for an out-of-lifecycle paint phase.
frame->DispatchBeforePrintEvent();
DCHECK_EQ(is_main_frame_, params->is_main_frame);
gfx::Rect bounds;
if (is_main_frame_ || params->clip_rect == gfx::Rect(0, 0, 0, 0)) {
auto size = frame->DocumentSize();
bounds = gfx::Rect(0, 0, size.width, size.height);
} else {
bounds = gfx::Rect(params->clip_rect.size());
}
cc::PaintRecorder recorder;
recorder.beginRecording(bounds.width(), bounds.height());
PaintPreviewTracker tracker(params->guid, routing_id_, is_main_frame_);
// TODO(crbug/1008885): Create a method on |canvas| to inject |tracker_| to
// propagate to graphics contexts and inner canvases
// TODO(crbug/1001109): Create a method on |frame| to execute the capture
// within Blink.
// Restore to before out-of-lifecycle paint phase.
frame->DispatchAfterPrintEvent();
// TODO(crbug/1011896): Determine if making this async would be beneficial.
*status = FinishRecording(recorder.finishRecordingAsPicture(), bounds,
&tracker, std::move(params->file), region);
}
} // namespace paint_preview
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef COMPONENTS_PAINT_PREVIEW_RENDERER_PAINT_PREVIEW_RECORDER_IMPL_H_
#define COMPONENTS_PAINT_PREVIEW_RENDERER_PAINT_PREVIEW_RECORDER_IMPL_H_
#include <stdint.h>
#include "base/macros.h"
#include "base/memory/weak_ptr.h"
#include "components/paint_preview/common/mojom/paint_preview_recorder.mojom.h"
#include "components/paint_preview/common/paint_preview_tracker.h"
#include "content/public/renderer/render_frame_observer.h"
#include "mojo/public/cpp/bindings/associated_receiver.h"
#include "mojo/public/cpp/bindings/pending_associated_receiver.h"
namespace base {
class ReadOnlySharedMemoryRegion;
} // namespace base
namespace content {
class RenderFrame;
} // namespace content
namespace paint_preview {
// PaintPreviewRecorderImpl handles the majority of the grunt work for capturing
// a paint preview of a RenderFrame.
class PaintPreviewRecorderImpl : public content::RenderFrameObserver,
mojom::PaintPreviewRecorder {
public:
PaintPreviewRecorderImpl(content::RenderFrame* render_frame);
~PaintPreviewRecorderImpl() override;
void CapturePaintPreview(mojom::PaintPreviewCaptureParamsPtr params,
CapturePaintPreviewCallback callback) override;
private:
// RenderFrameObserver implementation --------------------------------------
void OnDestruct() override;
// Helpers ------------------------------------------------------------------
void BindPaintPreviewRecorder(
mojo::PendingAssociatedReceiver<mojom::PaintPreviewRecorder> receiver);
// Handles the bulk of the capture.
void CapturePaintPreviewInternal(
const mojom::PaintPreviewCaptureParamsPtr& params,
base::ReadOnlySharedMemoryRegion* region,
mojom::PaintPreviewStatus* status);
bool is_painting_preview_;
const bool is_main_frame_;
const int32_t routing_id_;
mojo::AssociatedReceiver<mojom::PaintPreviewRecorder>
paint_preview_recorder_receiver_{this};
base::WeakPtrFactory<PaintPreviewRecorderImpl> weak_ptr_factory_{this};
DISALLOW_COPY_AND_ASSIGN(PaintPreviewRecorderImpl);
};
} // namespace paint_preview
#endif // COMPONENTS_PAINT_PREVIEW_RENDERER_PAINT_PREVIEW_RECORDER_IMPL_H_
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/paint_preview/renderer/paint_preview_recorder_utils.h"
#include <utility>
#include "base/bind.h"
#include "base/memory/shared_memory.h"
#include "components/paint_preview/common/file_stream.h"
#include "components/paint_preview/common/paint_preview_tracker.h"
#include "components/paint_preview/common/proto/paint_preview.pb.h"
#include "mojo/public/cpp/base/shared_memory_utils.h"
namespace paint_preview {
void ParseGlyphs(const cc::PaintOpBuffer* buffer,
PaintPreviewTracker* tracker) {
for (cc::PaintOpBuffer::Iterator it(buffer); it; ++it) {
if (it->GetType() == cc::PaintOpType::DrawTextBlob) {
auto* text_blob_op = static_cast<cc::DrawTextBlobOp*>(*it);
tracker->AddGlyphs(text_blob_op->blob.get());
} else if (it->GetType() == cc::PaintOpType::DrawRecord) {
// Recurse into nested records if they contain text blobs (equivalent to
// nested SkPictures).
auto* record_op = static_cast<cc::DrawRecordOp*>(*it);
if (record_op->HasText())
ParseGlyphs(record_op->record.get(), tracker);
}
}
}
bool SerializeAsSkPicture(sk_sp<const cc::PaintRecord> record,
PaintPreviewTracker* tracker,
const gfx::Rect& dimensions,
base::File file) {
if (!file.IsValid())
return false;
// base::Unretained is safe as |tracker| outlives the usage of
// |custom_callback|.
cc::PlaybackParams::CustomDataRasterCallback custom_callback =
base::BindRepeating(&PaintPreviewTracker::CustomDataToSkPictureCallback,
base::Unretained(tracker));
auto skp = ToSkPicture(
record, SkRect::MakeWH(dimensions.width(), dimensions.height()), nullptr,
custom_callback);
if (!skp)
return false;
TypefaceSerializationContext typeface_context(tracker->GetTypefaceUsageMap());
auto serial_procs = MakeSerialProcs(tracker->GetPictureSerializationContext(),
&typeface_context);
FileWStream stream(std::move(file));
skp->serialize(&stream, &serial_procs);
stream.flush();
stream.Close();
return true;
}
bool BuildAndSerializeProto(PaintPreviewTracker* tracker,
base::ReadOnlySharedMemoryRegion* region) {
PaintPreviewFrameProto proto;
proto.set_unguessable_token_high(tracker->Guid().GetHighForSerialization());
proto.set_unguessable_token_low(tracker->Guid().GetLowForSerialization());
proto.set_id(tracker->RoutingId());
proto.set_is_main_frame(tracker->IsMainFrame());
auto* proto_content_proxy_map = proto.mutable_content_id_proxy_id_map();
for (const auto& id_pair : *(tracker->GetPictureSerializationContext()))
proto_content_proxy_map->insert(
google::protobuf::MapPair<uint32_t, int64_t>(id_pair.first,
id_pair.second));
for (const auto& link : tracker->GetLinks())
*proto.add_links() = link;
base::MappedReadOnlyRegion region_mapping =
mojo::CreateReadOnlySharedMemoryRegion(proto.ByteSizeLong());
if (!region_mapping.IsValid())
return false;
bool success = proto.SerializeToArray(region_mapping.mapping.memory(),
proto.ByteSizeLong());
if (success)
*region = std::move(region_mapping.region);
return success;
}
} // namespace paint_preview
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef COMPONENTS_PAINT_PREVIEW_RENDERER_PAINT_PREVIEW_RECORDER_UTILS_H_
#define COMPONENTS_PAINT_PREVIEW_RENDERER_PAINT_PREVIEW_RECORDER_UTILS_H_
#include "base/files/file.h"
#include "cc/paint/paint_record.h"
#include "third_party/skia/include/core/SkRefCnt.h"
#include "ui/gfx/geometry/rect.h"
// These utilities are used by the PaintPreviewRecorderImpl. They are separate
// for testing purposes and to enforce restrictions caused by the lifetime of
// PaintPreviewServiceImpl being tied to it's associated RenderFrame.
namespace base {
class ReadOnlySharedMemoryRegion;
} // namespace base
namespace paint_preview {
class PaintPreviewTracker;
// Walks |buffer| to extract all the glyphs from its text blobs and writes
// 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.
bool SerializeAsSkPicture(sk_sp<const cc::PaintRecord> record,
PaintPreviewTracker* tracker,
const gfx::Rect& dimensions,
base::File file);
// Builds and serializes a proto to |region| using the data contained in
// |tracker|. Returns true on success.
// NOTE: |tracker| is effectively const here despite being passed by pointer.
bool BuildAndSerializeProto(PaintPreviewTracker* tracker,
base::ReadOnlySharedMemoryRegion* region);
} // namespace paint_preview
#endif // COMPONENTS_PAINT_PREVIEW_RENDERER_PAINT_PREVIEW_RECORDER_UTILS_H_
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/paint_preview/renderer/paint_preview_recorder_utils.h"
#include <string>
#include "base/containers/flat_map.h"
#include "base/containers/flat_set.h"
#include "base/files/file.h"
#include "base/files/scoped_temp_dir.h"
#include "base/memory/read_only_shared_memory_region.h"
#include "base/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/paint_preview_tracker.h"
#include "components/paint_preview/common/proto/paint_preview.pb.h"
#include "components/paint_preview/common/serial_utils.h"
#include "components/paint_preview/common/test_utils.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/skia/include/core/SkFont.h"
#include "third_party/skia/include/core/SkRefCnt.h"
#include "third_party/skia/include/core/SkTextBlob.h"
#include "third_party/skia/include/core/SkTypeface.h"
namespace paint_preview {
namespace {
constexpr int32_t kRoutingId = 1;
} // namespace
TEST(PaintPreviewServiceUtilsTest, TestParseGlyphs) {
auto typeface = SkTypeface::MakeDefault();
SkFont font(typeface);
std::string unichars_1 = "abc";
std::string unichars_2 = "efg";
auto blob_1 = SkTextBlob::MakeFromString(unichars_1.c_str(), font);
auto blob_2 = SkTextBlob::MakeFromString(unichars_2.c_str(), font);
cc::PaintFlags flags;
cc::PaintRecorder outer_recorder;
cc::PaintCanvas* outer_canvas = outer_recorder.beginRecording(100, 100);
outer_canvas->drawTextBlob(blob_1, 10, 10, flags);
cc::PaintRecorder inner_recorder;
cc::PaintCanvas* inner_canvas = inner_recorder.beginRecording(50, 50);
inner_canvas->drawTextBlob(blob_2, 15, 20, flags);
outer_canvas->drawPicture(inner_recorder.finishRecordingAsPicture());
auto record = outer_recorder.finishRecordingAsPicture();
PaintPreviewTracker tracker(base::UnguessableToken::Create(), kRoutingId,
true);
ParseGlyphs(record.get(), &tracker);
auto* usage_map = tracker.GetTypefaceUsageMap();
EXPECT_TRUE(usage_map->count(typeface->uniqueID()));
EXPECT_TRUE(
(*usage_map)[typeface->uniqueID()]->IsSet(typeface->unicharToGlyph('a')));
EXPECT_TRUE(
(*usage_map)[typeface->uniqueID()]->IsSet(typeface->unicharToGlyph('b')));
EXPECT_TRUE(
(*usage_map)[typeface->uniqueID()]->IsSet(typeface->unicharToGlyph('c')));
EXPECT_TRUE(
(*usage_map)[typeface->uniqueID()]->IsSet(typeface->unicharToGlyph('e')));
EXPECT_TRUE(
(*usage_map)[typeface->uniqueID()]->IsSet(typeface->unicharToGlyph('f')));
EXPECT_TRUE(
(*usage_map)[typeface->uniqueID()]->IsSet(typeface->unicharToGlyph('g')));
}
TEST(PaintPreviewServiceUtilsTest, TestSerializeAsSkPicture) {
PaintPreviewTracker tracker(base::UnguessableToken::Create(), kRoutingId,
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::flat_set<uint32_t> ctx;
uint32_t content_id =
tracker.CreateContentForRemoteFrame(gfx::Rect(10, 10), kRoutingId + 1);
canvas->recordCustomData(content_id);
ctx.insert(content_id);
content_id =
tracker.CreateContentForRemoteFrame(gfx::Rect(20, 20), kRoutingId + 2);
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();
EXPECT_TRUE(SerializeAsSkPicture(record, &tracker, dimensions,
std::move(write_file)));
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;
procs.fPictureProc = [](const void* data, size_t length, void* ctx) {
uint32_t content_id;
if (length < sizeof(content_id))
return MakeEmptyPicture();
memcpy(&content_id, data, sizeof(content_id));
auto* context = reinterpret_cast<base::flat_set<uint32_t>*>(ctx);
EXPECT_TRUE(context->count(content_id));
context->erase(content_id);
return MakeEmptyPicture();
};
procs.fPictureCtx = &ctx;
SkPicture::MakeFromStream(&rstream, &procs);
EXPECT_TRUE(ctx.empty());
}
TEST(PaintPreviewServiceUtilsTest, TestBuildAndSerializeProto) {
auto token = base::UnguessableToken::Create();
PaintPreviewTracker tracker(token, kRoutingId, true);
tracker.AnnotateLink("www.google.com", gfx::Rect(1, 2, 3, 4));
tracker.AnnotateLink("www.chromium.org", gfx::Rect(10, 20, 10, 20));
tracker.CreateContentForRemoteFrame(gfx::Rect(1, 1, 1, 1), kRoutingId + 1);
tracker.CreateContentForRemoteFrame(gfx::Rect(1, 2, 4, 8), kRoutingId + 2);
base::ReadOnlySharedMemoryRegion region;
EXPECT_TRUE(BuildAndSerializeProto(&tracker, &region));
PaintPreviewFrameProto proto;
EXPECT_TRUE(region.IsValid());
auto mapping = region.Map();
EXPECT_TRUE(mapping.IsValid());
EXPECT_TRUE(proto.ParseFromArray(mapping.memory(), mapping.size()));
EXPECT_TRUE(proto.is_main_frame());
EXPECT_EQ(static_cast<int>(proto.id()), kRoutingId);
EXPECT_EQ(proto.unguessable_token_low(),
tracker.Guid().GetLowForSerialization());
EXPECT_EQ(proto.unguessable_token_high(),
tracker.Guid().GetHighForSerialization());
EXPECT_EQ(proto.links_size(), 2);
EXPECT_EQ(static_cast<size_t>(proto.links_size()), tracker.GetLinks().size());
for (int i = 0; i < proto.links_size(); ++i)
EXPECT_THAT(proto.links(i), EqualsProto(tracker.GetLinks()[i]));
auto* content_map = tracker.GetPictureSerializationContext();
for (const auto& id_pair : proto.content_id_proxy_id_map()) {
auto it = content_map->find(id_pair.first);
EXPECT_NE(it, content_map->end());
EXPECT_EQ(id_pair.first, it->first);
EXPECT_EQ(id_pair.second, it->second);
}
}
} // namespace paint_preview
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