| // Copyright 2013 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 "chrome/browser/printing/print_view_manager_base.h" |
| |
| #include <memory> |
| #include <utility> |
| |
| #include "base/auto_reset.h" |
| #include "base/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/run_loop.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "base/task/current_thread.h" |
| #include "base/task/single_thread_task_runner.h" |
| #include "base/threading/thread_task_runner_handle.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/chrome_notification_types.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/simple_message_box.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/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/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/notification_details.h" |
| #include "content/public/browser/notification_service.h" |
| #include "content/public/browser/notification_source.h" |
| #include "content/public/browser/render_frame_host.h" |
| #include "content/public/browser/render_process_host.h" |
| #include "content/public/browser/render_view_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 "ui/base/l10n/l10n_util.h" |
| |
| #if defined(OS_WIN) |
| #include "printing/printing_features.h" |
| #endif |
| |
| #if BUILDFLAG(ENABLE_PRINT_PREVIEW) |
| #include "chrome/browser/printing/print_error_dialog.h" |
| #include "chrome/browser/printing/print_view_manager.h" |
| #include "components/prefs/pref_service.h" |
| #endif |
| |
| #if defined(OS_WIN) && BUILDFLAG(GOOGLE_CHROME_BRANDING) |
| #include "chrome/browser/win/conflicts/module_database.h" |
| #endif |
| |
| #if BUILDFLAG(IS_CHROMEOS_LACROS) |
| #include "base/callback_helpers.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "chrome/grit/generated_resources.h" |
| #include "chromeos/lacros/lacros_service.h" |
| #include "printing/print_settings.h" |
| #include "printing/printing_utils.h" |
| #include "ui/base/l10n/l10n_util.h" |
| #endif |
| |
| namespace printing { |
| |
| namespace { |
| |
| using PrintSettingsCallback = |
| base::OnceCallback<void(std::unique_ptr<PrinterQuery>)>; |
| |
| #if BUILDFLAG(IS_CHROMEOS_LACROS) |
| crosapi::mojom::PrintJobPtr PrintJobToMojom(int job_id, |
| PrintedDocument* document, |
| PrintJob::Source source, |
| const std::string& source_id) { |
| std::u16string title = SimplifyDocumentTitle(document->name()); |
| if (title.empty()) { |
| title = SimplifyDocumentTitle( |
| l10n_util::GetStringUTF16(IDS_DEFAULT_PRINT_DOCUMENT_TITLE)); |
| } |
| const PrintSettings& settings = document->settings(); |
| int duplex = static_cast<int>(settings.duplex_mode()); |
| DCHECK(duplex >= 0); |
| DCHECK(duplex < 3); |
| return crosapi::mojom::PrintJob::New( |
| base::UTF16ToUTF8(settings.device_name()), base::UTF16ToUTF8(title), |
| job_id, document->page_count(), source, source_id, settings.color(), |
| static_cast<crosapi::mojom::PrintJob::DuplexMode>(duplex), |
| settings.requested_media().size_microns, |
| settings.requested_media().vendor_id, settings.copies()); |
| } |
| #endif |
| |
| void ShowWarningMessageBox(const std::u16string& message) { |
| // Runs always on the UI thread. |
| static bool is_dialog_shown = false; |
| if (is_dialog_shown) |
| return; |
| // Block opening dialog from nested task. |
| base::AutoReset<bool> auto_reset(&is_dialog_shown, true); |
| |
| chrome::ShowWarningMessageBox(nullptr, std::u16string(), message); |
| } |
| |
| #if BUILDFLAG(ENABLE_PRINT_PREVIEW) |
| void OnPrintSettingsDoneWrapper(PrintSettingsCallback settings_callback, |
| std::unique_ptr<PrinterQuery> query) { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::IO); |
| |
| content::GetUIThreadTaskRunner({})->PostTask( |
| FROM_HERE, |
| base::BindOnce(std::move(settings_callback), std::move(query))); |
| } |
| |
| void CreateQueryWithSettings(base::Value job_settings, |
| int render_process_id, |
| int render_frame_id, |
| scoped_refptr<PrintQueriesQueue> queue, |
| PrintSettingsCallback callback) { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::IO); |
| |
| PrintSettingsCallback callback_wrapper = |
| base::BindOnce(OnPrintSettingsDoneWrapper, std::move(callback)); |
| std::unique_ptr<printing::PrinterQuery> printer_query = |
| queue->CreatePrinterQuery(render_process_id, render_frame_id); |
| auto* printer_query_ptr = printer_query.get(); |
| printer_query_ptr->SetSettings( |
| std::move(job_settings), |
| base::BindOnce(std::move(callback_wrapper), std::move(printer_query))); |
| } |
| #endif // BUILDFLAG(ENABLE_PRINT_PREVIEW) |
| |
| void GetDefaultPrintSettingsReplyOnIO( |
| scoped_refptr<PrintQueriesQueue> queue, |
| std::unique_ptr<PrinterQuery> printer_query, |
| mojom::PrintManagerHost::GetDefaultPrintSettingsCallback callback) { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::IO); |
| mojom::PrintParamsPtr params = mojom::PrintParams::New(); |
| if (printer_query && |
| printer_query->last_status() == mojom::ResultCode::kSuccess) { |
| RenderParamsFromPrintSettings(printer_query->settings(), params.get()); |
| params->document_cookie = printer_query->cookie(); |
| } |
| |
| content::GetUIThreadTaskRunner({})->PostTask( |
| FROM_HERE, base::BindOnce(std::move(callback), std::move(params))); |
| |
| // If printing was enabled. |
| if (printer_query) { |
| // If user hasn't cancelled. |
| if (printer_query->cookie() && printer_query->settings().dpi()) { |
| queue->QueuePrinterQuery(std::move(printer_query)); |
| } else { |
| printer_query->StopWorker(); |
| } |
| } |
| } |
| |
| void GetDefaultPrintSettingsOnIO( |
| mojom::PrintManagerHost::GetDefaultPrintSettingsCallback callback, |
| scoped_refptr<PrintQueriesQueue> queue, |
| int process_id, |
| int routing_id) { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::IO); |
| |
| std::unique_ptr<PrinterQuery> printer_query = queue->PopPrinterQuery(0); |
| if (!printer_query) |
| printer_query = queue->CreatePrinterQuery(process_id, routing_id); |
| |
| // 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->GetSettings( |
| PrinterQuery::GetSettingsAskParam::DEFAULTS, 0, false, |
| printing::mojom::MarginType::kDefaultMargins, false, false, |
| base::BindOnce(&GetDefaultPrintSettingsReplyOnIO, queue, |
| std::move(printer_query), std::move(callback))); |
| } |
| |
| mojom::PrintPagesParamsPtr CreateEmptyPrintPagesParamsPtr() { |
| auto params = mojom::PrintPagesParams::New(); |
| params->params = mojom::PrintParams::New(); |
| return params; |
| } |
| |
| #if BUILDFLAG(ENABLE_PRINT_PREVIEW) |
| #if defined(OS_WIN) |
| void NotifySystemDialogCancelled(base::WeakPtr<PrintViewManagerBase> manager) { |
| if (manager) |
| manager->SystemDialogCancelled(); |
| } |
| #endif // defined(OS_WIN) |
| |
| void UpdatePrintSettingsReplyOnIO( |
| scoped_refptr<PrintQueriesQueue> queue, |
| std::unique_ptr<PrinterQuery> printer_query, |
| mojom::PrintManagerHost::UpdatePrintSettingsCallback callback, |
| base::WeakPtr<PrintViewManagerBase> manager) { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::IO); |
| DCHECK(printer_query); |
| mojom::PrintPagesParamsPtr params = CreateEmptyPrintPagesParamsPtr(); |
| if (printer_query->last_status() == mojom::ResultCode::kSuccess) { |
| RenderParamsFromPrintSettings(printer_query->settings(), |
| params->params.get()); |
| params->params->document_cookie = printer_query->cookie(); |
| params->pages = PageRange::GetPages(printer_query->settings().ranges()); |
| } |
| bool canceled = printer_query->last_status() == mojom::ResultCode::kCanceled; |
| #if defined(OS_WIN) |
| if (canceled) { |
| content::GetUIThreadTaskRunner({})->PostTask( |
| FROM_HERE, |
| base::BindOnce(&NotifySystemDialogCancelled, std::move(manager))); |
| } |
| #endif |
| |
| content::GetUIThreadTaskRunner({})->PostTask( |
| FROM_HERE, |
| base::BindOnce(std::move(callback), std::move(params), canceled)); |
| |
| if (printer_query->cookie() && printer_query->settings().dpi()) { |
| queue->QueuePrinterQuery(std::move(printer_query)); |
| } else { |
| printer_query->StopWorker(); |
| } |
| } |
| |
| void UpdatePrintSettingsOnIO( |
| int32_t cookie, |
| mojom::PrintManagerHost::UpdatePrintSettingsCallback callback, |
| scoped_refptr<PrintQueriesQueue> queue, |
| base::Value job_settings, |
| base::WeakPtr<PrintViewManagerBase> manager) { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::IO); |
| std::unique_ptr<PrinterQuery> printer_query = queue->PopPrinterQuery(cookie); |
| if (!printer_query) { |
| printer_query = queue->CreatePrinterQuery( |
| content::ChildProcessHost::kInvalidUniqueID, MSG_ROUTING_NONE); |
| } |
| auto* printer_query_ptr = printer_query.get(); |
| printer_query_ptr->SetSettings( |
| std::move(job_settings), |
| base::BindOnce(&UpdatePrintSettingsReplyOnIO, queue, |
| std::move(printer_query), std::move(callback), |
| std::move(manager))); |
| } |
| #endif // BUILDFLAG(ENABLE_PRINT_PREVIEW) |
| |
| void ScriptedPrintReplyOnIO( |
| scoped_refptr<PrintQueriesQueue> queue, |
| std::unique_ptr<PrinterQuery> printer_query, |
| mojom::PrintManagerHost::ScriptedPrintCallback callback) { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::IO); |
| mojom::PrintPagesParamsPtr params = CreateEmptyPrintPagesParamsPtr(); |
| if (printer_query->last_status() == mojom::ResultCode::kSuccess && |
| printer_query->settings().dpi()) { |
| RenderParamsFromPrintSettings(printer_query->settings(), |
| params->params.get()); |
| params->params->document_cookie = printer_query->cookie(); |
| params->pages = PageRange::GetPages(printer_query->settings().ranges()); |
| } |
| bool has_valid_cookie = params->params->document_cookie; |
| bool has_dpi = !params->params->dpi.IsEmpty(); |
| content::GetUIThreadTaskRunner({})->PostTask( |
| FROM_HERE, base::BindOnce(std::move(callback), std::move(params))); |
| |
| if (has_dpi && has_valid_cookie) { |
| queue->QueuePrinterQuery(std::move(printer_query)); |
| } else { |
| printer_query->StopWorker(); |
| } |
| } |
| |
| void ScriptedPrintOnIO(mojom::ScriptedPrintParamsPtr params, |
| mojom::PrintManagerHost::ScriptedPrintCallback callback, |
| scoped_refptr<PrintQueriesQueue> queue, |
| int process_id, |
| int routing_id) { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::IO); |
| #if defined(OS_WIN) && BUILDFLAG(GOOGLE_CHROME_BRANDING) |
| ModuleDatabase::GetInstance()->DisableThirdPartyBlocking(); |
| #endif |
| |
| std::unique_ptr<PrinterQuery> printer_query = |
| queue->PopPrinterQuery(params->cookie); |
| if (!printer_query) { |
| printer_query = queue->CreatePrinterQuery(process_id, routing_id); |
| } |
| auto* printer_query_ptr = printer_query.get(); |
| printer_query_ptr->GetSettings( |
| PrinterQuery::GetSettingsAskParam::ASK_USER, params->expected_pages_count, |
| params->has_selection, params->margin_type, params->is_scripted, |
| params->is_modifiable, |
| base::BindOnce(&ScriptedPrintReplyOnIO, queue, std::move(printer_query), |
| std::move(callback))); |
| } |
| |
| } // namespace |
| |
| 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(), |
| base::BindRepeating(&PrintViewManagerBase::UpdatePrintingEnabled, |
| weak_ptr_factory_.GetWeakPtr())); |
| } |
| |
| PrintViewManagerBase::~PrintViewManagerBase() { |
| ReleasePrinterQuery(); |
| DisconnectFromCurrentPrintJob(); |
| } |
| |
| bool PrintViewManagerBase::PrintNow(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; |
| |
| // TODO(crbug.com/809738) Register with `PrintBackendServiceManager` when |
| // system print is enabled out-of-process. A corresponding unregister should |
| // go in `ReleasePrintJob()`. |
| |
| SetPrintingRFH(rfh); |
| GetPrintRenderFrame(rfh)->PrintRequestedPages(); |
| |
| for (auto& observer : GetObservers()) |
| observer.OnPrintNow(rfh); |
| |
| return true; |
| } |
| |
| #if BUILDFLAG(ENABLE_PRINT_PREVIEW) |
| void PrintViewManagerBase::PrintForPrintPreview( |
| base::Value job_settings, |
| scoped_refptr<base::RefCountedMemory> print_data, |
| content::RenderFrameHost* rfh, |
| PrinterHandler::PrintCallback callback) { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| PrintSettingsCallback settings_callback = |
| base::BindOnce(&PrintViewManagerBase::OnPrintSettingsDone, |
| weak_ptr_factory_.GetWeakPtr(), print_data, |
| job_settings.FindIntKey(kSettingPreviewPageCount).value(), |
| std::move(callback)); |
| content::GetIOThreadTaskRunner({})->PostTask( |
| FROM_HERE, |
| base::BindOnce(CreateQueryWithSettings, std::move(job_settings), |
| rfh->GetProcess()->GetID(), rfh->GetRoutingID(), queue_, |
| std::move(settings_callback))); |
| } |
| #endif // BUILDFLAG(ENABLE_PRINT_PREVIEW) |
| |
| void PrintViewManagerBase::PrintDocument( |
| scoped_refptr<base::RefCountedMemory> print_data, |
| const gfx::Size& page_size, |
| const gfx::Rect& content_area, |
| const gfx::Point& offsets) { |
| #if defined(OS_WIN) |
| const bool source_is_pdf = |
| !print_job_->document()->settings().is_modifiable(); |
| if (!printing::features::ShouldPrintUsingXps(source_is_pdf)) { |
| // Print using GDI, which first requires conversion to EMF. |
| print_job_->StartConversionToNativeFormat(print_data, page_size, |
| content_area, offsets); |
| 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) |
| void PrintViewManagerBase::OnPrintSettingsDone( |
| scoped_refptr<base::RefCountedMemory> print_data, |
| uint32_t page_count, |
| 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. This should only happen on Windows when |
| // the system dialog is cancelled. |
| if (printer_query->last_status() == mojom::ResultCode::kCanceled) { |
| queue_->QueuePrinterQuery(std::move(printer_query)); |
| #if defined(OS_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()) { |
| content::GetIOThreadTaskRunner({})->PostTask( |
| FROM_HERE, |
| base::BindOnce(&PrinterQuery::StopWorker, std::move(printer_query))); |
| 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, cookie, std::move(callback))); |
| } |
| |
| void PrintViewManagerBase::StartLocalPrintJob( |
| scoped_refptr<base::RefCountedMemory> print_data, |
| uint32_t page_count, |
| int cookie, |
| PrinterHandler::PrintCallback callback) { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| |
| set_cookie(cookie); |
| DidGetPrintedPagesCount(cookie, page_count); |
| |
| if (!PrintJobHasDocument(cookie)) { |
| std::move(callback).Run(base::Value("Failed to print")); |
| return; |
| } |
| |
| #if defined(OS_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()); |
| } |
| |
| void PrintViewManagerBase::UpdatePrintSettingsReply( |
| mojom::PrintManagerHost::UpdatePrintSettingsCallback callback, |
| mojom::PrintPagesParamsPtr params, |
| bool canceled) { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| set_cookie(params->params->document_cookie); |
| std::move(callback).Run(std::move(params), canceled); |
| } |
| |
| #endif // BUILDFLAG(ENABLE_PRINT_PREVIEW) |
| |
| void PrintViewManagerBase::GetDefaultPrintSettingsReply( |
| GetDefaultPrintSettingsCallback callback, |
| mojom::PrintParamsPtr params) { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| set_cookie(params->document_cookie); |
| std::move(callback).Run(std::move(params)); |
| } |
| |
| void PrintViewManagerBase::ScriptedPrintReply( |
| ScriptedPrintCallback callback, |
| int process_id, |
| mojom::PrintPagesParamsPtr params) { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| |
| if (!content::RenderProcessHost::FromID(process_id)) { |
| // Early return if the renderer is not alive. |
| return; |
| } |
| |
| set_cookie(params->params->document_cookie); |
| std::move(callback).Run(std::move(params)); |
| } |
| |
| void PrintViewManagerBase::UpdatePrintingEnabled() { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| // The Unretained() is safe because ForEachFrame() is synchronous. |
| web_contents()->ForEachFrame(base::BindRepeating( |
| &PrintViewManagerBase::SendPrintingEnabled, base::Unretained(this), |
| printing_enabled_.GetValue())); |
| } |
| |
| 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); |
| } |
| |
| #if BUILDFLAG(ENABLE_TAGGED_PDF) |
| 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 |
| |
| 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; |
| } |
| |
| void PrintViewManagerBase::OnComposePdfDone( |
| 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); |
| if (status != mojom::PrintCompositor::Status::kSuccess) { |
| DLOG(ERROR) << "Compositing pdf failed with error " << status; |
| std::move(callback).Run(false); |
| return; |
| } |
| |
| if (!print_job_->document()) { |
| std::move(callback).Run(false); |
| return; |
| } |
| |
| scoped_refptr<base::RefCountedSharedMemoryMapping> data = |
| base::RefCountedSharedMemoryMapping::CreateFromWholeRegion(region); |
| if (!data) { |
| std::move(callback).Run(false); |
| return; |
| } |
| |
| PrintDocument(data, page_size, content_area, physical_offsets); |
| std::move(callback).Run(true); |
| } |
| |
| void PrintViewManagerBase::DidPrintDocument( |
| mojom::DidPrintDocumentParamsPtr params, |
| DidPrintDocumentCallback callback) { |
| if (!PrintJobHasDocument(params->document_cookie)) { |
| std::move(callback).Run(false); |
| return; |
| } |
| |
| const mojom::DidPrintContentParams& content = *params->content; |
| if (!content.metafile_data_region.IsValid()) { |
| NOTREACHED() << "invalid memory handle"; |
| web_contents()->Stop(); |
| std::move(callback).Run(false); |
| return; |
| } |
| |
| if (IsOopifEnabled() && print_job_->document()->settings().is_modifiable()) { |
| auto* client = PrintCompositeClient::FromWebContents(web_contents()); |
| client->DoCompositeDocumentToPdf( |
| params->document_cookie, GetCurrentTargetFrame(), content, |
| base::BindOnce(&PrintViewManagerBase::OnComposePdfDone, |
| weak_ptr_factory_.GetWeakPtr(), 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"; |
| web_contents()->Stop(); |
| std::move(callback).Run(false); |
| return; |
| } |
| |
| PrintDocument(data, params->page_size, params->content_area, |
| params->physical_offsets); |
| std::move(callback).Run(true); |
| } |
| |
| void PrintViewManagerBase::GetDefaultPrintSettings( |
| GetDefaultPrintSettingsCallback callback) { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| if (!printing_enabled_.GetValue()) { |
| GetDefaultPrintSettingsReply(std::move(callback), |
| mojom::PrintParams::New()); |
| return; |
| } |
| |
| content::RenderFrameHost* render_frame_host = GetCurrentTargetFrame(); |
| auto callback_wrapper = |
| base::BindOnce(&PrintViewManagerBase::GetDefaultPrintSettingsReply, |
| weak_ptr_factory_.GetWeakPtr(), std::move(callback)); |
| content::GetIOThreadTaskRunner({})->PostTask( |
| FROM_HERE, |
| base::BindOnce(&GetDefaultPrintSettingsOnIO, std::move(callback_wrapper), |
| queue_, render_frame_host->GetProcess()->GetID(), |
| render_frame_host->GetRoutingID())); |
| } |
| |
| #if BUILDFLAG(ENABLE_PRINT_PREVIEW) |
| void PrintViewManagerBase::UpdatePrintSettings( |
| int32_t cookie, |
| base::Value job_settings, |
| UpdatePrintSettingsCallback callback) { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| if (!printing_enabled_.GetValue()) { |
| UpdatePrintSettingsReply(std::move(callback), |
| CreateEmptyPrintPagesParamsPtr(), false); |
| return; |
| } |
| |
| if (!job_settings.FindIntKey(kSettingPrinterType)) { |
| UpdatePrintSettingsReply(std::move(callback), |
| CreateEmptyPrintPagesParamsPtr(), false); |
| return; |
| } |
| |
| 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.SetIntKey(kSettingRasterizePdfDpi, value); |
| } |
| |
| auto callback_wrapper = |
| base::BindOnce(&PrintViewManagerBase::UpdatePrintSettingsReply, |
| weak_ptr_factory_.GetWeakPtr(), std::move(callback)); |
| content::GetIOThreadTaskRunner({})->PostTask( |
| FROM_HERE, |
| base::BindOnce(&UpdatePrintSettingsOnIO, cookie, |
| std::move(callback_wrapper), queue_, |
| std::move(job_settings), weak_ptr_factory_.GetWeakPtr())); |
| } |
| #endif // BUILDFLAG(ENABLE_PRINT_PREVIEW) |
| |
| 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(CreateEmptyPrintPagesParamsPtr()); |
| return; |
| } |
| int process_id = render_process_host->GetID(); |
| int routing_id = render_frame_host->GetRoutingID(); |
| auto callback_wrapper = base::BindOnce( |
| &PrintViewManagerBase::ScriptedPrintReply, weak_ptr_factory_.GetWeakPtr(), |
| std::move(callback), process_id); |
| content::GetIOThreadTaskRunner({})->PostTask( |
| FROM_HERE, base::BindOnce(&ScriptedPrintOnIO, std::move(params), |
| std::move(callback_wrapper), queue_, process_id, |
| routing_id)); |
| } |
| |
| void PrintViewManagerBase::PrintingFailed(int32_t cookie) { |
| // Note: Not redundant with cookie checks in the same method in other parts of |
| // the class hierarchy. |
| if (!IsValidCookie(cookie)) |
| return; |
| |
| PrintManager::PrintingFailed(cookie); |
| |
| #if BUILDFLAG(ENABLE_PRINT_PREVIEW) |
| ShowPrintErrorDialog(); |
| #endif |
| |
| ReleasePrinterQuery(); |
| } |
| |
| void PrintViewManagerBase::AddObserver(Observer& observer) { |
| observers_.AddObserver(&observer); |
| } |
| |
| void PrintViewManagerBase::RemoveObserver(Observer& observer) { |
| observers_.RemoveObserver(&observer); |
| } |
| |
| void PrintViewManagerBase::ShowInvalidPrinterSettingsError() { |
| base::ThreadTaskRunnerHandle::Get()->PostTask( |
| FROM_HERE, base::BindOnce(&ShowWarningMessageBox, |
| l10n_util::GetStringUTF16( |
| IDS_PRINT_INVALID_PRINTER_SETTINGS))); |
| } |
| |
| 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) |
| SendPrintingEnabled(printing_enabled_.GetValue(), render_frame_host); |
| } |
| |
| void PrintViewManagerBase::DidStartLoading() { |
| UpdatePrintingEnabled(); |
| } |
| |
| 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; |
| |
| printing_rfh_ = nullptr; |
| |
| PrintManager::PrintingRenderFrameDeleted(); |
| ReleasePrinterQuery(); |
| |
| 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 defined(OS_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 |
| |
| void PrintViewManagerBase::OnDocDone(int job_id, PrintedDocument* document) { |
| #if BUILDFLAG(IS_CHROMEOS_LACROS) |
| chromeos::LacrosService* service = chromeos::LacrosService::Get(); |
| if (!service->IsAvailable<crosapi::mojom::LocalPrinter>()) { |
| LOG(ERROR) << "Could not report print job queued"; |
| } else { |
| service->GetRemote<crosapi::mojom::LocalPrinter>()->CreatePrintJob( |
| PrintJobToMojom(job_id, document, print_job_->source(), |
| print_job_->source_id()), |
| base::DoNothing()); |
| } |
| #endif |
| #if defined(OS_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::OnFailed() { |
| 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()->GetMainFrame()->GetRenderViewHost() || |
| !web_contents() |
| ->GetMainFrame() |
| ->GetRenderViewHost() |
| ->IsRenderViewLive()) { |
| 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(); |
| } |
| } |
| |
| bool PrintViewManagerBase::CreateNewPrintJob( |
| 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()->GetMainFrame()->GetRenderViewHost() || |
| !web_contents() |
| ->GetMainFrame() |
| ->GetRenderViewHost() |
| ->IsRenderViewLive()) { |
| return false; |
| } |
| |
| DCHECK(!print_job_); |
| print_job_ = base::MakeRefCounted<PrintJob>(); |
| print_job_->Initialize(std::move(query), RenderSourceName(), number_pages()); |
| #if defined(OS_CHROMEOS) |
| print_job_->SetSource(web_contents()->GetBrowserContext()->IsOffTheRecord() |
| ? PrintJob::Source::PRINT_PREVIEW_INCOGNITO |
| : PrintJob::Source::PRINT_PREVIEW, |
| /*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) { |
| // We don't need the metafile data anymore because the printing is canceled. |
| print_job_->Cancel(); |
| quit_inner_loop_.Reset(); |
| #if defined(OS_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 (!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 happens 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) { |
| NOTREACHED(); |
| return false; |
| } |
| |
| if (!CreateNewPrintJob(std::move(queued_query))) { |
| // Don't kill anything. |
| return false; |
| } |
| |
| // 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; |
| } |
| |
| void PrintViewManagerBase::ReleasePrinterQuery() { |
| int current_cookie = cookie(); |
| if (!current_cookie) |
| return; |
| |
| set_cookie(0); |
| |
| PrintJobManager* print_job_manager = g_browser_process->print_job_manager(); |
| // May be NULL in tests. |
| if (!print_job_manager) |
| return; |
| |
| std::unique_ptr<PrinterQuery> printer_query = |
| queue_->PopPrinterQuery(current_cookie); |
| if (!printer_query) |
| return; |
| content::GetIOThreadTaskRunner({})->PostTask( |
| FROM_HERE, |
| base::BindOnce(&PrinterQuery::StopWorker, std::move(printer_query))); |
| } |
| |
| void PrintViewManagerBase::SendPrintingEnabled(bool enabled, |
| content::RenderFrameHost* rfh) { |
| if (rfh->IsRenderFrameLive()) |
| GetPrintRenderFrame(rfh)->SetPrintingEnabled(enabled); |
| } |
| |
| } // namespace printing |