Commit bdf51f8a authored by ckitagawa's avatar ckitagawa Committed by Commit Bot

Reland "[Paint Preview] Compositing Service"

This is a reland of ba5c7d6f

Reverted as PaintPreviewTest.TestInvalidProto broke on the
Win7 Test dbg and Linux Test dbg waterfall bots.

Fix: modify the test to avoid serializing an invalid proto
and instead just serialize a string. The point of the test
was to see that the invalid proto wouldn't deserialize
and that state would be handled properly.

Using TBR as lots of reviewers (some OOO) and there is
minimal change from original CL aside from one test.

Revert: 29b91f26

Original change's description:
> [Paint Preview] Compositing Service
>
> Adds a paint preview compositor service for compositing collections of
> SkPictures representing frames into bitmaps. This is very similar
> in principle to the PDF compositor service, but produces tileable
> bitmaps rather than a PDF.
>
> A client to this service should start the compositing collection
> portion of the service in a utility process. The client can then
> create and delete dedicated compositor instances for each group of
> SkPictures. Realistically only one compositor will be actively
> communicating at a time. However, multiple compositors can be
> "warm" (data is deserialized and in memory for fast switching).
>
> Bug: 1011430
> Change-Id: I21cb594cd94982f8d4fc5800d9ae4d74b5d9973b
> Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1842273
> Reviewed-by: Jochen Eisinger <jochen@chromium.org>
> Reviewed-by: Scott Violet <sky@chromium.org>
> Reviewed-by: Colin Blundell <blundell@chromium.org>
> Reviewed-by: Ken Buchanan <kenrb@chromium.org>
> Reviewed-by: Ian Vollick <vollick@chromium.org>
> Reviewed-by: Mehran Mahmoudi <mahmoudi@chromium.org>
> Commit-Queue: Calder Kitagawa <ckitagawa@chromium.org>
> Cr-Commit-Position: refs/heads/master@{#709946}

TBR=kenrb@chromium.org,jochen@chromium.org,blundell@chromium.org

Bug: 1011430
Change-Id: Ieafd0d2e197feb8b1d1711e05dd4acc6f185655f
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1886986Reviewed-by: default avatarScott Violet <sky@chromium.org>
Reviewed-by: default avatarIan Vollick <vollick@chromium.org>
Commit-Queue: Calder Kitagawa <ckitagawa@chromium.org>
Cr-Commit-Position: refs/heads/master@{#710748}
parent be71c8b9
......@@ -262,6 +262,7 @@ test("components_unittests") {
"//components/security_interstitials/content:unit_tests",
"//components/security_state/content:unit_tests",
"//components/services/heap_profiling:unit_tests",
"//components/services/paint_preview_compositor:unit_tests",
"//components/services/quarantine:unit_tests",
"//components/spellcheck/browser:unit_tests",
"//components/spellcheck/renderer:unit_tests",
......
......@@ -12,13 +12,27 @@ namespace paint_preview {
namespace {
// Supported by MSVC, g++, and clang++. Ensures no gaps in packing.
#pragma pack(push, 1)
struct SerializedRectData {
uint32_t content_id;
int64_t x;
int64_t y;
int64_t width;
int64_t height;
};
#pragma pack(pop)
// Serializes a SkPicture representing a subframe as a custom data placeholder.
sk_sp<SkData> SerializeSubframe(SkPicture* picture, void* ctx) {
const PictureSerializationContext* context =
reinterpret_cast<PictureSerializationContext*>(ctx);
uint32_t content_id = picture->uniqueID();
if (context->count(content_id))
return SkData::MakeWithCopy(&content_id, sizeof(content_id));
SerializedRectData rect_data = {
picture->uniqueID(), picture->cullRect().x(), picture->cullRect().y(),
picture->cullRect().width(), picture->cullRect().height()};
if (context->count(picture->uniqueID()))
return SkData::MakeWithCopy(&rect_data, sizeof(rect_data));
// Defers picture serialization behavior to Skia.
return nullptr;
}
......@@ -44,23 +58,23 @@ sk_sp<SkData> SerializeTypeface(SkTypeface* typeface, void* ctx) {
return subset_data;
}
// Deserializies a SkPicture within the main SkPicture. These represent
// subframes and require special decoding as they are custom data rather than a
// valid SkPicture.
// Deserializies a clip rect for a subframe within the main SkPicture. These
// represent subframes and require special decoding as they are custom data
// rather than a valid SkPicture.
// Precondition: the version of the SkPicture should be checked prior to
// invocation to ensure deserialization will succeed.
sk_sp<SkPicture> DeserializeSubframe(const void* data,
size_t length,
void* ctx) {
uint32_t content_id;
if (length < sizeof(content_id))
SerializedRectData rect_data;
if (length < sizeof(rect_data))
return MakeEmptyPicture();
memcpy(&content_id, data, sizeof(content_id));
memcpy(&rect_data, data, sizeof(rect_data));
auto* context = reinterpret_cast<DeserializationContext*>(ctx);
auto it = context->find(content_id);
if (it == context->end() || !it->second)
return MakeEmptyPicture();
return it->second;
context->insert(
{rect_data.content_id,
gfx::Rect(rect_data.x, rect_data.y, rect_data.width, rect_data.height)});
return MakeEmptyPicture();
}
} // namespace
......
......@@ -15,6 +15,7 @@
#include "third_party/skia/include/core/SkRefCnt.h"
#include "third_party/skia/include/core/SkSerialProcs.h"
#include "third_party/skia/include/core/SkTypeface.h"
#include "ui/gfx/geometry/rect.h"
namespace paint_preview {
......@@ -33,8 +34,8 @@ struct TypefaceSerializationContext {
base::flat_set<SkFontID> finished; // Should be empty on first use.
};
// Maps a content ID to a SkPicture.
using DeserializationContext = base::flat_map<uint32_t, sk_sp<SkPicture>>;
// Maps a content ID to a clip rect.
using DeserializationContext = base::flat_map<uint32_t, gfx::Rect>;
// Creates a no-op SkPicture.
sk_sp<SkPicture> MakeEmptyPicture();
......
......@@ -28,9 +28,6 @@ TEST(PaintPreviewSerialUtils, TestPictureProcs) {
EXPECT_TRUE(
picture_ctx.insert(std::make_pair(content_id, kFrameGuid)).second);
DeserializationContext deserial_ctx;
EXPECT_TRUE(deserial_ctx.insert(std::make_pair(content_id, pic)).second);
TypefaceUsageMap usage_map;
TypefaceSerializationContext typeface_ctx(&usage_map);
......@@ -38,16 +35,22 @@ TEST(PaintPreviewSerialUtils, TestPictureProcs) {
EXPECT_EQ(serial_procs.fPictureCtx, &picture_ctx);
EXPECT_EQ(serial_procs.fTypefaceCtx, &typeface_ctx);
DeserializationContext deserial_ctx;
SkDeserialProcs deserial_procs = MakeDeserialProcs(&deserial_ctx);
EXPECT_EQ(deserial_procs.fPictureCtx, &deserial_ctx);
// Check that serializing then deserialize the picture works.
// Check that serializing then deserialize the picture works produces a
// correct clip rect.
sk_sp<SkData> serial_pic_data =
serial_procs.fPictureProc(pic.get(), serial_procs.fPictureCtx);
sk_sp<SkPicture> deserial_pic = deserial_procs.fPictureProc(
serial_pic_data->data(), serial_pic_data->size(),
deserial_procs.fPictureCtx);
EXPECT_EQ(deserial_pic->uniqueID(), content_id);
EXPECT_TRUE(deserial_ctx.count(content_id));
EXPECT_EQ(deserial_ctx[content_id].x(), pic->cullRect().x());
EXPECT_EQ(deserial_ctx[content_id].y(), pic->cullRect().y());
EXPECT_EQ(deserial_ctx[content_id].width(), pic->cullRect().width());
EXPECT_EQ(deserial_ctx[content_id].height(), pic->cullRect().height());
}
TEST(PaintPreviewSerialUtils, TestSerialPictureNotInMap) {
......@@ -65,29 +68,6 @@ TEST(PaintPreviewSerialUtils, TestSerialPictureNotInMap) {
nullptr);
}
TEST(PaintPreviewSerialUtils, TestDeserialPictureNotInMap) {
uint32_t empty_content_id = 5;
DeserializationContext deserial_ctx;
EXPECT_TRUE(
deserial_ctx.insert(std::make_pair(empty_content_id, nullptr)).second);
SkDeserialProcs deserial_procs = MakeDeserialProcs(&deserial_ctx);
EXPECT_EQ(deserial_procs.fPictureCtx, &deserial_ctx);
sk_sp<SkPicture> deserial_pic =
deserial_procs.fPictureProc(nullptr, 0U, deserial_procs.fPictureCtx);
EXPECT_NE(deserial_pic, nullptr); // Produce empty pic rather than nullptr.
uint32_t missing_content_id = 5;
deserial_pic = deserial_procs.fPictureProc(&missing_content_id,
sizeof(missing_content_id),
deserial_procs.fPictureCtx);
EXPECT_NE(deserial_pic, nullptr); // Produce empty pic rather than nullptr.
deserial_pic = deserial_procs.fPictureProc(
&empty_content_id, sizeof(empty_content_id), deserial_procs.fPictureCtx);
EXPECT_NE(deserial_pic, nullptr); // Produce empty pic rather than nullptr.
}
TEST(PaintPreviewSerialUtils, TestSerialTypeface) {
PictureSerializationContext picture_ctx;
......
# 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")
assert(!is_ios, "Paint Previews are not supported on iOS.")
static_library("paint_preview_compositor") {
sources = [
"paint_preview_compositor_collection_impl.cc",
"paint_preview_compositor_collection_impl.h",
"paint_preview_compositor_impl.cc",
"paint_preview_compositor_impl.h",
"paint_preview_frame.cc",
"paint_preview_frame.h",
]
deps = [
"//base",
"//components/discardable_memory/client",
"//components/paint_preview/common",
"//components/paint_preview/common/proto",
"//mojo/public/cpp/bindings",
"//skia",
"//ui/gfx/geometry",
"//url",
]
if (is_win) {
deps += [ "//content/public/child" ]
}
public_deps = [
"//components/services/paint_preview_compositor/public/mojom",
]
}
source_set("unit_tests") {
testonly = true
sources = [
"paint_preview_compositor_collection_impl_unittest.cc",
"paint_preview_compositor_impl_unittest.cc",
]
deps = [
":paint_preview_compositor",
"//base",
"//base/test:test_support",
"//components/paint_preview/common",
"//components/paint_preview/common/proto",
"//skia",
"//testing/gmock",
"//testing/gtest",
]
}
test("paint_preview_compositor_unit_tests") {
deps = [
":unit_tests",
"//base",
"//base/test:test_support",
"//components/test:run_all_unittests",
]
}
include_rules = [
"+components/discardable_memory/client",
"+components/paint_preview",
"+content/public/child", # Windows direct write proxy access.
"+mojo/public/cpp",
"+third_party/skia/include/core",
"+ui/gfx/geometry",
]
file://components/paint_preview/OWNERS
# COMPONENT: Internals>FreezeDriedTabs
Paint Preview Compositor is a service for compositing SkPictures and metadata
representing the painted contents of a webpage (collection of RenderFrames) into
bitmaps. These bitmaps can be consumed by a UI to replay a static version of a
webpage without a renderer (effectively a large screenshot represented by
paint-ops).
// 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/services/paint_preview_compositor/paint_preview_compositor_collection_impl.h"
#include <utility>
#include "base/memory/discardable_memory.h"
#include "base/memory/discardable_memory_allocator.h"
#include "build/build_config.h"
#include "third_party/skia/include/core/SkFontMgr.h"
#if defined(OS_WIN)
#include "content/public/child/dwrite_font_proxy_init_win.h"
#endif
namespace paint_preview {
PaintPreviewCompositorCollectionImpl::PaintPreviewCompositorCollectionImpl(
mojo::PendingReceiver<mojom::PaintPreviewCompositorCollection> receiver,
bool initialize_environment,
scoped_refptr<base::SingleThreadTaskRunner> io_task_runner)
: io_task_runner_(std ::move(io_task_runner)) {
if (receiver)
receiver_.Bind(std::move(receiver));
if (!initialize_environment)
return;
#if defined(OS_WIN)
// Initialize direct write font proxy so skia can use it.
content::InitializeDWriteFontProxy();
#endif
// TODO(crbug/1013585): PDF compositor initializes Blink to leverage some
// codecs for images. This is a huge overhead and shouldn't be necessary for
// us. However, this may break some formats (WEBP?) so we may need to force
// encoding to PNG or we could provide our own codec implementations.
// Sanity check that fonts are working.
DCHECK(SkFontMgr::RefDefault()->countFamilies());
}
PaintPreviewCompositorCollectionImpl::~PaintPreviewCompositorCollectionImpl() {
#if defined(OS_WIN)
content::UninitializeDWriteFontProxy();
#endif
}
void PaintPreviewCompositorCollectionImpl::SetDiscardableSharedMemoryManager(
mojo::PendingRemote<
discardable_memory::mojom::DiscardableSharedMemoryManager> manager) {
mojo::PendingRemote<discardable_memory::mojom::DiscardableSharedMemoryManager>
manager_remote(std::move(manager));
discardable_shared_memory_manager_ = std::make_unique<
discardable_memory::ClientDiscardableSharedMemoryManager>(
std::move(manager_remote), io_task_runner_);
base::DiscardableMemoryAllocator::SetInstance(
discardable_shared_memory_manager_.get());
}
void PaintPreviewCompositorCollectionImpl::CreateCompositor(
mojo::PendingReceiver<mojom::PaintPreviewCompositor> receiver,
PaintPreviewCompositorCollectionImpl::CreateCompositorCallback callback) {
base::UnguessableToken token = base::UnguessableToken::Create();
compositors_.insert(
{token,
std::make_unique<PaintPreviewCompositorImpl>(
std::move(receiver),
base::BindOnce(&PaintPreviewCompositorCollectionImpl::OnDisconnect,
base::Unretained(this), token))});
std::move(callback).Run(token);
}
void PaintPreviewCompositorCollectionImpl::ListCompositors(
ListCompositorsCallback callback) {
std::vector<base::UnguessableToken> ids;
ids.reserve(compositors_.size());
for (const auto& compositor : compositors_)
ids.push_back(compositor.first);
std::move(callback).Run(std::move(ids));
}
void PaintPreviewCompositorCollectionImpl::OnDisconnect(
const base::UnguessableToken& id) {
compositors_.erase(id);
}
} // 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_SERVICES_PAINT_PREVIEW_COMPOSITOR_PAINT_PREVIEW_COMPOSITOR_COLLECTION_IMPL_H_
#define COMPONENTS_SERVICES_PAINT_PREVIEW_COMPOSITOR_PAINT_PREVIEW_COMPOSITOR_COLLECTION_IMPL_H_
#include <memory>
#include "base/callback.h"
#include "base/containers/flat_map.h"
#include "base/macros.h"
#include "base/memory/scoped_refptr.h"
#include "base/single_thread_task_runner.h"
#include "base/unguessable_token.h"
#include "components/discardable_memory/client/client_discardable_shared_memory_manager.h"
#include "components/services/paint_preview_compositor/paint_preview_compositor_impl.h"
#include "components/services/paint_preview_compositor/public/mojom/paint_preview_compositor.mojom.h"
#include "mojo/public/cpp/bindings/pending_receiver.h"
#include "mojo/public/cpp/bindings/receiver.h"
namespace discardable_memory {
class ClientDiscardableSharedMemoryManager;
}
namespace paint_preview {
class PaintPreviewCompositorCollectionImpl
: public mojom::PaintPreviewCompositorCollection {
public:
// Create a new PaintPreviewCompositorCollectionImpl bound to |receiver| (can
// be nullptr for tests). Will attempt to initialize required font access if
// |initialize_environment| is true. |io_task_runner| is used by the
// discardable memory manager client for operations on shared memory.
PaintPreviewCompositorCollectionImpl(
mojo::PendingReceiver<mojom::PaintPreviewCompositorCollection> receiver,
bool initialize_environment,
scoped_refptr<base::SingleThreadTaskRunner> io_task_runner);
~PaintPreviewCompositorCollectionImpl() override;
// PaintPreviewCompositorCollection implementation.
void SetDiscardableSharedMemoryManager(
mojo::PendingRemote<
discardable_memory::mojom::DiscardableSharedMemoryManager> manager)
override;
void CreateCompositor(
mojo::PendingReceiver<mojom::PaintPreviewCompositor> compositor,
CreateCompositorCallback callback) override;
void ListCompositors(ListCompositorsCallback callback) override;
private:
// Invoked by a |compositor| when it is disconnected from its remote. Used to
// delete the corresponding instance from |compositors_|.
void OnDisconnect(const base::UnguessableToken& id);
mojo::Receiver<mojom::PaintPreviewCompositorCollection> receiver_{this};
const scoped_refptr<base::SingleThreadTaskRunner> io_task_runner_;
std::unique_ptr<discardable_memory::ClientDiscardableSharedMemoryManager>
discardable_shared_memory_manager_;
base::flat_map<base::UnguessableToken,
std::unique_ptr<PaintPreviewCompositorImpl>>
compositors_;
DISALLOW_COPY_AND_ASSIGN(PaintPreviewCompositorCollectionImpl);
};
} // namespace paint_preview
#endif // COMPONENTS_SERVICES_PAINT_PREVIEW_COMPOSITOR_PAINT_PREVIEW_COMPOSITOR_COLLECTION_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/services/paint_preview_compositor/paint_preview_compositor_collection_impl.h"
#include "base/bind.h"
#include "base/test/task_environment.h"
#include "base/unguessable_token.h"
#include "mojo/public/cpp/bindings/remote.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace paint_preview {
namespace {
base::OnceCallback<void(const std::vector<base::UnguessableToken>&)>
ExpectedIdsCallbackFactory(
const std::vector<base::UnguessableToken>& expected_ids) {
return base::BindOnce(
[](const std::vector<base::UnguessableToken>& expected_compositor_ids,
const std::vector<base::UnguessableToken>& compositor_ids) {
EXPECT_THAT(compositor_ids, testing::UnorderedElementsAreArray(
expected_compositor_ids.begin(),
expected_compositor_ids.end()));
},
expected_ids);
}
} // namespace
TEST(PaintPreviewCompositorCollectionTest, TestAddCompositor) {
base::test::TaskEnvironment task_environment;
PaintPreviewCompositorCollectionImpl collection(mojo::NullReceiver(), false,
nullptr);
base::UnguessableToken token_1, token_2;
ASSERT_TRUE(token_1.is_empty());
ASSERT_TRUE(token_2.is_empty());
auto create_cb_1 = base::BindOnce(
[](base::UnguessableToken* out_token,
const base::UnguessableToken& token) { *out_token = token; },
base::Unretained(&token_1));
auto create_cb_2 = base::BindOnce(
[](base::UnguessableToken* out_token,
const base::UnguessableToken& token) { *out_token = token; },
base::Unretained(&token_2));
{
mojo::Remote<mojom::PaintPreviewCompositor> compositor_1;
collection.CreateCompositor(compositor_1.BindNewPipeAndPassReceiver(),
std::move(create_cb_1));
EXPECT_FALSE(token_1.is_empty());
EXPECT_TRUE(compositor_1.is_bound());
EXPECT_TRUE(compositor_1.is_connected());
collection.ListCompositors(ExpectedIdsCallbackFactory({token_1}));
{
mojo::Remote<mojom::PaintPreviewCompositor> compositor_2;
collection.CreateCompositor(compositor_2.BindNewPipeAndPassReceiver(),
std::move(create_cb_2));
EXPECT_FALSE(token_2.is_empty());
EXPECT_TRUE(compositor_2.is_bound());
EXPECT_TRUE(compositor_2.is_connected());
collection.ListCompositors(
ExpectedIdsCallbackFactory({token_1, token_2}));
}
task_environment.RunUntilIdle();
collection.ListCompositors(ExpectedIdsCallbackFactory({token_1}));
}
task_environment.RunUntilIdle();
auto expect_empty =
base::BindOnce([](const std::vector<base::UnguessableToken>& ids) {
EXPECT_TRUE(ids.empty());
});
collection.ListCompositors(std::move(expect_empty));
}
TEST(PaintPreviewCompositorCollectionTest,
TestCompositorRemoteOutlivesCollection) {
base::test::TaskEnvironment task_environment;
base::UnguessableToken token;
auto create_cb = base::BindOnce(
[](base::UnguessableToken* out_token,
const base::UnguessableToken& token) { *out_token = token; },
base::Unretained(&token));
ASSERT_TRUE(token.is_empty());
mojo::Remote<mojom::PaintPreviewCompositor> compositor;
{
PaintPreviewCompositorCollectionImpl collection(mojo::NullReceiver(), false,
nullptr);
collection.CreateCompositor(compositor.BindNewPipeAndPassReceiver(),
std::move(create_cb));
EXPECT_FALSE(token.is_empty());
EXPECT_TRUE(compositor.is_bound());
EXPECT_TRUE(compositor.is_connected());
}
task_environment.RunUntilIdle();
EXPECT_TRUE(compositor.is_bound());
EXPECT_FALSE(compositor.is_connected());
// Ensure this doesn't crash even if the collection is out of scope (thus all
// the receivers are deleted).
compositor->SetRootFrameUrl(GURL("https://www.foo.com"));
}
} // 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/services/paint_preview_compositor/paint_preview_compositor_impl.h"
#include <utility>
#include "components/paint_preview/common/file_stream.h"
#include "components/paint_preview/common/proto/paint_preview.pb.h"
#include "components/paint_preview/common/serial_utils.h"
#include "components/services/paint_preview_compositor/public/mojom/paint_preview_compositor.mojom.h"
#include "third_party/skia/include/core/SkBitmap.h"
#include "third_party/skia/include/core/SkCanvas.h"
#include "third_party/skia/include/core/SkImageInfo.h"
#include "third_party/skia/include/core/SkMatrix.h"
namespace paint_preview {
PaintPreviewCompositorImpl::PaintPreviewCompositorImpl(
mojo::PendingReceiver<mojom::PaintPreviewCompositor> receiver,
base::OnceClosure disconnect_handler) {
if (receiver) {
receiver_.Bind(std::move(receiver));
receiver_.set_disconnect_handler(std::move(disconnect_handler));
}
}
PaintPreviewCompositorImpl::~PaintPreviewCompositorImpl() {
receiver_.reset();
}
void PaintPreviewCompositorImpl::BeginComposite(
mojom::PaintPreviewBeginCompositeRequestPtr request,
BeginCompositeCallback callback) {
auto response = mojom::PaintPreviewBeginCompositeResponse::New();
auto mapping = request->proto.Map();
if (!mapping.IsValid()) {
std::move(callback).Run(
mojom::PaintPreviewCompositor::Status::kDeserializingFailure,
std::move(response));
return;
}
PaintPreviewProto paint_preview;
bool ok = paint_preview.ParseFromArray(mapping.memory(), mapping.size());
if (!ok) {
std::move(callback).Run(
mojom::PaintPreviewCompositor::Status::kDeserializingFailure,
std::move(response));
return;
}
if (!AddFrame(paint_preview.root_frame(), &request->file_map, &response)) {
std::move(callback).Run(
mojom::PaintPreviewCompositor::Status::kCompositingFailure,
std::move(response));
return;
}
response->root_frame_guid = paint_preview.root_frame().id();
for (const auto& subframe_proto : paint_preview.subframes())
AddFrame(subframe_proto, &request->file_map, &response);
std::move(callback).Run(mojom::PaintPreviewCompositor::Status::kSuccess,
std::move(response));
}
void PaintPreviewCompositorImpl::BitmapForFrame(
uint64_t frame_guid,
const gfx::Rect& clip_rect,
float scale_factor,
BitmapForFrameCallback callback) {
SkBitmap bitmap;
auto frame_it = frames_.find(frame_guid);
if (frame_it == frames_.end()) {
std::move(callback).Run(
mojom::PaintPreviewCompositor::Status::kCompositingFailure, bitmap);
return;
}
auto skp = frame_it->second.skp;
bitmap.allocPixels(
SkImageInfo::MakeN32Premul(clip_rect.width(), clip_rect.height()));
SkCanvas canvas(bitmap);
SkMatrix matrix;
matrix.setScaleTranslate(scale_factor, scale_factor, -clip_rect.x(),
-clip_rect.y());
canvas.drawPicture(skp, &matrix, nullptr);
std::move(callback).Run(mojom::PaintPreviewCompositor::Status::kSuccess,
bitmap);
}
void PaintPreviewCompositorImpl::SetRootFrameUrl(const GURL& url) {
url_ = url;
}
PaintPreviewFrame PaintPreviewCompositorImpl::DeserializeFrame(
const PaintPreviewFrameProto& frame_proto,
base::File file_handle) {
PaintPreviewFrame frame;
FileRStream rstream(std::move(file_handle));
DeserializationContext ctx;
SkDeserialProcs procs = MakeDeserialProcs(&ctx);
frame.skp = SkPicture::MakeFromStream(&rstream, &procs);
for (const auto& id_pair : frame_proto.content_id_proxy_id_map()) {
// It is possible that subframes recorded in this map were not captured
// (e.g. renderer crash, closed, etc.). Missing subframes are allowable
// since having just the main frame is sufficient to create a preview.
auto rect_it = ctx.find(id_pair.first);
if (rect_it == ctx.end())
continue;
mojom::SubframeClipRect rect;
rect.frame_guid = id_pair.second;
rect.clip_rect = rect_it->second;
frame.subframe_clip_rects.push_back(rect);
}
return frame;
}
bool PaintPreviewCompositorImpl::AddFrame(
const PaintPreviewFrameProto& frame_proto,
FileMap* file_map,
mojom::PaintPreviewBeginCompositeResponsePtr* response) {
uint64_t id = frame_proto.id();
auto file_it = file_map->find(id);
if (file_it == file_map->end() || !file_it->second.IsValid())
return false;
PaintPreviewFrame frame =
DeserializeFrame(frame_proto, std::move(file_it->second));
file_map->erase(file_it);
auto frame_data = mojom::FrameData::New();
SkRect sk_rect = frame.skp->cullRect();
frame_data->scroll_extents = gfx::Size(sk_rect.width(), sk_rect.height());
frame_data->subframes.reserve(frame.subframe_clip_rects.size());
for (const auto& subframe_clip_rect : frame.subframe_clip_rects)
frame_data->subframes.push_back(subframe_clip_rect.Clone());
(*response)->frames.insert({id, std::move(frame_data)});
frames_.insert({id, std::move(frame)});
return true;
}
} // 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_SERVICES_PAINT_PREVIEW_COMPOSITOR_PAINT_PREVIEW_COMPOSITOR_IMPL_H_
#define COMPONENTS_SERVICES_PAINT_PREVIEW_COMPOSITOR_PAINT_PREVIEW_COMPOSITOR_IMPL_H_
#include <stdint.h>
#include "base/callback.h"
#include "base/containers/flat_map.h"
#include "base/macros.h"
#include "components/paint_preview/common/proto/paint_preview.pb.h"
#include "components/services/paint_preview_compositor/paint_preview_frame.h"
#include "components/services/paint_preview_compositor/public/mojom/paint_preview_compositor.mojom.h"
#include "mojo/public/cpp/bindings/pending_receiver.h"
#include "mojo/public/cpp/bindings/receiver.h"
#include "ui/gfx/geometry/rect.h"
#include "url/gurl.h"
namespace paint_preview {
class PaintPreviewCompositorImpl : public mojom::PaintPreviewCompositor {
public:
using FileMap = base::flat_map<uint64_t, base::File>;
// Creates a new PaintPreviewCompositorImpl that receives mojo requests over
// |receiver|. |receiver| should be created by the remote and
// |disconnect_handler| is invoked when the remote closes the connection
// invalidating |receiver|.
//
// For testing |receiver| can be a NullReceiver (i.e. a 'local' instance not
// connected to a remote) and |disconnect_handler| should be a no-op.
explicit PaintPreviewCompositorImpl(
mojo::PendingReceiver<mojom::PaintPreviewCompositor> receiver,
base::OnceClosure disconnect_handler);
~PaintPreviewCompositorImpl() override;
// PaintPreviewCompositor implementation.
void BeginComposite(mojom::PaintPreviewBeginCompositeRequestPtr request,
BeginCompositeCallback callback) override;
void BitmapForFrame(uint64_t frame_guid,
const gfx::Rect& clip_rect,
float scale_factor,
BitmapForFrameCallback callback) override;
void SetRootFrameUrl(const GURL& url) override;
private:
// Deserializes the contents of |file_handle| and associates it with the
// metadata in |frame_proto|.
PaintPreviewFrame DeserializeFrame(const PaintPreviewFrameProto& frame_proto,
base::File file_handle);
// Adds |frame_proto| to |frames_| and copies required data into |response|.
// Consumes the corresponding file in |file_map|. Returns true on success.
bool AddFrame(const PaintPreviewFrameProto& frame_proto,
FileMap* file_map,
mojom::PaintPreviewBeginCompositeResponsePtr* response);
mojo::Receiver<mojom::PaintPreviewCompositor> receiver_{this};
GURL url_;
// A mapping from frame GUID to its associated data.
base::flat_map<int64_t, PaintPreviewFrame> frames_;
DISALLOW_COPY_AND_ASSIGN(PaintPreviewCompositorImpl);
};
} // namespace paint_preview
#endif // COMPONENTS_SERVICES_PAINT_PREVIEW_COMPOSITOR_PAINT_PREVIEW_COMPOSITOR_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/services/paint_preview_compositor/paint_preview_compositor_impl.h"
#include <stdint.h>
#include <utility>
#include "base/containers/flat_map.h"
#include "base/files/file.h"
#include "base/files/file_path.h"
#include "base/files/scoped_temp_dir.h"
#include "components/paint_preview/common/file_stream.h"
#include "components/paint_preview/common/serial_utils.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/skia/include/core/SkCanvas.h"
#include "third_party/skia/include/core/SkPicture.h"
#include "third_party/skia/include/core/SkPictureRecorder.h"
#include "third_party/skia/include/core/SkRefCnt.h"
namespace paint_preview {
namespace {
// Checks that |status| == |expected_status|. If |expected_status| == kSuccess,
// then also checks that;
// - |response->root_frame_guid| == |expected_root_frame_guid|
// - |response->subframe_rect_hierarchy| == |expected_data|
void BeginCompositeCallbackImpl(
mojom::PaintPreviewCompositor::Status expected_status,
uint64_t expected_root_frame_guid,
const base::flat_map<uint64_t, mojom::FrameDataPtr>& expected_data,
mojom::PaintPreviewCompositor::Status status,
mojom::PaintPreviewBeginCompositeResponsePtr response) {
EXPECT_EQ(status, expected_status);
if (expected_status != mojom::PaintPreviewCompositor::Status::kSuccess)
return;
EXPECT_EQ(response->root_frame_guid, expected_root_frame_guid);
EXPECT_EQ(response->frames.size(), expected_data.size());
for (const auto& frame : expected_data) {
EXPECT_TRUE(response->frames.count(frame.first));
EXPECT_EQ(response->frames[frame.first]->scroll_extents,
frame.second->scroll_extents);
size_t size = response->frames[frame.first]->subframes.size();
EXPECT_EQ(size, frame.second->subframes.size());
std::vector<std::pair<uint64_t, gfx::Rect>> response_subframes,
expected_subframes;
for (size_t i = 0; i < size; ++i) {
response_subframes.push_back(
{response->frames[frame.first]->subframes[i]->frame_guid,
response->frames[frame.first]->subframes[i]->clip_rect});
expected_subframes.push_back({frame.second->subframes[i]->frame_guid,
frame.second->subframes[i]->clip_rect});
}
EXPECT_THAT(response_subframes,
::testing::UnorderedElementsAreArray(expected_subframes));
}
}
// Checks that |status| == |expected_status|. If |expected_status| == kSuccess,
// then it also checks that |bitmap| and |expected_bitmap| are pixel identical.
void BitmapCallbackImpl(mojom::PaintPreviewCompositor::Status expected_status,
const SkBitmap& expected_bitmap,
mojom::PaintPreviewCompositor::Status status,
const SkBitmap& bitmap) {
EXPECT_EQ(status, expected_status);
if (expected_status != mojom::PaintPreviewCompositor::Status::kSuccess)
return;
EXPECT_EQ(bitmap.width(), expected_bitmap.width());
EXPECT_EQ(bitmap.height(), expected_bitmap.height());
EXPECT_EQ(bitmap.bytesPerPixel(), expected_bitmap.bytesPerPixel());
// Assert that all the bytes of the backing memory are equal. This check is
// only safe if all of the width, height and bytesPerPixel are equal between
// the two bitmaps.
EXPECT_EQ(memcmp(bitmap.getPixels(), expected_bitmap.getPixels(),
expected_bitmap.bytesPerPixel() * expected_bitmap.width() *
expected_bitmap.height()),
0);
}
// Encodes |proto| a ReadOnlySharedMemoryRegion.
base::ReadOnlySharedMemoryRegion ToReadOnlySharedMemory(
const PaintPreviewProto& proto) {
auto region = base::WritableSharedMemoryRegion::Create(proto.ByteSizeLong());
EXPECT_TRUE(region.IsValid());
auto mapping = region.Map();
EXPECT_TRUE(mapping.IsValid());
proto.SerializeToArray(mapping.memory(), mapping.size());
return base::WritableSharedMemoryRegion::ConvertToReadOnly(std::move(region));
}
SkRect ToSkRect(const gfx::Size& size) {
return SkRect::MakeWH(size.width(), size.height());
}
SkRect ToSkRect(const gfx::Rect& rect) {
return SkRect::MakeXYWH(rect.x(), rect.y(), rect.width(), rect.height());
}
void PopulateFrameProto(
PaintPreviewFrameProto* frame,
uint64_t id,
bool set_is_main_frame,
const base::FilePath& path,
const gfx::Size& scroll_extents,
std::vector<std::pair<uint64_t, gfx::Rect>> subframes,
base::flat_map<uint64_t, base::File>* file_map,
base::flat_map<uint64_t, mojom::FrameDataPtr>* expected_data) {
frame->set_id(id);
frame->set_is_main_frame(set_is_main_frame);
FileWStream wstream(base::File(
path, base::File::FLAG_CREATE_ALWAYS | base::File::FLAG_WRITE));
SkPictureRecorder recorder;
SkCanvas* canvas = recorder.beginRecording(ToSkRect(scroll_extents));
SkPaint paint;
paint.setStyle(SkPaint::kFill_Style);
paint.setColor(SK_ColorDKGRAY);
canvas->drawRect(ToSkRect(scroll_extents), paint);
PictureSerializationContext picture_context;
auto* cid_pid_map = frame->mutable_content_id_proxy_id_map();
mojom::FrameDataPtr frame_data = mojom::FrameData::New();
frame_data->scroll_extents = scroll_extents;
for (const auto& subframe : subframes) {
uint64_t subframe_id = subframe.first;
gfx::Rect clip_rect = subframe.second;
sk_sp<SkPicture> temp = SkPicture::MakePlaceholder(ToSkRect(clip_rect));
cid_pid_map->insert({temp->uniqueID(), subframe_id});
picture_context.insert({temp->uniqueID(), subframe_id});
canvas->drawPicture(temp.get());
frame_data->subframes.push_back(
mojom::SubframeClipRect::New(subframe_id, clip_rect));
}
sk_sp<SkPicture> pic = recorder.finishRecordingAsPicture();
// nullptr is safe only because no typefaces are serialized.
SkSerialProcs procs = MakeSerialProcs(&picture_context, nullptr);
pic->serialize(&wstream, &procs);
file_map->insert(
{id, base::File(path, base::File::FLAG_OPEN | base::File::FLAG_READ)});
expected_data->insert({id, std::move(frame_data)});
}
} // namespace
TEST(PaintPreviewCompositorTest, TestBeginComposite) {
base::ScopedTempDir temp_dir;
ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
GURL url("https://www.chromium.org");
PaintPreviewCompositorImpl compositor(mojo::NullReceiver(),
base::BindOnce([]() {}));
compositor.SetRootFrameUrl(url);
const uint64_t kRootFrameID = 1;
gfx::Size root_frame_scroll_extent(100, 200);
const uint64_t kSubframe_0_ID = 2;
gfx::Size subframe_0_scroll_extent(50, 75);
gfx::Rect subframe_0_clip_rect(10, 20, 30, 40);
const uint64_t kSubframe_0_0_ID = 3;
gfx::Size subframe_0_0_scroll_extent(20, 20);
gfx::Rect subframe_0_0_clip_rect(10, 10, 20, 20);
const uint64_t kSubframe_0_1_ID = 4;
gfx::Size subframe_0_1_scroll_extent(10, 5);
gfx::Rect subframe_0_1_clip_rect(10, 10, 30, 30);
const uint64_t kSubframe_1_ID = 5;
gfx::Size subframe_1_scroll_extent(1, 1);
gfx::Rect subframe_1_clip_rect(0, 0, 1, 1);
PaintPreviewProto proto;
base::flat_map<uint64_t, base::File> file_map;
base::flat_map<uint64_t, mojom::FrameDataPtr> expected_data;
PopulateFrameProto(proto.mutable_root_frame(), kRootFrameID, true,
temp_dir.GetPath().AppendASCII("root.skp"),
root_frame_scroll_extent,
{{kSubframe_0_ID, subframe_0_clip_rect},
{kSubframe_1_ID, subframe_1_clip_rect}},
&file_map, &expected_data);
PopulateFrameProto(proto.add_subframes(), kSubframe_0_ID, false,
temp_dir.GetPath().AppendASCII("subframe_0.skp"),
subframe_0_scroll_extent,
{{kSubframe_0_0_ID, subframe_0_0_clip_rect},
{kSubframe_0_1_ID, subframe_0_1_clip_rect}},
&file_map, &expected_data);
PopulateFrameProto(proto.add_subframes(), kSubframe_0_0_ID, false,
temp_dir.GetPath().AppendASCII("subframe_0_0.skp"),
subframe_0_0_scroll_extent, {}, &file_map, &expected_data);
PopulateFrameProto(proto.add_subframes(), kSubframe_0_1_ID, false,
temp_dir.GetPath().AppendASCII("subframe_0_1.skp"),
subframe_0_1_scroll_extent, {}, &file_map, &expected_data);
PopulateFrameProto(proto.add_subframes(), kSubframe_1_ID, false,
temp_dir.GetPath().AppendASCII("subframe_1.skp"),
subframe_1_scroll_extent, {}, &file_map, &expected_data);
// Missing a subframe SKP is still valid. Compositing will ignore it in the
// results.
file_map.erase(kSubframe_0_0_ID);
expected_data.erase(kSubframe_0_0_ID);
mojom::PaintPreviewBeginCompositeRequestPtr request =
mojom::PaintPreviewBeginCompositeRequest::New();
request->file_map = std::move(file_map);
request->proto = ToReadOnlySharedMemory(proto);
compositor.BeginComposite(
std::move(request),
base::BindOnce(&BeginCompositeCallbackImpl,
mojom::PaintPreviewCompositor::Status::kSuccess,
kRootFrameID, std::move(expected_data)));
}
// Ensure that depending on a frame multiple times works.
TEST(PaintPreviewCompositorTest, TestBeginCompositeDuplicate) {
base::ScopedTempDir temp_dir;
ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
PaintPreviewCompositorImpl compositor(mojo::NullReceiver(),
base::BindOnce([]() {}));
const uint64_t kRootFrameID = 1;
gfx::Size root_frame_scroll_extent(100, 200);
const uint64_t kSubframe_0_ID = 2;
gfx::Size subframe_0_scroll_extent(50, 75);
gfx::Rect subframe_0_clip_rect(10, 20, 30, 40);
PaintPreviewProto proto;
base::flat_map<uint64_t, base::File> file_map;
base::flat_map<uint64_t, mojom::FrameDataPtr> expected_data;
PopulateFrameProto(proto.mutable_root_frame(), kRootFrameID, true,
temp_dir.GetPath().AppendASCII("root.skp"),
root_frame_scroll_extent,
{{kSubframe_0_ID, subframe_0_clip_rect},
{kSubframe_0_ID, subframe_0_clip_rect}},
&file_map, &expected_data);
PopulateFrameProto(proto.add_subframes(), kSubframe_0_ID, false,
temp_dir.GetPath().AppendASCII("subframe_0.skp"),
subframe_0_scroll_extent, {}, &file_map, &expected_data);
mojom::PaintPreviewBeginCompositeRequestPtr request =
mojom::PaintPreviewBeginCompositeRequest::New();
request->file_map = std::move(file_map);
request->proto = ToReadOnlySharedMemory(proto);
compositor.BeginComposite(
std::move(request),
base::BindOnce(&BeginCompositeCallbackImpl,
mojom::PaintPreviewCompositor::Status::kSuccess,
kRootFrameID, std::move(expected_data)));
}
// Ensure that a loop in frames works.
TEST(PaintPreviewCompositorTest, TestBeginCompositeLoop) {
base::ScopedTempDir temp_dir;
ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
PaintPreviewCompositorImpl compositor(mojo::NullReceiver(),
base::BindOnce([]() {}));
const uint64_t kRootFrameID = 1;
gfx::Size root_frame_scroll_extent(100, 200);
const uint64_t kSubframe_0_ID = 2;
gfx::Size subframe_0_scroll_extent(50, 75);
gfx::Rect subframe_0_clip_rect(10, 20, 30, 40);
PaintPreviewProto proto;
base::flat_map<uint64_t, base::File> file_map;
base::flat_map<uint64_t, mojom::FrameDataPtr> expected_data;
PopulateFrameProto(
proto.mutable_root_frame(), kRootFrameID, true,
temp_dir.GetPath().AppendASCII("root.skp"), root_frame_scroll_extent,
{{kSubframe_0_ID, subframe_0_clip_rect}}, &file_map, &expected_data);
PopulateFrameProto(proto.add_subframes(), kSubframe_0_ID, false,
temp_dir.GetPath().AppendASCII("subframe_0.skp"),
subframe_0_scroll_extent,
{{kRootFrameID, subframe_0_clip_rect}}, &file_map,
&expected_data);
mojom::PaintPreviewBeginCompositeRequestPtr request =
mojom::PaintPreviewBeginCompositeRequest::New();
request->file_map = std::move(file_map);
request->proto = ToReadOnlySharedMemory(proto);
compositor.BeginComposite(
std::move(request),
base::BindOnce(&BeginCompositeCallbackImpl,
mojom::PaintPreviewCompositor::Status::kSuccess,
kRootFrameID, std::move(expected_data)));
}
// Ensure that a frame referencing itself works correctly.
TEST(PaintPreviewCompositorTest, TestBeginCompositeSelfReference) {
base::ScopedTempDir temp_dir;
ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
PaintPreviewCompositorImpl compositor(mojo::NullReceiver(),
base::BindOnce([]() {}));
const uint64_t kRootFrameID = 1;
gfx::Size root_frame_scroll_extent(100, 200);
gfx::Rect root_frame_clip_rect(10, 20, 30, 40);
PaintPreviewProto proto;
base::flat_map<uint64_t, base::File> file_map;
base::flat_map<uint64_t, mojom::FrameDataPtr> expected_data;
PopulateFrameProto(
proto.mutable_root_frame(), kRootFrameID, true,
temp_dir.GetPath().AppendASCII("root.skp"), root_frame_scroll_extent,
{{kRootFrameID, root_frame_clip_rect}}, &file_map, &expected_data);
mojom::PaintPreviewBeginCompositeRequestPtr request =
mojom::PaintPreviewBeginCompositeRequest::New();
request->file_map = std::move(file_map);
request->proto = ToReadOnlySharedMemory(proto);
compositor.BeginComposite(
std::move(request),
base::BindOnce(&BeginCompositeCallbackImpl,
mojom::PaintPreviewCompositor::Status::kSuccess,
kRootFrameID, std::move(expected_data)));
}
TEST(PaintPreviewCompositorTest, TestInvalidRegionHandling) {
PaintPreviewCompositorImpl compositor(mojo::NullReceiver(),
base::BindOnce([]() {}));
mojom::PaintPreviewBeginCompositeRequestPtr request =
mojom::PaintPreviewBeginCompositeRequest::New();
compositor.BeginComposite(
std::move(request),
base::BindOnce(
&BeginCompositeCallbackImpl,
mojom::PaintPreviewCompositor::Status::kDeserializingFailure, 0,
base::flat_map<uint64_t, mojom::FrameDataPtr>()));
}
TEST(PaintPreviewCompositorTest, TestInvalidProto) {
PaintPreviewCompositorImpl compositor(mojo::NullReceiver(),
base::BindOnce([]() {}));
mojom::PaintPreviewBeginCompositeRequestPtr request =
mojom::PaintPreviewBeginCompositeRequest::New();
std::string test_data = "hello world";
auto region = base::WritableSharedMemoryRegion::Create(test_data.size());
ASSERT_TRUE(region.IsValid());
auto mapping = region.Map();
ASSERT_TRUE(mapping.IsValid());
memcpy(mapping.memory(), test_data.data(), mapping.size());
// These calls log errors without a newline (from the proto lib). As a
// result, the Android gtest parser fails to parse the test status. To work
// around this gobble the log message.
{
testing::internal::CaptureStdout();
request->proto =
base::WritableSharedMemoryRegion::ConvertToReadOnly(std::move(region));
compositor.BeginComposite(
std::move(request),
base::BindOnce(
&BeginCompositeCallbackImpl,
mojom::PaintPreviewCompositor::Status::kDeserializingFailure, 0,
base::flat_map<uint64_t, mojom::FrameDataPtr>()));
LOG(ERROR) << testing::internal::GetCapturedStdout();
}
}
TEST(PaintPreviewCompositorTest, TestInvalidRootFrame) {
base::ScopedTempDir temp_dir;
ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
PaintPreviewCompositorImpl compositor(mojo::NullReceiver(),
base::BindOnce([]() {}));
mojom::PaintPreviewBeginCompositeRequestPtr request =
mojom::PaintPreviewBeginCompositeRequest::New();
PaintPreviewProto proto;
const uint64_t kRootFrameID = 1;
base::flat_map<uint64_t, base::File> file_map;
base::flat_map<uint64_t, mojom::FrameDataPtr> expected_data;
PopulateFrameProto(proto.mutable_root_frame(), kRootFrameID, true,
temp_dir.GetPath().AppendASCII("root.skp"),
gfx::Size(1, 1), {}, &file_map, &expected_data);
file_map.erase(kRootFrameID); // Missing a SKP for the root file is invalid.
request->file_map = std::move(file_map);
request->proto = ToReadOnlySharedMemory(proto);
compositor.BeginComposite(
std::move(request),
base::BindOnce(&BeginCompositeCallbackImpl,
mojom::PaintPreviewCompositor::Status::kCompositingFailure,
0, base::flat_map<uint64_t, mojom::FrameDataPtr>()));
}
TEST(PaintPreviewCompositorTest, TestComposite) {
base::ScopedTempDir temp_dir;
ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
PaintPreviewCompositorImpl compositor(mojo::NullReceiver(),
base::BindOnce([]() {}));
const uint64_t kRootFrameID = 1;
gfx::Size root_frame_scroll_extent(100, 200);
PaintPreviewProto proto;
base::flat_map<uint64_t, base::File> file_map;
base::flat_map<uint64_t, mojom::FrameDataPtr> expected_data;
PopulateFrameProto(proto.mutable_root_frame(), kRootFrameID, true,
temp_dir.GetPath().AppendASCII("root.skp"),
root_frame_scroll_extent, {}, &file_map, &expected_data);
mojom::PaintPreviewBeginCompositeRequestPtr request =
mojom::PaintPreviewBeginCompositeRequest::New();
request->file_map = std::move(file_map);
request->proto = ToReadOnlySharedMemory(proto);
compositor.BeginComposite(
std::move(request),
base::BindOnce(&BeginCompositeCallbackImpl,
mojom::PaintPreviewCompositor::Status::kSuccess,
kRootFrameID, std::move(expected_data)));
gfx::Rect rect(200, 400);
SkBitmap bitmap;
bitmap.allocPixels(SkImageInfo::MakeN32Premul(rect.width(), rect.height()));
SkCanvas canvas(bitmap);
SkPaint paint;
paint.setStyle(SkPaint::kFill_Style);
paint.setColor(SK_ColorDKGRAY);
canvas.drawRect(ToSkRect(rect), paint);
compositor.BitmapForFrame(
kRootFrameID, rect, 2,
base::BindOnce(&BitmapCallbackImpl,
mojom::PaintPreviewCompositor::Status::kSuccess, bitmap));
compositor.BitmapForFrame(
kRootFrameID + 1, rect, 2,
base::BindOnce(&BitmapCallbackImpl,
mojom::PaintPreviewCompositor::Status::kCompositingFailure,
bitmap));
}
} // 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/services/paint_preview_compositor/paint_preview_frame.h"
namespace paint_preview {
PaintPreviewFrame::PaintPreviewFrame() = default;
PaintPreviewFrame::~PaintPreviewFrame() = default;
PaintPreviewFrame::PaintPreviewFrame(PaintPreviewFrame&& other) = default;
PaintPreviewFrame& PaintPreviewFrame::operator=(PaintPreviewFrame&& other) =
default;
} // 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_SERVICES_PAINT_PREVIEW_COMPOSITOR_PAINT_PREVIEW_FRAME_H_
#define COMPONENTS_SERVICES_PAINT_PREVIEW_COMPOSITOR_PAINT_PREVIEW_FRAME_H_
#include <vector>
#include "base/macros.h"
#include "components/services/paint_preview_compositor/public/mojom/paint_preview_compositor.mojom.h"
#include "third_party/skia/include/core/SkPicture.h"
#include "third_party/skia/include/core/SkRefCnt.h"
namespace paint_preview {
// A deserialized in-memory representation of a PaintPreviewFrame and its
// associated subframe clip rects.
struct PaintPreviewFrame {
public:
PaintPreviewFrame();
~PaintPreviewFrame();
PaintPreviewFrame(PaintPreviewFrame&& other);
PaintPreviewFrame& operator=(PaintPreviewFrame&& other);
sk_sp<SkPicture> skp;
std::vector<mojom::SubframeClipRect> subframe_clip_rects;
private:
DISALLOW_COPY_AND_ASSIGN(PaintPreviewFrame);
};
} // namespace paint_preview
#endif // COMPONENTS_SERVICES_PAINT_PREVIEW_COMPOSITOR_PAINT_PREVIEW_FRAME_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("//mojo/public/tools/bindings/mojom.gni")
mojom("mojom") {
sources = [
"paint_preview_compositor.mojom",
]
public_deps = [
"//components/discardable_memory/public/mojom",
"//mojo/public/mojom/base",
"//skia/public/mojom",
"//ui/gfx/geometry/mojom",
"//url/mojom:url_mojom_gurl",
]
}
per-file *.mojom=set noparent
per-file *.mojom=file://ipc/SECURITY_OWNERS
per-file *.typemap=set noparent
per-file *.typemap=file://ipc/SECURITY_OWNERS
per-file *_mojom_traits*.*=set noparent
per-file *_mojom_traits*.*=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 "skia/public/mojom/bitmap.mojom";
import "ui/gfx/geometry/mojom/geometry.mojom";
import "url/mojom/url.mojom";
// A request sent to the PaintPreviewCompositor to request deserialization.
struct PaintPreviewBeginCompositeRequest {
// A serialized PaintPreviewProto.
mojo_base.mojom.ReadOnlySharedMemoryRegion proto;
// A mapping between frame GUIDs and file handles.
map<uint64, mojo_base.mojom.File> file_map;
};
// A struct representing the clip rect of a subframe within its parent.
struct SubframeClipRect {
uint64 frame_guid;
gfx.mojom.Rect clip_rect;
};
// A struct representing the scroll extents and subframes of a parent frame.
struct FrameData {
// The dimensions of the frame.
gfx.mojom.Size scroll_extents;
// This is not a map because a parent can, in theory, embed the same subframe
// multiple times.
array<SubframeClipRect> subframes;
};
// A response sent as a result of a begin composite request. It provides the
// ID of the root frame (used for the primary UI) and the map can be used for
// looking up the data associated with each frame.
struct PaintPreviewBeginCompositeResponse {
uint64 root_frame_guid;
map<uint64, FrameData> frames;
};
// A compositor that converts a single paint preview into bitmaps.
interface PaintPreviewCompositor {
// The status of the action requested of the compositor.
enum Status {
// The request succeeded.
kSuccess = 0,
// Indicates there was an issue deserializing the proto or root frame.
// Problematic subframes are omitted since compositing can occur without
// them.
kDeserializingFailure = 1,
// Indicates there was a failure to composite a bitmap for the requested
// frame. Either; 1. it failed to deserialize or 2. there was an issue with
// the parameters provided.
kCompositingFailure = 2,
};
// Starts the compositing process for |request|. On success returns
// |response| containing metadata required for UI. |status| will be non-zero
// on failure.
BeginComposite(PaintPreviewBeginCompositeRequest request) =>
(Status status, PaintPreviewBeginCompositeResponse? response);
// Requests a bitmap associated with |frame_guid| the dimensions and
// location of the bitmap will match |clip_rect| at scale |scale_factor|.
// Returns |bitmap| on success and will indicate failure via a non-zero
// |status|.
BitmapForFrame(uint64 frame_guid, gfx.mojom.Rect clip_rect,
float scale_factor) => (Status status, skia.mojom.Bitmap? bitmap);
// Sets the root frame of the compositor. Used for tracing and diagnostics.
SetRootFrameUrl(url.mojom.Url url);
};
// Holds a collection of PaintPreviewCompositor instances running in the same
// process.
interface PaintPreviewCompositorCollection {
// Provides an interface for managing discardable shared memory regions. Must
// be called before calling any methods on managed PaintPreviewCompositors.
SetDiscardableSharedMemoryManager(
pending_remote<discardable_memory.mojom.DiscardableSharedMemoryManager>
manager);
// Adds a PaintPreviewCompositor to the utility process. Returns an ID for
// the compositor.
CreateCompositor(pending_receiver<PaintPreviewCompositor> compositor)
=> (mojo_base.mojom.UnguessableToken compositor_id);
// Returns a list of active compositor IDs.
ListCompositors() => (array<mojo_base.mojom.UnguessableToken> compositor_ids);
};
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