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") { ...@@ -262,6 +262,7 @@ test("components_unittests") {
"//components/security_interstitials/content:unit_tests", "//components/security_interstitials/content:unit_tests",
"//components/security_state/content:unit_tests", "//components/security_state/content:unit_tests",
"//components/services/heap_profiling:unit_tests", "//components/services/heap_profiling:unit_tests",
"//components/services/paint_preview_compositor:unit_tests",
"//components/services/quarantine:unit_tests", "//components/services/quarantine:unit_tests",
"//components/spellcheck/browser:unit_tests", "//components/spellcheck/browser:unit_tests",
"//components/spellcheck/renderer:unit_tests", "//components/spellcheck/renderer:unit_tests",
......
...@@ -12,13 +12,27 @@ namespace paint_preview { ...@@ -12,13 +12,27 @@ namespace paint_preview {
namespace { 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. // Serializes a SkPicture representing a subframe as a custom data placeholder.
sk_sp<SkData> SerializeSubframe(SkPicture* picture, void* ctx) { sk_sp<SkData> SerializeSubframe(SkPicture* picture, void* ctx) {
const PictureSerializationContext* context = const PictureSerializationContext* context =
reinterpret_cast<PictureSerializationContext*>(ctx); reinterpret_cast<PictureSerializationContext*>(ctx);
uint32_t content_id = picture->uniqueID(); SerializedRectData rect_data = {
if (context->count(content_id)) picture->uniqueID(), picture->cullRect().x(), picture->cullRect().y(),
return SkData::MakeWithCopy(&content_id, sizeof(content_id)); 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. // Defers picture serialization behavior to Skia.
return nullptr; return nullptr;
} }
...@@ -44,23 +58,23 @@ sk_sp<SkData> SerializeTypeface(SkTypeface* typeface, void* ctx) { ...@@ -44,23 +58,23 @@ sk_sp<SkData> SerializeTypeface(SkTypeface* typeface, void* ctx) {
return subset_data; return subset_data;
} }
// Deserializies a SkPicture within the main SkPicture. These represent // Deserializies a clip rect for a subframe within the main SkPicture. These
// subframes and require special decoding as they are custom data rather than a // represent subframes and require special decoding as they are custom data
// valid SkPicture. // rather than a valid SkPicture.
// Precondition: the version of the SkPicture should be checked prior to // Precondition: the version of the SkPicture should be checked prior to
// invocation to ensure deserialization will succeed. // invocation to ensure deserialization will succeed.
sk_sp<SkPicture> DeserializeSubframe(const void* data, sk_sp<SkPicture> DeserializeSubframe(const void* data,
size_t length, size_t length,
void* ctx) { void* ctx) {
uint32_t content_id; SerializedRectData rect_data;
if (length < sizeof(content_id)) if (length < sizeof(rect_data))
return MakeEmptyPicture(); return MakeEmptyPicture();
memcpy(&content_id, data, sizeof(content_id)); memcpy(&rect_data, data, sizeof(rect_data));
auto* context = reinterpret_cast<DeserializationContext*>(ctx); auto* context = reinterpret_cast<DeserializationContext*>(ctx);
auto it = context->find(content_id); context->insert(
if (it == context->end() || !it->second) {rect_data.content_id,
return MakeEmptyPicture(); gfx::Rect(rect_data.x, rect_data.y, rect_data.width, rect_data.height)});
return it->second; return MakeEmptyPicture();
} }
} // namespace } // namespace
......
...@@ -15,6 +15,7 @@ ...@@ -15,6 +15,7 @@
#include "third_party/skia/include/core/SkRefCnt.h" #include "third_party/skia/include/core/SkRefCnt.h"
#include "third_party/skia/include/core/SkSerialProcs.h" #include "third_party/skia/include/core/SkSerialProcs.h"
#include "third_party/skia/include/core/SkTypeface.h" #include "third_party/skia/include/core/SkTypeface.h"
#include "ui/gfx/geometry/rect.h"
namespace paint_preview { namespace paint_preview {
...@@ -33,8 +34,8 @@ struct TypefaceSerializationContext { ...@@ -33,8 +34,8 @@ struct TypefaceSerializationContext {
base::flat_set<SkFontID> finished; // Should be empty on first use. base::flat_set<SkFontID> finished; // Should be empty on first use.
}; };
// Maps a content ID to a SkPicture. // Maps a content ID to a clip rect.
using DeserializationContext = base::flat_map<uint32_t, sk_sp<SkPicture>>; using DeserializationContext = base::flat_map<uint32_t, gfx::Rect>;
// Creates a no-op SkPicture. // Creates a no-op SkPicture.
sk_sp<SkPicture> MakeEmptyPicture(); sk_sp<SkPicture> MakeEmptyPicture();
......
...@@ -28,9 +28,6 @@ TEST(PaintPreviewSerialUtils, TestPictureProcs) { ...@@ -28,9 +28,6 @@ TEST(PaintPreviewSerialUtils, TestPictureProcs) {
EXPECT_TRUE( EXPECT_TRUE(
picture_ctx.insert(std::make_pair(content_id, kFrameGuid)).second); 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; TypefaceUsageMap usage_map;
TypefaceSerializationContext typeface_ctx(&usage_map); TypefaceSerializationContext typeface_ctx(&usage_map);
...@@ -38,16 +35,22 @@ TEST(PaintPreviewSerialUtils, TestPictureProcs) { ...@@ -38,16 +35,22 @@ TEST(PaintPreviewSerialUtils, TestPictureProcs) {
EXPECT_EQ(serial_procs.fPictureCtx, &picture_ctx); EXPECT_EQ(serial_procs.fPictureCtx, &picture_ctx);
EXPECT_EQ(serial_procs.fTypefaceCtx, &typeface_ctx); EXPECT_EQ(serial_procs.fTypefaceCtx, &typeface_ctx);
DeserializationContext deserial_ctx;
SkDeserialProcs deserial_procs = MakeDeserialProcs(&deserial_ctx); SkDeserialProcs deserial_procs = MakeDeserialProcs(&deserial_ctx);
EXPECT_EQ(deserial_procs.fPictureCtx, &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 = sk_sp<SkData> serial_pic_data =
serial_procs.fPictureProc(pic.get(), serial_procs.fPictureCtx); serial_procs.fPictureProc(pic.get(), serial_procs.fPictureCtx);
sk_sp<SkPicture> deserial_pic = deserial_procs.fPictureProc( sk_sp<SkPicture> deserial_pic = deserial_procs.fPictureProc(
serial_pic_data->data(), serial_pic_data->size(), serial_pic_data->data(), serial_pic_data->size(),
deserial_procs.fPictureCtx); 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) { TEST(PaintPreviewSerialUtils, TestSerialPictureNotInMap) {
...@@ -65,29 +68,6 @@ TEST(PaintPreviewSerialUtils, TestSerialPictureNotInMap) { ...@@ -65,29 +68,6 @@ TEST(PaintPreviewSerialUtils, TestSerialPictureNotInMap) {
nullptr); 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) { TEST(PaintPreviewSerialUtils, TestSerialTypeface) {
PictureSerializationContext picture_ctx; 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_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