Skip to content

Commit

Permalink
Let users to print a webpage into pdf through PrintToPDF devtools com…
Browse files Browse the repository at this point in the history
…mand

under headless chrome.

This patch includes:
1) adding a new print manager, i.e., HeadlessPrintManager to handle all the
   printing related IPCs for headless.
2) plumbing through the pdf data from HeadlessPrintManager to
   HeadlessDevToolsManagerDelegate to handle the PrintToPDF command.
3) adding a new option to PrintWebViewHelper on Mac to return all the printed
   pages in the first PrintHostMsg_DidPrintPage message as a single file.
   This makes it conform to Linux and Windows, which simplify the logic in
   HeadlessPrintManager.

This patch guarantees that HeadlessPrintManager is the only IPC entry point.
In the future, if there are requirements to support the same devtools command
in regular chrome, we may consider a framework to ensure multiple print managers
to work properly under a content embedder.

BUG=603559

Review-Url: https://codereview.chromium.org/2780433002
Cr-Commit-Position: refs/heads/master@{#463105}
  • Loading branch information
jzfeng authored and Commit bot committed Apr 8, 2017
1 parent 56d5720 commit 0cbec8b
Show file tree
Hide file tree
Showing 24 changed files with 685 additions and 78 deletions.
1 change: 0 additions & 1 deletion build/args/headless.gn
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ icu_use_data_file = false
# to simplify deployment.
v8_use_external_startup_data = false

enable_basic_printing = false
enable_nacl = false
enable_print_preview = false
enable_remoting = false
Expand Down
6 changes: 6 additions & 0 deletions components/printing/renderer/print_web_view_helper.cc
Original file line number Diff line number Diff line change
Expand Up @@ -894,6 +894,12 @@ bool PrintWebViewHelper::Delegate::IsScriptedPrintEnabled() {
return true;
}

#if defined(OS_MACOSX)
bool PrintWebViewHelper::Delegate::UseSingleMetafile() {
return false;
}
#endif

PrintWebViewHelper::PrintWebViewHelper(content::RenderFrame* render_frame,
std::unique_ptr<Delegate> delegate)
: content::RenderFrameObserver(render_frame),
Expand Down
14 changes: 12 additions & 2 deletions components/printing/renderer/print_web_view_helper.h
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,15 @@ class PrintWebViewHelper
// Returns true if printing is overridden and the default behavior should be
// skipped for |frame|.
virtual bool OverridePrint(blink::WebLocalFrame* frame) = 0;

#if defined(OS_MACOSX)
// If true, all the printed pages are returned in the first
// PrintHostMsg_DidPrintPage metafile, like on Linux and Windows.
// NOTE: PrintHostMsg_DidPrintPage messages for all pages contain the same
// page and content area, which may lead to bug when these two parameters
// are different per page.
virtual bool UseSingleMetafile();
#endif
};

PrintWebViewHelper(content::RenderFrame* render_frame,
Expand Down Expand Up @@ -283,8 +292,9 @@ class PrintWebViewHelper

// Prints the page listed in |params|.
#if defined(OS_MACOSX)
void PrintPageInternal(const PrintMsg_PrintPage_Params& params,
blink::WebLocalFrame* frame);
void PrintPagesInternal(const PrintMsg_PrintPage_Params& params,
blink::WebLocalFrame* frame,
const std::vector<int>& printed_pages);
#else
void PrintPageInternal(const PrintMsg_PrintPage_Params& params,
blink::WebLocalFrame* frame,
Expand Down
29 changes: 19 additions & 10 deletions components/printing/renderer/print_web_view_helper_mac.mm
Original file line number Diff line number Diff line change
Expand Up @@ -30,30 +30,35 @@

PrintMsg_PrintPage_Params page_params;
page_params.params = print_params;
for (int page_number : printed_pages) {
page_params.page_number = page_number;
PrintPageInternal(page_params, frame);

if (delegate_->UseSingleMetafile()) {
PrintPagesInternal(page_params, frame, printed_pages);
return true;
}

for (int page_number : printed_pages)
PrintPagesInternal(page_params, frame, std::vector<int>{page_number});
return true;
}
#endif // BUILDFLAG(ENABLE_BASIC_PRINTING)

void PrintWebViewHelper::PrintPageInternal(
void PrintWebViewHelper::PrintPagesInternal(
const PrintMsg_PrintPage_Params& params,
blink::WebLocalFrame* frame) {
blink::WebLocalFrame* frame,
const std::vector<int>& printed_pages) {
PdfMetafileSkia metafile(PDF_SKIA_DOCUMENT_TYPE);
CHECK(metafile.Init());

int page_number = params.page_number;
gfx::Size page_size_in_dpi;
gfx::Rect content_area_in_dpi;
RenderPage(print_pages_params_->params, page_number, frame, false, &metafile,
&page_size_in_dpi, &content_area_in_dpi);
for (int page_number : printed_pages) {
RenderPage(params.params, page_number, frame, false, &metafile,
&page_size_in_dpi, &content_area_in_dpi);
}
metafile.FinishDocument();

PrintHostMsg_DidPrintPage_Params page_params;
page_params.data_size = metafile.GetDataSize();
page_params.page_number = page_number;
page_params.document_cookie = params.params.document_cookie;
page_params.page_size = page_size_in_dpi;
page_params.content_area = content_area_in_dpi;
Expand All @@ -65,7 +70,11 @@
page_params.data_size = 0;
}

Send(new PrintHostMsg_DidPrintPage(routing_id(), page_params));
for (int page_number : printed_pages) {
page_params.page_number = page_number;
Send(new PrintHostMsg_DidPrintPage(routing_id(), page_params));
page_params.metafile_data_handle = base::SharedMemoryHandle();
}
}

#if BUILDFLAG(ENABLE_PRINT_PREVIEW)
Expand Down
7 changes: 4 additions & 3 deletions content/public/browser/devtools_manager_delegate.cc
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,10 @@ base::DictionaryValue* DevToolsManagerDelegate::HandleCommand(
return nullptr;
}

bool DevToolsManagerDelegate::HandleAsyncCommand(DevToolsAgentHost* agent_host,
base::DictionaryValue* command,
CommandCallback callback) {
bool DevToolsManagerDelegate::HandleAsyncCommand(
DevToolsAgentHost* agent_host,
base::DictionaryValue* command,
const CommandCallback& callback) {
return false;
}

Expand Down
4 changes: 3 additions & 1 deletion content/public/browser/devtools_manager_delegate.h
Original file line number Diff line number Diff line change
Expand Up @@ -51,9 +51,11 @@ class CONTENT_EXPORT DevToolsManagerDelegate {

using CommandCallback =
base::Callback<void(std::unique_ptr<base::DictionaryValue> response)>;
// Handle async command, feed response to CommandCallback when it is ready.
virtual bool HandleAsyncCommand(DevToolsAgentHost* agent_host,
base::DictionaryValue* command,
CommandCallback callback);
const CommandCallback& callback);

// Should return discovery page HTML that should list available tabs
// and provide attach links.
virtual std::string GetDiscoveryPageHTML();
Expand Down
8 changes: 8 additions & 0 deletions headless/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,8 @@ static_library("headless_lib") {
"lib/browser/headless_permission_manager.h",
"lib/browser/headless_platform_event_source.cc",
"lib/browser/headless_platform_event_source.h",
"lib/browser/headless_print_manager.cc",
"lib/browser/headless_print_manager.h",
"lib/browser/headless_shell_application_mac.mm",
"lib/browser/headless_shell_application_mac.h",
"lib/browser/headless_url_request_context_getter.cc",
Expand All @@ -228,6 +230,10 @@ static_library("headless_lib") {
"lib/headless_content_client.h",
"lib/headless_content_main_delegate.cc",
"lib/headless_content_main_delegate.h",
"lib/renderer/headless_content_renderer_client.cc",
"lib/renderer/headless_content_renderer_client.h",
"lib/renderer/headless_print_web_view_helper_delegate.cc",
"lib/renderer/headless_print_web_view_helper_delegate.h",
"public/headless_browser.cc",
"public/headless_browser.h",
"public/headless_browser_context.h",
Expand Down Expand Up @@ -292,6 +298,8 @@ static_library("headless_lib") {
":version_header",
"//base",
"//components/crash/content/browser",
"//components/printing/browser",
"//components/printing/renderer",
"//components/security_state/content",
"//components/security_state/core",
"//content/public/app:both",
Expand Down
117 changes: 78 additions & 39 deletions headless/app/headless_shell.cc
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#include <memory>
#include <sstream>
#include <string>
#include <utility>

#include "base/base64.h"
#include "base/base_switches.h"
Expand Down Expand Up @@ -32,6 +33,8 @@ namespace {
const char kDevToolsHttpServerAddress[] = "127.0.0.1";
// Default file name for screenshot. Can be overriden by "--screenshot" switch.
const char kDefaultScreenshotFileName[] = "screenshot.png";
// Default file name for pdf. Can be overriden by "--print-to-pdf" switch.
const char kDefaultPDFFileName[] = "output.pdf";

bool ParseWindowSize(std::string window_size, gfx::Size* parsed_window_size) {
int width, height = 0;
Expand Down Expand Up @@ -255,6 +258,9 @@ void HeadlessShell::OnPageReady() {
} else if (base::CommandLine::ForCurrentProcess()->HasSwitch(
switches::kScreenshot)) {
CaptureScreenshot();
} else if (base::CommandLine::ForCurrentProcess()->HasSwitch(
switches::kPrintToPDF)) {
PrintToPDF();
} else {
Shutdown();
}
Expand Down Expand Up @@ -319,73 +325,101 @@ void HeadlessShell::CaptureScreenshot() {

void HeadlessShell::OnScreenshotCaptured(
std::unique_ptr<page::CaptureScreenshotResult> result) {
if (!result) {
LOG(ERROR) << "Capture screenshot failed";
Shutdown();
return;
}
WriteFile(switches::kScreenshot, kDefaultScreenshotFileName,
result->GetData());
}

void HeadlessShell::PrintToPDF() {
devtools_client_->GetPage()->GetExperimental()->PrintToPDF(
page::PrintToPDFParams::Builder().Build(),
base::Bind(&HeadlessShell::OnPDFCreated, weak_factory_.GetWeakPtr()));
}

void HeadlessShell::OnPDFCreated(
std::unique_ptr<page::PrintToPDFResult> result) {
if (!result) {
LOG(ERROR) << "Print to PDF failed";
Shutdown();
return;
}
WriteFile(switches::kPrintToPDF, kDefaultPDFFileName, result->GetData());
}

void HeadlessShell::WriteFile(const std::string& file_path_switch,
const std::string& default_file_name,
const std::string& base64_data) {
base::FilePath file_name =
base::CommandLine::ForCurrentProcess()->GetSwitchValuePath(
switches::kScreenshot);
if (file_name.empty()) {
file_name = base::FilePath().AppendASCII(kDefaultScreenshotFileName);
}
file_path_switch);
if (file_name.empty())
file_name = base::FilePath().AppendASCII(default_file_name);

screenshot_file_proxy_.reset(
new base::FileProxy(browser_->BrowserFileThread().get()));
if (!screenshot_file_proxy_->CreateOrOpen(
file_proxy_.reset(new base::FileProxy(browser_->BrowserFileThread().get()));
if (!file_proxy_->CreateOrOpen(
file_name, base::File::FLAG_CREATE_ALWAYS | base::File::FLAG_WRITE,
base::Bind(&HeadlessShell::OnScreenshotFileOpened,
weak_factory_.GetWeakPtr(),
base::Passed(std::move(result)), file_name))) {
base::Bind(&HeadlessShell::OnFileOpened, weak_factory_.GetWeakPtr(),
base64_data, file_name))) {
// Operation could not be started.
OnScreenshotFileOpened(nullptr, file_name, base::File::FILE_ERROR_FAILED);
OnFileOpened(std::string(), file_name, base::File::FILE_ERROR_FAILED);
}
}

void HeadlessShell::OnScreenshotFileOpened(
std::unique_ptr<page::CaptureScreenshotResult> result,
const base::FilePath file_name,
base::File::Error error_code) {
if (!screenshot_file_proxy_->IsValid()) {
LOG(ERROR) << "Writing screenshot to file " << file_name.value()
void HeadlessShell::OnFileOpened(const std::string& base64_data,
const base::FilePath file_name,
base::File::Error error_code) {
if (!file_proxy_->IsValid()) {
LOG(ERROR) << "Writing to file " << file_name.value()
<< " was unsuccessful, could not open file: "
<< base::File::ErrorToString(error_code);
return;
}

std::string decoded_png;
base::Base64Decode(result->GetData(), &decoded_png);
std::string decoded_data;
if (!base::Base64Decode(base64_data, &decoded_data)) {
LOG(ERROR) << "Failed to decode base64 data";
OnFileWritten(file_name, base64_data.size(), base::File::FILE_ERROR_FAILED,
0);
return;
}

scoped_refptr<net::IOBufferWithSize> buf =
new net::IOBufferWithSize(decoded_png.size());
memcpy(buf->data(), decoded_png.data(), decoded_png.size());
new net::IOBufferWithSize(decoded_data.size());
memcpy(buf->data(), decoded_data.data(), decoded_data.size());

if (!screenshot_file_proxy_->Write(
if (!file_proxy_->Write(
0, buf->data(), buf->size(),
base::Bind(&HeadlessShell::OnScreenshotFileWritten,
weak_factory_.GetWeakPtr(), file_name, buf->size()))) {
base::Bind(&HeadlessShell::OnFileWritten, weak_factory_.GetWeakPtr(),
file_name, buf->size()))) {
// Operation may have completed successfully or failed.
OnScreenshotFileWritten(file_name, buf->size(),
base::File::FILE_ERROR_FAILED, 0);
OnFileWritten(file_name, buf->size(), base::File::FILE_ERROR_FAILED, 0);
}
}

void HeadlessShell::OnScreenshotFileWritten(const base::FilePath file_name,
const int length,
base::File::Error error_code,
int write_result) {
void HeadlessShell::OnFileWritten(const base::FilePath file_name,
const int length,
base::File::Error error_code,
int write_result) {
if (write_result < length) {
// TODO(eseckler): Support recovering from partial writes.
LOG(ERROR) << "Writing screenshot to file " << file_name.value()
<< " was unsuccessful: " << net::ErrorToString(write_result);
LOG(ERROR) << "Writing to file " << file_name.value()
<< " was unsuccessful: "
<< base::File::ErrorToString(error_code);
} else {
LOG(INFO) << "Screenshot written to file " << file_name.value() << "."
<< std::endl;
LOG(INFO) << "Written to file " << file_name.value() << ".";
}
if (!screenshot_file_proxy_->Close(
base::Bind(&HeadlessShell::OnScreenshotFileClosed,
weak_factory_.GetWeakPtr()))) {
if (!file_proxy_->Close(base::Bind(&HeadlessShell::OnFileClosed,
weak_factory_.GetWeakPtr()))) {
// Operation could not be started.
OnScreenshotFileClosed(base::File::FILE_ERROR_FAILED);
OnFileClosed(base::File::FILE_ERROR_FAILED);
}
}

void HeadlessShell::OnScreenshotFileClosed(base::File::Error error_code) {
void HeadlessShell::OnFileClosed(base::File::Error error_code) {
Shutdown();
}

Expand Down Expand Up @@ -417,6 +451,11 @@ bool ValidateCommandLine(const base::CommandLine& command_line) {
<< "when remote debugging is enabled.";
return false;
}
if (command_line.HasSwitch(switches::kPrintToPDF)) {
LOG(ERROR) << "Print to PDF is disabled "
<< "when remote debugging is enabled.";
return false;
}
if (command_line.HasSwitch(switches::kTimeout)) {
LOG(ERROR) << "Navigation timeout is disabled "
<< "when remote debugging is enabled.";
Expand Down
Loading

0 comments on commit 0cbec8b

Please sign in to comment.