Commit 8d737563 authored by Wei Li's avatar Wei Li Committed by Commit Bot

Complete pdf compositor service

Add interfaces and functions to allow passing in and compositing
contents from multiple frames in pdf compositor service.

Each frame is identified by its global unique id (rph id and rfh id).
Content id is a unique id within its process. Each frame may have oop
subframes. The pdf compositor service will keep track of all the frame
contents and their relationship. When all subframes are painted, the
service can composite the entire frame and convert it into PDF format.

Please refer document goo.gl/ACV6VM for more explanations.

BUG=455764

Change-Id: I3213d9ae0ca85d0ea695dd13acb00938499aa73b
Reviewed-on: https://chromium-review.googlesource.com/767912Reviewed-by: default avatarDaniel Cheng <dcheng@chromium.org>
Reviewed-by: default avatarLei Zhang <thestig@chromium.org>
Commit-Queue: Wei Li <weili@chromium.org>
Cr-Commit-Position: refs/heads/master@{#531687}
parent 23d38ad4
...@@ -24,10 +24,14 @@ ...@@ -24,10 +24,14 @@
#include "components/printing/browser/print_composite_client.h" #include "components/printing/browser/print_composite_client.h"
#include "components/printing/browser/print_manager_utils.h" #include "components/printing/browser/print_manager_utils.h"
#include "components/printing/common/print_messages.h" #include "components/printing/common/print_messages.h"
#include "components/printing/service/public/cpp/pdf_service_mojo_types.h"
#include "components/printing/service/public/cpp/pdf_service_mojo_utils.h" #include "components/printing/service/public/cpp/pdf_service_mojo_utils.h"
#include "content/public/browser/browser_thread.h" #include "content/public/browser/browser_thread.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/render_process_host.h"
#include "content/public/browser/web_contents.h" #include "content/public/browser/web_contents.h"
#include "content/public/browser/web_ui.h" #include "content/public/browser/web_ui.h"
#include "printing/common/pdf_metafile_utils.h"
#include "printing/page_size_margins.h" #include "printing/page_size_margins.h"
#include "printing/print_job_constants.h" #include "printing/print_job_constants.h"
#include "printing/print_settings.h" #include "printing/print_settings.h"
...@@ -125,6 +129,7 @@ void PrintPreviewMessageHandler::OnDidGetPreviewPageCount( ...@@ -125,6 +129,7 @@ void PrintPreviewMessageHandler::OnDidGetPreviewPageCount(
} }
void PrintPreviewMessageHandler::OnDidPreviewPage( void PrintPreviewMessageHandler::OnDidPreviewPage(
content::RenderFrameHost* render_frame_host,
const PrintHostMsg_DidPreviewPage_Params& params) { const PrintHostMsg_DidPreviewPage_Params& params) {
int page_number = params.page_number; int page_number = params.page_number;
if (page_number < FIRST_PAGE_INDEX || !params.data_size) if (page_number < FIRST_PAGE_INDEX || !params.data_size)
...@@ -139,8 +144,12 @@ void PrintPreviewMessageHandler::OnDidPreviewPage( ...@@ -139,8 +144,12 @@ void PrintPreviewMessageHandler::OnDidPreviewPage(
DCHECK(client); DCHECK(client);
// Use utility process to convert skia metafile to pdf. // Use utility process to convert skia metafile to pdf.
client->DoComposite( client->DoCompositePageToPdf(
params.metafile_data_handle, params.data_size, params.document_cookie,
GenFrameGuid(render_frame_host->GetProcess()->GetID(),
render_frame_host->GetRoutingID()),
params.page_number, params.metafile_data_handle, params.data_size,
ContentToFrameMap(),
base::BindOnce(&PrintPreviewMessageHandler::OnCompositePdfPageDone, base::BindOnce(&PrintPreviewMessageHandler::OnCompositePdfPageDone,
weak_ptr_factory_.GetWeakPtr(), params.page_number, weak_ptr_factory_.GetWeakPtr(), params.page_number,
params.preview_request_id)); params.preview_request_id));
...@@ -152,6 +161,7 @@ void PrintPreviewMessageHandler::OnDidPreviewPage( ...@@ -152,6 +161,7 @@ void PrintPreviewMessageHandler::OnDidPreviewPage(
} }
void PrintPreviewMessageHandler::OnMetafileReadyForPrinting( void PrintPreviewMessageHandler::OnMetafileReadyForPrinting(
content::RenderFrameHost* render_frame_host,
const PrintHostMsg_DidPreviewDocument_Params& params) { const PrintHostMsg_DidPreviewDocument_Params& params) {
// Always try to stop the worker. // Always try to stop the worker.
StopWorker(params.document_cookie); StopWorker(params.document_cookie);
...@@ -169,8 +179,11 @@ void PrintPreviewMessageHandler::OnMetafileReadyForPrinting( ...@@ -169,8 +179,11 @@ void PrintPreviewMessageHandler::OnMetafileReadyForPrinting(
auto* client = PrintCompositeClient::FromWebContents(web_contents()); auto* client = PrintCompositeClient::FromWebContents(web_contents());
DCHECK(client); DCHECK(client);
client->DoComposite( client->DoCompositeDocumentToPdf(
params.metafile_data_handle, params.data_size, params.document_cookie,
GenFrameGuid(render_frame_host->GetProcess()->GetID(),
render_frame_host->GetRoutingID()),
params.metafile_data_handle, params.data_size, ContentToFrameMap(),
base::BindOnce(&PrintPreviewMessageHandler::OnCompositePdfDocumentDone, base::BindOnce(&PrintPreviewMessageHandler::OnCompositePdfDocumentDone,
weak_ptr_factory_.GetWeakPtr(), weak_ptr_factory_.GetWeakPtr(),
params.expected_pages_count, params.preview_request_id)); params.expected_pages_count, params.preview_request_id));
...@@ -294,6 +307,9 @@ bool PrintPreviewMessageHandler::OnMessageReceived( ...@@ -294,6 +307,9 @@ bool PrintPreviewMessageHandler::OnMessageReceived(
render_frame_host) render_frame_host)
IPC_MESSAGE_HANDLER(PrintHostMsg_RequestPrintPreview, IPC_MESSAGE_HANDLER(PrintHostMsg_RequestPrintPreview,
OnRequestPrintPreview) OnRequestPrintPreview)
IPC_MESSAGE_HANDLER(PrintHostMsg_DidPreviewPage, OnDidPreviewPage)
IPC_MESSAGE_HANDLER(PrintHostMsg_MetafileReadyForPrinting,
OnMetafileReadyForPrinting)
IPC_MESSAGE_UNHANDLED(handled = false) IPC_MESSAGE_UNHANDLED(handled = false)
IPC_END_MESSAGE_MAP() IPC_END_MESSAGE_MAP()
if (handled) if (handled)
...@@ -303,9 +319,6 @@ bool PrintPreviewMessageHandler::OnMessageReceived( ...@@ -303,9 +319,6 @@ bool PrintPreviewMessageHandler::OnMessageReceived(
IPC_BEGIN_MESSAGE_MAP(PrintPreviewMessageHandler, message) IPC_BEGIN_MESSAGE_MAP(PrintPreviewMessageHandler, message)
IPC_MESSAGE_HANDLER(PrintHostMsg_DidGetPreviewPageCount, IPC_MESSAGE_HANDLER(PrintHostMsg_DidGetPreviewPageCount,
OnDidGetPreviewPageCount) OnDidGetPreviewPageCount)
IPC_MESSAGE_HANDLER(PrintHostMsg_DidPreviewPage, OnDidPreviewPage)
IPC_MESSAGE_HANDLER(PrintHostMsg_MetafileReadyForPrinting,
OnMetafileReadyForPrinting)
IPC_MESSAGE_HANDLER(PrintHostMsg_PrintPreviewFailed, IPC_MESSAGE_HANDLER(PrintHostMsg_PrintPreviewFailed,
OnPrintPreviewFailed) OnPrintPreviewFailed)
IPC_MESSAGE_HANDLER(PrintHostMsg_DidGetDefaultPageLayout, IPC_MESSAGE_HANDLER(PrintHostMsg_DidGetDefaultPageLayout,
......
...@@ -64,8 +64,10 @@ class PrintPreviewMessageHandler ...@@ -64,8 +64,10 @@ class PrintPreviewMessageHandler
bool has_custom_page_size_style); bool has_custom_page_size_style);
void OnDidGetPreviewPageCount( void OnDidGetPreviewPageCount(
const PrintHostMsg_DidGetPreviewPageCount_Params& params); const PrintHostMsg_DidGetPreviewPageCount_Params& params);
void OnDidPreviewPage(const PrintHostMsg_DidPreviewPage_Params& params); void OnDidPreviewPage(content::RenderFrameHost* render_frame_host,
const PrintHostMsg_DidPreviewPage_Params& params);
void OnMetafileReadyForPrinting( void OnMetafileReadyForPrinting(
content::RenderFrameHost* render_frame_host,
const PrintHostMsg_DidPreviewDocument_Params& params); const PrintHostMsg_DidPreviewDocument_Params& params);
void OnPrintPreviewFailed(int document_cookie); void OnPrintPreviewFailed(int document_cookie);
void OnPrintPreviewCancelled(int document_cookie); void OnPrintPreviewCancelled(int document_cookie);
......
...@@ -34,6 +34,7 @@ ...@@ -34,6 +34,7 @@
#include "components/printing/browser/print_composite_client.h" #include "components/printing/browser/print_composite_client.h"
#include "components/printing/browser/print_manager_utils.h" #include "components/printing/browser/print_manager_utils.h"
#include "components/printing/common/print_messages.h" #include "components/printing/common/print_messages.h"
#include "components/printing/service/public/cpp/pdf_service_mojo_types.h"
#include "components/printing/service/public/cpp/pdf_service_mojo_utils.h" #include "components/printing/service/public/cpp/pdf_service_mojo_utils.h"
#include "content/public/browser/browser_thread.h" #include "content/public/browser/browser_thread.h"
#include "content/public/browser/notification_details.h" #include "content/public/browser/notification_details.h"
...@@ -333,6 +334,7 @@ void PrintViewManagerBase::OnComposePdfDone( ...@@ -333,6 +334,7 @@ void PrintViewManagerBase::OnComposePdfDone(
} }
void PrintViewManagerBase::OnDidPrintDocument( void PrintViewManagerBase::OnDidPrintDocument(
content::RenderFrameHost* render_frame_host,
const PrintHostMsg_DidPrintDocument_Params& params) { const PrintHostMsg_DidPrintDocument_Params& params) {
PrintedDocument* document = GetDocument(params.document_cookie); PrintedDocument* document = GetDocument(params.document_cookie);
if (!document) if (!document)
...@@ -347,12 +349,15 @@ void PrintViewManagerBase::OnDidPrintDocument( ...@@ -347,12 +349,15 @@ void PrintViewManagerBase::OnDidPrintDocument(
auto* client = PrintCompositeClient::FromWebContents(web_contents()); auto* client = PrintCompositeClient::FromWebContents(web_contents());
if (IsOopifEnabled() && !client->for_preview() && if (IsOopifEnabled() && !client->for_preview() &&
document->settings().is_modifiable()) { document->settings().is_modifiable()) {
client->DoComposite(params.metafile_data_handle, params.data_size, client->DoCompositeDocumentToPdf(
base::BindOnce(&PrintViewManagerBase::OnComposePdfDone, params.document_cookie,
weak_ptr_factory_.GetWeakPtr(), params)); GenFrameGuid(render_frame_host->GetProcess()->GetID(),
render_frame_host->GetRoutingID()),
params.metafile_data_handle, params.data_size, ContentToFrameMap(),
base::BindOnce(&PrintViewManagerBase::OnComposePdfDone,
weak_ptr_factory_.GetWeakPtr(), params));
return; return;
} }
std::unique_ptr<base::SharedMemory> shared_buf = std::unique_ptr<base::SharedMemory> shared_buf =
std::make_unique<base::SharedMemory>(params.metafile_data_handle, true); std::make_unique<base::SharedMemory>(params.metafile_data_handle, true);
if (!shared_buf->Map(params.data_size)) { if (!shared_buf->Map(params.data_size)) {
...@@ -435,8 +440,16 @@ bool PrintViewManagerBase::OnMessageReceived( ...@@ -435,8 +440,16 @@ bool PrintViewManagerBase::OnMessageReceived(
const IPC::Message& message, const IPC::Message& message,
content::RenderFrameHost* render_frame_host) { content::RenderFrameHost* render_frame_host) {
bool handled = true; bool handled = true;
IPC_BEGIN_MESSAGE_MAP(PrintViewManagerBase, message) IPC_BEGIN_MESSAGE_MAP_WITH_PARAM(PrintViewManagerBase, message,
render_frame_host)
IPC_MESSAGE_HANDLER(PrintHostMsg_DidPrintDocument, OnDidPrintDocument) IPC_MESSAGE_HANDLER(PrintHostMsg_DidPrintDocument, OnDidPrintDocument)
IPC_MESSAGE_UNHANDLED(handled = false)
IPC_END_MESSAGE_MAP()
if (handled)
return true;
handled = true;
IPC_BEGIN_MESSAGE_MAP(PrintViewManagerBase, message)
IPC_MESSAGE_HANDLER(PrintHostMsg_ShowInvalidPrinterSettingsError, IPC_MESSAGE_HANDLER(PrintHostMsg_ShowInvalidPrinterSettingsError,
OnShowInvalidPrinterSettingsError) OnShowInvalidPrinterSettingsError)
IPC_MESSAGE_UNHANDLED(handled = false) IPC_MESSAGE_UNHANDLED(handled = false)
......
...@@ -106,7 +106,8 @@ class PrintViewManagerBase : public content::NotificationObserver, ...@@ -106,7 +106,8 @@ class PrintViewManagerBase : public content::NotificationObserver,
void OnDidGetPrintedPagesCount(int cookie, int number_pages) override; void OnDidGetPrintedPagesCount(int cookie, int number_pages) override;
void OnPrintingFailed(int cookie) override; void OnPrintingFailed(int cookie) override;
void OnShowInvalidPrinterSettingsError(); void OnShowInvalidPrinterSettingsError();
void OnDidPrintDocument(const PrintHostMsg_DidPrintDocument_Params& params); void OnDidPrintDocument(content::RenderFrameHost* render_frame_host,
const PrintHostMsg_DidPrintDocument_Params& params);
// IPC message handlers for service. // IPC message handlers for service.
void OnComposePdfDone(const PrintHostMsg_DidPrintDocument_Params& params, void OnComposePdfDone(const PrintHostMsg_DidPrintDocument_Params& params,
......
...@@ -19,7 +19,7 @@ static_library("browser") { ...@@ -19,7 +19,7 @@ static_library("browser") {
deps = [ deps = [
"//base", "//base",
"//components/printing/common", "//components/printing/common",
"//components/printing/service/public/cpp:client", "//components/printing/service/public/interfaces",
"//printing", "//printing",
"//services/service_manager/public/cpp", "//services/service_manager/public/cpp",
] ]
......
include_rules = [ include_rules = [
"+components/printing/service/public/cpp", "+components/printing/service/public/interfaces",
"+content/public/browser", "+content/public/browser",
"+mojo/public/cpp/system", "+mojo/public/cpp/system",
"+services/service_manager/public/cpp", "+services/service_manager/public/cpp",
......
...@@ -4,12 +4,13 @@ ...@@ -4,12 +4,13 @@
#include "components/printing/browser/print_composite_client.h" #include "components/printing/browser/print_composite_client.h"
#include <memory>
#include <utility> #include <utility>
#include "base/threading/thread_task_runner_handle.h" #include "base/bind.h"
#include "base/memory/shared_memory_handle.h"
#include "content/public/browser/browser_thread.h" #include "content/public/browser/browser_thread.h"
#include "content/public/common/service_manager_connection.h" #include "content/public/common/service_manager_connection.h"
#include "mojo/public/cpp/system/platform_handle.h"
#include "services/service_manager/public/cpp/connector.h" #include "services/service_manager/public/cpp/connector.h"
DEFINE_WEB_CONTENTS_USER_DATA_KEY(printing::PrintCompositeClient); DEFINE_WEB_CONTENTS_USER_DATA_KEY(printing::PrintCompositeClient);
...@@ -17,28 +18,118 @@ DEFINE_WEB_CONTENTS_USER_DATA_KEY(printing::PrintCompositeClient); ...@@ -17,28 +18,118 @@ DEFINE_WEB_CONTENTS_USER_DATA_KEY(printing::PrintCompositeClient);
namespace printing { namespace printing {
PrintCompositeClient::PrintCompositeClient(content::WebContents* web_contents) PrintCompositeClient::PrintCompositeClient(content::WebContents* web_contents)
: for_preview_(false) {} : for_preview_(false) {
DCHECK(web_contents);
}
PrintCompositeClient::~PrintCompositeClient() {} PrintCompositeClient::~PrintCompositeClient() {}
void PrintCompositeClient::CreateConnectorRequest() { void PrintCompositeClient::DoCompositePageToPdf(
connector_ = service_manager::Connector::Create(&connector_request_); int document_cookie,
content::ServiceManagerConnection::GetForProcess() uint64_t frame_guid,
->GetConnector() int page_num,
->BindConnectorRequest(std::move(connector_request_)); base::SharedMemoryHandle handle,
uint32_t data_size,
const ContentToFrameMap& subframe_content_map,
mojom::PdfCompositor::CompositePageToPdfCallback callback) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
auto& compositor = GetCompositeRequest(document_cookie, page_num);
DCHECK(data_size);
mojo::ScopedSharedBufferHandle buffer_handle = mojo::WrapSharedMemoryHandle(
handle, data_size,
mojo::UnwrappedSharedMemoryHandleProtection::kReadOnly);
// Since this class owns compositor, compositor will be gone when this class
// is destructed. Mojo won't call its callback in that case so it is safe to
// use unretained |this| pointer here.
compositor->CompositePageToPdf(
frame_guid, page_num, std::move(buffer_handle), subframe_content_map,
base::BindOnce(&PrintCompositeClient::OnDidCompositePageToPdf,
base::Unretained(this), page_num, document_cookie,
std::move(callback)));
} }
void PrintCompositeClient::DoComposite( void PrintCompositeClient::DoCompositeDocumentToPdf(
int document_cookie,
uint64_t frame_guid,
base::SharedMemoryHandle handle, base::SharedMemoryHandle handle,
uint32_t data_size, uint32_t data_size,
mojom::PdfCompositor::CompositePdfCallback callback) { const ContentToFrameMap& subframe_content_map,
mojom::PdfCompositor::CompositeDocumentToPdfCallback callback) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI); DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
auto& compositor = GetCompositeRequest(document_cookie, base::nullopt);
DCHECK(data_size); DCHECK(data_size);
mojo::ScopedSharedBufferHandle buffer_handle = mojo::WrapSharedMemoryHandle(
handle, data_size,
mojo::UnwrappedSharedMemoryHandleProtection::kReadOnly);
// Since this class owns compositor, compositor will be gone when this class
// is destructed. Mojo won't call its callback in that case so it is safe to
// use unretained |this| pointer here.
compositor->CompositeDocumentToPdf(
frame_guid, std::move(buffer_handle), subframe_content_map,
base::BindOnce(&PrintCompositeClient::OnDidCompositeDocumentToPdf,
base::Unretained(this), document_cookie,
std::move(callback)));
}
void PrintCompositeClient::OnDidCompositePageToPdf(
int page_num,
int document_cookie,
printing::mojom::PdfCompositor::CompositePageToPdfCallback callback,
printing::mojom::PdfCompositor::Status status,
mojo::ScopedSharedBufferHandle handle) {
RemoveCompositeRequest(document_cookie, page_num);
std::move(callback).Run(status, std::move(handle));
}
void PrintCompositeClient::OnDidCompositeDocumentToPdf(
int document_cookie,
printing::mojom::PdfCompositor::CompositeDocumentToPdfCallback callback,
printing::mojom::PdfCompositor::Status status,
mojo::ScopedSharedBufferHandle handle) {
RemoveCompositeRequest(document_cookie, base::nullopt);
std::move(callback).Run(status, std::move(handle));
}
mojom::PdfCompositorPtr& PrintCompositeClient::GetCompositeRequest(
int cookie,
base::Optional<int> page_num) {
int page_no =
page_num == base::nullopt ? kPageNumForWholeDoc : page_num.value();
std::pair<int, int> key = std::make_pair(cookie, page_no);
auto iter = compositor_map_.find(key);
if (iter != compositor_map_.end())
return iter->second;
auto iterator = compositor_map_.emplace(key, CreateCompositeRequest()).first;
return iterator->second;
}
void PrintCompositeClient::RemoveCompositeRequest(
int cookie,
base::Optional<int> page_num) {
int page_no =
page_num == base::nullopt ? kPageNumForWholeDoc : page_num.value();
std::pair<int, int> key = std::make_pair(cookie, page_no);
auto iter = compositor_map_.find(key);
if (iter == compositor_map_.end())
return;
compositor_map_.erase(iter);
}
if (!connector_) mojom::PdfCompositorPtr PrintCompositeClient::CreateCompositeRequest() {
CreateConnectorRequest(); if (!connector_) {
Composite(connector_.get(), handle, data_size, std::move(callback), service_manager::mojom::ConnectorRequest connector_request;
base::ThreadTaskRunnerHandle::Get()); connector_ = service_manager::Connector::Create(&connector_request);
content::ServiceManagerConnection::GetForProcess()
->GetConnector()
->BindConnectorRequest(std::move(connector_request));
}
mojom::PdfCompositorPtr compositor;
connector_->BindInterface(mojom::kServiceName, &compositor);
return compositor;
} }
} // namespace printing } // namespace printing
...@@ -5,35 +5,88 @@ ...@@ -5,35 +5,88 @@
#ifndef COMPONENTS_PRINTING_BROWSER_PRINT_COMPOSITE_CLIENT_H_ #ifndef COMPONENTS_PRINTING_BROWSER_PRINT_COMPOSITE_CLIENT_H_
#define COMPONENTS_PRINTING_BROWSER_PRINT_COMPOSITE_CLIENT_H_ #define COMPONENTS_PRINTING_BROWSER_PRINT_COMPOSITE_CLIENT_H_
#include "components/printing/service/public/cpp/pdf_compositor_client.h" #include <map>
#include <memory>
#include "base/optional.h"
#include "components/printing/service/public/interfaces/pdf_compositor.mojom.h"
#include "content/public/browser/web_contents_observer.h" #include "content/public/browser/web_contents_observer.h"
#include "content/public/browser/web_contents_user_data.h" #include "content/public/browser/web_contents_user_data.h"
#include "services/service_manager/public/cpp/connector.h"
namespace printing { namespace printing {
// Class to manage print requests and their communication with pdf
// compositor service.
// Each composite request have a separate interface pointer to connect
// with remote service.
class PrintCompositeClient class PrintCompositeClient
: public PdfCompositorClient, : public content::WebContentsUserData<PrintCompositeClient> {
public content::WebContentsUserData<PrintCompositeClient> {
public: public:
using ContentToFrameMap = std::unordered_map<uint32_t, uint64_t>;
explicit PrintCompositeClient(content::WebContents* web_contents); explicit PrintCompositeClient(content::WebContents* web_contents);
~PrintCompositeClient() override; ~PrintCompositeClient() override;
// NOTE: |handle| must be a READ-ONLY base::SharedMemoryHandle, i.e. one // NOTE: |handle| must be a READ-ONLY base::SharedMemoryHandle, i.e. one
// acquired by base::SharedMemory::GetReadOnlyHandle(). // acquired by base::SharedMemory::GetReadOnlyHandle().
void DoComposite(base::SharedMemoryHandle handle, void DoCompositePageToPdf(
uint32_t data_size, int cookie,
mojom::PdfCompositor::CompositePdfCallback callback); uint64_t frame_guid,
int page_num,
base::SharedMemoryHandle handle,
uint32_t data_size,
const ContentToFrameMap& subframe_content_map,
mojom::PdfCompositor::CompositePageToPdfCallback callback);
void DoCompositeDocumentToPdf(
int cookie,
uint64_t frame_guid,
base::SharedMemoryHandle handle,
uint32_t data_size,
const ContentToFrameMap& subframe_content_map,
mojom::PdfCompositor::CompositeDocumentToPdfCallback callback);
void set_for_preview(bool for_preview) { for_preview_ = for_preview; } void set_for_preview(bool for_preview) { for_preview_ = for_preview; }
bool for_preview() const { return for_preview_; } bool for_preview() const { return for_preview_; }
private: private:
void CreateConnectorRequest(); // Since page number is always non-negative, use this value to indicate it is
// for the whole document -- no page number specified.
static constexpr int kPageNumForWholeDoc = -1;
service_manager::mojom::ConnectorRequest connector_request_; // Callback functions for getting the replies.
std::unique_ptr<service_manager::Connector> connector_; void OnDidCompositePageToPdf(
int page_num,
int document_cookie,
printing::mojom::PdfCompositor::CompositePageToPdfCallback callback,
printing::mojom::PdfCompositor::Status status,
mojo::ScopedSharedBufferHandle handle);
void OnDidCompositeDocumentToPdf(
int document_cookie,
printing::mojom::PdfCompositor::CompositeDocumentToPdfCallback callback,
printing::mojom::PdfCompositor::Status status,
mojo::ScopedSharedBufferHandle handle);
// Get the request, but doesn't own it.
mojom::PdfCompositorPtr& GetCompositeRequest(int cookie,
base::Optional<int> page_num);
// Find an existing request or create a new one, and own it.
void RemoveCompositeRequest(int cookie, base::Optional<int> page_num);
mojom::PdfCompositorPtr CreateCompositeRequest();
// Whether this client is created for print preview dialog.
bool for_preview_; bool for_preview_;
std::unique_ptr<service_manager::Connector> connector_;
// Stores the mapping between <document cookie, page number> and their
// corresponding requests.
std::map<std::pair<int, int>, mojom::PdfCompositorPtr> compositor_map_;
DISALLOW_COPY_AND_ASSIGN(PrintCompositeClient); DISALLOW_COPY_AND_ASSIGN(PrintCompositeClient);
}; };
......
...@@ -265,6 +265,9 @@ IPC_STRUCT_BEGIN(PrintHostMsg_DidPreviewPage_Params) ...@@ -265,6 +265,9 @@ IPC_STRUCT_BEGIN(PrintHostMsg_DidPreviewPage_Params)
// The id of the preview request. // The id of the preview request.
IPC_STRUCT_MEMBER(int, preview_request_id) IPC_STRUCT_MEMBER(int, preview_request_id)
// Cookie for the document to ensure correctness.
IPC_STRUCT_MEMBER(int, document_cookie)
IPC_STRUCT_END() IPC_STRUCT_END()
// Parameters sent along with the page count. // Parameters sent along with the page count.
......
...@@ -2104,6 +2104,8 @@ bool PrintRenderFrameHelper::PreviewPageRendered(int page_number, ...@@ -2104,6 +2104,8 @@ bool PrintRenderFrameHelper::PreviewPageRendered(int page_number,
preview_page_params.page_number = page_number; preview_page_params.page_number = page_number;
preview_page_params.preview_request_id = preview_page_params.preview_request_id =
print_pages_params_->params.preview_request_id; print_pages_params_->params.preview_request_id;
preview_page_params.document_cookie =
print_pages_params_->params.document_cookie;
Send(new PrintHostMsg_DidPreviewPage(routing_id(), preview_page_params)); Send(new PrintHostMsg_DidPreviewPage(routing_id(), preview_page_params));
return true; return true;
......
...@@ -42,6 +42,7 @@ if (enable_basic_printing || enable_print_preview) { ...@@ -42,6 +42,7 @@ if (enable_basic_printing || enable_print_preview) {
source_set("unit_tests") { source_set("unit_tests") {
testonly = true testonly = true
sources = [ sources = [
"pdf_compositor_impl_unittest.cc",
"pdf_compositor_service_unittest.cc", "pdf_compositor_service_unittest.cc",
] ]
...@@ -57,6 +58,7 @@ if (enable_basic_printing || enable_print_preview) { ...@@ -57,6 +58,7 @@ if (enable_basic_printing || enable_print_preview) {
"//components/printing/service/public/interfaces", "//components/printing/service/public/interfaces",
"//mojo/common", "//mojo/common",
"//services/service_manager/public/cpp:service_test_support", "//services/service_manager/public/cpp:service_test_support",
"//skia",
"//testing/gmock", "//testing/gmock",
"//testing/gtest", "//testing/gtest",
] ]
......
...@@ -4,17 +4,17 @@ ...@@ -4,17 +4,17 @@
#include "components/printing/service/pdf_compositor_impl.h" #include "components/printing/service/pdf_compositor_impl.h"
#include <utility> #include <tuple>
#include <vector>
#include "base/logging.h" #include "base/logging.h"
#include "base/memory/shared_memory.h"
#include "base/memory/shared_memory_handle.h" #include "base/memory/shared_memory_handle.h"
#include "components/printing/service/public/cpp/pdf_service_mojo_types.h"
#include "components/printing/service/public/cpp/pdf_service_mojo_utils.h" #include "components/printing/service/public/cpp/pdf_service_mojo_utils.h"
#include "mojo/public/cpp/system/platform_handle.h" #include "mojo/public/cpp/system/platform_handle.h"
#include "printing/common/pdf_metafile_utils.h" #include "printing/common/pdf_metafile_utils.h"
#include "third_party/skia/include/core/SkCanvas.h" #include "third_party/skia/include/core/SkCanvas.h"
#include "third_party/skia/include/core/SkDocument.h" #include "third_party/skia/include/core/SkDocument.h"
#include "third_party/skia/include/core/SkSerialProcs.h"
#include "third_party/skia/src/utils/SkMultiPictureDocument.h" #include "third_party/skia/src/utils/SkMultiPictureDocument.h"
namespace printing { namespace printing {
...@@ -26,29 +26,157 @@ PdfCompositorImpl::PdfCompositorImpl( ...@@ -26,29 +26,157 @@ PdfCompositorImpl::PdfCompositorImpl(
PdfCompositorImpl::~PdfCompositorImpl() = default; PdfCompositorImpl::~PdfCompositorImpl() = default;
void PdfCompositorImpl::CompositePdf( void PdfCompositorImpl::AddSubframeContent(
mojo::ScopedSharedBufferHandle sk_handle, uint64_t frame_guid,
mojom::PdfCompositor::CompositePdfCallback callback) { mojo::ScopedSharedBufferHandle serialized_content,
DCHECK(sk_handle.is_valid()); const ContentToFrameMap& subframe_content_map) {
// Add this frame and its serialized content.
DCHECK(!base::ContainsKey(frame_info_map_, frame_guid));
frame_info_map_[frame_guid] = std::make_unique<FrameInfo>(
GetShmFromMojoHandle(std::move(serialized_content)),
subframe_content_map);
std::unique_ptr<base::SharedMemory> shm = // If the request is not here yet, we do nothing more.
GetShmFromMojoHandle(std::move(sk_handle)); // Otherwise, we need to check whether the request actually waits on this
// frame content.
if (!request_)
return;
// Get the pending list which is a list of subframes this frame needs
// but still unavailable.
std::vector<uint64_t> pending_subframes;
for (auto& subframe_content : subframe_content_map) {
auto subframe_guid = subframe_content.second;
if (!base::ContainsKey(frame_info_map_, subframe_guid))
pending_subframes.push_back(subframe_guid);
}
// Check for the request's pending list.
// If the request needs the this frame, we can remove the dependecy, but
// update with this frame's pending list.
auto& pending_list = request_->pending_subframes;
if (!pending_list.erase(frame_guid)) {
// The request doesn't directly waits on this frame, simply return.
return;
}
std::copy(pending_subframes.begin(), pending_subframes.end(),
std::inserter(pending_list, pending_list.end()));
if (pending_list.empty()) {
// If the request doesn't wait on any subframes which means it is ready,
// fullfill the request now.
auto iter = frame_info_map_.find(request_->frame_guid);
DCHECK(iter != frame_info_map_.end());
auto& frame_info = iter->second;
FullfillRequest(request_->frame_guid, request_->page_number,
std::move(frame_info->serialized_content),
frame_info->subframe_content_map,
std::move(request_->callback));
}
}
void PdfCompositorImpl::CompositePageToPdf(
uint64_t frame_guid,
uint32_t page_num,
mojo::ScopedSharedBufferHandle serialized_content,
const ContentToFrameMap& subframe_content_map,
mojom::PdfCompositor::CompositePageToPdfCallback callback) {
HandleCompositionRequest(frame_guid, page_num, std::move(serialized_content),
subframe_content_map, std::move(callback));
}
void PdfCompositorImpl::CompositeDocumentToPdf(
uint64_t frame_guid,
mojo::ScopedSharedBufferHandle serialized_content,
const ContentToFrameMap& subframe_content_map,
mojom::PdfCompositor::CompositeDocumentToPdfCallback callback) {
HandleCompositionRequest(frame_guid, base::nullopt,
std::move(serialized_content), subframe_content_map,
std::move(callback));
}
bool PdfCompositorImpl::IsReadyToComposite(
uint64_t frame_guid,
const ContentToFrameMap& subframe_content_map,
base::flat_set<uint64_t>* pending_subframes) {
pending_subframes->clear();
base::flat_set<uint64_t> visited_frames;
visited_frames.insert(frame_guid);
CheckFramesForReadiness(frame_guid, subframe_content_map, pending_subframes,
&visited_frames);
return pending_subframes->empty();
}
void PdfCompositorImpl::CheckFramesForReadiness(
uint64_t frame_guid,
const ContentToFrameMap& subframe_content_map,
base::flat_set<uint64_t>* pending_subframes,
base::flat_set<uint64_t>* visited) {
for (auto& subframe_content : subframe_content_map) {
auto subframe_guid = subframe_content.second;
// If this frame has been checked, skip it.
auto result = visited->insert(subframe_guid);
if (!result.second)
continue;
auto iter = frame_info_map_.find(subframe_guid);
if (iter == frame_info_map_.end()) {
pending_subframes->insert(subframe_guid);
} else {
CheckFramesForReadiness(subframe_guid, iter->second->subframe_content_map,
pending_subframes, visited);
}
}
}
void PdfCompositorImpl::HandleCompositionRequest(
uint64_t frame_guid,
base::Optional<uint32_t> page_num,
mojo::ScopedSharedBufferHandle serialized_content,
const ContentToFrameMap& subframe_content_map,
CompositeToPdfCallback callback) {
base::flat_set<uint64_t> pending_subframes;
if (IsReadyToComposite(frame_guid, subframe_content_map,
&pending_subframes)) {
FullfillRequest(frame_guid, page_num,
GetShmFromMojoHandle(std::move(serialized_content)),
subframe_content_map, std::move(callback));
return;
}
SkMemoryStream stream(shm->memory(), shm->mapped_size()); // When it is not ready yet, keep its information and
// wait until all dependent subframes are ready.
DCHECK(!base::ContainsKey(frame_info_map_, frame_guid));
frame_info_map_[frame_guid] = std::make_unique<FrameInfo>(
GetShmFromMojoHandle(std::move(serialized_content)),
subframe_content_map);
request_ = std::make_unique<RequestInfo>(
frame_guid, page_num, std::move(pending_subframes), std::move(callback));
}
mojom::PdfCompositor::Status PdfCompositorImpl::CompositeToPdf(
uint64_t frame_guid,
base::Optional<uint32_t> page_num,
std::unique_ptr<base::SharedMemory> shared_mem,
const ContentToFrameMap& subframe_content_map,
mojo::ScopedSharedBufferHandle* handle) {
DeserializationContext subframes =
GetDeserializationContext(subframe_content_map);
// Read in content and convert it into pdf.
SkMemoryStream stream(shared_mem->memory(), shared_mem->mapped_size());
int page_count = SkMultiPictureDocumentReadPageCount(&stream); int page_count = SkMultiPictureDocumentReadPageCount(&stream);
if (!page_count) { if (!page_count) {
DLOG(ERROR) << "CompositePdf: No page is read."; DLOG(ERROR) << "CompositeToPdf: No page is read.";
std::move(callback).Run(mojom::PdfCompositor::Status::CONTENT_FORMAT_ERROR, return mojom::PdfCompositor::Status::CONTENT_FORMAT_ERROR;
mojo::ScopedSharedBufferHandle());
return;
} }
std::vector<SkDocumentPage> pages(page_count); std::vector<SkDocumentPage> pages(page_count);
if (!SkMultiPictureDocumentRead(&stream, pages.data(), page_count)) { // TODO(weili): Change the default functions to actual implementation.
DLOG(ERROR) << "CompositePdf: Page reading failed."; SkDeserialProcs procs;
std::move(callback).Run(mojom::PdfCompositor::Status::CONTENT_FORMAT_ERROR, if (!SkMultiPictureDocumentRead(&stream, pages.data(), page_count, &procs)) {
mojo::ScopedSharedBufferHandle()); DLOG(ERROR) << "CompositeToPdf: Page reading failed.";
return; return mojom::PdfCompositor::Status::CONTENT_FORMAT_ERROR;
} }
SkDynamicMemoryWStream wstream; SkDynamicMemoryWStream wstream;
...@@ -61,16 +189,86 @@ void PdfCompositorImpl::CompositePdf( ...@@ -61,16 +189,86 @@ void PdfCompositorImpl::CompositePdf(
} }
doc->close(); doc->close();
mojo::ScopedSharedBufferHandle buffer = *handle = mojo::SharedBufferHandle::Create(wstream.bytesWritten());
mojo::SharedBufferHandle::Create(wstream.bytesWritten()); DCHECK((*handle).is_valid());
DCHECK(buffer.is_valid());
mojo::ScopedSharedBufferMapping mapping = buffer->Map(wstream.bytesWritten()); mojo::ScopedSharedBufferMapping mapping =
(*handle)->Map(wstream.bytesWritten());
DCHECK(mapping); DCHECK(mapping);
wstream.copyToAndReset(mapping.get()); wstream.copyToAndReset(mapping.get());
std::move(callback).Run(mojom::PdfCompositor::Status::SUCCESS, return mojom::PdfCompositor::Status::SUCCESS;
std::move(buffer)); }
sk_sp<SkPicture> PdfCompositorImpl::CompositeSubframe(uint64_t frame_guid) {
// The content of this frame should be available.
auto iter = frame_info_map_.find(frame_guid);
DCHECK(iter != frame_info_map_.end());
std::unique_ptr<FrameInfo>& frame_info = iter->second;
frame_info->composited = true;
// Composite subframes first.
DeserializationContext subframes =
GetDeserializationContext(frame_info->subframe_content_map);
// Composite the entire frame.
SkMemoryStream stream(iter->second->serialized_content->memory(),
iter->second->serialized_content->mapped_size());
// TODO(weili): Change the default functions to actual implementation.
SkDeserialProcs procs;
iter->second->content = SkPicture::MakeFromStream(&stream, &procs);
return iter->second->content;
} }
PdfCompositorImpl::DeserializationContext
PdfCompositorImpl::GetDeserializationContext(
const ContentToFrameMap& subframe_content_map) {
DeserializationContext subframes;
for (auto& content_info : subframe_content_map) {
uint64_t frame_guid = content_info.first;
uint32_t content_id = content_info.second;
auto frame_iter = frame_info_map_.find(frame_guid);
if (frame_iter == frame_info_map_.end())
continue;
if (frame_iter->second->composited)
subframes[content_id] = frame_iter->second->content;
else
subframes[content_id] = CompositeSubframe(frame_iter->first);
}
return subframes;
}
void PdfCompositorImpl::FullfillRequest(
uint64_t frame_guid,
base::Optional<uint32_t> page_num,
std::unique_ptr<base::SharedMemory> serialized_content,
const ContentToFrameMap& subframe_content_map,
CompositeToPdfCallback callback) {
mojo::ScopedSharedBufferHandle handle;
auto status =
CompositeToPdf(frame_guid, page_num, std::move(serialized_content),
subframe_content_map, &handle);
std::move(callback).Run(status, std::move(handle));
}
PdfCompositorImpl::FrameInfo::FrameInfo(
std::unique_ptr<base::SharedMemory> content,
const ContentToFrameMap& map)
: serialized_content(std::move(content)), subframe_content_map(map) {}
PdfCompositorImpl::FrameInfo::~FrameInfo() {}
PdfCompositorImpl::RequestInfo::RequestInfo(
uint64_t frame_guid,
base::Optional<uint32_t> page_num,
const base::flat_set<uint64_t>& pending_subframes,
mojom::PdfCompositor::CompositePageToPdfCallback callback)
: frame_guid(frame_guid),
page_number(page_num),
pending_subframes(pending_subframes),
callback(std::move(callback)) {}
PdfCompositorImpl::RequestInfo::~RequestInfo() {}
} // namespace printing } // namespace printing
...@@ -5,13 +5,23 @@ ...@@ -5,13 +5,23 @@
#ifndef COMPONENTS_PRINTING_SERVICE_PDF_COMPOSITOR_IMPL_H_ #ifndef COMPONENTS_PRINTING_SERVICE_PDF_COMPOSITOR_IMPL_H_
#define COMPONENTS_PRINTING_SERVICE_PDF_COMPOSITOR_IMPL_H_ #define COMPONENTS_PRINTING_SERVICE_PDF_COMPOSITOR_IMPL_H_
#include <map>
#include <memory> #include <memory>
#include <string> #include <string>
#include <vector>
#include "base/containers/flat_map.h"
#include "base/containers/flat_set.h"
#include "base/macros.h" #include "base/macros.h"
#include "base/memory/ref_counted_memory.h"
#include "base/memory/shared_memory.h"
#include "base/optional.h"
#include "components/printing/service/public/cpp/pdf_service_mojo_types.h"
#include "components/printing/service/public/interfaces/pdf_compositor.mojom.h" #include "components/printing/service/public/interfaces/pdf_compositor.mojom.h"
#include "mojo/public/cpp/system/buffer.h" #include "mojo/public/cpp/system/buffer.h"
#include "services/service_manager/public/cpp/service_context_ref.h" #include "services/service_manager/public/cpp/service_context_ref.h"
#include "third_party/skia/include/core/SkPicture.h"
#include "third_party/skia/include/core/SkRefCnt.h"
namespace printing { namespace printing {
...@@ -22,14 +32,137 @@ class PdfCompositorImpl : public mojom::PdfCompositor { ...@@ -22,14 +32,137 @@ class PdfCompositorImpl : public mojom::PdfCompositor {
std::unique_ptr<service_manager::ServiceContextRef> service_ref); std::unique_ptr<service_manager::ServiceContextRef> service_ref);
~PdfCompositorImpl() override; ~PdfCompositorImpl() override;
void CompositePdf( // mojom::PdfCompositor
mojo::ScopedSharedBufferHandle sk_handle, void AddSubframeContent(
mojom::PdfCompositor::CompositePdfCallback callback) override; uint64_t frame_guid,
mojo::ScopedSharedBufferHandle serialized_content,
const ContentToFrameMap& subframe_content_map) override;
void CompositePageToPdf(
uint64_t frame_guid,
uint32_t page_num,
mojo::ScopedSharedBufferHandle serialized_content,
const ContentToFrameMap& subframe_content_map,
mojom::PdfCompositor::CompositePageToPdfCallback callback) override;
void CompositeDocumentToPdf(
uint64_t frame_guid,
mojo::ScopedSharedBufferHandle serialized_content,
const ContentToFrameMap& subframe_content_map,
mojom::PdfCompositor::CompositeDocumentToPdfCallback callback) override;
private: private:
FRIEND_TEST_ALL_PREFIXES(PdfCompositorImplTest, IsReadyToComposite);
FRIEND_TEST_ALL_PREFIXES(PdfCompositorImplTest, MultiLayerDependency);
FRIEND_TEST_ALL_PREFIXES(PdfCompositorImplTest, DependencyLoop);
// The map needed during content deserialization. It stores the mapping
// between content id and its actual content.
using DeserializationContext = base::flat_map<uint32_t, sk_sp<SkPicture>>;
// This is the uniform underlying type for both
// mojom::PdfCompositor::CompositePageToPdfCallback and
// mojom::PdfCompositor::CompositeDocumentToPdfCallback.
using CompositeToPdfCallback =
base::OnceCallback<void(PdfCompositor::Status,
mojo::ScopedSharedBufferHandle)>;
// Other than content, it also stores the mapping for all the subframe content
// to the frames they refer to, and status during frame composition.
struct FrameInfo {
FrameInfo(std::unique_ptr<base::SharedMemory> content,
const ContentToFrameMap& map);
~FrameInfo();
// Serialized SkPicture content of this frame.
std::unique_ptr<base::SharedMemory> serialized_content;
// Frame content after composition with subframe content.
sk_sp<SkPicture> content;
// Subframe content id and its corresponding frame guid.
ContentToFrameMap subframe_content_map;
// The following fields are used for storing composition status.
// Set to true when this frame's |serialized_content| is composed with
// subframe content and the final result is stored in |content|.
bool composited = false;
};
// Stores the mapping between frame's global unique ids and their
// corresponding frame information.
using FrameMap = base::flat_map<uint64_t, std::unique_ptr<FrameInfo>>;
// Stores the page or document's request information.
struct RequestInfo {
RequestInfo(uint64_t frame_guid,
base::Optional<uint32_t> page_num,
const base::flat_set<uint64_t>& pending_subframes,
CompositeToPdfCallback callback);
~RequestInfo();
uint64_t frame_guid;
base::Optional<uint32_t> page_number;
// All pending frame ids whose content is not available but needed
// for composition.
base::flat_set<uint64_t> pending_subframes;
CompositeToPdfCallback callback;
};
// Check whether the frame with a list of subframe content is ready to
// composite. If not, return all unavailable frames' ids in
// |pending_subframes|.
bool IsReadyToComposite(uint64_t frame_guid,
const ContentToFrameMap& subframe_content_map,
base::flat_set<uint64_t>* pending_subframes);
// Recursive check all the frames |frame_guid| depends on and put those
// not ready in |pending_subframes|.
void CheckFramesForReadiness(uint64_t frame_guid,
const ContentToFrameMap& subframe_content_map,
base::flat_set<uint64_t>* pending_subframes,
base::flat_set<uint64_t>* visited);
// The internal implementation for handling page and documentation composition
// requests.
void HandleCompositionRequest(
uint64_t frame_guid,
base::Optional<uint32_t> page_num,
mojo::ScopedSharedBufferHandle serialized_content,
const ContentToFrameMap& subframe_content_ids,
CompositeToPdfCallback callback);
// The core function for content composition and conversion to a pdf file.
mojom::PdfCompositor::Status CompositeToPdf(
uint64_t frame_guid,
base::Optional<uint32_t> page_num,
std::unique_ptr<base::SharedMemory> shared_mem,
const ContentToFrameMap& subframe_content_map,
mojo::ScopedSharedBufferHandle* handle);
// Composite the content of a subframe.
sk_sp<SkPicture> CompositeSubframe(uint64_t frame_guid);
bool CheckForPendingFrame(uint64_t frame_guid,
base::flat_set<uint64_t> visited_frames);
DeserializationContext GetDeserializationContext(
const ContentToFrameMap& subframe_content_map);
void FullfillRequest(uint64_t frame_guid,
base::Optional<uint32_t> page_num,
std::unique_ptr<base::SharedMemory> serialized_content,
const ContentToFrameMap& subframe_content_map,
CompositeToPdfCallback callback);
const std::unique_ptr<service_manager::ServiceContextRef> service_ref_; const std::unique_ptr<service_manager::ServiceContextRef> service_ref_;
const std::string creator_; const std::string creator_;
// Keep track of all frames' information indexed by frame id.
FrameMap frame_info_map_;
std::unique_ptr<RequestInfo> request_;
DISALLOW_COPY_AND_ASSIGN(PdfCompositorImpl); DISALLOW_COPY_AND_ASSIGN(PdfCompositorImpl);
}; };
......
// Copyright 2017 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 <memory>
#include "base/callback.h"
#include "base/run_loop.h"
#include "base/test/scoped_task_environment.h"
#include "components/printing/service/pdf_compositor_impl.h"
#include "components/printing/service/public/cpp/pdf_service_mojo_types.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace printing {
class PdfCompositorImplTest : public testing::Test {
public:
PdfCompositorImplTest()
: task_environment_(
base::test::ScopedTaskEnvironment::MainThreadType::IO),
run_loop_(std::make_unique<base::RunLoop>()),
is_ready_(false) {}
void OnIsReadyToCompositeCallback(bool is_ready) {
is_ready_ = is_ready;
run_loop_->Quit();
}
bool ResultFromCallback() {
run_loop_->Run();
run_loop_ = std::make_unique<base::RunLoop>();
return is_ready_;
}
protected:
base::test::ScopedTaskEnvironment task_environment_;
std::unique_ptr<base::RunLoop> run_loop_;
bool is_ready_;
};
TEST_F(PdfCompositorImplTest, IsReadyToComposite) {
PdfCompositorImpl impl("unittest", nullptr);
// Frame 2 and 3 are painted.
impl.AddSubframeContent(2u, mojo::SharedBufferHandle::Create(10),
ContentToFrameMap());
impl.AddSubframeContent(3u, mojo::SharedBufferHandle::Create(10),
ContentToFrameMap());
// Frame 1 contains content 3 which corresponds to frame 2.
// Frame 1 should be ready as frame 2 is ready.
ContentToFrameMap subframe_content_map;
subframe_content_map[3u] = 2u;
base::flat_set<uint64_t> pending_subframes;
bool is_ready = impl.IsReadyToComposite(1u, std::move(subframe_content_map),
&pending_subframes);
EXPECT_TRUE(is_ready);
EXPECT_TRUE(pending_subframes.empty());
// If another page of frame 1 needs content 2 which corresponds to frame 3.
// This page is ready since frame 3 was painted also.
subframe_content_map.clear();
subframe_content_map[2u] = 3u;
is_ready = impl.IsReadyToComposite(1u, std::move(subframe_content_map),
&pending_subframes);
EXPECT_TRUE(is_ready);
EXPECT_TRUE(pending_subframes.empty());
// Frame 1 with content 1, 2 and 3 should not be ready since content 1's
// content in frame 4 is not painted yet.
subframe_content_map.clear();
subframe_content_map[1u] = 4u;
subframe_content_map[2u] = 3u;
subframe_content_map[3u] = 2u;
is_ready = impl.IsReadyToComposite(1u, std::move(subframe_content_map),
&pending_subframes);
EXPECT_FALSE(is_ready);
ASSERT_EQ(pending_subframes.size(), 1u);
EXPECT_EQ(*pending_subframes.begin(), 4u);
// Add content of frame 4. Now it is ready for composition.
subframe_content_map.clear();
subframe_content_map[1u] = 4u;
subframe_content_map[2u] = 3u;
subframe_content_map[3u] = 2u;
impl.AddSubframeContent(4u, mojo::SharedBufferHandle::Create(10),
ContentToFrameMap());
is_ready = impl.IsReadyToComposite(1u, std::move(subframe_content_map),
&pending_subframes);
EXPECT_TRUE(is_ready);
EXPECT_TRUE(pending_subframes.empty());
}
TEST_F(PdfCompositorImplTest, MultiLayerDependency) {
PdfCompositorImpl impl("unittest", nullptr);
// Frame 3 has content 1 which refers to subframe 1.
ContentToFrameMap subframe_content_map;
subframe_content_map[1u] = 1u;
impl.AddSubframeContent(3u, mojo::SharedBufferHandle::Create(10),
std::move(subframe_content_map));
// Frame 5 has content 3 which refers to subframe 3.
// Although frame 3's content is added, its subframe 1's content is not added.
// So frame 5 is not ready.
subframe_content_map.clear();
subframe_content_map[3u] = 3u;
base::flat_set<uint64_t> pending_subframes;
bool is_ready = impl.IsReadyToComposite(5u, std::move(subframe_content_map),
&pending_subframes);
EXPECT_FALSE(is_ready);
ASSERT_EQ(pending_subframes.size(), 1u);
EXPECT_EQ(*pending_subframes.begin(), 1u);
// Frame 6 is not ready either since it needs frame 5 to be ready.
subframe_content_map.clear();
subframe_content_map[1u] = 5u;
is_ready = impl.IsReadyToComposite(6u, std::move(subframe_content_map),
&pending_subframes);
EXPECT_FALSE(is_ready);
ASSERT_EQ(pending_subframes.size(), 1u);
EXPECT_EQ(*pending_subframes.begin(), 5u);
// When frame 1's content is added, frame 5 is ready.
impl.AddSubframeContent(1u, mojo::SharedBufferHandle::Create(10),
ContentToFrameMap());
subframe_content_map.clear();
subframe_content_map[3u] = 3u;
is_ready = impl.IsReadyToComposite(5u, std::move(subframe_content_map),
&pending_subframes);
EXPECT_TRUE(is_ready);
EXPECT_TRUE(pending_subframes.empty());
// Add frame 5's content.
subframe_content_map.clear();
subframe_content_map[3u] = 3u;
impl.AddSubframeContent(5u, mojo::SharedBufferHandle::Create(10),
std::move(subframe_content_map));
// Frame 6 is ready too.
subframe_content_map.clear();
subframe_content_map[1u] = 5u;
is_ready = impl.IsReadyToComposite(6u, std::move(subframe_content_map),
&pending_subframes);
EXPECT_TRUE(is_ready);
EXPECT_TRUE(pending_subframes.empty());
}
TEST_F(PdfCompositorImplTest, DependencyLoop) {
PdfCompositorImpl impl("unittest", nullptr);
// Frame 3 has content 1, which refers to frame 1.
// Frame 1 has content 3, which refers to frame 3.
ContentToFrameMap subframe_content_map;
subframe_content_map[3u] = 3u;
impl.AddSubframeContent(1u, mojo::SharedBufferHandle::Create(10),
std::move(subframe_content_map));
subframe_content_map.clear();
subframe_content_map[1u] = 1u;
impl.AddSubframeContent(3u, mojo::SharedBufferHandle::Create(10),
std::move(subframe_content_map));
// Both frame 1 and 3 are painted, frame 5 should be ready.
base::flat_set<uint64_t> pending_subframes;
subframe_content_map.clear();
subframe_content_map[1u] = 3u;
bool is_ready = impl.IsReadyToComposite(5u, std::move(subframe_content_map),
&pending_subframes);
EXPECT_TRUE(is_ready);
EXPECT_TRUE(pending_subframes.empty());
// Frame 6 has content 7, which refers to frame 7.
subframe_content_map.clear();
subframe_content_map[7u] = 7u;
impl.AddSubframeContent(6, mojo::SharedBufferHandle::Create(10),
std::move(subframe_content_map));
// Frame 7 should be ready since frame 6's own content is added and it only
// depends on frame 7.
subframe_content_map.clear();
subframe_content_map[6u] = 6u;
is_ready = impl.IsReadyToComposite(7u, std::move(subframe_content_map),
&pending_subframes);
EXPECT_TRUE(is_ready);
EXPECT_TRUE(pending_subframes.empty());
}
} // namespace printing
...@@ -14,6 +14,7 @@ ...@@ -14,6 +14,7 @@
#include "cc/paint/paint_flags.h" #include "cc/paint/paint_flags.h"
#include "cc/paint/skia_paint_canvas.h" #include "cc/paint/skia_paint_canvas.h"
#include "components/printing/service/pdf_compositor_service.h" #include "components/printing/service/pdf_compositor_service.h"
#include "components/printing/service/public/cpp/pdf_service_mojo_types.h"
#include "components/printing/service/public/interfaces/pdf_compositor.mojom.h" #include "components/printing/service/public/interfaces/pdf_compositor.mojom.h"
#include "mojo/public/cpp/bindings/binding_set.h" #include "mojo/public/cpp/bindings/binding_set.h"
#include "mojo/public/cpp/system/platform_handle.h" #include "mojo/public/cpp/system/platform_handle.h"
...@@ -86,14 +87,14 @@ class PdfCompositorServiceTest : public service_manager::test::ServiceTest { ...@@ -86,14 +87,14 @@ class PdfCompositorServiceTest : public service_manager::test::ServiceTest {
PdfCompositorServiceTest() : ServiceTest("pdf_compositor_service_unittest") {} PdfCompositorServiceTest() : ServiceTest("pdf_compositor_service_unittest") {}
~PdfCompositorServiceTest() override {} ~PdfCompositorServiceTest() override {}
MOCK_METHOD1(CallbackOnSuccess, void(mojo::SharedBufferHandle)); MOCK_METHOD1(CallbackOnCompositeSuccess, void(mojo::SharedBufferHandle));
MOCK_METHOD1(CallbackOnError, void(mojom::PdfCompositor::Status)); MOCK_METHOD1(CallbackOnCompositeStatus, void(mojom::PdfCompositor::Status));
void OnCallback(mojom::PdfCompositor::Status status, void OnCompositeToPdfCallback(mojom::PdfCompositor::Status status,
mojo::ScopedSharedBufferHandle handle) { mojo::ScopedSharedBufferHandle handle) {
if (status == mojom::PdfCompositor::Status::SUCCESS) if (status == mojom::PdfCompositor::Status::SUCCESS)
CallbackOnSuccess(handle.get()); CallbackOnCompositeSuccess(handle.get());
else else
CallbackOnError(status); CallbackOnCompositeStatus(status);
run_loop_->Quit(); run_loop_->Quit();
} }
...@@ -123,7 +124,7 @@ class PdfCompositorServiceTest : public service_manager::test::ServiceTest { ...@@ -123,7 +124,7 @@ class PdfCompositorServiceTest : public service_manager::test::ServiceTest {
return std::make_unique<PdfServiceTestClient>(this); return std::make_unique<PdfServiceTestClient>(this);
} }
base::SharedMemoryHandle CreateMSKPInSharedMemory() { mojo::ScopedSharedBufferHandle CreateMSKP() {
SkDynamicMemoryWStream stream; SkDynamicMemoryWStream stream;
sk_sp<SkDocument> doc = SkMakeMultiPictureDocument(&stream); sk_sp<SkDocument> doc = SkMakeMultiPictureDocument(&stream);
cc::SkiaPaintCanvas canvas(doc->beginPage(800, 600)); cc::SkiaPaintCanvas canvas(doc->beginPage(800, 600));
...@@ -144,22 +145,23 @@ class PdfCompositorServiceTest : public service_manager::test::ServiceTest { ...@@ -144,22 +145,23 @@ class PdfCompositorServiceTest : public service_manager::test::ServiceTest {
base::SharedMemory shared_memory; base::SharedMemory shared_memory;
if (shared_memory.Create(options) && shared_memory.Map(len)) { if (shared_memory.Create(options) && shared_memory.Map(len)) {
stream.copyTo(shared_memory.memory()); stream.copyTo(shared_memory.memory());
return shared_memory.GetReadOnlyHandle(); auto handle = shared_memory.GetReadOnlyHandle();
return mojo::WrapSharedMemoryHandle(
handle, handle.GetSize(),
mojo::UnwrappedSharedMemoryHandleProtection::kReadOnly);
} }
return base::SharedMemoryHandle(); return mojo::ScopedSharedBufferHandle();
} }
void CallCompositorWithSuccess(mojom::PdfCompositorPtr ptr) { void CallCompositorWithSuccess(mojom::PdfCompositorPtr ptr) {
auto handle = CreateMSKPInSharedMemory(); static constexpr uint64_t kFrameGuid = 1234;
ASSERT_TRUE(handle.IsValid()); auto handle = CreateMSKP();
mojo::ScopedSharedBufferHandle buffer_handle = mojo::WrapSharedMemoryHandle( ASSERT_TRUE(handle->is_valid());
handle, handle.GetSize(), EXPECT_CALL(*this, CallbackOnCompositeSuccess(testing::_)).Times(1);
mojo::UnwrappedSharedMemoryHandleProtection::kReadOnly); ptr->CompositeDocumentToPdf(
ASSERT_TRUE(buffer_handle->is_valid()); kFrameGuid, std::move(handle), ContentToFrameMap(),
EXPECT_CALL(*this, CallbackOnSuccess(testing::_)).Times(1); base::BindOnce(&PdfCompositorServiceTest::OnCompositeToPdfCallback,
ptr->CompositePdf(std::move(buffer_handle), base::Unretained(this)));
base::BindOnce(&PdfCompositorServiceTest::OnCallback,
base::Unretained(this)));
run_loop_->Run(); run_loop_->Run();
} }
...@@ -171,23 +173,25 @@ class PdfCompositorServiceTest : public service_manager::test::ServiceTest { ...@@ -171,23 +173,25 @@ class PdfCompositorServiceTest : public service_manager::test::ServiceTest {
DISALLOW_COPY_AND_ASSIGN(PdfCompositorServiceTest); DISALLOW_COPY_AND_ASSIGN(PdfCompositorServiceTest);
}; };
// Test callback is called on error conditions in service. // Test callback function is called on error conditions in service.
TEST_F(PdfCompositorServiceTest, InvokeCallbackOnContentError) { TEST_F(PdfCompositorServiceTest, InvokeCallbackOnContentError) {
EXPECT_CALL(*this, CallbackOnError( EXPECT_CALL(*this, CallbackOnCompositeStatus(
mojom::PdfCompositor::Status::CONTENT_FORMAT_ERROR)) mojom::PdfCompositor::Status::CONTENT_FORMAT_ERROR))
.Times(1); .Times(1);
compositor_->CompositePdf( compositor_->CompositeDocumentToPdf(
mojo::SharedBufferHandle::Create(10), 5u, mojo::SharedBufferHandle::Create(10), ContentToFrameMap(),
base::BindOnce(&PdfCompositorServiceTest::OnCallback, base::BindOnce(&PdfCompositorServiceTest::OnCompositeToPdfCallback,
base::Unretained(this))); base::Unretained(this)));
run_loop_->Run(); run_loop_->Run();
} }
// Test callback function is called upon success.
TEST_F(PdfCompositorServiceTest, InvokeCallbackOnSuccess) { TEST_F(PdfCompositorServiceTest, InvokeCallbackOnSuccess) {
CallCompositorWithSuccess(std::move(compositor_)); CallCompositorWithSuccess(std::move(compositor_));
} }
TEST_F(PdfCompositorServiceTest, ServiceInstances) { // Test coexistence of multiple service instances.
TEST_F(PdfCompositorServiceTest, MultipleServiceInstances) {
// One service can bind multiple interfaces. // One service can bind multiple interfaces.
mojom::PdfCompositorPtr another_compositor; mojom::PdfCompositorPtr another_compositor;
ASSERT_FALSE(another_compositor); ASSERT_FALSE(another_compositor);
...@@ -200,4 +204,48 @@ TEST_F(PdfCompositorServiceTest, ServiceInstances) { ...@@ -200,4 +204,48 @@ TEST_F(PdfCompositorServiceTest, ServiceInstances) {
CallCompositorWithSuccess(std::move(another_compositor)); CallCompositorWithSuccess(std::move(another_compositor));
} }
// Test data structures and content of multiple service instances
// are independent from each other.
TEST_F(PdfCompositorServiceTest, IndependentServiceInstances) {
// Create a new connection 2.
mojom::PdfCompositorPtr compositor2;
ASSERT_FALSE(compositor2);
connector()->BindInterface(mojom::kServiceName, &compositor2);
ASSERT_TRUE(compositor2);
// In original connection, add frame 4 with content 2 referring
// to subframe 1.
compositor_->AddSubframeContent(1u, CreateMSKP(), ContentToFrameMap());
// Original connection can use this subframe 1.
EXPECT_CALL(*this, CallbackOnCompositeSuccess(testing::_)).Times(1);
ContentToFrameMap subframe_content_map;
subframe_content_map[2u] = 1u;
compositor_->CompositeDocumentToPdf(
4u, CreateMSKP(), std::move(subframe_content_map),
base::BindOnce(&PdfCompositorServiceTest::OnCompositeToPdfCallback,
base::Unretained(this)));
run_loop_->Run();
testing::Mock::VerifyAndClearExpectations(this);
// Connection 2 doesn't know about subframe 1.
subframe_content_map.clear();
subframe_content_map[2u] = 1u;
EXPECT_CALL(*this, CallbackOnCompositeSuccess(testing::_)).Times(0);
compositor2->CompositeDocumentToPdf(
4u, CreateMSKP(), std::move(subframe_content_map),
base::BindOnce(&PdfCompositorServiceTest::OnCompositeToPdfCallback,
base::Unretained(this)));
testing::Mock::VerifyAndClearExpectations(this);
// Add info about subframe 1 to connection 2 so it can use it.
EXPECT_CALL(*this, CallbackOnCompositeSuccess(testing::_)).Times(1);
// Add subframe 1's content.
// Now all content needed for previous request is ready.
compositor2->AddSubframeContent(1u, CreateMSKP(), ContentToFrameMap());
run_loop_ = std::make_unique<base::RunLoop>();
run_loop_->Run();
}
} // namespace printing } // namespace printing
...@@ -4,18 +4,6 @@ ...@@ -4,18 +4,6 @@
import("//mojo/public/tools/bindings/mojom.gni") import("//mojo/public/tools/bindings/mojom.gni")
source_set("client") {
sources = [
"pdf_compositor_client.cc",
"pdf_compositor_client.h",
]
public_deps = [
"//components/printing/service/public/interfaces",
"//services/service_manager/public/cpp",
]
}
source_set("factory") { source_set("factory") {
sources = [ sources = [
"pdf_compositor_service_factory.cc", "pdf_compositor_service_factory.cc",
......
// Copyright 2017 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/printing/service/public/cpp/pdf_compositor_client.h"
#include <utility>
#include "mojo/public/cpp/system/platform_handle.h"
namespace printing {
namespace {
// Helper callback which owns an PdfCompositorPtr until invoked. This keeps the
// PdfCompositor pipe open just long enough to dispatch a reply, at which point
// the reply is forwarded to the wrapped |callback|.
void OnCompositePdf(
printing::mojom::PdfCompositorPtr compositor,
printing::mojom::PdfCompositor::CompositePdfCallback callback,
scoped_refptr<base::SequencedTaskRunner> task_runner,
mojom::PdfCompositor::Status status,
mojo::ScopedSharedBufferHandle pdf_handle) {
task_runner->PostTask(FROM_HERE, base::BindOnce(std::move(callback), status,
base::Passed(&pdf_handle)));
}
} // namespace
PdfCompositorClient::PdfCompositorClient() : compositor_(nullptr) {}
PdfCompositorClient::~PdfCompositorClient() {}
void PdfCompositorClient::Connect(service_manager::Connector* connector) {
DCHECK(!compositor_.is_bound());
connector->BindInterface(mojom::kServiceName, &compositor_);
}
void PdfCompositorClient::Composite(
service_manager::Connector* connector,
base::SharedMemoryHandle handle,
size_t data_size,
mojom::PdfCompositor::CompositePdfCallback callback,
scoped_refptr<base::SequencedTaskRunner> callback_task_runner) {
DCHECK(data_size);
if (!compositor_)
Connect(connector);
mojo::ScopedSharedBufferHandle buffer_handle = mojo::WrapSharedMemoryHandle(
handle, data_size,
mojo::UnwrappedSharedMemoryHandleProtection::kReadOnly);
compositor_->CompositePdf(
std::move(buffer_handle),
base::BindOnce(&OnCompositePdf, base::Passed(&compositor_),
std::move(callback), callback_task_runner));
}
} // namespace printing
// Copyright 2017 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_PRINTING_SERVICE_PUBLIC_CPP_PDF_COMPOSITOR_CLIENT_H_
#define COMPONENTS_PRINTING_SERVICE_PUBLIC_CPP_PDF_COMPOSITOR_CLIENT_H_
#include "base/memory/shared_memory_handle.h"
#include "components/printing/service/public/interfaces/pdf_compositor.mojom.h"
#include "services/service_manager/public/cpp/connector.h"
namespace printing {
// Helper class to composite a pdf via the pdf_compositor service.
class PdfCompositorClient {
public:
PdfCompositorClient();
~PdfCompositorClient();
// Composite the final picture and convert into a PDF file.
//
// NOTE: |handle| must be a READ-ONLY base::SharedMemoryHandle, i.e. one
// acquired by base::SharedMemory::GetReadOnlyHandle().
void Composite(service_manager::Connector* connector,
base::SharedMemoryHandle handle,
size_t data_size,
mojom::PdfCompositor::CompositePdfCallback callback,
scoped_refptr<base::SequencedTaskRunner> callback_task_runner);
private:
// Connect to the service.
void Connect(service_manager::Connector* connector);
mojom::PdfCompositorPtr compositor_;
DISALLOW_COPY_AND_ASSIGN(PdfCompositorClient);
};
} // namespace printing
#endif // COMPONENTS_PRINTING_SERVICE_PUBLIC_CPP_PDF_COMPOSITOR_CLIENT_H_
// Copyright 2018 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_PRINTING_SERVICE_PUBLIC_CPP_PDF_SERVICE_MOJO_TYPES_H_
#define COMPONENTS_PRINTING_SERVICE_PUBLIC_CPP_PDF_SERVICE_MOJO_TYPES_H_
#include <unordered_map>
namespace printing {
// Create an alias for map<uint32, uint64> type.
using ContentToFrameMap = std::unordered_map<uint32_t, uint64_t>;
} // namespace printing
#endif // COMPONENTS_PRINTING_SERVICE_PUBLIC_CPP_PDF_SERVICE_MOJO_TYPES_H_
...@@ -6,9 +6,8 @@ module printing.mojom; ...@@ -6,9 +6,8 @@ module printing.mojom;
const string kServiceName = "pdf_compositor"; const string kServiceName = "pdf_compositor";
// TODO(weili): Add support for printing frames from different processes.
interface PdfCompositor { interface PdfCompositor {
// The status of CompositePdf execution. // The status of composition and conversion execution.
enum Status { enum Status {
SUCCESS, SUCCESS,
HANDLE_MAP_ERROR, HANDLE_MAP_ERROR,
...@@ -16,10 +15,31 @@ interface PdfCompositor { ...@@ -16,10 +15,31 @@ interface PdfCompositor {
COMPOSTING_FAILURE, COMPOSTING_FAILURE,
}; };
// Currently directly convert passed in page data to a PDF file. // Add the content of a subframe for composition.
// |sk_handle| points to a buffer of a Skia MultiPictureDocument. // |frame_guid| is this subframe's global unique id.
// |status| records the function execution status. // |serialized_content| points to a buffer of a serialized Skia picture which
// |pdf_handle| points to the generated PDF file buffer upon success. // has the painted content of this frame.
CompositePdf(handle<shared_buffer> sk_handle) // |subframe_content_map| records content id and its corresponding frame's
// global unique id.
AddSubframeContent(uint64 frame_guid, handle<shared_buffer> serialized_content,
map<uint32, uint64> subframe_content_info);
// Request to composite a page and convert it into a PDF file.
// |frame_guid| is the global unique id of the frame to be composited.
// |page_num| is zero-based sequence number of page.
// |sk_handle| points to a buffer of a Skia MultiPictureDocument which has
// the drawing content of this frame or a page of this frame.
// |subframe_content_map| records content id and its corresponding frame's
// global unique id.
CompositePageToPdf(uint64 frame_guid, uint32 page_num,
handle<shared_buffer> sk_handle,
map<uint32, uint64> subframe_content_info)
=> (Status status, handle<shared_buffer>? pdf_handle);
// Request to composite the entire document and convert it into a PDF file.
// All the arguments carry the same meaning as CompositePageToPdf() above,
// except this call doesn't have |page_num|.
CompositeDocumentToPdf(uint64 frame_guid, handle<shared_buffer> sk_handle,
map<uint32, uint64> subframe_content_info)
=> (Status status, handle<shared_buffer>? pdf_handle); => (Status status, handle<shared_buffer>? pdf_handle);
}; };
...@@ -42,4 +42,8 @@ sk_sp<SkDocument> MakePdfDocument(const std::string& creator, ...@@ -42,4 +42,8 @@ sk_sp<SkDocument> MakePdfDocument(const std::string& creator,
return SkDocument::MakePDF(stream, metadata); return SkDocument::MakePDF(stream, metadata);
} }
uint64_t GenFrameGuid(int process_id, int frame_id) {
return static_cast<uint64_t>(process_id) << 32 | frame_id;
}
} // namespace printing } // namespace printing
...@@ -24,6 +24,8 @@ enum class SkiaDocumentType { ...@@ -24,6 +24,8 @@ enum class SkiaDocumentType {
sk_sp<SkDocument> MakePdfDocument(const std::string& creator, sk_sp<SkDocument> MakePdfDocument(const std::string& creator,
SkWStream* stream); SkWStream* stream);
uint64_t GenFrameGuid(int process_id, int frame_id);
} // namespace printing } // namespace printing
#endif // PRINTING_COMMON_PDF_METAFILE_UTILS_H_ #endif // PRINTING_COMMON_PDF_METAFILE_UTILS_H_
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