Commit 29b91f26 authored by Kamila Hasanbega's avatar Kamila Hasanbega Committed by Commit Bot

Revert "[Paint Preview] Compositing Service"

This reverts commit ba5c7d6f.

Reason for revert: It breaks several tests 
https://ci.chromium.org/p/chromium/builders/ci/Win7%20Tests%20%28dbg%29%281%29
https://ci.chromium.org/p/chromium/builders/ci/Linux%20Tests%20%28dbg%29%281%29


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=vollick@chromium.org,sky@chromium.org,kenrb@chromium.org,blundell@chromium.org,jochen@chromium.org,blundell@google.com,mahmoudi@chromium.org,ckitagawa@chromium.org

Change-Id: Idbeb4bb3989d803f1efcde86f1066b791731b08b
No-Presubmit: true
No-Tree-Checks: true
No-Try: true
Bug: 1011430
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1886610Reviewed-by: default avatarKamila Hasanbega <hkamila@chromium.org>
Commit-Queue: Kamila Hasanbega <hkamila@chromium.org>
Cr-Commit-Position: refs/heads/master@{#710251}
parent cc6dca01
...@@ -260,7 +260,6 @@ test("components_unittests") { ...@@ -260,7 +260,6 @@ 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,27 +12,13 @@ namespace paint_preview { ...@@ -12,27 +12,13 @@ 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);
SerializedRectData rect_data = { uint32_t content_id = picture->uniqueID();
picture->uniqueID(), picture->cullRect().x(), picture->cullRect().y(), if (context->count(content_id))
picture->cullRect().width(), picture->cullRect().height()}; return SkData::MakeWithCopy(&content_id, sizeof(content_id));
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;
} }
...@@ -58,23 +44,23 @@ sk_sp<SkData> SerializeTypeface(SkTypeface* typeface, void* ctx) { ...@@ -58,23 +44,23 @@ sk_sp<SkData> SerializeTypeface(SkTypeface* typeface, void* ctx) {
return subset_data; return subset_data;
} }
// Deserializies a clip rect for a subframe within the main SkPicture. These // Deserializies a SkPicture within the main SkPicture. These represent
// represent subframes and require special decoding as they are custom data // subframes and require special decoding as they are custom data rather than a
// rather than a valid SkPicture. // 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) {
SerializedRectData rect_data; uint32_t content_id;
if (length < sizeof(rect_data)) if (length < sizeof(content_id))
return MakeEmptyPicture(); return MakeEmptyPicture();
memcpy(&rect_data, data, sizeof(rect_data)); memcpy(&content_id, data, sizeof(content_id));
auto* context = reinterpret_cast<DeserializationContext*>(ctx); auto* context = reinterpret_cast<DeserializationContext*>(ctx);
context->insert( auto it = context->find(content_id);
{rect_data.content_id, if (it == context->end() || !it->second)
gfx::Rect(rect_data.x, rect_data.y, rect_data.width, rect_data.height)});
return MakeEmptyPicture(); return MakeEmptyPicture();
return it->second;
} }
} // namespace } // namespace
......
...@@ -15,7 +15,6 @@ ...@@ -15,7 +15,6 @@
#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 {
...@@ -34,8 +33,8 @@ struct TypefaceSerializationContext { ...@@ -34,8 +33,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 clip rect. // Maps a content ID to a SkPicture.
using DeserializationContext = base::flat_map<uint32_t, gfx::Rect>; using DeserializationContext = base::flat_map<uint32_t, sk_sp<SkPicture>>;
// Creates a no-op SkPicture. // Creates a no-op SkPicture.
sk_sp<SkPicture> MakeEmptyPicture(); sk_sp<SkPicture> MakeEmptyPicture();
......
...@@ -28,6 +28,9 @@ TEST(PaintPreviewSerialUtils, TestPictureProcs) { ...@@ -28,6 +28,9 @@ 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);
...@@ -35,22 +38,16 @@ TEST(PaintPreviewSerialUtils, TestPictureProcs) { ...@@ -35,22 +38,16 @@ 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 produces a // Check that serializing then deserialize the picture works.
// 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_TRUE(deserial_ctx.count(content_id)); EXPECT_EQ(deserial_pic->uniqueID(), 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) {
...@@ -68,6 +65,29 @@ TEST(PaintPreviewSerialUtils, TestSerialPictureNotInMap) { ...@@ -68,6 +65,29 @@ 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_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();
PaintPreviewProto proto;
proto.mutable_root_frame();
// 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 =
ToReadOnlySharedMemory(proto); // An empty proto is invalid.
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