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

[Paint Preview] Base Public Keyed Service

Adds a base class for creating keyed services based that use paint
previews. This class will expose all the necessary APIs for using
paint previews such that no other API surface for paint previews should
be required for features using them.

Classes that extend this service should manage:
- Lifecycle of paint previews (deletion, etc.)
- Handling incognito/off-the-record behavior

Bug: 1030269
Change-Id: I982da36f7a2ca3e04c3d3575747a25cf8461d1cd
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1887728Reviewed-by: default avatarIan Vollick <vollick@chromium.org>
Reviewed-by: default avatarHenrique Nakashima <hnakashima@chromium.org>
Reviewed-by: default avatarMehran Mahmoudi <mahmoudi@chromium.org>
Commit-Queue: Calder Kitagawa <ckitagawa@chromium.org>
Cr-Commit-Position: refs/heads/master@{#721465}
parent 1a0c79a8
...@@ -4,8 +4,9 @@ ...@@ -4,8 +4,9 @@
import("//testing/test.gni") import("//testing/test.gni")
if (!is_ios) { assert(!is_ios, "Paint Previews are not supported on iOS.")
static_library("browser") {
static_library("browser") {
sources = [ sources = [
"compositor_utils.cc", "compositor_utils.cc",
"compositor_utils.h", "compositor_utils.h",
...@@ -13,6 +14,8 @@ if (!is_ios) { ...@@ -13,6 +14,8 @@ if (!is_ios) {
"file_manager.h", "file_manager.h",
"hit_tester.cc", "hit_tester.cc",
"hit_tester.h", "hit_tester.h",
"paint_preview_base_service.cc",
"paint_preview_base_service.h",
"paint_preview_client.cc", "paint_preview_client.cc",
"paint_preview_client.h", "paint_preview_client.h",
] ]
...@@ -38,14 +41,15 @@ if (!is_ios) { ...@@ -38,14 +41,15 @@ if (!is_ios) {
"//components/paint_preview/common/proto", "//components/paint_preview/common/proto",
"//components/services/paint_preview_compositor/public/mojom", "//components/services/paint_preview_compositor/public/mojom",
] ]
} }
source_set("unit_tests") { source_set("unit_tests") {
testonly = true testonly = true
sources = [ sources = [
"file_manager_unittest.cc", "file_manager_unittest.cc",
"hit_tester_unittest.cc", "hit_tester_unittest.cc",
"paint_preview_base_service_unittest.cc",
"paint_preview_client_unittest.cc", "paint_preview_client_unittest.cc",
] ]
...@@ -61,14 +65,13 @@ if (!is_ios) { ...@@ -61,14 +65,13 @@ if (!is_ios) {
"//ui/gfx/geometry", "//ui/gfx/geometry",
"//url", "//url",
] ]
} }
test("paint_preview_browser_unit_tests") { test("paint_preview_browser_unit_tests") {
deps = [ deps = [
":unit_tests", ":unit_tests",
"//base", "//base",
"//base/test:test_support", "//base/test:test_support",
"//components/test:run_all_unittests", "//components/test:run_all_unittests",
] ]
}
} }
...@@ -3,6 +3,7 @@ include_rules = [ ...@@ -3,6 +3,7 @@ include_rules = [
"+components/discardable_memory/service", "+components/discardable_memory/service",
"+components/services/paint_preview_compositor/public/mojom", "+components/services/paint_preview_compositor/public/mojom",
"+components/strings/grit/components_strings.h", "+components/strings/grit/components_strings.h",
"+components/keyed_service/core",
"+content/public/browser", "+content/public/browser",
"+content/public/test", "+content/public/test",
"+third_party/blink/public/common", "+third_party/blink/public/common",
......
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/paint_preview/browser/paint_preview_base_service.h"
#include <memory>
#include <utility>
#include "base/callback.h"
#include "base/files/file_path.h"
#include "base/logging.h"
#include "base/task/post_task.h"
#include "components/keyed_service/core/keyed_service.h"
#include "components/paint_preview/browser/file_manager.h"
#include "components/paint_preview/browser/paint_preview_client.h"
#include "components/paint_preview/common/mojom/paint_preview_recorder.mojom.h"
#include "content/public/browser/web_contents.h"
namespace paint_preview {
namespace {
const char kPaintPreviewDir[] = "paint_preview";
} // namespace
PaintPreviewBaseService::PaintPreviewBaseService(
const base::FilePath& path,
const std::string& ascii_feature_name,
bool is_off_the_record)
: file_manager_(
path.AppendASCII(kPaintPreviewDir).AppendASCII(ascii_feature_name)),
is_off_the_record_(is_off_the_record) {}
PaintPreviewBaseService::~PaintPreviewBaseService() = default;
void PaintPreviewBaseService::CapturePaintPreview(
content::WebContents* web_contents,
const base::FilePath& root_dir,
gfx::Rect clip_rect,
OnCapturedCallback callback) {
CapturePaintPreview(web_contents, web_contents->GetMainFrame(), root_dir,
clip_rect, std::move(callback));
}
void PaintPreviewBaseService::CapturePaintPreview(
content::WebContents* web_contents,
content::RenderFrameHost* render_frame_host,
const base::FilePath& root_dir,
gfx::Rect clip_rect,
OnCapturedCallback callback) {
PaintPreviewClient::CreateForWebContents(web_contents); // Is a singleton.
auto* client = PaintPreviewClient::FromWebContents(web_contents);
if (!client) {
std::move(callback).Run(kClientCreationFailed, nullptr);
return;
}
PaintPreviewClient::PaintPreviewParams params;
params.document_guid = base::UnguessableToken::Create();
params.clip_rect = clip_rect;
params.is_main_frame = (render_frame_host == web_contents->GetMainFrame());
params.root_dir = root_dir;
client->CapturePaintPreview(
params, render_frame_host,
base::BindOnce(&PaintPreviewBaseService::OnCaptured,
weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
}
void PaintPreviewBaseService::OnCaptured(
OnCapturedCallback callback,
base::UnguessableToken guid,
mojom::PaintPreviewStatus status,
std::unique_ptr<PaintPreviewProto> proto) {
if (status != mojom::PaintPreviewStatus::kOk) {
DVLOG(1) << "ERROR: Paint Preview failed to capture for document "
<< guid.ToString() << " with error " << status;
std::move(callback).Run(kCaptureFailed, nullptr);
return;
}
std::move(callback).Run(kOk, std::move(proto));
}
} // namespace paint_preview
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef COMPONENTS_PAINT_PREVIEW_BROWSER_PAINT_PREVIEW_BASE_SERVICE_H_
#define COMPONENTS_PAINT_PREVIEW_BROWSER_PAINT_PREVIEW_BASE_SERVICE_H_
#include <memory>
#include "base/callback.h"
#include "base/files/file_path.h"
#include "base/macros.h"
#include "components/keyed_service/core/keyed_service.h"
#include "components/paint_preview/browser/file_manager.h"
#include "components/paint_preview/common/mojom/paint_preview_recorder.mojom.h"
#include "components/paint_preview/common/proto/paint_preview.pb.h"
#include "content/public/browser/web_contents.h"
namespace paint_preview {
// A base KeyedService that serves as the Public API for Paint Previews.
// Features that want to use Paint Previews should extend this class.
// The KeyedService provides a 1:1 mapping between the service and a key or
// profile allowing each feature built on Paint Previews to reliably store
// necessary data to the right directory on disk.
//
// Implementations of this service should be created by implementing a factory
// that extends one of:
// - BrowserContextKeyedServiceFactory
// OR preferably the
// - SimpleKeyedServiceFactory
class PaintPreviewBaseService : public KeyedService {
public:
enum CaptureStatus {
kOk = 0,
kClientCreationFailed,
kCaptureFailed,
};
using OnCapturedCallback =
base::OnceCallback<void(CaptureStatus,
std::unique_ptr<PaintPreviewProto>)>;
// Creates a service instance for a feature. Artifacts produced will live in
// |profile_dir|/paint_preview/|ascii_feature_name|. Implementers of the
// factory can also elect their factory to not construct services in the event
// a profile |is_off_the_record|.
PaintPreviewBaseService(const base::FilePath& profile_dir,
const std::string& ascii_feature_name,
bool is_off_the_record);
~PaintPreviewBaseService() override;
// Returns the file manager for the directory associated with the service.
FileManager* GetFileManager() { return &file_manager_; }
// Returns whether the created service is off the record.
bool IsOffTheRecord() const { return is_off_the_record_; }
// The following methods both capture a Paint Preview; however, their behavior
// and intended use is different. The first method is intended for capturing
// full page contents. Generally, this is what you should be using for most
// features. The second method is intended for capturing just an individual
// RenderFrameHost and its descendents. This is intended for capturing
// individual subframes and should be used for only a few use cases.
//
// NOTE: |root_dir| in the following methods should be created using
// GetFileManager()->CreateOrGetDirectoryFor(<GURL>). However, to provide
// flexibility in managing the lifetime of created objects and ease cleanup
// if a capture fails the service implementation is responsible for
// implementing this management and tracking the directories in existence.
// Data in a directory will contain:
// - paint_preview.pb (the metadata proto)
// - a number of SKPs listed as <guid>.skp (one per frame)
//
// Captures the main frame of |web_contents| (an observer for capturing Paint
// Previews is created for web contents if it does not exist). The capture is
// attributed to the URL of the main frame and is stored in |root_dir|. The
// captured area is clipped to |clip_rect| if it is non-zero. On completion
// the status of the capture is provided via |callback|.
void CapturePaintPreview(content::WebContents* web_contents,
const base::FilePath& root_dir,
gfx::Rect clip_rect,
OnCapturedCallback callback);
// Same as above except |render_frame_host| is directly captured rather than
// the main frame.
void CapturePaintPreview(content::WebContents* web_contents,
content::RenderFrameHost* render_frame_host,
const base::FilePath& root_dir,
gfx::Rect clip_rect,
OnCapturedCallback callback);
private:
void OnCaptured(OnCapturedCallback callback,
base::UnguessableToken guid,
mojom::PaintPreviewStatus status,
std::unique_ptr<PaintPreviewProto> proto);
FileManager file_manager_;
bool is_off_the_record_;
base::WeakPtrFactory<PaintPreviewBaseService> weak_ptr_factory_{this};
DISALLOW_COPY_AND_ASSIGN(PaintPreviewBaseService);
};
} // namespace paint_preview
#endif // COMPONENTS_PAINT_PREVIEW_BROWSER_PAINT_PREVIEW_BASE_SERVICE_H_
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/paint_preview/browser/paint_preview_base_service.h"
#include "base/files/scoped_temp_dir.h"
#include "base/macros.h"
#include "base/no_destructor.h"
#include "build/build_config.h"
#include "components/keyed_service/core/simple_dependency_manager.h"
#include "components/keyed_service/core/simple_factory_key.h"
#include "components/keyed_service/core/simple_key_map.h"
#include "components/keyed_service/core/simple_keyed_service_factory.h"
#include "components/paint_preview/common/mojom/paint_preview_recorder.mojom.h"
#include "components/paint_preview/common/test_utils.h"
#include "content/public/browser/render_process_host.h"
#include "content/public/test/test_browser_context.h"
#include "content/public/test/test_renderer_host.h"
#include "mojo/public/cpp/bindings/associated_receiver.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/common/associated_interfaces/associated_interface_provider.h"
namespace paint_preview {
namespace {
const char kTestFeatureDir[] = "test_feature";
// Builds a PaintPreviewBaseService associated with |key|.
std::unique_ptr<KeyedService> BuildService(SimpleFactoryKey* key) {
return std::make_unique<PaintPreviewBaseService>(
key->GetPath(), kTestFeatureDir, key->IsOffTheRecord());
}
// Returns the GUID corresponding to |rfh|.
uint64_t FrameGuid(content::RenderFrameHost* rfh) {
return static_cast<uint64_t>(rfh->GetProcess()->GetID()) << 32 |
rfh->GetRoutingID();
}
} // namespace
class MockPaintPreviewRecorder : public mojom::PaintPreviewRecorder {
public:
MockPaintPreviewRecorder() = default;
~MockPaintPreviewRecorder() override = default;
void CapturePaintPreview(
mojom::PaintPreviewCaptureParamsPtr params,
mojom::PaintPreviewRecorder::CapturePaintPreviewCallback callback)
override {
{
base::ScopedAllowBlockingForTesting scope;
CheckParams(std::move(params));
std::move(callback).Run(status_, std::move(response_));
}
}
void SetExpectedParams(mojom::PaintPreviewCaptureParamsPtr params) {
expected_params_ = std::move(params);
}
void SetResponse(mojom::PaintPreviewStatus status,
mojom::PaintPreviewCaptureResponsePtr response) {
status_ = status;
response_ = std::move(response);
}
void BindRequest(mojo::ScopedInterfaceEndpointHandle handle) {
binding_.Bind(mojo::PendingAssociatedReceiver<mojom::PaintPreviewRecorder>(
std::move(handle)));
}
private:
void CheckParams(mojom::PaintPreviewCaptureParamsPtr input_params) {
// Ignore GUID and File as this is internal information not known by the
// Keyed Service API.
EXPECT_EQ(input_params->clip_rect, expected_params_->clip_rect);
EXPECT_EQ(input_params->is_main_frame, expected_params_->is_main_frame);
}
mojom::PaintPreviewCaptureParamsPtr expected_params_;
mojom::PaintPreviewStatus status_;
mojom::PaintPreviewCaptureResponsePtr response_;
mojo::AssociatedReceiver<mojom::PaintPreviewRecorder> binding_{this};
DISALLOW_COPY_AND_ASSIGN(MockPaintPreviewRecorder);
};
// An approximation of a SimpleKeyedServiceFactory for a keyed
// PaintPerviewBaseService. This is different than a "real" version as it
// uses base::NoDestructor rather than base::Singleton.
class PaintPreviewBaseServiceFactory : public SimpleKeyedServiceFactory {
public:
static PaintPreviewBaseServiceFactory* GetInstance() {
// Use NoDestructor rather than a singleton due to lifetime behavior in
// tests.
static base::NoDestructor<PaintPreviewBaseServiceFactory> factory;
return factory.get();
}
static PaintPreviewBaseService* GetForKey(SimpleFactoryKey* key) {
return static_cast<PaintPreviewBaseService*>(
GetInstance()->GetServiceForKey(key, true));
}
PaintPreviewBaseServiceFactory()
: SimpleKeyedServiceFactory("PaintPreviewBaseService",
SimpleDependencyManager::GetInstance()) {}
~PaintPreviewBaseServiceFactory() override = default;
private:
std::unique_ptr<KeyedService> BuildServiceInstanceFor(
SimpleFactoryKey* key) const override {
return BuildService(key);
}
SimpleFactoryKey* GetKeyToUse(SimpleFactoryKey* key) const override {
return key;
}
};
class PaintPreviewBaseServiceTest : public content::RenderViewHostTestHarness {
public:
PaintPreviewBaseServiceTest() = default;
~PaintPreviewBaseServiceTest() override = default;
protected:
void SetUp() override {
content::RenderViewHostTestHarness::SetUp();
key_ = std::make_unique<SimpleFactoryKey>(
browser_context()->GetPath(), browser_context()->IsOffTheRecord());
PaintPreviewBaseServiceFactory::GetInstance()->SetTestingFactory(
key_.get(), base::BindRepeating(&BuildService));
}
void OverrideInterface(MockPaintPreviewRecorder* service) {
blink::AssociatedInterfaceProvider* remote_interfaces =
main_rfh()->GetRemoteAssociatedInterfaces();
remote_interfaces->OverrideBinderForTesting(
mojom::PaintPreviewRecorder::Name_,
base::BindRepeating(&MockPaintPreviewRecorder::BindRequest,
base::Unretained(service)));
}
PaintPreviewBaseService* CreateService() {
return PaintPreviewBaseServiceFactory::GetForKey(key_.get());
}
private:
std::unique_ptr<SimpleFactoryKey> key_ = nullptr;
DISALLOW_COPY_AND_ASSIGN(PaintPreviewBaseServiceTest);
};
TEST_F(PaintPreviewBaseServiceTest, CaptureMainFrame) {
MockPaintPreviewRecorder recorder;
auto params = mojom::PaintPreviewCaptureParams::New();
params->clip_rect = gfx::Rect(0, 0, 0, 0);
params->is_main_frame = true;
recorder.SetExpectedParams(std::move(params));
auto response = mojom::PaintPreviewCaptureResponse::New();
response->id = main_rfh()->GetRoutingID();
recorder.SetResponse(mojom::PaintPreviewStatus::kOk, std::move(response));
OverrideInterface(&recorder);
auto* service = CreateService();
EXPECT_FALSE(service->IsOffTheRecord());
base::FilePath path;
ASSERT_TRUE(service->GetFileManager()->CreateOrGetDirectoryFor(
web_contents()->GetLastCommittedURL(), &path));
base::RunLoop loop;
service->CapturePaintPreview(
web_contents(), path, gfx::Rect(0, 0, 0, 0),
base::BindOnce(
[](base::OnceClosure quit_closure,
PaintPreviewBaseService::CaptureStatus expected_status,
const base::FilePath& expected_path, uint64_t expected_guid,
PaintPreviewBaseService::CaptureStatus status,
std::unique_ptr<PaintPreviewProto> proto) {
EXPECT_EQ(status, expected_status);
EXPECT_TRUE(proto->has_root_frame());
EXPECT_EQ(proto->subframes_size(), 0);
EXPECT_TRUE(proto->root_frame().is_main_frame());
#if defined(OS_WIN)
base::FilePath path = base::FilePath(
base::UTF8ToUTF16(proto->root_frame().file_path()));
#else
base::FilePath path =
base::FilePath(proto->root_frame().file_path());
#endif
EXPECT_EQ(path.DirName(), expected_path);
EXPECT_EQ(proto->root_frame().id(), expected_guid);
std::move(quit_closure).Run();
},
loop.QuitClosure(), PaintPreviewBaseService::CaptureStatus::kOk, path,
FrameGuid(main_rfh())));
loop.Run();
}
TEST_F(PaintPreviewBaseServiceTest, CaptureFailed) {
MockPaintPreviewRecorder recorder;
auto params = mojom::PaintPreviewCaptureParams::New();
params->clip_rect = gfx::Rect(0, 0, 0, 0);
params->is_main_frame = true;
recorder.SetExpectedParams(std::move(params));
auto response = mojom::PaintPreviewCaptureResponse::New();
response->id = main_rfh()->GetRoutingID();
recorder.SetResponse(mojom::PaintPreviewStatus::kFailed, std::move(response));
OverrideInterface(&recorder);
auto* service = CreateService();
EXPECT_FALSE(service->IsOffTheRecord());
base::FilePath path;
ASSERT_TRUE(service->GetFileManager()->CreateOrGetDirectoryFor(
web_contents()->GetLastCommittedURL(), &path));
base::RunLoop loop;
service->CapturePaintPreview(
web_contents(), path, gfx::Rect(0, 0, 0, 0),
base::BindOnce(
[](base::OnceClosure quit_closure,
PaintPreviewBaseService::CaptureStatus expected_status,
PaintPreviewBaseService::CaptureStatus status,
std::unique_ptr<PaintPreviewProto> proto) {
EXPECT_EQ(status, expected_status);
EXPECT_EQ(proto, nullptr);
std::move(quit_closure).Run();
},
loop.QuitClosure(),
PaintPreviewBaseService::CaptureStatus::kCaptureFailed));
loop.Run();
}
} // namespace paint_preview
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment