| // Copyright 2022 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/printer_query_oop.h" |
| |
| #include <memory> |
| #include <utility> |
| |
| #include "base/check_op.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "base/types/expected.h" |
| #include "chrome/browser/browser_process.h" |
| #include "chrome/browser/printing/oop_features.h" |
| #include "chrome/browser/printing/print_backend_service_manager.h" |
| #include "chrome/browser/printing/print_job_worker_oop.h" |
| #include "components/device_event_log/device_event_log.h" |
| #include "content/public/browser/global_routing_id.h" |
| #include "content/public/browser/web_contents.h" |
| #include "printing/buildflags/buildflags.h" |
| |
| namespace printing { |
| |
| PrinterQueryOop::PrinterQueryOop(content::GlobalRenderFrameHostId rfh_id) |
| : PrinterQuery(rfh_id) {} |
| |
| PrinterQueryOop::~PrinterQueryOop() = default; |
| |
| std::unique_ptr<PrintJobWorker> PrinterQueryOop::TransferContextToNewWorker( |
| PrintJob* print_job) { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| // TODO(crbug.com/40256381) Do extra setup on the worker as needed for |
| // supporting OOP system print dialogs. |
| return CreatePrintJobWorkerOop(print_job); |
| } |
| |
| #if BUILDFLAG(IS_WIN) |
| void PrinterQueryOop::UpdatePrintableArea( |
| PrintSettings* print_settings, |
| OnDidUpdatePrintableAreaCallback callback) { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| |
| std::string printer_name = base::UTF16ToUTF8(print_settings->device_name()); |
| PRINTER_LOG(EVENT) << "Updating paper printable area via service for " |
| << printer_name; |
| |
| PrintBackendServiceManager& service_mgr = |
| PrintBackendServiceManager::GetInstance(); |
| |
| // Caller is required to ensure `print_settings` stays alive until `callback` |
| // runs. |
| service_mgr.GetPaperPrintableArea( |
| printer_name, print_settings->requested_media(), |
| base::BindOnce(&PrinterQueryOop::OnDidGetPaperPrintableArea, |
| weak_factory_.GetWeakPtr(), print_settings, |
| std::move(callback))); |
| } |
| #endif |
| |
| void PrinterQueryOop::SetClientId( |
| PrintBackendServiceManager::ClientId client_id) { |
| query_with_ui_client_id_ = client_id; |
| } |
| |
| void PrinterQueryOop::OnDidUseDefaultSettings( |
| SettingsCallback callback, |
| base::expected<PrintSettings, mojom::ResultCode> print_settings) { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| mojom::ResultCode result; |
| if (!print_settings.has_value()) { |
| result = print_settings.error(); |
| DCHECK_NE(result, mojom::ResultCode::kSuccess); |
| PRINTER_LOG(ERROR) << "Error trying to use default settings via service: " |
| << result; |
| |
| // TODO(crbug.com/40561724) Fill in support for handling of access-denied |
| // result code. Blocked on crbug.com/1243873 for Windows. |
| } else { |
| VLOG(1) << "Use default settings from service complete"; |
| result = mojom::ResultCode::kSuccess; |
| printing_context()->SetPrintSettings(print_settings.value()); |
| } |
| |
| InvokeSettingsCallback(std::move(callback), result); |
| } |
| |
| #if BUILDFLAG(ENABLE_OOP_BASIC_PRINT_DIALOG) |
| void PrinterQueryOop::OnDidAskUserForSettings( |
| SettingsCallback callback, |
| base::expected<PrintSettings, mojom::ResultCode> print_settings) { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| mojom::ResultCode result; |
| if (print_settings.has_value()) { |
| VLOG(1) << "Ask user for settings from service complete"; |
| result = mojom::ResultCode::kSuccess; |
| printing_context()->SetPrintSettings(print_settings.value()); |
| |
| // Use the same PrintBackendService for querying and printing, so that the |
| // same device context can be used with both. |
| print_document_client_id_ = |
| PrintBackendServiceManager::GetInstance() |
| .RegisterPrintDocumentClientReusingClientRemote( |
| *query_with_ui_client_id_); |
| if (!print_document_client_id_.has_value()) { |
| // A failure after getting settings, override result to failure. |
| result = mojom::ResultCode::kFailed; |
| PRINTER_LOG(ERROR) |
| << "Error after getting settings due to client registration failure; " |
| "service or renderer likely has terminated"; |
| } |
| } else { |
| result = print_settings.error(); |
| DCHECK_NE(result, mojom::ResultCode::kSuccess); |
| if (result != mojom::ResultCode::kCanceled) { |
| PRINTER_LOG(ERROR) << "Error getting settings from user via service: " |
| << result; |
| } |
| |
| // TODO(crbug.com/40561724) Fill in support for handling of access-denied |
| // result code. Blocked on crbug.com/1243873 for Windows. |
| } |
| |
| InvokeSettingsCallback(std::move(callback), result); |
| } |
| #else // BUILDFLAG(ENABLE_OOP_BASIC_PRINT_DIALOG) |
| void PrinterQueryOop::OnDidAskUserForSettings( |
| SettingsCallback callback, |
| std::unique_ptr<PrintSettings> new_settings, |
| mojom::ResultCode result) { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| if (result == mojom::ResultCode::kSuccess) { |
| // Want the same PrintBackend service as the query so that we use the same |
| // device context. |
| print_document_client_id_ = |
| PrintBackendServiceManager::GetInstance() |
| .RegisterPrintDocumentClientReusingClientRemote( |
| *query_with_ui_client_id_); |
| if (!print_document_client_id_.has_value()) { |
| // A failure after getting settings, override result to failure. |
| result = mojom::ResultCode::kFailed; |
| PRINTER_LOG(ERROR) |
| << "Error after getting settings due to client registration failure; " |
| "service or renderer likely has terminated"; |
| } |
| } |
| std::move(callback).Run(std::move(new_settings), result); |
| } |
| #endif // BUILDFLAG(ENABLE_OOP_BASIC_PRINT_DIALOG) |
| |
| void PrinterQueryOop::UseDefaultSettings(SettingsCallback callback) { |
| #if BUILDFLAG(ENABLE_OOP_BASIC_PRINT_DIALOG) |
| CHECK(query_with_ui_client_id_.has_value()); |
| |
| PRINTER_LOG(EVENT) << "Using printer default settings via service"; |
| |
| // Any settings selected from the system dialog could need to be retained |
| // for printing, so establish a printing context. |
| CHECK(!context_id_.has_value()); |
| SendEstablishPrintingContext(*query_with_ui_client_id_, |
| /*printer_name=*/std::string()); |
| SendUseDefaultSettings(std::move(callback)); |
| #else |
| // `PrintingContextLinux::UseDefaultSettings()` is to be called prior to |
| // `AskUserForSettings()` to establish a base device context. If the system |
| // print dialog will be invoked from within the browser process, then that |
| // default setup needs to happen in browser as well. |
| PrinterQuery::UseDefaultSettings(std::move(callback)); |
| #endif |
| } |
| |
| void PrinterQueryOop::GetSettingsWithUI(uint32_t document_page_count, |
| bool has_selection, |
| bool is_scripted, |
| SettingsCallback callback) { |
| // Save the print target type from the settings, since this will be needed |
| // later when printing is started. |
| print_from_system_dialog_ = true; |
| |
| #if BUILDFLAG(ENABLE_OOP_BASIC_PRINT_DIALOG) |
| PRINTER_LOG(EVENT) << "Getting printer settings from user via service"; |
| SendAskUserForSettings(document_page_count, has_selection, is_scripted, |
| std::move(callback)); |
| #else |
| // Invoke the browser version of getting settings with the system UI: |
| // - macOS: It is impossible to invoke a system dialog UI from a service |
| // utility and have that dialog be application modal for a window that |
| // was launched by the browser process. |
| // - Linux: TODO(crbug.com/40561724) Determine if Linux Wayland can be |
| // made |
| // to have a system dialog be modal against an application window in the |
| // browser process. |
| // - Other platforms don't have a system print UI or do not use OOP |
| // printing, so this does not matter. |
| PrinterQuery::GetSettingsWithUI( |
| document_page_count, has_selection, is_scripted, |
| base::BindOnce(&PrinterQueryOop::OnDidAskUserForSettings, |
| weak_factory_.GetWeakPtr(), std::move(callback))); |
| #endif |
| } |
| |
| void PrinterQueryOop::UpdatePrintSettings(base::Value::Dict new_settings, |
| SettingsCallback callback) { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| |
| // Do not take a const reference, as `new_settings` will be modified below. |
| std::string device_name = *new_settings.FindString(kSettingDeviceName); |
| |
| // Remember if this is from system print dialog, since this will be needed |
| // later when printing is started. |
| print_from_system_dialog_ = |
| new_settings.FindBool(kSettingShowSystemDialog).value_or(false); |
| |
| // A device name is required for printing documents. If the device name is |
| // empty then this is for a system print dialog, for which a destination is |
| // not yet known. |
| PrintBackendServiceManager& service_mgr = |
| PrintBackendServiceManager::GetInstance(); |
| PrintBackendServiceManager::ClientId client_id; |
| std::string printer_name; |
| if (print_from_system_dialog_) { |
| CHECK(!print_document_client_id_.has_value()); |
| client_id = *query_with_ui_client_id_; |
| #if BUILDFLAG(ENABLE_OOP_BASIC_PRINT_DIALOG) && BUILDFLAG(IS_WIN) |
| // `PrintingContextWin::UpdatePrintSettings()` is special because it can |
| // invoke `AskUserForSettings()` and cause a system dialog to be displayed. |
| // Running a dialog causes an exit to webpage-initiated fullscreen. |
| // http://crbug.com/728276 |
| content::WebContents* web_contents = GetWebContents(); |
| if (web_contents && web_contents->IsFullscreen()) { |
| web_contents->ExitFullscreen(true); |
| } |
| #endif |
| } else { |
| // Print the document from Print Preview. |
| CHECK(!query_with_ui_client_id_.has_value()); |
| CHECK(!print_document_client_id_.has_value()); |
| |
| print_document_client_id_ = |
| service_mgr.RegisterPrintDocumentClient(device_name); |
| client_id = *print_document_client_id_; |
| printer_name = device_name; |
| } |
| SendEstablishPrintingContext(client_id, printer_name); |
| |
| VLOG(1) << "Updating print settings via service for " << device_name; |
| |
| service_mgr.UpdatePrintSettings( |
| client_id, printer_name, *context_id_, std::move(new_settings), |
| base::BindOnce(&PrinterQueryOop::OnDidUpdatePrintSettings, |
| weak_factory_.GetWeakPtr(), device_name, |
| std::move(callback))); |
| } |
| |
| void PrinterQueryOop::OnDidUpdatePrintSettings( |
| const std::string& device_name, |
| SettingsCallback callback, |
| base::expected<PrintSettings, mojom::ResultCode> print_settings) { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| mojom::ResultCode result; |
| if (!print_settings.has_value()) { |
| result = print_settings.error(); |
| DCHECK_NE(result, mojom::ResultCode::kSuccess); |
| PRINTER_LOG(ERROR) << "Error updating print settings via service for `" |
| << device_name << "`: " << result; |
| |
| // `PrintViewManagerBase` owns the client ID, so `PrinterQueryOop` must not |
| // unregister it. Just drop any local reference to it. |
| query_with_ui_client_id_.reset(); |
| |
| // With the failure to update the setting, the registered client must be |
| // released. The context ID is also no longer relevant to use. |
| if (print_document_client_id_.has_value()) { |
| PrintBackendServiceManager::GetInstance().UnregisterClient( |
| print_document_client_id_.value()); |
| print_document_client_id_.reset(); |
| CHECK(context_id_.has_value()); |
| context_id_.reset(); |
| } |
| |
| // TODO(crbug.com/40561724) Fill in support for handling of access-denied |
| // result code. |
| } else { |
| VLOG(1) << "Update print settings via service complete for " << device_name; |
| result = mojom::ResultCode::kSuccess; |
| printing_context()->SetPrintSettings(print_settings.value()); |
| |
| if (query_with_ui_client_id_.has_value()) { |
| // Use the same PrintBackendService for querying and printing, so that the |
| // same device context can be used with both. |
| CHECK(!print_document_client_id_.has_value()); |
| print_document_client_id_ = |
| PrintBackendServiceManager::GetInstance() |
| .RegisterPrintDocumentClientReusingClientRemote( |
| *query_with_ui_client_id_); |
| if (!print_document_client_id_.has_value()) { |
| // A failure after getting settings, override result to failure. |
| result = mojom::ResultCode::kFailed; |
| PRINTER_LOG(ERROR) |
| << "Error after updating print settings due to client registration " |
| "failure; service or renderer likely has terminated"; |
| } |
| } |
| } |
| InvokeSettingsCallback(std::move(callback), result); |
| } |
| |
| #if BUILDFLAG(IS_WIN) |
| void PrinterQueryOop::OnDidGetPaperPrintableArea( |
| PrintSettings* print_settings, |
| OnDidUpdatePrintableAreaCallback callback, |
| const gfx::Rect& printable_area_um) { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| if (printable_area_um.IsEmpty()) { |
| std::move(callback).Run(/*success=*/false); |
| return; |
| } |
| |
| print_settings->UpdatePrinterPrintableArea(printable_area_um); |
| std::move(callback).Run(/*success=*/true); |
| } |
| #endif |
| |
| void PrinterQueryOop::SendEstablishPrintingContext( |
| PrintBackendServiceManager::ClientId client_id, |
| const std::string& printer_name) { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| DCHECK(ShouldPrintJobOop()); |
| |
| DVLOG(1) << "Establishing printing context for system print"; |
| |
| #if BUILDFLAG(ENABLE_OOP_BASIC_PRINT_DIALOG) |
| content::WebContents* web_contents = GetWebContents(); |
| gfx::NativeView parent_view = |
| web_contents ? web_contents->GetTopLevelNativeWindow() : nullptr; |
| #endif |
| |
| PrintBackendServiceManager& service_mgr = |
| PrintBackendServiceManager::GetInstance(); |
| |
| context_id_ = service_mgr.EstablishPrintingContext(client_id, printer_name |
| #if BUILDFLAG(ENABLE_OOP_BASIC_PRINT_DIALOG) |
| , |
| parent_view |
| #endif |
| ); |
| } |
| |
| void PrinterQueryOop::SendUseDefaultSettings(SettingsCallback callback) { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| DCHECK(ShouldPrintJobOop()); |
| CHECK(query_with_ui_client_id_.has_value()); |
| |
| PrintBackendServiceManager& service_mgr = |
| PrintBackendServiceManager::GetInstance(); |
| |
| service_mgr.UseDefaultSettings( |
| *query_with_ui_client_id_, *context_id_, |
| base::BindOnce(&PrinterQueryOop::OnDidUseDefaultSettings, |
| weak_factory_.GetWeakPtr(), std::move(callback))); |
| } |
| |
| #if BUILDFLAG(ENABLE_OOP_BASIC_PRINT_DIALOG) |
| void PrinterQueryOop::SendAskUserForSettings(uint32_t document_page_count, |
| bool has_selection, |
| bool is_scripted, |
| SettingsCallback callback) { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| DCHECK(ShouldPrintJobOop()); |
| |
| if (document_page_count > kMaxPageCount) { |
| InvokeSettingsCallback(std::move(callback), mojom::ResultCode::kFailed); |
| return; |
| } |
| |
| content::WebContents* web_contents = GetWebContents(); |
| |
| // Running a dialog causes an exit to webpage-initiated fullscreen. |
| // http://crbug.com/728276 |
| if (web_contents && web_contents->IsFullscreen()) { |
| web_contents->ExitFullscreen(true); |
| } |
| |
| PrintBackendServiceManager& service_mgr = |
| PrintBackendServiceManager::GetInstance(); |
| service_mgr.AskUserForSettings( |
| *query_with_ui_client_id_, *context_id_, document_page_count, |
| has_selection, is_scripted, |
| base::BindOnce(&PrinterQueryOop::OnDidAskUserForSettings, |
| weak_factory_.GetWeakPtr(), std::move(callback))); |
| } |
| #endif // BUILDFLAG(ENABLE_OOP_BASIC_PRINT_DIALOG) |
| |
| std::unique_ptr<PrintJobWorkerOop> PrinterQueryOop::CreatePrintJobWorkerOop( |
| PrintJob* print_job) { |
| return std::make_unique<PrintJobWorkerOop>( |
| std::move(printing_context_delegate_), std::move(printing_context_), |
| print_document_client_id_, context_id_, print_job, |
| print_from_system_dialog_); |
| } |
| |
| } // namespace printing |