| // Copyright 2013 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "chrome/browser/printing/print_view_manager_base.h" |
| |
| #include <memory> |
| #include <string_view> |
| #include <utility> |
| #include <vector> |
| |
| #include "base/feature_list.h" |
| #include "base/functional/bind.h" |
| #include "base/location.h" |
| #include "base/memory/read_only_shared_memory_region.h" |
| #include "base/memory/ref_counted_memory.h" |
| #include "base/numerics/safe_conversions.h" |
| #include "base/observer_list.h" |
| #include "base/run_loop.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "base/task/current_thread.h" |
| #include "base/timer/timer.h" |
| #include "build/build_config.h" |
| #include "build/chromeos_buildflags.h" |
| #include "chrome/browser/bad_message.h" |
| #include "chrome/browser/browser_process.h" |
| #include "chrome/browser/printing/print_compositor_util.h" |
| #include "chrome/browser/printing/print_error_dialog.h" |
| #include "chrome/browser/printing/print_job.h" |
| #include "chrome/browser/printing/print_job_manager.h" |
| #include "chrome/browser/printing/print_view_manager_common.h" |
| #include "chrome/browser/printing/printer_query.h" |
| #include "chrome/browser/profiles/profile.h" |
| #include "chrome/browser/ui/webui/print_preview/printer_handler.h" |
| #include "chrome/common/pref_names.h" |
| #include "chrome/grit/generated_resources.h" |
| #include "components/device_event_log/device_event_log.h" |
| #include "components/enterprise/buildflags/buildflags.h" |
| #include "components/prefs/pref_service.h" |
| #include "components/printing/browser/print_composite_client.h" |
| #include "components/printing/browser/print_manager_utils.h" |
| #include "components/printing/common/print.mojom.h" |
| #include "components/printing/common/print_params.h" |
| #include "components/services/print_compositor/public/cpp/print_service_mojo_types.h" |
| #include "content/public/browser/browser_task_traits.h" |
| #include "content/public/browser/browser_thread.h" |
| #include "content/public/browser/global_routing_id.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 "mojo/public/cpp/system/buffer.h" |
| #include "printing/buildflags/buildflags.h" |
| #include "printing/metafile_skia.h" |
| #include "printing/mojom/print.mojom.h" |
| #include "printing/print_settings.h" |
| #include "printing/printed_document.h" |
| #include "printing/printing_utils.h" |
| #include "ui/base/l10n/l10n_util.h" |
| |
| #if BUILDFLAG(ENABLE_PRINT_PREVIEW) |
| #include "chrome/browser/printing/print_view_manager.h" |
| #include "printing/print_settings_conversion.h" |
| #endif |
| |
| #if BUILDFLAG(ENABLE_OOP_PRINTING) |
| #include "chrome/browser/printing/oop_features.h" |
| #include "chrome/browser/printing/print_backend_service_manager.h" |
| #endif |
| |
| #if BUILDFLAG(IS_WIN) |
| #include "chrome/browser/printing/xps_features.h" |
| #endif |
| |
| #if BUILDFLAG(IS_WIN) && BUILDFLAG(GOOGLE_CHROME_BRANDING) |
| #include "chrome/browser/win/conflicts/module_database.h" |
| #endif |
| |
| #if BUILDFLAG(ENTERPRISE_CONTENT_ANALYSIS) |
| #include "chrome/browser/enterprise/data_protection/print_utils.h" |
| #endif |
| |
| namespace printing { |
| |
| namespace { |
| |
| void OnDidGetDefaultPrintSettings( |
| scoped_refptr<PrintQueriesQueue> queue, |
| bool want_pdf_settings, |
| std::unique_ptr<PrinterQuery> printer_query, |
| mojom::PrintManagerHost::GetDefaultPrintSettingsCallback callback) { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| |
| if (printer_query->last_status() != mojom::ResultCode::kSuccess) { |
| if (!want_pdf_settings) { |
| ShowPrintErrorDialogForInvalidPrinterError(); |
| } |
| std::move(callback).Run(nullptr); |
| return; |
| } |
| |
| mojom::PrintParamsPtr params = mojom::PrintParams::New(); |
| RenderParamsFromPrintSettings(printer_query->settings(), params.get()); |
| params->document_cookie = printer_query->cookie(); |
| |
| if (!PrintMsgPrintParamsIsValid(*params)) { |
| if (!want_pdf_settings) { |
| ShowPrintErrorDialogForInvalidPrinterError(); |
| } |
| std::move(callback).Run(nullptr); |
| return; |
| } |
| |
| std::move(callback).Run(std::move(params)); |
| queue->QueuePrinterQuery(std::move(printer_query)); |
| } |
| |
| void OnDidScriptedPrint( |
| scoped_refptr<PrintQueriesQueue> queue, |
| std::unique_ptr<PrinterQuery> printer_query, |
| mojom::PrintManagerHost::ScriptedPrintCallback callback) { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| |
| if (printer_query->last_status() != mojom::ResultCode::kSuccess || |
| !printer_query->settings().dpi()) { |
| // Notify user of the error, unless it was explicitly canceled. |
| if (printer_query->last_status() != mojom::ResultCode::kCanceled) { |
| ShowPrintErrorDialogForGenericError(); |
| } |
| std::move(callback).Run(nullptr); |
| return; |
| } |
| |
| auto params = mojom::PrintPagesParams::New(); |
| params->params = mojom::PrintParams::New(); |
| RenderParamsFromPrintSettings(printer_query->settings(), |
| params->params.get()); |
| params->params->document_cookie = printer_query->cookie(); |
| if (!PrintMsgPrintParamsIsValid(*params->params)) { |
| std::move(callback).Run(nullptr); |
| return; |
| } |
| |
| params->pages = printer_query->settings().ranges(); |
| std::move(callback).Run(std::move(params)); |
| queue->QueuePrinterQuery(std::move(printer_query)); |
| } |
| |
| #if BUILDFLAG(ENABLE_PRINT_PREVIEW) |
| std::string PrintMsgPrintParamsErrorDetails(const mojom::PrintParams& params) { |
| std::vector<std::string_view> details; |
| |
| if (params.content_size.IsEmpty()) { |
| details.push_back("content size is empty"); |
| } |
| if (params.page_size.IsEmpty()) { |
| details.push_back("page size is empty"); |
| } |
| if (params.printable_area.IsEmpty()) { |
| details.push_back("printable area is empty"); |
| } |
| if (!params.document_cookie) { |
| details.push_back("invalid document cookie"); |
| } |
| if (params.dpi.width() <= kMinDpi || params.dpi.height() <= kMinDpi) { |
| details.push_back("invalid DPI dimensions"); |
| } |
| if (params.margin_top < 0 || params.margin_left < 0) { |
| details.push_back("invalid margins"); |
| } |
| |
| return base::JoinString(details, "; "); |
| } |
| #endif // BUILDFLAG(ENABLE_PRINT_PREVIEW) |
| |
| } // namespace |
| |
| BASE_FEATURE(kCheckPrintRfhIsActive, |
| "CheckPrintRfhIsActive", |
| base::FEATURE_ENABLED_BY_DEFAULT); |
| |
| PrintViewManagerBase::PrintViewManagerBase(content::WebContents* web_contents) |
| : PrintManager(web_contents), |
| queue_(g_browser_process->print_job_manager()->queue()) { |
| DCHECK(queue_); |
| Profile* profile = |
| Profile::FromBrowserContext(web_contents->GetBrowserContext()); |
| printing_enabled_.Init(prefs::kPrintingEnabled, profile->GetPrefs()); |
| } |
| |
| PrintViewManagerBase::~PrintViewManagerBase() { |
| ReleasePrinterQuery(); |
| DisconnectFromCurrentPrintJob(); |
| } |
| |
| #if BUILDFLAG(IS_WIN) && BUILDFLAG(GOOGLE_CHROME_BRANDING) |
| // TODO(crbug.com/41419019): Remove `DisableThirdPartyBlocking()` once OOP |
| // printing is always enabled for Windows. |
| // static |
| void PrintViewManagerBase::DisableThirdPartyBlocking() { |
| #if BUILDFLAG(ENABLE_OOP_PRINTING) && BUILDFLAG(ENABLE_OOP_BASIC_PRINT_DIALOG) |
| const bool loads_print_drivers_in_browser_process = !ShouldPrintJobOop(); |
| #else |
| constexpr bool loads_print_drivers_in_browser_process = true; |
| #endif |
| if (loads_print_drivers_in_browser_process) { |
| ModuleDatabase::DisableThirdPartyBlocking(); |
| } |
| } |
| #endif // BUILDFLAG(IS_WIN) && BUILDFLAG(GOOGLE_CHROME_BRANDING) |
| |
| bool PrintViewManagerBase::PrintNow(content::RenderFrameHost* rfh) { |
| if (!StartPrintCommon(rfh)) { |
| return false; |
| } |
| |
| GetPrintRenderFrame(rfh)->PrintRequestedPages(); |
| |
| for (auto& observer : GetTestObservers()) { |
| observer.OnPrintNow(rfh); |
| } |
| return true; |
| } |
| |
| void PrintViewManagerBase::PrintNodeUnderContextMenu( |
| content::RenderFrameHost* rfh) { |
| if (!StartPrintCommon(rfh)) { |
| return; |
| } |
| |
| GetPrintRenderFrame(rfh)->PrintNodeUnderContextMenu(); |
| |
| for (auto& observer : GetTestObservers()) { |
| observer.OnPrintNow(rfh); |
| } |
| } |
| |
| #if BUILDFLAG(ENABLE_PRINT_PREVIEW) |
| void PrintViewManagerBase::PrintForPrintPreview( |
| base::Value::Dict job_settings, |
| scoped_refptr<base::RefCountedMemory> print_data, |
| content::RenderFrameHost* rfh, |
| PrinterHandler::PrintCallback callback) { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| |
| #if BUILDFLAG(ENABLE_OOP_PRINTING) || BUILDFLAG(ENTERPRISE_CONTENT_ANALYSIS) |
| bool show_system_dialog = |
| job_settings.FindBool(kSettingShowSystemDialog).value_or(false); |
| #endif |
| |
| #if BUILDFLAG(ENABLE_OOP_PRINTING) |
| if (show_system_dialog && ShouldPrintJobOop()) { |
| if (!RegisterSystemPrintClient()) { |
| // Platform unable to support system print dialog at this time, treat |
| // this as a cancel. |
| std::move(callback).Run( |
| base::Value("Concurrent system print not allowed")); |
| return; |
| } |
| } |
| #endif |
| |
| std::unique_ptr<printing::PrinterQuery> printer_query = |
| queue()->CreatePrinterQuery(rfh->GetGlobalId()); |
| auto* printer_query_ptr = printer_query.get(); |
| const int page_count = job_settings.FindInt(kSettingPreviewPageCount).value(); |
| |
| #if BUILDFLAG(ENABLE_OOP_PRINTING) |
| if (query_with_ui_client_id().has_value()) { |
| printer_query->SetClientId(query_with_ui_client_id().value()); |
| } |
| #endif |
| printer_query_ptr->SetSettings( |
| std::move(job_settings), |
| base::BindOnce(&PrintViewManagerBase::OnPrintSettingsDone, |
| weak_ptr_factory_.GetWeakPtr(), print_data, page_count, |
| #if BUILDFLAG(ENTERPRISE_CONTENT_ANALYSIS) |
| show_system_dialog, |
| #endif |
| std::move(callback), std::move(printer_query))); |
| } |
| #endif // BUILDFLAG(ENABLE_PRINT_PREVIEW) |
| |
| void PrintViewManagerBase::PrintToPdf( |
| content::RenderFrameHost* rfh, |
| const std::string& page_ranges, |
| mojom::PrintPagesParamsPtr print_pages_params, |
| print_to_pdf::PdfPrintJob::PrintToPdfCallback callback) { |
| print_to_pdf::PdfPrintJob::StartJob( |
| web_contents(), rfh, GetPrintRenderFrame(rfh), page_ranges, |
| std::move(print_pages_params), std::move(callback)); |
| } |
| |
| void PrintViewManagerBase::PrintDocument( |
| scoped_refptr<base::RefCountedMemory> print_data, |
| const gfx::Size& page_size, |
| const gfx::Rect& content_area, |
| const gfx::Point& offsets) { |
| #if BUILDFLAG(ENTERPRISE_CONTENT_ANALYSIS) |
| if (content_analysis_before_printing_document_) { |
| std::move(content_analysis_before_printing_document_) |
| .Run(print_data, page_size, content_area, offsets); |
| return; |
| } |
| #endif |
| |
| #if BUILDFLAG(IS_WIN) |
| const bool source_is_pdf = |
| !print_job_->document()->settings().is_modifiable(); |
| if (!ShouldPrintUsingXps(source_is_pdf)) { |
| // Print using GDI, which first requires conversion to EMF. |
| print_job_->StartConversionToNativeFormat( |
| print_data, page_size, content_area, offsets, |
| web_contents()->GetLastCommittedURL()); |
| return; |
| } |
| #endif |
| |
| std::unique_ptr<MetafileSkia> metafile = std::make_unique<MetafileSkia>(); |
| CHECK(metafile->InitFromData(*print_data)); |
| |
| // Update the rendered document. It will send notifications to the listener. |
| PrintedDocument* document = print_job_->document(); |
| document->SetDocument(std::move(metafile)); |
| ShouldQuitFromInnerMessageLoop(); |
| } |
| |
| #if BUILDFLAG(ENABLE_PRINT_PREVIEW) |
| #if BUILDFLAG(IS_WIN) |
| void PrintViewManagerBase::OnDidUpdatePrintableArea( |
| std::unique_ptr<PrinterQuery> printer_query, |
| base::Value::Dict job_settings, |
| std::unique_ptr<PrintSettings> print_settings, |
| UpdatePrintSettingsCallback callback, |
| bool success) { |
| if (!success) { |
| PRINTER_LOG(ERROR) << "Unable to update printable area for " |
| << base::UTF16ToUTF8(print_settings->device_name()) |
| << " (paper vendor id " |
| << print_settings->requested_media().vendor_id << ")"; |
| std::move(callback).Run(nullptr); |
| return; |
| } |
| PRINTER_LOG(EVENT) << "Paper printable area updated for vendor id " |
| << print_settings->requested_media().vendor_id; |
| CompleteUpdatePrintSettings(std::move(job_settings), |
| std::move(print_settings), std::move(callback)); |
| } |
| #endif |
| |
| void PrintViewManagerBase::CompleteUpdatePrintSettings( |
| base::Value::Dict job_settings, |
| std::unique_ptr<PrintSettings> print_settings, |
| UpdatePrintSettingsCallback callback) { |
| mojom::PrintPagesParamsPtr settings = mojom::PrintPagesParams::New(); |
| settings->pages = GetPageRangesFromJobSettings(job_settings); |
| settings->params = mojom::PrintParams::New(); |
| RenderParamsFromPrintSettings(*print_settings, settings->params.get()); |
| settings->params->document_cookie = PrintSettings::NewCookie(); |
| if (!PrintMsgPrintParamsIsValid(*settings->params)) { |
| mojom::PrinterType printer_type = static_cast<mojom::PrinterType>( |
| *job_settings.FindInt(kSettingPrinterType)); |
| PRINTER_LOG(ERROR) << "Printer settings invalid for " |
| << base::UTF16ToUTF8(print_settings->device_name()) |
| << " (destination type " << printer_type << "): " |
| << PrintMsgPrintParamsErrorDetails(*settings->params); |
| std::move(callback).Run(nullptr); |
| return; |
| } |
| |
| set_cookie(settings->params->document_cookie); |
| std::move(callback).Run(std::move(settings)); |
| } |
| |
| void PrintViewManagerBase::OnPrintSettingsDone( |
| scoped_refptr<base::RefCountedMemory> print_data, |
| uint32_t page_count, |
| #if BUILDFLAG(ENTERPRISE_CONTENT_ANALYSIS) |
| bool show_system_dialog, |
| #endif |
| PrinterHandler::PrintCallback callback, |
| std::unique_ptr<printing::PrinterQuery> printer_query) { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| DCHECK(printer_query); |
| |
| // Check if the job was cancelled. With out-of-process printing, this could |
| // happen if we detect that another system print dialog is already being |
| // displayed. Otherwise this should only happen on Windows when the system |
| // dialog is cancelled. |
| if (printer_query->last_status() == mojom::ResultCode::kCanceled) { |
| #if BUILDFLAG(ENABLE_OOP_PRINTING) |
| if (ShouldPrintJobOop()) { |
| UnregisterSystemPrintClient(); |
| } |
| #endif |
| #if BUILDFLAG(IS_WIN) |
| content::GetUIThreadTaskRunner({})->PostTask( |
| FROM_HERE, base::BindOnce(&PrintViewManagerBase::SystemDialogCancelled, |
| weak_ptr_factory_.GetWeakPtr())); |
| #endif |
| std::move(callback).Run(base::Value()); |
| return; |
| } |
| |
| if (!printer_query->cookie() || !printer_query->settings().dpi()) { |
| PRINTER_LOG(ERROR) << "Unable to update print settings"; |
| #if BUILDFLAG(ENABLE_OOP_PRINTING) |
| if (ShouldPrintJobOop()) { |
| UnregisterSystemPrintClient(); |
| } |
| #endif |
| ShowPrintErrorDialogForGenericError(); |
| std::move(callback).Run(base::Value("Update settings failed")); |
| return; |
| } |
| |
| // Post task so that the query has time to reset the callback before calling |
| // DidGetPrintedPagesCount(). |
| int cookie = printer_query->cookie(); |
| queue()->QueuePrinterQuery(std::move(printer_query)); |
| content::GetUIThreadTaskRunner({})->PostTask( |
| FROM_HERE, |
| base::BindOnce(&PrintViewManagerBase::StartLocalPrintJob, |
| weak_ptr_factory_.GetWeakPtr(), print_data, page_count, |
| #if BUILDFLAG(ENTERPRISE_CONTENT_ANALYSIS) |
| show_system_dialog, |
| #endif |
| cookie, std::move(callback))); |
| } |
| |
| void PrintViewManagerBase::StartLocalPrintJob( |
| scoped_refptr<base::RefCountedMemory> print_data, |
| uint32_t page_count, |
| #if BUILDFLAG(ENTERPRISE_CONTENT_ANALYSIS) |
| bool show_system_dialog, |
| #endif |
| int cookie, |
| PrinterHandler::PrintCallback callback) { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| |
| #if BUILDFLAG(ENTERPRISE_CONTENT_ANALYSIS) |
| // Populating `content_analysis_before_printing_document_` if needed should be |
| // done first in this function's workflow, this way other code can check if |
| // content analysis is going to happen and delay starting `print_job_` to |
| // avoid needlessly prompting the user. |
| using enterprise_data_protection::PrintScanningContext; |
| auto context = show_system_dialog |
| ? PrintScanningContext::kSystemPrintBeforePrintDocument |
| : PrintScanningContext::kNormalPrintBeforePrintDocument; |
| |
| std::optional<enterprise_connectors::ContentAnalysisDelegate::Data> |
| scanning_data = enterprise_data_protection::GetPrintAnalysisData( |
| web_contents(), context); |
| |
| if (scanning_data) { |
| set_content_analysis_before_printing_document(base::BindOnce( |
| &PrintViewManagerBase::ContentAnalysisBeforePrintingDocument, |
| weak_ptr_factory_.GetWeakPtr(), std::move(*scanning_data))); |
| } |
| #endif // BUILDFLAG(ENTERPRISE_CONTENT_ANALYSIS) |
| |
| set_cookie(cookie); |
| DidGetPrintedPagesCount(cookie, page_count); |
| |
| if (!PrintJobHasDocument(cookie)) { |
| std::move(callback).Run(base::Value("Failed to print")); |
| return; |
| } |
| |
| #if BUILDFLAG(IS_WIN) |
| print_job_->ResetPageMapping(); |
| #endif |
| |
| const printing::PrintSettings& settings = print_job_->settings(); |
| gfx::Size page_size = settings.page_setup_device_units().physical_size(); |
| gfx::Rect content_area = |
| gfx::Rect(0, 0, page_size.width(), page_size.height()); |
| |
| PrintDocument(print_data, page_size, content_area, |
| settings.page_setup_device_units().printable_area().origin()); |
| std::move(callback).Run(base::Value()); |
| } |
| #endif // BUILDFLAG(ENABLE_PRINT_PREVIEW) |
| |
| void PrintViewManagerBase::GetDefaultPrintSettingsReply( |
| GetDefaultPrintSettingsCallback callback, |
| mojom::PrintParamsPtr params) { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| #if BUILDFLAG(ENABLE_OOP_PRINTING) |
| if (ShouldPrintJobOop() && !params) { |
| // The attempt to use the default settings failed. There should be no |
| // subsequent call to get settings from the user that would normally be |
| // shared as part of this client registration. Immediately notify the |
| // service manager that this client is no longer needed. |
| UnregisterSystemPrintClient(); |
| } |
| #endif |
| if (params) { |
| set_cookie(params->document_cookie); |
| std::move(callback).Run(std::move(params)); |
| } else { |
| set_cookie(PrintSettings::NewInvalidCookie()); |
| std::move(callback).Run(nullptr); |
| } |
| } |
| |
| void PrintViewManagerBase::ScriptedPrintReply( |
| ScriptedPrintCallback callback, |
| int process_id, |
| mojom::PrintPagesParamsPtr params) { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| |
| #if BUILDFLAG(ENABLE_OOP_PRINTING) |
| if (ShouldPrintJobOop()) { |
| // Finished getting all settings (defaults and from user), no further need |
| // to be registered as a system print client. |
| UnregisterSystemPrintClient(); |
| } |
| #endif // BUILDFLAG(ENABLE_OOP_PRINTING) |
| if (!content::RenderProcessHost::FromID(process_id)) { |
| // Early return if the renderer is not alive. |
| return; |
| } |
| |
| if (params) { |
| set_cookie(params->params->document_cookie); |
| std::move(callback).Run(std::move(params)); |
| } else { |
| set_cookie(PrintSettings::NewInvalidCookie()); |
| std::move(callback).Run(nullptr); |
| } |
| } |
| |
| void PrintViewManagerBase::NavigationStopped() { |
| // Cancel the current job, wait for the worker to finish. |
| TerminatePrintJob(true); |
| } |
| |
| std::u16string PrintViewManagerBase::RenderSourceName() { |
| std::u16string name(web_contents()->GetTitle()); |
| if (name.empty()) |
| name = l10n_util::GetStringUTF16(IDS_DEFAULT_PRINT_DOCUMENT_TITLE); |
| return name; |
| } |
| |
| void PrintViewManagerBase::DidGetPrintedPagesCount(int32_t cookie, |
| uint32_t number_pages) { |
| PrintManager::DidGetPrintedPagesCount(cookie, number_pages); |
| OpportunisticallyCreatePrintJob(cookie); |
| } |
| |
| bool PrintViewManagerBase::PrintJobHasDocument(int cookie) { |
| if (!OpportunisticallyCreatePrintJob(cookie)) |
| return false; |
| |
| // These checks may fail since we are completely asynchronous. Old spurious |
| // messages can be received if one of the processes is overloaded. |
| PrintedDocument* document = print_job_->document(); |
| return document && document->cookie() == cookie; |
| } |
| |
| bool PrintViewManagerBase::OnComposePdfDoneImpl( |
| int document_cookie, |
| const gfx::Size& page_size, |
| const gfx::Rect& content_area, |
| const gfx::Point& physical_offsets, |
| mojom::PrintCompositor::Status status, |
| base::ReadOnlySharedMemoryRegion region) { |
| if (status != mojom::PrintCompositor::Status::kSuccess) { |
| DLOG(ERROR) << "Compositing pdf failed with error " << status; |
| return false; |
| } |
| |
| if (!print_job_ || !print_job_->document() || |
| print_job_->document()->cookie() != document_cookie) { |
| return false; |
| } |
| |
| DCHECK(region.IsValid()); |
| DCHECK(LooksLikePdf(region.Map().GetMemoryAsSpan<const uint8_t>())); |
| scoped_refptr<base::RefCountedSharedMemoryMapping> data = |
| base::RefCountedSharedMemoryMapping::CreateFromWholeRegion(region); |
| if (!data) |
| return false; |
| |
| PrintDocument(data, page_size, content_area, physical_offsets); |
| return true; |
| } |
| |
| void PrintViewManagerBase::OnComposeDocumentDone( |
| int document_cookie, |
| const gfx::Size& page_size, |
| const gfx::Rect& content_area, |
| const gfx::Point& physical_offsets, |
| DidPrintDocumentCallback callback, |
| mojom::PrintCompositor::Status status, |
| base::ReadOnlySharedMemoryRegion region) { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| |
| bool success = |
| OnComposePdfDoneImpl(document_cookie, page_size, content_area, |
| physical_offsets, status, std::move(region)); |
| OnDidPrintDocument(std::move(callback), success); |
| } |
| |
| void PrintViewManagerBase::OnDidPrintDocument(DidPrintDocumentCallback callback, |
| bool succeeded) { |
| std::move(callback).Run(succeeded); |
| for (auto& observer : GetTestObservers()) { |
| observer.OnDidPrintDocument(); |
| } |
| } |
| |
| void PrintViewManagerBase::DidPrintDocument( |
| mojom::DidPrintDocumentParamsPtr params, |
| DidPrintDocumentCallback callback) { |
| if (!PrintJobHasDocument(params->document_cookie)) { |
| OnDidPrintDocument(std::move(callback), /*succeeded=*/false); |
| return; |
| } |
| |
| const mojom::DidPrintContentParams& content = *params->content; |
| if (!content.metafile_data_region.IsValid()) { |
| NOTREACHED() << "invalid memory handle"; |
| } |
| |
| if (IsOopifEnabled() && print_job_->document()->settings().is_modifiable()) { |
| auto* client = PrintCompositeClient::FromWebContents(web_contents()); |
| client->CompositeDocument( |
| params->document_cookie, GetCurrentTargetFrame(), content, |
| ui::AXTreeUpdate(), mojom::GenerateDocumentOutline::kNone, |
| GetCompositorDocumentType(), |
| base::BindOnce(&PrintViewManagerBase::OnComposeDocumentDone, |
| weak_ptr_factory_.GetWeakPtr(), params->document_cookie, |
| params->page_size, params->content_area, |
| params->physical_offsets, std::move(callback))); |
| return; |
| } |
| auto data = base::RefCountedSharedMemoryMapping::CreateFromWholeRegion( |
| content.metafile_data_region); |
| if (!data) { |
| NOTREACHED() << "couldn't map"; |
| } |
| |
| PrintDocument(data, params->page_size, params->content_area, |
| params->physical_offsets); |
| OnDidPrintDocument(std::move(callback), /*succeeded=*/true); |
| } |
| |
| void PrintViewManagerBase::GetDefaultPrintSettings( |
| GetDefaultPrintSettingsCallback callback) { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| if (!GetPrintingEnabledBooleanPref()) { |
| GetDefaultPrintSettingsReply(std::move(callback), nullptr); |
| return; |
| } |
| |
| content::RenderFrameHost* render_frame_host = GetCurrentTargetFrame(); |
| if (base::FeatureList::IsEnabled(kCheckPrintRfhIsActive) && |
| !render_frame_host->IsActive()) { |
| // Only active RFHs should show UI elements. |
| GetDefaultPrintSettingsReply(std::move(callback), nullptr); |
| return; |
| } |
| |
| #if BUILDFLAG(ENABLE_OOP_PRINTING) |
| if (ShouldPrintJobOop() && |
| #if BUILDFLAG(ENTERPRISE_CONTENT_ANALYSIS) |
| !analyzing_content_ && |
| #endif |
| !query_with_ui_client_id().has_value()) { |
| // Script initiated print, this is first signal of start of printing. |
| RegisterSystemPrintClient(); |
| } |
| #endif |
| |
| content::RenderProcessHost* render_process_host = |
| render_frame_host->GetProcess(); |
| auto callback_wrapper = |
| base::BindOnce(&PrintViewManagerBase::GetDefaultPrintSettingsReply, |
| weak_ptr_factory_.GetWeakPtr(), std::move(callback)); |
| std::unique_ptr<PrinterQuery> printer_query = |
| queue()->PopPrinterQuery(PrintSettings::NewInvalidCookie()); |
| if (!printer_query) { |
| printer_query = |
| queue()->CreatePrinterQuery(render_frame_host->GetGlobalId()); |
| #if BUILDFLAG(ENABLE_OOP_PRINTING) |
| if (query_with_ui_client_id().has_value()) { |
| printer_query->SetClientId(query_with_ui_client_id().value()); |
| } |
| #endif |
| } |
| |
| // Sometimes it is desired to get the PDF settings as opposed to the settings |
| // of the default system print driver. |
| #if BUILDFLAG(ENTERPRISE_CONTENT_ANALYSIS) |
| bool want_pdf_settings = analyzing_content_; |
| #else |
| bool want_pdf_settings = false; |
| #endif |
| |
| // Loads default settings. This is asynchronous, only the mojo message sender |
| // will hang until the settings are retrieved. |
| auto* printer_query_ptr = printer_query.get(); |
| printer_query_ptr->GetDefaultSettings( |
| base::BindOnce(&OnDidGetDefaultPrintSettings, queue_, want_pdf_settings, |
| std::move(printer_query), std::move(callback_wrapper)), |
| !render_process_host->IsPdf(), want_pdf_settings); |
| } |
| |
| #if BUILDFLAG(ENABLE_PRINT_PREVIEW) |
| void PrintViewManagerBase::UpdatePrintSettings( |
| base::Value::Dict job_settings, |
| UpdatePrintSettingsCallback callback) { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| if (!GetPrintingEnabledBooleanPref()) { |
| std::move(callback).Run(nullptr); |
| return; |
| } |
| |
| std::optional<int> printer_type_value = |
| job_settings.FindInt(kSettingPrinterType); |
| if (!printer_type_value) { |
| std::move(callback).Run(nullptr); |
| return; |
| } |
| |
| mojom::PrinterType printer_type = |
| static_cast<mojom::PrinterType>(*printer_type_value); |
| if (printer_type != mojom::PrinterType::kExtension && |
| printer_type != mojom::PrinterType::kPdf && |
| printer_type != mojom::PrinterType::kLocal) { |
| std::move(callback).Run(nullptr); |
| return; |
| } |
| |
| // `job_settings` does not yet contain the rasterized PDF dpi, so if the user |
| // has the print preference set, fetch it for use in |
| // `PrintSettingsFromJobSettings()`. |
| content::BrowserContext* context = |
| web_contents() ? web_contents()->GetBrowserContext() : nullptr; |
| PrefService* prefs = |
| context ? Profile::FromBrowserContext(context)->GetPrefs() : nullptr; |
| if (prefs && prefs->HasPrefPath(prefs::kPrintRasterizePdfDpi)) { |
| int value = prefs->GetInteger(prefs::kPrintRasterizePdfDpi); |
| if (value > 0) |
| job_settings.Set(kSettingRasterizePdfDpi, value); |
| } |
| |
| std::unique_ptr<PrintSettings> print_settings = |
| PrintSettingsFromJobSettings(job_settings); |
| if (!print_settings) { |
| std::move(callback).Run(nullptr); |
| return; |
| } |
| |
| bool open_in_external_preview = |
| job_settings.contains(kSettingOpenPDFInPreview); |
| if (!open_in_external_preview && |
| (printer_type == mojom::PrinterType::kPdf || |
| printer_type == mojom::PrinterType::kExtension)) { |
| if (print_settings->page_setup_device_units().printable_area().IsEmpty()) { |
| PrinterQuery::ApplyDefaultPrintableAreaToVirtualPrinterPrintSettings( |
| *print_settings); |
| } |
| } |
| |
| #if BUILDFLAG(IS_WIN) |
| // TODO(crbug.com/40260379): Remove this if the printable areas can be made |
| // fully available from `PrintBackend::GetPrinterSemanticCapsAndDefaults()` |
| // for in-browser queries. |
| if (printer_type == mojom::PrinterType::kLocal) { |
| // Without a document cookie to find a previous query, must generate a |
| // fresh printer query each time, even if the paper size didn't change. |
| std::unique_ptr<PrinterQuery> printer_query = |
| queue()->CreatePrinterQuery(GetCurrentTargetFrame()->GetGlobalId()); |
| |
| auto* printer_query_ptr = printer_query.get(); |
| auto* print_settings_ptr = print_settings.get(); |
| printer_query_ptr->UpdatePrintableArea( |
| print_settings_ptr, |
| base::BindOnce(&PrintViewManagerBase::OnDidUpdatePrintableArea, |
| weak_ptr_factory_.GetWeakPtr(), std::move(printer_query), |
| std::move(job_settings), std::move(print_settings), |
| std::move(callback))); |
| return; |
| } |
| #endif |
| |
| CompleteUpdatePrintSettings(std::move(job_settings), |
| std::move(print_settings), std::move(callback)); |
| } |
| |
| void PrintViewManagerBase::SetAccessibilityTree( |
| int32_t cookie, |
| const ui::AXTreeUpdate& accessibility_tree) { |
| auto* client = PrintCompositeClient::FromWebContents(web_contents()); |
| if (client) { |
| client->SetAccessibilityTree(cookie, accessibility_tree); |
| } |
| } |
| #endif // BUILDFLAG(ENABLE_PRINT_PREVIEW) |
| |
| void PrintViewManagerBase::IsPrintingEnabled( |
| IsPrintingEnabledCallback callback) { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| std::move(callback).Run(GetPrintingEnabledBooleanPref()); |
| } |
| |
| void PrintViewManagerBase::ScriptedPrint(mojom::ScriptedPrintParamsPtr params, |
| ScriptedPrintCallback callback) { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| |
| content::RenderFrameHost* render_frame_host = GetCurrentTargetFrame(); |
| content::RenderProcessHost* render_process_host = |
| render_frame_host->GetProcess(); |
| if (params->is_scripted && render_frame_host->IsNestedWithinFencedFrame()) { |
| // The renderer should have checked and disallowed the request for fenced |
| // frames in ChromeClient. Ignore the request and mark it as bad if it |
| // didn't happen for some reason. |
| bad_message::ReceivedBadMessage( |
| render_process_host, bad_message::PVMB_SCRIPTED_PRINT_FENCED_FRAME); |
| std::move(callback).Run(nullptr); |
| return; |
| } |
| #if BUILDFLAG(ENABLE_OOP_PRINTING) |
| if (ShouldPrintJobOop() && !query_with_ui_client_id().has_value()) { |
| // Renderer process has requested settings outside of the expected setup. |
| std::move(callback).Run(nullptr); |
| return; |
| } |
| #endif |
| #if BUILDFLAG(ENTERPRISE_CONTENT_ANALYSIS) |
| std::optional<enterprise_connectors::ContentAnalysisDelegate::Data> |
| scanning_data = enterprise_data_protection::GetPrintAnalysisData( |
| web_contents(), enterprise_data_protection::PrintScanningContext:: |
| kBeforeSystemDialog); |
| if (scanning_data) { |
| set_content_analysis_before_printing_document(base::BindOnce( |
| &PrintViewManagerBase::ContentAnalysisBeforePrintingDocument, |
| weak_ptr_factory_.GetWeakPtr(), std::move(*scanning_data))); |
| } |
| #endif // BUILDFLAG(ENTERPRISE_CONTENT_ANALYSIS) |
| |
| CompleteScriptedPrint(render_frame_host, std::move(params), |
| std::move(callback)); |
| } |
| |
| void PrintViewManagerBase::PrintingFailed(int32_t cookie, |
| mojom::PrintFailureReason reason) { |
| // Note: Not redundant with cookie checks in the same method in other parts of |
| // the class hierarchy. |
| if (!IsValidCookie(cookie)) |
| return; |
| |
| PrintManager::PrintingFailed(cookie, reason); |
| |
| // `PrintingFailed()` can occur because asynchronous compositing results |
| // don't complete until after a print job has already failed and been |
| // destroyed. In such cases the error notification to the user will |
| // have already been displayed, and a second message should not be |
| // shown. |
| if (print_job_ && print_job_->document() && |
| print_job_->document()->cookie() == cookie) { |
| ShowPrintErrorDialogForGenericError(); |
| } |
| |
| ReleasePrinterQuery(); |
| } |
| |
| void PrintViewManagerBase::AddTestObserver(TestObserver& observer) { |
| test_observers_.AddObserver(&observer); |
| } |
| |
| void PrintViewManagerBase::RemoveTestObserver(TestObserver& observer) { |
| test_observers_.RemoveObserver(&observer); |
| } |
| |
| void PrintViewManagerBase::RenderFrameHostStateChanged( |
| content::RenderFrameHost* render_frame_host, |
| content::RenderFrameHost::LifecycleState /*old_state*/, |
| content::RenderFrameHost::LifecycleState new_state) { |
| if (new_state == content::RenderFrameHost::LifecycleState::kActive && |
| render_frame_host->GetProcess()->IsPdf() && |
| !render_frame_host->GetMainFrame()->GetParentOrOuterDocument()) { |
| GetPrintRenderFrame(render_frame_host)->ConnectToPdfRenderer(); |
| } |
| } |
| |
| void PrintViewManagerBase::RenderFrameDeleted( |
| content::RenderFrameHost* render_frame_host) { |
| PrintManager::RenderFrameDeleted(render_frame_host); |
| |
| // Terminates or cancels the print job if one was pending. |
| if (render_frame_host != printing_rfh_) |
| return; |
| |
| for (auto& observer : GetTestObservers()) { |
| observer.OnRenderFrameDeleted(); |
| } |
| |
| printing_rfh_ = nullptr; |
| |
| PrintManager::PrintingRenderFrameDeleted(); |
| ReleasePrinterQuery(); |
| #if BUILDFLAG(ENABLE_OOP_PRINTING) |
| if (ShouldPrintJobOop()) { |
| UnregisterSystemPrintClient(); |
| } |
| #endif |
| |
| if (!print_job_) |
| return; |
| |
| scoped_refptr<PrintedDocument> document(print_job_->document()); |
| if (document) { |
| // If IsComplete() returns false, the document isn't completely rendered. |
| // Since our renderer is gone, there's nothing to do, cancel it. Otherwise, |
| // the print job may finish without problem. |
| TerminatePrintJob(!document->IsComplete()); |
| } |
| } |
| |
| #if BUILDFLAG(IS_WIN) && BUILDFLAG(ENABLE_PRINT_PREVIEW) |
| void PrintViewManagerBase::SystemDialogCancelled() { |
| // System dialog was cancelled. Clean up the print job and notify the |
| // BackgroundPrintingManager. |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| ReleasePrinterQuery(); |
| TerminatePrintJob(true); |
| } |
| #endif |
| |
| bool PrintViewManagerBase::GetPrintingEnabledBooleanPref() const { |
| return printing_enabled_.GetValue(); |
| } |
| |
| void PrintViewManagerBase::OnDocDone(int job_id, PrintedDocument* document) { |
| #if BUILDFLAG(IS_ANDROID) |
| DCHECK_LE(number_pages(), kMaxPageCount); |
| PdfWritingDone(base::checked_cast<int>(number_pages())); |
| #endif |
| } |
| |
| void PrintViewManagerBase::OnJobDone() { |
| // Printing is done, we don't need it anymore. |
| // print_job_->is_job_pending() may still be true, depending on the order |
| // of object registration. |
| printing_succeeded_ = true; |
| ReleasePrintJob(); |
| } |
| |
| void PrintViewManagerBase::OnCanceling() { |
| canceling_job_ = true; |
| } |
| |
| void PrintViewManagerBase::OnFailed() { |
| if (!canceling_job_) |
| ShowPrintErrorDialogForGenericError(); |
| |
| TerminatePrintJob(true); |
| } |
| |
| bool PrintViewManagerBase::RenderAllMissingPagesNow() { |
| if (!print_job_ || !print_job_->is_job_pending()) |
| return false; |
| |
| // Is the document already complete? |
| if (print_job_->document() && print_job_->document()->IsComplete()) { |
| printing_succeeded_ = true; |
| return true; |
| } |
| |
| // We can't print if there is no renderer. |
| if (!web_contents() || |
| !web_contents()->GetPrimaryMainFrame()->IsRenderFrameLive()) { |
| return false; |
| } |
| |
| // WebContents is either dying or a second consecutive request to print |
| // happened before the first had time to finish. We need to render all the |
| // pages in an hurry if a print_job_ is still pending. No need to wait for it |
| // to actually spool the pages, only to have the renderer generate them. Run |
| // a message loop until we get our signal that the print job is satisfied. |
| // `quit_inner_loop_` will be called as soon as |
| // print_job_->document()->IsComplete() is true in DidPrintDocument(). The |
| // check is done in ShouldQuitFromInnerMessageLoop(). |
| // BLOCKS until all the pages are received. (Need to enable recursive task) |
| // WARNING: Do not do any work after RunInnerMessageLoop() returns, as `this` |
| // may have gone away. |
| if (!RunInnerMessageLoop()) { |
| // This function is always called from DisconnectFromCurrentPrintJob() so we |
| // know that the job will be stopped/canceled in any case. |
| return false; |
| } |
| return true; |
| } |
| |
| void PrintViewManagerBase::ShouldQuitFromInnerMessageLoop() { |
| // Look at the reason. |
| DCHECK(print_job_->document()); |
| if (print_job_->document() && print_job_->document()->IsComplete() && |
| quit_inner_loop_) { |
| // We are in a message loop created by RenderAllMissingPagesNow. Quit from |
| // it. |
| std::move(quit_inner_loop_).Run(); |
| } |
| } |
| |
| scoped_refptr<PrintJob> PrintViewManagerBase::CreatePrintJob( |
| PrintJobManager* print_job_manager) { |
| return base::MakeRefCounted<PrintJob>(print_job_manager); |
| } |
| |
| bool PrintViewManagerBase::SetupNewPrintJob( |
| std::unique_ptr<PrinterQuery> query) { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| DCHECK(!quit_inner_loop_); |
| DCHECK(query); |
| |
| // Disconnect the current `print_job_`. |
| auto weak_this = weak_ptr_factory_.GetWeakPtr(); |
| DisconnectFromCurrentPrintJob(); |
| if (!weak_this) |
| return false; |
| |
| // We can't print if there is no renderer. |
| if (!web_contents()->GetPrimaryMainFrame()->IsRenderFrameLive()) { |
| return false; |
| } |
| |
| DCHECK(!print_job_); |
| print_job_ = CreatePrintJob(g_browser_process->print_job_manager()); |
| print_job_->Initialize(std::move(query), RenderSourceName(), number_pages()); |
| #if BUILDFLAG(IS_CHROMEOS) |
| print_job_->SetSource(web_contents()->GetBrowserContext()->IsOffTheRecord() |
| ? PrintJob::Source::kPrintPreviewIncognito |
| : PrintJob::Source::kPrintPreview, |
| /*source_id=*/""); |
| #endif |
| print_job_->AddObserver(*this); |
| |
| printing_succeeded_ = false; |
| return true; |
| } |
| |
| void PrintViewManagerBase::DisconnectFromCurrentPrintJob() { |
| // Make sure all the necessary rendered page are done. Don't bother with the |
| // return value. |
| auto weak_this = weak_ptr_factory_.GetWeakPtr(); |
| bool result = RenderAllMissingPagesNow(); |
| if (!weak_this) |
| return; |
| |
| // Verify that assertion. |
| if (print_job_ && print_job_->document() && |
| !print_job_->document()->IsComplete()) { |
| DCHECK(!result); |
| // That failed. |
| TerminatePrintJob(true); |
| } else { |
| // DO NOT wait for the job to finish. |
| ReleasePrintJob(); |
| } |
| } |
| |
| void PrintViewManagerBase::TerminatePrintJob(bool cancel) { |
| if (!print_job_) |
| return; |
| |
| if (cancel) { |
| canceling_job_ = true; |
| |
| // We don't need the metafile data anymore because the printing is canceled. |
| print_job_->Cancel(); |
| quit_inner_loop_.Reset(); |
| #if BUILDFLAG(IS_ANDROID) |
| PdfWritingDone(0); |
| #endif |
| } else { |
| DCHECK(!quit_inner_loop_); |
| DCHECK(!print_job_->document() || print_job_->document()->IsComplete()); |
| |
| // WebContents is either dying or navigating elsewhere. We need to render |
| // all the pages in an hurry if a print job is still pending. This does the |
| // trick since it runs a blocking message loop: |
| print_job_->Stop(); |
| } |
| ReleasePrintJob(); |
| } |
| |
| void PrintViewManagerBase::ReleasePrintJob() { |
| content::RenderFrameHost* rfh = printing_rfh_; |
| printing_rfh_ = nullptr; |
| |
| #if BUILDFLAG(ENABLE_OOP_PRINTING) |
| if (ShouldPrintJobOop()) { |
| // Ensure that any residual registration of printing client is released. |
| // This might be necessary in some abnormal cases, such as the associated |
| // render process having terminated. |
| #if BUILDFLAG(ENTERPRISE_CONTENT_ANALYSIS) |
| if (!analyzing_content_) { |
| UnregisterSystemPrintClient(); |
| } |
| #else |
| UnregisterSystemPrintClient(); |
| #endif |
| } |
| #endif |
| |
| if (!print_job_) |
| return; |
| |
| if (rfh) { |
| // printing_rfh_ should only ever point to a RenderFrameHost with a live |
| // RenderFrame. |
| DCHECK(rfh->IsRenderFrameLive()); |
| GetPrintRenderFrame(rfh)->PrintingDone(printing_succeeded_); |
| } |
| |
| print_job_->RemoveObserver(*this); |
| |
| // Don't close the worker thread. |
| print_job_ = nullptr; |
| } |
| |
| bool PrintViewManagerBase::RunInnerMessageLoop() { |
| // This value may actually be too low: |
| // |
| // - If we're looping because of printer settings initialization, the premise |
| // here is that some poor users have their print server away on a VPN over a |
| // slow connection. In this situation, the simple fact of opening the printer |
| // can be dead slow. On the other side, we don't want to die infinitely for a |
| // real network error. Give the printer 60 seconds to comply. |
| // |
| // - If we're looping because of renderer page generation, the renderer could |
| // be CPU bound, the page overly complex/large or the system just |
| // memory-bound. |
| static constexpr base::TimeDelta kPrinterSettingsTimeout = base::Seconds(60); |
| base::OneShotTimer quit_timer; |
| base::RunLoop run_loop{base::RunLoop::Type::kNestableTasksAllowed}; |
| quit_timer.Start(FROM_HERE, kPrinterSettingsTimeout, |
| run_loop.QuitWhenIdleClosure()); |
| |
| quit_inner_loop_ = run_loop.QuitClosure(); |
| |
| auto weak_this = weak_ptr_factory_.GetWeakPtr(); |
| run_loop.Run(); |
| if (!weak_this) |
| return false; |
| |
| // If the inner-loop quit closure is still set then we timed out. |
| bool success = !quit_inner_loop_; |
| quit_inner_loop_.Reset(); |
| |
| return success; |
| } |
| |
| bool PrintViewManagerBase::OpportunisticallyCreatePrintJob(int cookie) { |
| if (print_job_) |
| return true; |
| |
| if (!cookie) { |
| // Out of sync. It may happen since we are completely asynchronous. Old |
| // spurious message can happen if one of the processes is overloaded. |
| return false; |
| } |
| |
| // The job was initiated by a script. Time to get the corresponding worker |
| // thread. |
| std::unique_ptr<PrinterQuery> queued_query = queue()->PopPrinterQuery(cookie); |
| if (!queued_query) { |
| // Out of sync. It may happen since we are completely asynchronous, when |
| // an error occurs during the first setup of a print job. |
| return false; |
| } |
| |
| if (!SetupNewPrintJob(std::move(queued_query))) { |
| // Don't kill anything. |
| return false; |
| } |
| |
| #if BUILDFLAG(ENTERPRISE_CONTENT_ANALYSIS) |
| // Don't start printing if enterprise checks are being performed to check if |
| // printing is allowed, or if content analysis is going to take place right |
| // before starting `print_job_`. |
| if (analyzing_content_ || content_analysis_before_printing_document_) { |
| return true; |
| } |
| #endif |
| |
| // Settings are already loaded. Go ahead. This will set |
| // print_job_->is_job_pending() to true. |
| print_job_->StartPrinting(); |
| return true; |
| } |
| |
| bool PrintViewManagerBase::IsCrashed() { |
| return web_contents()->IsCrashed(); |
| } |
| |
| void PrintViewManagerBase::SetPrintingRFH(content::RenderFrameHost* rfh) { |
| // Do not allow any print operation during prerendering. |
| if (rfh->GetLifecycleState() == |
| content::RenderFrameHost::LifecycleState::kPrerendering) { |
| // If we come here during prerendering, it's because either: |
| // 1) Renderer did something unexpected (indicates a compromised renderer), |
| // or 2) Some plumbing in the browser side is wrong (wrong code). |
| // mojo::ReportBadMessage() below will let the renderer crash for 1), or |
| // will hit DCHECK for 2). |
| mojo::ReportBadMessage( |
| "The print's message shouldn't reach here during prerendering."); |
| return; |
| } |
| DCHECK(!printing_rfh_); |
| // Protect against future unsafety, since printing_rfh_ is cleared by |
| // RenderFrameDeleted(), which will not be called if the render frame is not |
| // live. |
| CHECK(rfh->IsRenderFrameLive()); |
| printing_rfh_ = rfh; |
| } |
| |
| bool PrintViewManagerBase::StartPrintCommon(content::RenderFrameHost* rfh) { |
| // Remember the ID for `rfh`, to enable checking that the `RenderFrameHost` |
| // is still valid after a possible inner message loop runs in |
| // `DisconnectFromCurrentPrintJob()`. |
| content::GlobalRenderFrameHostId rfh_id = rfh->GetGlobalId(); |
| auto weak_this = weak_ptr_factory_.GetWeakPtr(); |
| DisconnectFromCurrentPrintJob(); |
| if (!weak_this) { |
| return false; |
| } |
| |
| // Don't print / print preview crashed tabs. |
| if (IsCrashed()) { |
| return false; |
| } |
| |
| // Don't print if `rfh` is no longer live. |
| if (!content::RenderFrameHost::FromID(rfh_id) || !rfh->IsRenderFrameLive()) { |
| return false; |
| } |
| |
| #if BUILDFLAG(ENABLE_OOP_PRINTING) |
| if (ShouldPrintJobOop()) { |
| // Register this worker so that the service persists as long as the user |
| // keeps the system print dialog UI displayed. |
| if (!RegisterSystemPrintClient()) { |
| return false; |
| } |
| } |
| #endif |
| |
| SetPrintingRFH(rfh); |
| return true; |
| } |
| |
| #if BUILDFLAG(ENABLE_OOP_PRINTING) |
| bool PrintViewManagerBase::RegisterSystemPrintClient() { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| DCHECK(ShouldPrintJobOop()); |
| DCHECK(!query_with_ui_client_id().has_value()); |
| query_with_ui_client_id_ = |
| PrintBackendServiceManager::GetInstance().RegisterQueryWithUiClient(); |
| bool registered = query_with_ui_client_id().has_value(); |
| if (!registered) { |
| PRINTER_LOG(DEBUG) << "Unable to initiate a concurrent system print dialog"; |
| } |
| for (auto& observer : GetTestObservers()) { |
| observer.OnRegisterSystemPrintClient(registered); |
| } |
| return registered; |
| } |
| |
| void PrintViewManagerBase::UnregisterSystemPrintClient() { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| DCHECK(ShouldPrintJobOop()); |
| if (!query_with_ui_client_id().has_value()) { |
| return; |
| } |
| |
| PrintBackendServiceManager::GetInstance().UnregisterClient( |
| query_with_ui_client_id().value()); |
| query_with_ui_client_id_.reset(); |
| } |
| #endif // BUILDFLAG(ENABLE_OOP_PRINTING) |
| |
| void PrintViewManagerBase::ReleasePrinterQuery() { |
| int current_cookie = cookie(); |
| if (!current_cookie) |
| return; |
| |
| set_cookie(PrintSettings::NewInvalidCookie()); |
| |
| PrintJobManager* print_job_manager = g_browser_process->print_job_manager(); |
| // May be NULL in tests. |
| if (!print_job_manager) |
| return; |
| |
| // Let `printer_query` go out of scope to release it. |
| std::unique_ptr<PrinterQuery> printer_query = |
| queue()->PopPrinterQuery(current_cookie); |
| } |
| |
| void PrintViewManagerBase::CompleteScriptedPrint( |
| content::RenderFrameHost* rfh, |
| mojom::ScriptedPrintParamsPtr params, |
| ScriptedPrintCallback callback) { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| content::RenderProcessHost* render_process_host = rfh->GetProcess(); |
| auto callback_wrapper = base::BindOnce( |
| &PrintViewManagerBase::ScriptedPrintReply, weak_ptr_factory_.GetWeakPtr(), |
| std::move(callback), render_process_host->GetDeprecatedID()); |
| #if BUILDFLAG(IS_WIN) && BUILDFLAG(GOOGLE_CHROME_BRANDING) |
| DisableThirdPartyBlocking(); |
| #endif |
| |
| std::unique_ptr<PrinterQuery> printer_query = |
| queue()->PopPrinterQuery(params->cookie); |
| if (!printer_query) |
| printer_query = queue()->CreatePrinterQuery(rfh->GetGlobalId()); |
| |
| auto* printer_query_ptr = printer_query.get(); |
| printer_query_ptr->GetSettingsFromUser( |
| params->expected_pages_count, params->has_selection, params->margin_type, |
| params->is_scripted, !render_process_host->IsPdf(), |
| base::BindOnce(&OnDidScriptedPrint, queue_, std::move(printer_query), |
| std::move(callback_wrapper))); |
| } |
| |
| #if BUILDFLAG(ENTERPRISE_CONTENT_ANALYSIS) |
| void PrintViewManagerBase::CompletePrintDocumentAfterContentAnalysis( |
| scoped_refptr<base::RefCountedMemory> print_data, |
| const gfx::Size& page_size, |
| const gfx::Rect& content_area, |
| const gfx::Point& offsets, |
| bool allowed) { |
| if (!allowed || IsCrashed()) { |
| ReleasePrinterQuery(); |
| print_job_->CleanupAfterContentAnalysisDenial(); |
| TerminatePrintJob(/*cancel=*/true); |
| return; |
| } |
| print_job_->StartPrinting(); |
| PrintDocument(print_data, page_size, content_area, offsets); |
| } |
| |
| void PrintViewManagerBase::ContentAnalysisBeforePrintingDocument( |
| enterprise_connectors::ContentAnalysisDelegate::Data scanning_data, |
| scoped_refptr<base::RefCountedMemory> print_data, |
| const gfx::Size& page_size, |
| const gfx::Rect& content_area, |
| const gfx::Point& offsets) { |
| scanning_data.printer_name = |
| base::UTF16ToUTF8(print_job_->document()->settings().device_name()); |
| |
| auto on_verdict = base::BindOnce( |
| &PrintViewManagerBase::CompletePrintDocumentAfterContentAnalysis, |
| weak_ptr_factory_.GetWeakPtr(), print_data, page_size, content_area, |
| offsets); |
| |
| enterprise_data_protection::PrintIfAllowedByPolicy( |
| print_data, web_contents()->GetOutermostWebContents(), |
| std::move(scanning_data), std::move(on_verdict)); |
| } |
| |
| void PrintViewManagerBase::set_analyzing_content(bool analyzing) { |
| PRINTER_LOG(EVENT) << (analyzing ? "Starting" : "Completed") |
| << " content analysis"; |
| analyzing_content_ = analyzing; |
| } |
| |
| void PrintViewManagerBase::set_content_analysis_before_printing_document( |
| PrintDocumentCallback callback) { |
| content_analysis_before_printing_document_ = std::move(callback); |
| } |
| #endif // BUILDFLAG(ENTERPRISE_CONTENT_ANALYSIS) |
| |
| } // namespace printing |