| // 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 "headless/lib/browser/headless_print_manager.h" |
| |
| #include <memory> |
| #include <utility> |
| #include <vector> |
| |
| #include "base/memory/ref_counted_memory.h" |
| #include "base/strings/string_number_conversions.h" |
| #include "base/strings/string_piece.h" |
| #include "base/strings/string_split.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "components/printing/browser/print_manager_utils.h" |
| #include "components/printing/common/print_messages.h" |
| #include "content/public/browser/render_view_host.h" |
| #include "printing/print_job_constants.h" |
| #include "printing/units.h" |
| |
| namespace headless { |
| |
| HeadlessPrintSettings::HeadlessPrintSettings() |
| : prefer_css_page_size(false), |
| landscape(false), |
| display_header_footer(false), |
| should_print_backgrounds(false), |
| scale(1), |
| ignore_invalid_page_ranges(false) {} |
| |
| HeadlessPrintManager::HeadlessPrintManager(content::WebContents* web_contents) |
| : PrintManager(web_contents) { |
| Reset(); |
| } |
| |
| HeadlessPrintManager::~HeadlessPrintManager() = default; |
| |
| // static |
| std::string HeadlessPrintManager::PrintResultToString(PrintResult result) { |
| switch (result) { |
| case PRINT_SUCCESS: |
| return std::string(); // no error message |
| case PRINTING_FAILED: |
| return "Printing failed"; |
| case INVALID_PRINTER_SETTINGS: |
| return "Show invalid printer settings error"; |
| case INVALID_MEMORY_HANDLE: |
| return "Invalid memory handle"; |
| case METAFILE_MAP_ERROR: |
| return "Map to shared memory error"; |
| case METAFILE_INVALID_HEADER: |
| return "Invalid metafile header"; |
| case METAFILE_GET_DATA_ERROR: |
| return "Get data from metafile error"; |
| case SIMULTANEOUS_PRINT_ACTIVE: |
| return "The previous printing job hasn't finished"; |
| case PAGE_RANGE_SYNTAX_ERROR: |
| return "Page range syntax error"; |
| case PAGE_COUNT_EXCEEDED: |
| return "Page range exceeds page count"; |
| default: |
| NOTREACHED(); |
| return "Unknown PrintResult"; |
| } |
| } |
| |
| // static |
| HeadlessPrintManager::PageRangeStatus |
| HeadlessPrintManager::PageRangeTextToPages(base::StringPiece page_range_text, |
| bool ignore_invalid_page_ranges, |
| int pages_count, |
| std::vector<int>* pages) { |
| printing::PageRanges page_ranges; |
| for (const auto& range_string : |
| base::SplitStringPiece(page_range_text, ",", base::TRIM_WHITESPACE, |
| base::SPLIT_WANT_NONEMPTY)) { |
| printing::PageRange range; |
| if (range_string.find("-") == base::StringPiece::npos) { |
| if (!base::StringToInt(range_string, &range.from)) |
| return SYNTAX_ERROR; |
| range.to = range.from; |
| } else if (range_string == "-") { |
| range.from = 1; |
| range.to = pages_count; |
| } else if (range_string.starts_with("-")) { |
| range.from = 1; |
| if (!base::StringToInt(range_string.substr(1), &range.to)) |
| return SYNTAX_ERROR; |
| } else if (range_string.ends_with("-")) { |
| range.to = pages_count; |
| if (!base::StringToInt(range_string.substr(0, range_string.length() - 1), |
| &range.from)) |
| return SYNTAX_ERROR; |
| } else { |
| auto tokens = base::SplitStringPiece( |
| range_string, "-", base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY); |
| if (tokens.size() != 2 || !base::StringToInt(tokens[0], &range.from) || |
| !base::StringToInt(tokens[1], &range.to)) |
| return SYNTAX_ERROR; |
| } |
| |
| if (range.from < 1 || range.from > range.to) { |
| if (!ignore_invalid_page_ranges) |
| return SYNTAX_ERROR; |
| continue; |
| } |
| if (range.from > pages_count) { |
| if (!ignore_invalid_page_ranges) |
| return LIMIT_ERROR; |
| continue; |
| } |
| |
| if (range.to > pages_count) |
| range.to = pages_count; |
| |
| // Page numbers are 1-based in the dictionary. |
| // Page numbers are 0-based for the print settings. |
| range.from--; |
| range.to--; |
| page_ranges.push_back(range); |
| } |
| *pages = printing::PageRange::GetPages(page_ranges); |
| return PRINT_NO_ERROR; |
| } |
| |
| void HeadlessPrintManager::GetPDFContents(content::RenderFrameHost* rfh, |
| const HeadlessPrintSettings& settings, |
| GetPDFCallback callback) { |
| DCHECK(callback); |
| |
| if (callback_) { |
| std::move(callback).Run(SIMULTANEOUS_PRINT_ACTIVE, |
| base::MakeRefCounted<base::RefCountedString>()); |
| return; |
| } |
| printing_rfh_ = rfh; |
| callback_ = std::move(callback); |
| print_params_ = GetPrintParamsFromSettings(settings); |
| page_ranges_text_ = settings.page_ranges; |
| ignore_invalid_page_ranges_ = settings.ignore_invalid_page_ranges; |
| rfh->Send(new PrintMsg_PrintPages(rfh->GetRoutingID())); |
| } |
| |
| std::unique_ptr<PrintMsg_PrintPages_Params> |
| HeadlessPrintManager::GetPrintParamsFromSettings( |
| const HeadlessPrintSettings& settings) { |
| printing::PrintSettings print_settings; |
| print_settings.set_dpi(printing::kPointsPerInch); |
| print_settings.set_should_print_backgrounds( |
| settings.should_print_backgrounds); |
| print_settings.set_scale_factor(settings.scale); |
| print_settings.SetOrientation(settings.landscape); |
| |
| print_settings.set_display_header_footer(settings.display_header_footer); |
| if (print_settings.display_header_footer()) { |
| url::Replacements<char> url_sanitizer; |
| url_sanitizer.ClearUsername(); |
| url_sanitizer.ClearPassword(); |
| std::string url = printing_rfh_->GetLastCommittedURL() |
| .ReplaceComponents(url_sanitizer) |
| .spec(); |
| print_settings.set_url(base::UTF8ToUTF16(url)); |
| } |
| |
| print_settings.set_margin_type(printing::CUSTOM_MARGINS); |
| print_settings.SetCustomMargins(settings.margins_in_points); |
| |
| gfx::Rect printable_area_device_units(settings.paper_size_in_points); |
| print_settings.SetPrinterPrintableArea(settings.paper_size_in_points, |
| printable_area_device_units, true); |
| |
| auto print_params = std::make_unique<PrintMsg_PrintPages_Params>(); |
| printing::RenderParamsFromPrintSettings(print_settings, |
| &print_params->params); |
| print_params->params.document_cookie = printing::PrintSettings::NewCookie(); |
| print_params->params.header_template = |
| base::UTF8ToUTF16(settings.header_template); |
| print_params->params.footer_template = |
| base::UTF8ToUTF16(settings.footer_template); |
| print_params->params.prefer_css_page_size = settings.prefer_css_page_size; |
| return print_params; |
| } |
| |
| bool HeadlessPrintManager::OnMessageReceived( |
| const IPC::Message& message, |
| content::RenderFrameHost* render_frame_host) { |
| if (!printing_rfh_ && |
| (message.type() == PrintHostMsg_GetDefaultPrintSettings::ID || |
| message.type() == PrintHostMsg_ScriptedPrint::ID)) { |
| std::string type; |
| switch (message.type()) { |
| case PrintHostMsg_GetDefaultPrintSettings::ID: |
| type = "GetDefaultPrintSettings"; |
| break; |
| case PrintHostMsg_ScriptedPrint::ID: |
| type = "ScriptedPrint"; |
| break; |
| default: |
| type = "Unknown"; |
| break; |
| } |
| DLOG(ERROR) |
| << "Unexpected message received before GetPDFContents is called: " |
| << type; |
| |
| // TODO: consider propagating the error back to the caller, rather than |
| // effectively dropping the request. |
| render_frame_host->Send(IPC::SyncMessage::GenerateReply(&message)); |
| return true; |
| } |
| |
| bool handled = true; |
| IPC_BEGIN_MESSAGE_MAP(HeadlessPrintManager, message) |
| IPC_MESSAGE_HANDLER(PrintHostMsg_ShowInvalidPrinterSettingsError, |
| OnShowInvalidPrinterSettingsError) |
| IPC_MESSAGE_UNHANDLED(handled = false) |
| IPC_END_MESSAGE_MAP() |
| return handled || PrintManager::OnMessageReceived(message, render_frame_host); |
| } |
| |
| void HeadlessPrintManager::OnGetDefaultPrintSettings( |
| content::RenderFrameHost* render_frame_host, |
| IPC::Message* reply_msg) { |
| PrintHostMsg_GetDefaultPrintSettings::WriteReplyParams(reply_msg, |
| print_params_->params); |
| // Intentionally using |printing_rfh_| instead of |render_frame_host| |
| // parameter. |
| printing_rfh_->Send(reply_msg); |
| } |
| |
| void HeadlessPrintManager::OnScriptedPrint( |
| content::RenderFrameHost* render_frame_host, |
| const PrintHostMsg_ScriptedPrint_Params& params, |
| IPC::Message* reply_msg) { |
| PageRangeStatus status = |
| PageRangeTextToPages(page_ranges_text_, ignore_invalid_page_ranges_, |
| params.expected_pages_count, &print_params_->pages); |
| // Intentionally using |printing_rfh_| instead of |render_frame_host| |
| // parameter. |
| switch (status) { |
| case SYNTAX_ERROR: |
| printing_rfh_->Send(reply_msg); |
| ReleaseJob(PAGE_RANGE_SYNTAX_ERROR); |
| return; |
| case LIMIT_ERROR: |
| printing_rfh_->Send(reply_msg); |
| ReleaseJob(PAGE_COUNT_EXCEEDED); |
| return; |
| case PRINT_NO_ERROR: |
| PrintHostMsg_ScriptedPrint::WriteReplyParams(reply_msg, *print_params_); |
| printing_rfh_->Send(reply_msg); |
| return; |
| default: |
| NOTREACHED(); |
| return; |
| } |
| } |
| |
| void HeadlessPrintManager::OnShowInvalidPrinterSettingsError() { |
| ReleaseJob(INVALID_PRINTER_SETTINGS); |
| } |
| |
| void HeadlessPrintManager::OnPrintingFailed(int cookie) { |
| ReleaseJob(PRINTING_FAILED); |
| } |
| |
| void HeadlessPrintManager::OnDidPrintDocument( |
| content::RenderFrameHost* render_frame_host, |
| const PrintHostMsg_DidPrintDocument_Params& params) { |
| auto& content = params.content; |
| if (!content.metafile_data_region.IsValid()) { |
| ReleaseJob(INVALID_MEMORY_HANDLE); |
| return; |
| } |
| base::ReadOnlySharedMemoryMapping map = content.metafile_data_region.Map(); |
| if (!map.IsValid()) { |
| ReleaseJob(METAFILE_MAP_ERROR); |
| return; |
| } |
| data_ = std::string(static_cast<const char*>(map.memory()), map.size()); |
| ReleaseJob(PRINT_SUCCESS); |
| } |
| |
| void HeadlessPrintManager::Reset() { |
| printing_rfh_ = nullptr; |
| callback_.Reset(); |
| print_params_.reset(); |
| page_ranges_text_.clear(); |
| ignore_invalid_page_ranges_ = false; |
| data_.clear(); |
| } |
| |
| void HeadlessPrintManager::ReleaseJob(PrintResult result) { |
| if (!callback_) { |
| DLOG(ERROR) << "ReleaseJob is called when callback_ is null. Check whether " |
| "ReleaseJob is called more than once."; |
| return; |
| } |
| |
| if (result == PRINT_SUCCESS) { |
| std::move(callback_).Run(result, |
| base::RefCountedString::TakeString(&data_)); |
| } else { |
| std::move(callback_).Run(result, |
| base::MakeRefCounted<base::RefCountedString>()); |
| } |
| printing_rfh_->Send(new PrintMsg_PrintingDone(printing_rfh_->GetRoutingID(), |
| result == PRINT_SUCCESS)); |
| Reset(); |
| } |
| |
| WEB_CONTENTS_USER_DATA_KEY_IMPL(HeadlessPrintManager) |
| |
| } // namespace headless |