| // Copyright 2020 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_backend_service_manager.h" |
| |
| #include <memory> |
| #include <string> |
| #include <string_view> |
| #include <type_traits> |
| #include <utility> |
| #include <variant> |
| |
| #include "base/check_op.h" |
| #include "base/containers/contains.h" |
| #include "base/containers/flat_map.h" |
| #include "base/containers/flat_set.h" |
| #include "base/functional/bind.h" |
| #include "base/logging.h" |
| #include "base/memory/scoped_refptr.h" |
| #include "base/notreached.h" |
| #include "base/threading/thread_restrictions.h" |
| #include "base/time/time.h" |
| #include "base/types/expected.h" |
| #include "base/unguessable_token.h" |
| #include "build/build_config.h" |
| #include "chrome/browser/browser_process.h" |
| #include "chrome/grit/generated_resources.h" |
| #include "chrome/services/printing/public/mojom/print_backend_service.mojom.h" |
| #include "components/crash/core/common/crash_keys.h" |
| #include "content/public/browser/browser_thread.h" |
| #include "content/public/browser/service_process_host.h" |
| #include "mojo/public/cpp/bindings/remote.h" |
| #include "printing/backend/print_backend.h" |
| #include "printing/buildflags/buildflags.h" |
| #include "printing/printing_context.h" |
| #include "printing/printing_features.h" |
| |
| #if BUILDFLAG(IS_LINUX) |
| #include "content/public/common/content_switches.h" |
| #include "ui/linux/linux_ui.h" |
| #endif |
| |
| #if BUILDFLAG(IS_WIN) |
| #include "base/win/win_util.h" |
| #include "chrome/browser/printing/printer_xml_parser_impl.h" |
| #include "chrome/services/printing/public/mojom/printer_xml_parser.mojom.h" |
| #include "mojo/public/cpp/bindings/pending_receiver.h" |
| #include "printing/backend/win_helper.h" |
| #include "printing/printed_page_win.h" |
| #include "ui/views/win/hwnd_util.h" |
| #endif |
| |
| #if BUILDFLAG(ENABLE_OOP_BASIC_PRINT_DIALOG) |
| #include "base/notreached.h" |
| #endif |
| |
| namespace printing { |
| |
| namespace { |
| |
| // For fetching remote IDs when there is no printer name. |
| constexpr char kEmptyPrinterName[] = ""; |
| |
| PrintBackendServiceManager* g_print_backend_service_manager_singleton = nullptr; |
| |
| // Helper when dispatching callbacks with base::expected<copyable, copyable>. |
| template <typename T, typename E, typename... Args> |
| requires(std::copy_constructible<T>) |
| void MaybeCloneOrCopy( |
| base::OnceCallback<void(base::expected<T, E>, Args... rest)> cb, |
| const base::expected<T, E>& result, |
| Args... rest) { |
| // Implicit copy of result. |
| std::move(cb).Run(result, rest...); |
| } |
| |
| // Helper when dispatching callbacks with base::expected<!copyable, copyable>. |
| // Copies the the expected type using mojo's Clone(). |
| template <typename T, typename E, typename... Args> |
| requires(!std::copy_constructible<T>) |
| void MaybeCloneOrCopy( |
| base::OnceCallback<void(base::expected<T, E>, Args... rest)> cb, |
| const base::expected<T, E>& result, |
| Args... rest) { |
| base::expected<T, E> clone; |
| if (result.has_value()) { |
| clone = base::ok(result.value().Clone()); |
| } else { |
| clone = base::unexpected(result.error()); |
| } |
| std::move(cb).Run(std::move(clone), rest...); |
| } |
| |
| #if BUILDFLAG(ENABLE_OOP_BASIC_PRINT_DIALOG) |
| // TODO(crbug.com/40561724): Update for other platforms as they are made able |
| // to support modal dialogs from OOP. |
| uint32_t NativeViewToUint(gfx::NativeView view) { |
| #if BUILDFLAG(IS_WIN) |
| return base::win::HandleToUint32(views::HWNDForNativeView(view)); |
| #else |
| NOTREACHED(); |
| #endif |
| } |
| #endif |
| |
| } // namespace |
| |
| PrintBackendServiceManager::CallbackContext::CallbackContext() = default; |
| |
| PrintBackendServiceManager::CallbackContext::CallbackContext( |
| PrintBackendServiceManager::CallbackContext&& other) noexcept = default; |
| |
| PrintBackendServiceManager::CallbackContext::~CallbackContext() = default; |
| |
| PrintBackendServiceManager::ServiceAndCallbackContext:: |
| ServiceAndCallbackContext( |
| CallbackContext callback_context, |
| const mojo::Remote<mojom::PrintBackendService>& backend_service) |
| : context(std::move(callback_context)), service(backend_service) {} |
| |
| PrintBackendServiceManager::ServiceAndCallbackContext:: |
| ~ServiceAndCallbackContext() = default; |
| |
| PrintBackendServiceManager::PrintBackendServiceManager() = default; |
| |
| PrintBackendServiceManager::~PrintBackendServiceManager() = default; |
| |
| // static |
| void PrintBackendServiceManager::LaunchPersistentService() { |
| PrintBackendServiceManager& service_mgr = GetInstance(); |
| // Registering a query client causes a service to be launched. |
| service_mgr.persistent_service_ = true; |
| std::ignore = service_mgr.RegisterQueryClient(); |
| } |
| |
| // static |
| std::string PrintBackendServiceManager::ClientTypeToString( |
| ClientType client_type) { |
| switch (client_type) { |
| case ClientType::kQuery: |
| return "kQuery"; |
| case ClientType::kQueryWithUi: |
| return "kQueryWithUi"; |
| case ClientType::kPrintDocument: |
| return "kPrintDocument"; |
| } |
| } |
| |
| // static |
| void PrintBackendServiceManager::LogCallToRemote( |
| std::string_view name, |
| const CallbackContext& context) { |
| DVLOG(1) << "Sending " << name << " on remote `" << context.remote_id |
| << "`, saved callback ID of " << context.saved_callback_id; |
| } |
| |
| // static |
| void PrintBackendServiceManager::LogCallbackFromRemote( |
| std::string_view name, |
| const CallbackContext& context) { |
| DVLOG(1) << name << " completed for remote `" << context.remote_id |
| << "` saved callback ID " << context.saved_callback_id; |
| } |
| |
| void PrintBackendServiceManager::SetCrashKeys(const std::string& printer_name) { |
| if (sandboxed_service_remote_for_test_) |
| return; |
| |
| // TODO(crbug.com/40777132) Remove local call for driver info, don't want |
| // any residual accesses left into the printer drivers from the browser |
| // process. |
| base::ScopedAllowBlocking allow_blocking; |
| scoped_refptr<PrintBackend> print_backend = |
| PrintBackend::CreateInstance(g_browser_process->GetApplicationLocale()); |
| crash_keys_ = std::make_unique<crash_keys::ScopedPrinterInfo>( |
| printer_name, print_backend->GetPrinterDriverInfo(printer_name)); |
| } |
| |
| PrintBackendServiceManager::ClientId |
| PrintBackendServiceManager::RegisterQueryClient() { |
| return *RegisterClient(ClientType::kQuery, kEmptyPrinterName); |
| } |
| |
| std::optional<PrintBackendServiceManager::ClientId> |
| PrintBackendServiceManager::RegisterQueryWithUiClient() { |
| return RegisterClient(ClientType::kQueryWithUi, kEmptyPrinterName); |
| } |
| PrintBackendServiceManager::ClientId |
| PrintBackendServiceManager::RegisterPrintDocumentClient( |
| const std::string& printer_name) { |
| DCHECK_NE(printer_name, kEmptyPrinterName); |
| return *RegisterClient(ClientType::kPrintDocument, printer_name); |
| } |
| |
| std::optional<PrintBackendServiceManager::ClientId> |
| PrintBackendServiceManager::RegisterPrintDocumentClientReusingClientRemote( |
| ClientId id) { |
| const auto iter = query_with_ui_clients_.find(id); |
| if (iter == query_with_ui_clients_.end()) { |
| // If the client is gone then that suggests that the renderer process |
| // terminated while the system print dialog was displayed. Proceeding |
| // with printing will not be possible in such a case. |
| VLOG(1) << "Registering a print document client reusing client " << id |
| << " failed because that client is no longer registered"; |
| return std::nullopt; |
| } |
| return RegisterClient(ClientType::kPrintDocument, iter->second); |
| } |
| |
| void PrintBackendServiceManager::UnregisterClient(ClientId id) { |
| // Determine which client type has this ID, and remove it once found. |
| std::optional<ClientType> client_type; |
| std::optional<RemoteId> remote_id; |
| if (query_clients_.erase(id) != 0) { |
| client_type = ClientType::kQuery; |
| } else if (query_with_ui_clients_.erase(id) != 0) { |
| client_type = ClientType::kQueryWithUi; |
| } else { |
| for (auto& item : print_document_clients_) { |
| ClientsSet& clients = item.second; |
| if (clients.erase(id) != 0) { |
| client_type = ClientType::kPrintDocument; |
| remote_id = item.first; |
| if (clients.empty()) |
| print_document_clients_.erase(item); |
| break; |
| } |
| } |
| } |
| if (!client_type.has_value()) { |
| DVLOG(1) << "Unknown client ID " << id |
| << ", is client being unregistered multiple times?"; |
| return; |
| } |
| VLOG(1) << "Unregistering client with ID " << id << " (client type " |
| << ClientTypeToString(client_type.value()) |
| << ") from print backend service."; |
| |
| if (!remote_id.has_value()) { |
| remote_id = GetRemoteIdForPrinterName(kEmptyPrinterName); |
| } |
| std::optional<base::TimeDelta> new_timeout = |
| DetermineIdleTimeoutUpdateOnUnregisteredClient(client_type.value(), |
| remote_id.value()); |
| if (new_timeout.has_value()) |
| UpdateServiceIdleTimeoutByRemoteId(remote_id.value(), new_timeout.value()); |
| |
| #if BUILDFLAG(IS_WIN) |
| if (base::FeatureList::IsEnabled(features::kReadPrinterCapabilitiesWithXps) && |
| query_clients_.empty()) { |
| xml_parser_.reset(); |
| } |
| #endif // BUILDFLAG(IS_WIN) |
| } |
| |
| void PrintBackendServiceManager::EnumeratePrinters( |
| mojom::PrintBackendService::EnumeratePrintersCallback callback) { |
| ServiceAndCallbackContext result = |
| GetServiceAndCallbackContextForQuery(kEmptyPrinterName); |
| SaveCallback( |
| GetRemoteSavedEnumeratePrintersCallbacks(result.context.is_sandboxed), |
| result.context.remote_id, result.context.saved_callback_id, |
| std::move(callback)); |
| |
| LogCallToRemote("EnumeratePrinters", result.context); |
| // Safe to use base::Unretained(this) since `this` is a global singleton |
| // which never goes away. |
| result.service.get()->EnumeratePrinters( |
| base::BindOnce(&PrintBackendServiceManager::OnDidEnumeratePrinters, |
| base::Unretained(this), std::move(result.context))); |
| } |
| |
| void PrintBackendServiceManager::FetchCapabilities( |
| const std::string& printer_name, |
| mojom::PrintBackendService::FetchCapabilitiesCallback callback) { |
| ServiceAndCallbackContext result = |
| GetServiceAndCallbackContextForQuery(printer_name); |
| SaveCallback( |
| GetRemoteSavedFetchCapabilitiesCallbacks(result.context.is_sandboxed), |
| result.context.remote_id, result.context.saved_callback_id, |
| std::move(callback)); |
| |
| SetCrashKeys(printer_name); |
| |
| LogCallToRemote("FetchCapabilities", result.context); |
| // Safe to use base::Unretained(this) since `this` is a global singleton |
| // which never goes away. |
| result.service.get()->FetchCapabilities( |
| printer_name, |
| base::BindOnce(&PrintBackendServiceManager::OnDidFetchCapabilities, |
| base::Unretained(this), std::move(result.context))); |
| } |
| |
| void PrintBackendServiceManager::GetDefaultPrinterName( |
| mojom::PrintBackendService::GetDefaultPrinterNameCallback callback) { |
| ServiceAndCallbackContext result = |
| GetServiceAndCallbackContextForQuery(kEmptyPrinterName); |
| SaveCallback( |
| GetRemoteSavedGetDefaultPrinterNameCallbacks(result.context.is_sandboxed), |
| result.context.remote_id, result.context.saved_callback_id, |
| std::move(callback)); |
| |
| LogCallToRemote("GetDefaultPrinterName", result.context); |
| // Safe to use base::Unretained(this) since `this` is a global singleton |
| // which never goes away. |
| result.service.get()->GetDefaultPrinterName( |
| base::BindOnce(&PrintBackendServiceManager::OnDidGetDefaultPrinterName, |
| base::Unretained(this), std::move(result.context))); |
| } |
| |
| #if BUILDFLAG(IS_CHROMEOS) |
| void PrintBackendServiceManager::GetPrinterSemanticCapsAndDefaults( |
| const std::string& printer_name, |
| mojom::PrintBackendService::GetPrinterSemanticCapsAndDefaultsCallback |
| callback) { |
| ServiceAndCallbackContext result = |
| GetServiceAndCallbackContextForQuery(printer_name); |
| SaveCallback(GetRemoteSavedGetPrinterSemanticCapsAndDefaultsCallbacks( |
| result.context.is_sandboxed), |
| result.context.remote_id, result.context.saved_callback_id, |
| std::move(callback)); |
| |
| SetCrashKeys(printer_name); |
| |
| LogCallToRemote("GetPrinterSemanticCapsAndDefaults", result.context); |
| // Safe to use base::Unretained(this) since `this` is a global singleton |
| // which never goes away. |
| result.service.get()->GetPrinterSemanticCapsAndDefaults( |
| printer_name, |
| base::BindOnce( |
| &PrintBackendServiceManager::OnDidGetPrinterSemanticCapsAndDefaults, |
| base::Unretained(this), std::move(result.context))); |
| } |
| #endif // BUILDFLAG(IS_CHROMEOS) |
| |
| #if BUILDFLAG(IS_WIN) |
| void PrintBackendServiceManager::GetPaperPrintableArea( |
| const std::string& printer_name, |
| const PrintSettings::RequestedMedia& media, |
| mojom::PrintBackendService::GetPaperPrintableAreaCallback callback) { |
| ServiceAndCallbackContext result = |
| GetServiceAndCallbackContextForQuery(printer_name); |
| SaveCallback( |
| GetRemoteSavedGetPaperPrintableAreaCallbacks(result.context.is_sandboxed), |
| result.context.remote_id, result.context.saved_callback_id, |
| std::move(callback)); |
| |
| SetCrashKeys(printer_name); |
| |
| LogCallToRemote("GetPaperPrintableArea", result.context); |
| // Safe to use base::Unretained(this) since `this` is a global singleton |
| // which never goes away. |
| result.service.get()->GetPaperPrintableArea( |
| printer_name, media, |
| base::BindOnce(&PrintBackendServiceManager::OnDidGetPaperPrintableArea, |
| base::Unretained(this), std::move(result.context))); |
| } |
| #endif |
| |
| PrintBackendServiceManager::ContextId |
| PrintBackendServiceManager::EstablishPrintingContext( |
| ClientId client_id, |
| const std::string& printer_name |
| #if BUILDFLAG(ENABLE_OOP_BASIC_PRINT_DIALOG) |
| , |
| gfx::NativeView parent_view |
| #endif |
| ) { |
| // This call is intended for use with a service that will not be reclaimed |
| // due to an idle timeout. The client could be used for a system print |
| // dialog and/or for printing a document. Either `kQueryWithUi` or |
| // `kPrintDocument` would satisfy guaranteeing this persists for as long as |
| // could be needed. The particular use case that it is being used with can |
| // be deduced from the provided `printer_name`, since the printer cannot be |
| // known ahead of time for a system print dialog query, but must be known |
| // when printing a document. |
| ServiceAndCallbackContext result = |
| printer_name.empty() ? GetServiceAndCallbackContextForQueryWithUiClient( |
| client_id, kEmptyPrinterName) |
| : GetServiceAndCallbackContextForPrintDocumentClient( |
| client_id, printer_name); |
| |
| LogCallToRemote("EstablishPrintingContext", result.context); |
| ContextId context_id = ContextId(++last_context_id_); |
| result.service.get()->EstablishPrintingContext(*context_id |
| #if BUILDFLAG(ENABLE_OOP_BASIC_PRINT_DIALOG) |
| , |
| NativeViewToUint(parent_view) |
| #endif |
| ); |
| return context_id; |
| } |
| |
| void PrintBackendServiceManager::UseDefaultSettings( |
| ClientId client_id, |
| ContextId context_id, |
| mojom::PrintBackendService::UseDefaultSettingsCallback callback) { |
| // Even though this call does not require a UI, it is used exclusively as |
| // part of preparation for system print. It is called immediately before a |
| // call to `AskDefaultSettings()`. Since that call requires `kQueryWithUi`, |
| // it will behave better to ensure this uses the same type to reuse the same |
| // process. |
| ServiceAndCallbackContext result = |
| GetServiceAndCallbackContextForQueryWithUiClient(client_id, |
| kEmptyPrinterName); |
| SaveCallback( |
| GetRemoteSavedUseDefaultSettingsCallbacks(result.context.is_sandboxed), |
| result.context.remote_id, result.context.saved_callback_id, |
| std::move(callback)); |
| |
| LogCallToRemote("UseDefaultSettings", result.context); |
| // Safe to use base::Unretained(this) since `this` is a global singleton |
| // which never goes away. |
| result.service.get()->UseDefaultSettings( |
| *context_id, |
| base::BindOnce(&PrintBackendServiceManager::OnDidUseDefaultSettings, |
| base::Unretained(this), std::move(result.context))); |
| } |
| |
| #if BUILDFLAG(ENABLE_OOP_BASIC_PRINT_DIALOG) |
| void PrintBackendServiceManager::AskUserForSettings( |
| ClientId client_id, |
| ContextId context_id, |
| int max_pages, |
| bool has_selection, |
| bool is_scripted, |
| mojom::PrintBackendService::AskUserForSettingsCallback callback) { |
| ServiceAndCallbackContext result = |
| GetServiceAndCallbackContextForQueryWithUiClient(client_id, |
| kEmptyPrinterName); |
| SaveCallback( |
| GetRemoteSavedAskUserForSettingsCallbacks(result.context.is_sandboxed), |
| result.context.remote_id, result.context.saved_callback_id, |
| std::move(callback)); |
| |
| LogCallToRemote("AskUserForSettings", result.context); |
| // Safe to use base::Unretained(this) since `this` is a global singleton |
| // which never goes away. |
| result.service.get()->AskUserForSettings( |
| *context_id, max_pages, has_selection, is_scripted, |
| base::BindOnce(&PrintBackendServiceManager::OnDidAskUserForSettings, |
| base::Unretained(this), std::move(result.context))); |
| } |
| #endif // BUILDFLAG(ENABLE_OOP_BASIC_PRINT_DIALOG) |
| |
| void PrintBackendServiceManager::UpdatePrintSettings( |
| ClientId client_id, |
| const std::string& printer_name, |
| ContextId context_id, |
| base::Value::Dict job_settings, |
| mojom::PrintBackendService::UpdatePrintSettingsCallback callback) { |
| // A blank `printer_name` indicates the destination is unknown, which occurs |
| // when initiating a system print dialog. When printing a document the |
| // destination must be known. |
| ServiceAndCallbackContext result = |
| printer_name.empty() ? GetServiceAndCallbackContextForQueryWithUiClient( |
| client_id, printer_name) |
| : GetServiceAndCallbackContextForPrintDocumentClient( |
| client_id, printer_name); |
| SaveCallback( |
| GetRemoteSavedUpdatePrintSettingsCallbacks(result.context.is_sandboxed), |
| result.context.remote_id, result.context.saved_callback_id, |
| std::move(callback)); |
| |
| SetCrashKeys(printer_name); |
| |
| LogCallToRemote("UpdatePrintSettings", result.context); |
| // Safe to use base::Unretained(this) since `this` is a global singleton |
| // which never goes away. |
| result.service.get()->UpdatePrintSettings( |
| *context_id, std::move(job_settings), |
| base::BindOnce(&PrintBackendServiceManager::OnDidUpdatePrintSettings, |
| base::Unretained(this), std::move(result.context))); |
| } |
| |
| void PrintBackendServiceManager::StartPrinting( |
| ClientId client_id, |
| const std::string& printer_name, |
| ContextId context_id, |
| int document_cookie, |
| const std::u16string& document_name, |
| #if !BUILDFLAG(ENABLE_OOP_BASIC_PRINT_DIALOG) |
| std::optional<PrintSettings> settings, |
| #endif |
| mojom::PrintBackendService::StartPrintingCallback callback) { |
| ServiceAndCallbackContext result = |
| GetServiceAndCallbackContextForPrintDocumentClient(client_id, |
| printer_name); |
| SaveCallback( |
| GetRemoteSavedStartPrintingCallbacks(result.context.is_sandboxed), |
| result.context.remote_id, result.context.saved_callback_id, |
| std::move(callback)); |
| |
| SetCrashKeys(printer_name); |
| |
| LogCallToRemote("StartPrinting", result.context); |
| // Safe to use base::Unretained(this) since `this` is a global singleton |
| // which never goes away. |
| result.service.get()->StartPrinting( |
| *context_id, document_cookie, document_name, |
| #if !BUILDFLAG(ENABLE_OOP_BASIC_PRINT_DIALOG) |
| settings, |
| #endif |
| base::BindOnce(&PrintBackendServiceManager::OnDidStartPrinting, |
| base::Unretained(this), std::move(result.context))); |
| } |
| |
| #if BUILDFLAG(IS_WIN) |
| void PrintBackendServiceManager::RenderPrintedPage( |
| ClientId client_id, |
| const std::string& printer_name, |
| int document_cookie, |
| const PrintedPage& page, |
| mojom::MetafileDataType page_data_type, |
| base::ReadOnlySharedMemoryRegion serialized_page_data, |
| mojom::PrintBackendService::RenderPrintedPageCallback callback) { |
| ServiceAndCallbackContext result = |
| GetServiceAndCallbackContextForPrintDocumentClient(client_id, |
| printer_name); |
| SaveCallback( |
| GetRemoteSavedRenderPrintedPageCallbacks(result.context.is_sandboxed), |
| result.context.remote_id, result.context.saved_callback_id, |
| std::move(callback)); |
| |
| SetCrashKeys(printer_name); |
| |
| // Page numbers are 0-based for the printing context. |
| const uint32_t page_index = page.page_number() - 1; |
| |
| LogCallToRemote("RenderPrintedPage", result.context); |
| // Safe to use base::Unretained(this) since `this` is a global singleton |
| // which never goes away. |
| result.service.get()->RenderPrintedPage( |
| document_cookie, page_index, page_data_type, |
| std::move(serialized_page_data), page.page_size(), |
| page.page_content_rect(), page.shrink_factor(), |
| base::BindOnce(&PrintBackendServiceManager::OnDidRenderPrintedPage, |
| base::Unretained(this), std::move(result.context))); |
| } |
| #endif // BUILDFLAG(IS_WIN) |
| |
| void PrintBackendServiceManager::RenderPrintedDocument( |
| ClientId client_id, |
| const std::string& printer_name, |
| int document_cookie, |
| uint32_t page_count, |
| mojom::MetafileDataType data_type, |
| base::ReadOnlySharedMemoryRegion serialized_data, |
| mojom::PrintBackendService::RenderPrintedDocumentCallback callback) { |
| ServiceAndCallbackContext result = |
| GetServiceAndCallbackContextForPrintDocumentClient(client_id, |
| printer_name); |
| SaveCallback( |
| GetRemoteSavedRenderPrintedDocumentCallbacks(result.context.is_sandboxed), |
| result.context.remote_id, result.context.saved_callback_id, |
| std::move(callback)); |
| |
| SetCrashKeys(printer_name); |
| |
| LogCallToRemote("RenderPrintedDocument", result.context); |
| // Safe to use base::Unretained(this) since `this` is a global singleton |
| // which never goes away. |
| result.service.get()->RenderPrintedDocument( |
| document_cookie, page_count, data_type, std::move(serialized_data), |
| base::BindOnce(&PrintBackendServiceManager::OnDidRenderPrintedDocument, |
| base::Unretained(this), std::move(result.context))); |
| } |
| |
| void PrintBackendServiceManager::DocumentDone( |
| ClientId client_id, |
| const std::string& printer_name, |
| int document_cookie, |
| mojom::PrintBackendService::DocumentDoneCallback callback) { |
| ServiceAndCallbackContext result = |
| GetServiceAndCallbackContextForPrintDocumentClient(client_id, |
| printer_name); |
| SaveCallback(GetRemoteSavedDocumentDoneCallbacks(result.context.is_sandboxed), |
| result.context.remote_id, result.context.saved_callback_id, |
| std::move(callback)); |
| |
| SetCrashKeys(printer_name); |
| |
| LogCallToRemote("DocumentDone", result.context); |
| // Safe to use base::Unretained(this) since `this` is a global singleton |
| // which never goes away. |
| result.service.get()->DocumentDone( |
| document_cookie, |
| base::BindOnce(&PrintBackendServiceManager::OnDidDocumentDone, |
| base::Unretained(this), std::move(result.context))); |
| } |
| |
| void PrintBackendServiceManager::Cancel( |
| ClientId client_id, |
| const std::string& printer_name, |
| int document_cookie, |
| mojom::PrintBackendService::CancelCallback callback) { |
| ServiceAndCallbackContext result = |
| GetServiceAndCallbackContextForPrintDocumentClient(client_id, |
| printer_name); |
| SaveCallback(GetRemoteSavedCancelCallbacks(result.context.is_sandboxed), |
| result.context.remote_id, result.context.saved_callback_id, |
| std::move(callback)); |
| |
| SetCrashKeys(printer_name); |
| |
| LogCallToRemote("Cancel", result.context); |
| // Safe to use base::Unretained(this) since `this` is a global singleton |
| // which never goes away. |
| result.service.get()->Cancel( |
| document_cookie, |
| base::BindOnce(&PrintBackendServiceManager::OnDidCancel, |
| base::Unretained(this), std::move(result.context))); |
| } |
| |
| bool PrintBackendServiceManager::PrinterDriverFoundToRequireElevatedPrivilege( |
| const std::string& printer_name) const { |
| return drivers_requiring_elevated_privilege_.contains(printer_name); |
| } |
| |
| void PrintBackendServiceManager:: |
| SetPrinterDriverFoundToRequireElevatedPrivilege( |
| const std::string& printer_name) { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| VLOG(1) << "Destination '" << printer_name |
| << "' requires elevated privileges."; |
| drivers_requiring_elevated_privilege_.emplace(printer_name); |
| } |
| |
| void PrintBackendServiceManager::SetServiceForTesting( |
| mojo::Remote<mojom::PrintBackendService>* remote) { |
| sandboxed_service_remote_for_test_ = remote; |
| if (!sandboxed_service_remote_for_test_) { |
| return; |
| } |
| |
| // Safe to use base::Unretained(this) since `this` is a global singleton |
| // which never goes away. |
| sandboxed_service_remote_for_test_->set_disconnect_handler(base::BindOnce( |
| &PrintBackendServiceManager::OnRemoteDisconnected, base::Unretained(this), |
| /*sandboxed=*/true, |
| GetRemoteIdForPrinterName(/*printer_name=*/std::string()))); |
| } |
| |
| void PrintBackendServiceManager::SetServiceForFallbackTesting( |
| mojo::Remote<mojom::PrintBackendService>* remote) { |
| unsandboxed_service_remote_for_test_ = remote; |
| if (!unsandboxed_service_remote_for_test_) { |
| return; |
| } |
| |
| // Safe to use base::Unretained(this) since `this` is a global singleton |
| // which never goes away. |
| unsandboxed_service_remote_for_test_->set_disconnect_handler(base::BindOnce( |
| &PrintBackendServiceManager::OnRemoteDisconnected, base::Unretained(this), |
| /*sandboxed=*/false, |
| GetRemoteIdForPrinterName(/*printer_name=*/std::string()))); |
| } |
| |
| // static |
| PrintBackendServiceManager& PrintBackendServiceManager::GetInstance() { |
| if (!g_print_backend_service_manager_singleton) { |
| g_print_backend_service_manager_singleton = |
| new PrintBackendServiceManager(); |
| } |
| return *g_print_backend_service_manager_singleton; |
| } |
| |
| // static |
| void PrintBackendServiceManager::ResetForTesting() { |
| if (g_print_backend_service_manager_singleton) { |
| delete g_print_backend_service_manager_singleton; |
| g_print_backend_service_manager_singleton = nullptr; |
| } |
| } |
| |
| PrintBackendServiceManager::RemoteId |
| PrintBackendServiceManager::GetRemoteIdForPrinterName( |
| const std::string& printer_name) { |
| #if BUILDFLAG(IS_WIN) |
| if (!sandboxed_service_remote_for_test_ && |
| !features::kEnableOopPrintDriversSingleProcess.Get()) { |
| // Windows drivers are not thread safe. Use a process per driver to prevent |
| // bad interactions when interfacing to multiple drivers in parallel. |
| // https://crbug.com/957242 |
| auto iter = remote_id_map_.find(printer_name); |
| if (iter != remote_id_map_.end()) { |
| return iter->second; |
| } |
| |
| // No remote yet for this printer so make one. |
| return remote_id_map_ |
| .insert({printer_name, RemoteId(++remote_id_sequence_)}) |
| .first->second; |
| } |
| #endif |
| |
| // Just a single process that services all printers. |
| return RemoteId(1); |
| } |
| |
| PrintBackendServiceManager::RemoteId |
| PrintBackendServiceManager::GetRemoteIdForQueryWithUiClientId( |
| ClientId client_id) const { |
| const auto& iter = query_with_ui_clients_.find(client_id); |
| CHECK(iter != query_with_ui_clients_.cend()); |
| return iter->second; |
| } |
| |
| PrintBackendServiceManager::RemoteId |
| PrintBackendServiceManager::GetRemoteIdForPrintDocumentClientId( |
| ClientId client_id) const { |
| for (const auto& item : print_document_clients_) { |
| const ClientsSet& clients = item.second; |
| if (clients.contains(client_id)) { |
| return item.first; |
| } |
| } |
| NOTREACHED(); |
| } |
| |
| std::optional<PrintBackendServiceManager::ClientId> |
| PrintBackendServiceManager::RegisterClient( |
| ClientType client_type, |
| std::variant<std::string, RemoteId> destination) { |
| ClientId client_id = ClientId(++last_client_id_); |
| RemoteId remote_id = |
| std::holds_alternative<std::string>(destination) |
| ? GetRemoteIdForPrinterName( |
| /*printer_name=*/std::get<std::string>(destination)) |
| : std::get<RemoteId>(destination); |
| |
| VLOG(1) << "Registering a client with ID " << client_id << " (client type " |
| << ClientTypeToString(client_type) << ") for print backend service."; |
| switch (client_type) { |
| case ClientType::kQuery: |
| query_clients_.insert(client_id); |
| break; |
| case ClientType::kQueryWithUi: |
| #if !BUILDFLAG(ENABLE_CONCURRENT_BASIC_PRINT_DIALOGS) |
| if (!query_with_ui_clients_.empty()) |
| return std::nullopt; |
| #endif |
| query_with_ui_clients_.insert({client_id, remote_id}); |
| break; |
| case ClientType::kPrintDocument: |
| print_document_clients_[remote_id].insert(client_id); |
| break; |
| } |
| |
| // A new client registration is a signal of impending activity to a print |
| // backend service. Performance can be improved if we ensure that an initial |
| // service is ready for when the first Mojo call should happen shortly after |
| // this registration. |
| // It is possible that there might have been prior clients registered that |
| // persisted for a long time (e.g., a tab with a Print Preview left open |
| // indefinitely). We use a long timeout against idleness for that scenario, |
| // so we want to perform this optimization check every time regardless of |
| // number of clients registered. |
| // System print is a special case because it can display a system dialog and |
| // is window modal. In this scenario we do not want the print backend to |
| // self-terminate even if the user is idle for a long period of time. |
| if (base::Contains(sandboxed_remotes_bundles_, remote_id) || |
| base::Contains(unsandboxed_remotes_bundles_, remote_id) || |
| sandboxed_service_remote_for_test_) { |
| // Service already existed, possibly was recently marked for being reset |
| // with a short timeout or is already in use for other client types. |
| // Determine if any adjustment to the timeout is actually necessary. |
| std::optional<base::TimeDelta> new_timeout = |
| DetermineIdleTimeoutUpdateOnRegisteredClient(client_type, remote_id); |
| if (new_timeout.has_value()) |
| UpdateServiceIdleTimeoutByRemoteId(remote_id, new_timeout.value()); |
| } else { |
| // Service not already available, so launch it now so that it will be |
| // ready by the time the client gets to point of invoking a Mojo call. |
| if (std::holds_alternative<RemoteId>(destination)) { |
| // When the destination is to reuse an existing remote, and that remote |
| // is gone, then any expected context in that remote is also gone. Such |
| // a loss of context should be treated as a failure for the user's request |
| // to print the document, so return nullopt for the client ID. |
| return std::nullopt; |
| } |
| bool should_sandbox = ShouldServiceBeSandboxed( |
| /*printer_name=*/std::get<std::string>(destination), client_type); |
| GetService(remote_id, client_type, should_sandbox); |
| } |
| |
| return client_id; |
| } |
| |
| size_t PrintBackendServiceManager::GetClientsRegisteredCount() const { |
| size_t clients_count = query_clients_.size() + query_with_ui_clients_.size(); |
| for (auto& item : print_document_clients_) |
| clients_count += item.second.size(); |
| return clients_count; |
| } |
| |
| #if BUILDFLAG(IS_WIN) |
| bool PrintBackendServiceManager::PrinterDriverKnownToRequireElevatedPrivilege( |
| const std::string& printer_name, |
| ClientType client_type) const { |
| // Any Windows printer driver which causes a UI dialog to be displayed does |
| // not work if printing is started from within a sandboxed environment. |
| // crbug.com/1243873 |
| switch (client_type) { |
| case ClientType::kQuery: |
| return false; |
| case ClientType::kQueryWithUi: |
| // Guaranteed to display the system print dialog. |
| return true; |
| case ClientType::kPrintDocument: |
| // Drivers with a print port that results in saving to a file will cause |
| // a system dialog to be displayed. |
| return DoesDriverDisplayFileDialogForPrinting(printer_name); |
| } |
| } |
| #endif // BUILDFLAG(IS_WIN) |
| |
| bool PrintBackendServiceManager::ShouldServiceBeSandboxed( |
| const std::string& printer_name, |
| ClientType client_type) const { |
| // Determine if sandboxing is appropriate. This might be already known for |
| // certain drivers/configurations, or learned during runtime. |
| bool should_sandbox = |
| features::kEnableOopPrintDriversSandbox.Get() && |
| !PrinterDriverFoundToRequireElevatedPrivilege(printer_name); |
| #if BUILDFLAG(IS_WIN) |
| if (should_sandbox) { |
| should_sandbox = !PrinterDriverKnownToRequireElevatedPrivilege(printer_name, |
| client_type); |
| } |
| #endif |
| DVLOG(1) << "Print Backend service for " << ClientTypeToString(client_type) |
| << " use to printer `" << printer_name << "` should" |
| << (should_sandbox ? "" : " not") << " be sandboxed"; |
| return should_sandbox; |
| } |
| |
| const mojo::Remote<mojom::PrintBackendService>& |
| PrintBackendServiceManager::GetService(const RemoteId& remote_id, |
| ClientType client_type, |
| bool sandboxed) { |
| // Performance is improved if a service is launched ahead of the time it will |
| // be needed by client callers. |
| DCHECK_GT(GetClientsRegisteredCount(), 0u); |
| |
| if (sandboxed_service_remote_for_test_) { |
| // The presence of a sandboxed remote for testing signals a testing |
| // environment. If no unsandboxed test service was provided for fallback |
| // processing then use the sandboxed one for that as well. |
| if (!sandboxed && unsandboxed_service_remote_for_test_) { |
| return *unsandboxed_service_remote_for_test_; |
| } |
| |
| return *sandboxed_service_remote_for_test_; |
| } |
| |
| if (sandboxed) { |
| return GetServiceFromBundle(remote_id, client_type, /*sandboxed=*/true, |
| sandboxed_remotes_bundles_); |
| } |
| return GetServiceFromBundle(remote_id, client_type, /*sandboxed=*/false, |
| unsandboxed_remotes_bundles_); |
| } |
| |
| template <class T> |
| mojo::Remote<mojom::PrintBackendService>& |
| PrintBackendServiceManager::GetServiceFromBundle( |
| const RemoteId& remote_id, |
| ClientType client_type, |
| bool sandboxed, |
| RemotesBundleMap<T>& bundle_map) { |
| auto iter = bundle_map.find(remote_id); |
| if (iter == bundle_map.end()) { |
| // First time for this `remote_id`. |
| auto result = |
| bundle_map.emplace(remote_id, std::make_unique<RemotesBundle<T>>()); |
| iter = result.first; |
| } |
| |
| RemotesBundle<T>* bundle = iter->second.get(); |
| mojo::Remote<mojom::PrintBackendService>& service = bundle->service; |
| if (!service) { |
| VLOG(1) << "Launching print backend " |
| << (sandboxed ? "sandboxed" : "unsandboxed") << " for `" |
| << remote_id << "`"; |
| |
| std::vector<std::string> extra_switches; |
| #if BUILDFLAG(IS_LINUX) |
| if (auto* linux_ui = ui::LinuxUi::instance()) { |
| extra_switches = linux_ui->GetCmdLineFlagsForCopy(); |
| } |
| extra_switches.push_back(switches::kMessageLoopTypeUi); |
| #endif |
| mojo::Remote<T>& host = bundle->host; |
| content::ServiceProcessHost::Launch( |
| host.BindNewPipeAndPassReceiver(), |
| content::ServiceProcessHost::Options() |
| .WithDisplayName(IDS_UTILITY_PROCESS_PRINT_BACKEND_SERVICE_NAME) |
| .WithExtraCommandLineSwitches(std::move(extra_switches)) |
| .Pass()); |
| host->BindBackend(service.BindNewPipeAndPassReceiver()); |
| |
| // Ensure that if the interface is ever disconnected (e.g. the service |
| // process crashes) then we will drop our handle to the remote. |
| // Safe to use base::Unretained(this) since `this` is a global singleton |
| // which never goes away. |
| service.set_disconnect_handler( |
| base::BindOnce(&PrintBackendServiceManager::OnRemoteDisconnected, |
| base::Unretained(this), sandboxed, remote_id)); |
| |
| // We may want to have the service terminated when idle. |
| SetServiceIdleHandler(service, sandboxed, remote_id, |
| GetClientTypeIdleTimeout(client_type)); |
| #if BUILDFLAG(IS_WIN) |
| // Initialize the new service for the desired locale. Bind |
| // PrintBackendService with a Remote that allows pass-through requests to an |
| // XML parser. |
| mojo::PendingRemote<mojom::PrinterXmlParser> remote; |
| if (base::FeatureList::IsEnabled( |
| features::kReadPrinterCapabilitiesWithXps)) { |
| if (!xml_parser_) |
| xml_parser_ = std::make_unique<PrinterXmlParserImpl>(); |
| remote = xml_parser_->GetRemote(); |
| } |
| service->Init(g_browser_process->GetApplicationLocale(), std::move(remote)); |
| #else |
| // Initialize the new service for the desired locale. |
| service->Init(g_browser_process->GetApplicationLocale()); |
| #endif // BUILDFLAG(IS_WIN) |
| } |
| |
| return service; |
| } |
| |
| constexpr base::TimeDelta PrintBackendServiceManager::GetClientTypeIdleTimeout( |
| ClientType client_type) const { |
| if (persistent_service_) { |
| // Intentionally keep service around indefinitely. |
| return base::TimeDelta::Max(); |
| } |
| |
| switch (client_type) { |
| case ClientType::kQuery: |
| // Want a long timeout so that the service is available across typical |
| // user interactions but will get reclaimed should the user leave it |
| // unused indefinitely. E.g., a Print Preview left open in a tab while |
| // user has moved on to other tabs. |
| return kClientsRegisteredResetOnIdleTimeout; |
| |
| case ClientType::kQueryWithUi: |
| // Want the service to remain indefinitely, since this case supports a |
| // window-modal system dialog being invoked from the service. |
| return base::TimeDelta::Max(); |
| |
| case ClientType::kPrintDocument: |
| // Some print jobs can take a very long time to print. Choosing some |
| // threshold for reclaiming is hard to make and still have the effect |
| // of service reclamation meaningful. Err towards not accidentally |
| // terminating an in-progress print job and let the service remain open |
| // indefinitely, instead relying upon the registered clients mechanism |
| // to reinstate a short timeout once a print job has completed. |
| // For Windows there is the additional case where the driver might need |
| // to display a file save system dialog (e.g., if the driver sends to |
| // port FILE:), which is another window-modal system dialog invoked from |
| // the service for which we would want to wait indefinitely. |
| return base::TimeDelta::Max(); |
| } |
| } |
| |
| bool PrintBackendServiceManager::HasQueryWithUiClientForRemoteId( |
| const RemoteId& remote_id) const { |
| for (auto& item : query_with_ui_clients_) { |
| if (item.second == remote_id) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| bool PrintBackendServiceManager::HasPrintDocumentClientForRemoteId( |
| const RemoteId& remote_id) const { |
| return GetPrintDocumentClientsCountForRemoteId(remote_id) > 0; |
| } |
| |
| size_t PrintBackendServiceManager::GetPrintDocumentClientsCountForRemoteId( |
| const RemoteId& remote_id) const { |
| auto iter = print_document_clients_.find(remote_id); |
| if (iter != print_document_clients_.end()) { |
| DCHECK(!iter->second.empty()); |
| return iter->second.size(); |
| } |
| return 0; |
| } |
| |
| std::optional<base::TimeDelta> |
| PrintBackendServiceManager::DetermineIdleTimeoutUpdateOnRegisteredClient( |
| ClientType registered_client_type, |
| const RemoteId& remote_id) const { |
| switch (registered_client_type) { |
| case ClientType::kQuery: |
| DCHECK(!query_clients_.empty()); |
| |
| // Other query types have longer timeouts, so no need to update if |
| // any of them have clients. |
| if (!query_with_ui_clients_.empty() || |
| HasPrintDocumentClientForRemoteId(remote_id)) { |
| return std::nullopt; |
| } |
| |
| if (query_clients_.size() > 1) |
| return std::nullopt; |
| |
| // First client of type and no others will need an update. |
| break; |
| |
| case ClientType::kQueryWithUi: |
| #if BUILDFLAG(ENABLE_CONCURRENT_BASIC_PRINT_DIALOGS) |
| // No need to update if there were other query with UI clients. |
| if (query_with_ui_clients_.size() > 1) |
| return std::nullopt; |
| #else |
| // A modal system dialog, of which there should only ever be at most one |
| // of these. |
| DCHECK_EQ(query_with_ui_clients_.size(), 1u); |
| #endif |
| |
| // This is the longest timeout. No need to update if there is a similarly |
| // long timeout due to a printing client. |
| if (HasPrintDocumentClientForRemoteId(remote_id)) { |
| return std::nullopt; |
| } |
| break; |
| |
| case ClientType::kPrintDocument: |
| size_t clients_count = GetPrintDocumentClientsCountForRemoteId(remote_id); |
| DCHECK_GT(clients_count, 0u); |
| |
| // No need to update if there were other printing clients for same remote |
| // ID. |
| if (clients_count > 1) |
| return std::nullopt; |
| |
| // This is the longest timeout. No need to update if there is a similarly |
| // long timeout due to query with UI. |
| if (!query_with_ui_clients_.empty()) |
| return std::nullopt; |
| break; |
| } |
| |
| return GetClientTypeIdleTimeout(registered_client_type); |
| } |
| |
| std::optional<base::TimeDelta> |
| PrintBackendServiceManager::DetermineIdleTimeoutUpdateOnUnregisteredClient( |
| ClientType unregistered_client_type, |
| const RemoteId& remote_id) const { |
| switch (unregistered_client_type) { |
| case ClientType::kQuery: |
| // Other query types have longer timeouts, so no need to update if |
| // any of them have clients. |
| if (HasQueryWithUiClientForRemoteId(remote_id) || |
| HasPrintDocumentClientForRemoteId(remote_id)) { |
| return std::nullopt; |
| } |
| |
| if (!query_clients_.empty()) |
| return std::nullopt; |
| |
| // No remaining clients, can switch to short timeout for quick |
| // reclamation. |
| return kNoClientsRegisteredResetOnIdleTimeout; |
| |
| case ClientType::kQueryWithUi: |
| #if BUILDFLAG(IS_LINUX) |
| // No need to update if there were other query with UI clients. |
| if (HasQueryWithUiClientForRemoteId(remote_id)) { |
| return std::nullopt; |
| } |
| #else |
| // A modal system dialog, of which there should only ever be at most one |
| // of these. If one was dropped, it should now be empty. |
| DCHECK(query_with_ui_clients_.empty()); |
| #endif |
| |
| // This is the longest timeout, so no need to update if there is a |
| // printing client for this `remote_id`. |
| if (HasPrintDocumentClientForRemoteId(remote_id)) { |
| return std::nullopt; |
| } |
| |
| // New timeout depends upon existence of other queries. |
| return query_clients_.empty() ? kNoClientsRegisteredResetOnIdleTimeout |
| : kClientsRegisteredResetOnIdleTimeout; |
| |
| case ClientType::kPrintDocument: |
| // No need to update if there were other printing clients or query with |
| // UI clients for same remote ID. |
| if (HasQueryWithUiClientForRemoteId(remote_id) || |
| HasPrintDocumentClientForRemoteId(remote_id)) { |
| return std::nullopt; |
| } |
| |
| // New timeout depends upon existence of other queries. |
| return query_clients_.empty() ? kNoClientsRegisteredResetOnIdleTimeout |
| : kClientsRegisteredResetOnIdleTimeout; |
| } |
| } |
| |
| void PrintBackendServiceManager::SetServiceIdleHandler( |
| mojo::Remote<printing::mojom::PrintBackendService>& service, |
| bool sandboxed, |
| const RemoteId& remote_id, |
| const base::TimeDelta& timeout) { |
| DVLOG(1) << "Updating idle timeout for " |
| << (sandboxed ? "sandboxed" : "unsandboxed") |
| << " print backend service id `" << remote_id << "` to " << timeout; |
| // Safe to use base::Unretained(this) since `this` is a global singleton |
| // which never goes away. |
| service.set_idle_handler( |
| timeout, |
| base::BindRepeating(&PrintBackendServiceManager::OnIdleTimeout, |
| base::Unretained(this), sandboxed, remote_id)); |
| |
| // TODO(crbug.com/40775634) Make a superfluous call to the service, just to |
| // cause an IPC that will in turn make the adjusted timeout value actually |
| // take effect. |
| service->Poke(); |
| } |
| |
| void PrintBackendServiceManager::UpdateServiceIdleTimeoutByRemoteId( |
| const RemoteId& remote_id, |
| const base::TimeDelta& timeout) { |
| auto sandboxed_iter = sandboxed_remotes_bundles_.find(remote_id); |
| if (sandboxed_iter != sandboxed_remotes_bundles_.end()) { |
| RemotesBundle<mojom::SandboxedPrintBackendHost>* bundle = |
| sandboxed_iter->second.get(); |
| SetServiceIdleHandler(bundle->service, /*sandboxed=*/true, remote_id, |
| timeout); |
| } |
| auto unsandboxed_iter = unsandboxed_remotes_bundles_.find(remote_id); |
| if (unsandboxed_iter != unsandboxed_remotes_bundles_.end()) { |
| RemotesBundle<mojom::UnsandboxedPrintBackendHost>* bundle = |
| unsandboxed_iter->second.get(); |
| SetServiceIdleHandler(bundle->service, /*sandboxed=*/false, remote_id, |
| timeout); |
| } |
| } |
| |
| void PrintBackendServiceManager::OnIdleTimeout(bool sandboxed, |
| const RemoteId& remote_id) { |
| DVLOG(1) << "Print Backend service idle timeout for " |
| << (sandboxed ? "sandboxed" : "unsandboxed") << " remote id `" |
| << remote_id << "`"; |
| if (sandboxed) { |
| sandboxed_remotes_bundles_.erase(remote_id); |
| } else { |
| unsandboxed_remotes_bundles_.erase(remote_id); |
| } |
| } |
| |
| void PrintBackendServiceManager::OnRemoteDisconnected( |
| bool sandboxed, |
| const RemoteId& remote_id) { |
| DVLOG(1) << "Print Backend service disconnected for " |
| << (sandboxed ? "sandboxed" : "unsandboxed") << " remote id `" |
| << remote_id << "`"; |
| if (sandboxed) { |
| sandboxed_remotes_bundles_.erase(remote_id); |
| } else { |
| unsandboxed_remotes_bundles_.erase(remote_id); |
| } |
| RunSavedResultCallbacks(GetRemoteSavedEnumeratePrintersCallbacks(sandboxed), |
| remote_id, |
| base::unexpected(mojom::ResultCode::kFailed)); |
| RunSavedResultCallbacks(GetRemoteSavedFetchCapabilitiesCallbacks(sandboxed), |
| remote_id, |
| base::unexpected(mojom::ResultCode::kFailed)); |
| RunSavedResultCallbacks( |
| GetRemoteSavedGetDefaultPrinterNameCallbacks(sandboxed), remote_id, |
| base::unexpected(mojom::ResultCode::kFailed)); |
| #if BUILDFLAG(IS_CHROMEOS) |
| RunSavedResultCallbacks( |
| GetRemoteSavedGetPrinterSemanticCapsAndDefaultsCallbacks(sandboxed), |
| remote_id, base::unexpected(mojom::ResultCode::kFailed)); |
| #endif |
| #if BUILDFLAG(IS_WIN) |
| RunSavedCallbacks(GetRemoteSavedGetPaperPrintableAreaCallbacks(sandboxed), |
| remote_id, gfx::Rect()); |
| #endif |
| RunSavedResultCallbacks(GetRemoteSavedUseDefaultSettingsCallbacks(sandboxed), |
| remote_id, |
| base::unexpected(mojom::ResultCode::kFailed)); |
| #if BUILDFLAG(ENABLE_OOP_BASIC_PRINT_DIALOG) |
| RunSavedResultCallbacks(GetRemoteSavedAskUserForSettingsCallbacks(sandboxed), |
| remote_id, |
| base::unexpected(mojom::ResultCode::kFailed)); |
| #endif |
| RunSavedResultCallbacks(GetRemoteSavedUpdatePrintSettingsCallbacks(sandboxed), |
| remote_id, |
| base::unexpected(mojom::ResultCode::kFailed)); |
| RunSavedCallbacks(GetRemoteSavedStartPrintingCallbacks(sandboxed), remote_id, |
| mojom::ResultCode::kFailed, PrintingContext::kNoPrintJobId); |
| #if BUILDFLAG(IS_WIN) |
| RunSavedCallbacks(GetRemoteSavedRenderPrintedPageCallbacks(sandboxed), |
| remote_id, mojom::ResultCode::kFailed); |
| #endif |
| RunSavedCallbacks(GetRemoteSavedRenderPrintedDocumentCallbacks(sandboxed), |
| remote_id, mojom::ResultCode::kFailed); |
| RunSavedCallbacks(GetRemoteSavedDocumentDoneCallbacks(sandboxed), remote_id, |
| mojom::ResultCode::kFailed); |
| RunSavedCallbacks(GetRemoteSavedCancelCallbacks(sandboxed), remote_id); |
| } |
| |
| PrintBackendServiceManager::RemoteSavedEnumeratePrintersCallbacks& |
| PrintBackendServiceManager::GetRemoteSavedEnumeratePrintersCallbacks( |
| bool sandboxed) { |
| return sandboxed ? sandboxed_saved_enumerate_printers_callbacks_ |
| : unsandboxed_saved_enumerate_printers_callbacks_; |
| } |
| |
| PrintBackendServiceManager::RemoteSavedFetchCapabilitiesCallbacks& |
| PrintBackendServiceManager::GetRemoteSavedFetchCapabilitiesCallbacks( |
| bool sandboxed) { |
| return sandboxed ? sandboxed_saved_fetch_capabilities_callbacks_ |
| : unsandboxed_saved_fetch_capabilities_callbacks_; |
| } |
| |
| PrintBackendServiceManager::RemoteSavedGetDefaultPrinterNameCallbacks& |
| PrintBackendServiceManager::GetRemoteSavedGetDefaultPrinterNameCallbacks( |
| bool sandboxed) { |
| return sandboxed ? sandboxed_saved_get_default_printer_name_callbacks_ |
| : unsandboxed_saved_get_default_printer_name_callbacks_; |
| } |
| |
| #if BUILDFLAG(IS_CHROMEOS) |
| PrintBackendServiceManager:: |
| RemoteSavedGetPrinterSemanticCapsAndDefaultsCallbacks& |
| PrintBackendServiceManager:: |
| GetRemoteSavedGetPrinterSemanticCapsAndDefaultsCallbacks( |
| bool sandboxed) { |
| return sandboxed |
| ? sandboxed_saved_get_printer_semantic_caps_and_defaults_callbacks_ |
| : unsandboxed_saved_get_printer_semantic_caps_and_defaults_callbacks_; |
| } |
| #endif |
| |
| #if BUILDFLAG(IS_WIN) |
| PrintBackendServiceManager::RemoteSavedGetPaperPrintableAreaCallbacks& |
| PrintBackendServiceManager::GetRemoteSavedGetPaperPrintableAreaCallbacks( |
| bool sandboxed) { |
| return sandboxed ? sandboxed_saved_get_paper_printable_area_callbacks_ |
| : unsandboxed_saved_get_paper_printable_area_callbacks_; |
| } |
| |
| #endif |
| |
| PrintBackendServiceManager::RemoteSavedUseDefaultSettingsCallbacks& |
| PrintBackendServiceManager::GetRemoteSavedUseDefaultSettingsCallbacks( |
| bool sandboxed) { |
| return sandboxed ? sandboxed_saved_use_default_settings_callbacks_ |
| : unsandboxed_saved_use_default_settings_callbacks_; |
| } |
| |
| #if BUILDFLAG(ENABLE_OOP_BASIC_PRINT_DIALOG) |
| PrintBackendServiceManager::RemoteSavedAskUserForSettingsCallbacks& |
| PrintBackendServiceManager::GetRemoteSavedAskUserForSettingsCallbacks( |
| bool sandboxed) { |
| return sandboxed ? sandboxed_saved_ask_user_for_settings_callbacks_ |
| : unsandboxed_saved_ask_user_for_settings_callbacks_; |
| } |
| #endif |
| |
| PrintBackendServiceManager::RemoteSavedUpdatePrintSettingsCallbacks& |
| PrintBackendServiceManager::GetRemoteSavedUpdatePrintSettingsCallbacks( |
| bool sandboxed) { |
| return sandboxed ? sandboxed_saved_update_print_settings_callbacks_ |
| : unsandboxed_saved_update_print_settings_callbacks_; |
| } |
| |
| PrintBackendServiceManager::RemoteSavedStartPrintingCallbacks& |
| PrintBackendServiceManager::GetRemoteSavedStartPrintingCallbacks( |
| bool sandboxed) { |
| return sandboxed ? sandboxed_saved_start_printing_callbacks_ |
| : unsandboxed_saved_start_printing_callbacks_; |
| } |
| |
| #if BUILDFLAG(IS_WIN) |
| PrintBackendServiceManager::RemoteSavedRenderPrintedPageCallbacks& |
| PrintBackendServiceManager::GetRemoteSavedRenderPrintedPageCallbacks( |
| bool sandboxed) { |
| return sandboxed ? sandboxed_saved_render_printed_page_callbacks_ |
| : unsandboxed_saved_render_printed_page_callbacks_; |
| } |
| #endif |
| |
| PrintBackendServiceManager::RemoteSavedRenderPrintedDocumentCallbacks& |
| PrintBackendServiceManager::GetRemoteSavedRenderPrintedDocumentCallbacks( |
| bool sandboxed) { |
| return sandboxed ? sandboxed_saved_render_printed_document_callbacks_ |
| : unsandboxed_saved_render_printed_document_callbacks_; |
| } |
| |
| PrintBackendServiceManager::RemoteSavedDocumentDoneCallbacks& |
| PrintBackendServiceManager::GetRemoteSavedDocumentDoneCallbacks( |
| bool sandboxed) { |
| return sandboxed ? sandboxed_saved_document_done_callbacks_ |
| : unsandboxed_saved_document_done_callbacks_; |
| } |
| |
| PrintBackendServiceManager::RemoteSavedCancelCallbacks& |
| PrintBackendServiceManager::GetRemoteSavedCancelCallbacks(bool sandboxed) { |
| return sandboxed ? sandboxed_saved_cancel_callbacks_ |
| : unsandboxed_saved_cancel_callbacks_; |
| } |
| |
| PrintBackendServiceManager::ServiceAndCallbackContext |
| PrintBackendServiceManager::GetServiceAndCallbackContextForQuery( |
| const std::string& printer_name) { |
| CallbackContext context; |
| context.remote_id = GetRemoteIdForPrinterName(printer_name); |
| context.saved_callback_id = base::UnguessableToken::Create(); |
| context.is_sandboxed = |
| ShouldServiceBeSandboxed(printer_name, ClientType::kQuery); |
| auto& service = |
| GetService(context.remote_id, ClientType::kQuery, context.is_sandboxed); |
| return ServiceAndCallbackContext(std::move(context), service); |
| } |
| |
| PrintBackendServiceManager::ServiceAndCallbackContext |
| PrintBackendServiceManager::GetServiceAndCallbackContextForQueryWithUiClient( |
| ClientId client_id, |
| const std::string& printer_name) { |
| CallbackContext context; |
| context.remote_id = GetRemoteIdForQueryWithUiClientId(client_id); |
| context.saved_callback_id = base::UnguessableToken::Create(); |
| context.is_sandboxed = |
| ShouldServiceBeSandboxed(printer_name, ClientType::kQueryWithUi); |
| auto& service = GetService(context.remote_id, ClientType::kQueryWithUi, |
| context.is_sandboxed); |
| return ServiceAndCallbackContext(std::move(context), service); |
| } |
| |
| PrintBackendServiceManager::ServiceAndCallbackContext |
| PrintBackendServiceManager::GetServiceAndCallbackContextForPrintDocumentClient( |
| ClientId client_id, |
| const std::string& printer_name) { |
| CallbackContext context; |
| context.remote_id = GetRemoteIdForPrintDocumentClientId(client_id); |
| context.saved_callback_id = base::UnguessableToken::Create(); |
| context.is_sandboxed = |
| ShouldServiceBeSandboxed(printer_name, ClientType::kPrintDocument); |
| auto& service = GetService(context.remote_id, ClientType::kPrintDocument, |
| context.is_sandboxed); |
| return ServiceAndCallbackContext(std::move(context), service); |
| } |
| |
| template <class... T, class... X> |
| void PrintBackendServiceManager::SaveCallback( |
| RemoteSavedCallbacks<T...>& saved_callbacks, |
| const RemoteId& remote_id, |
| const base::UnguessableToken& saved_callback_id, |
| base::OnceCallback<void(X...)> callback) { |
| saved_callbacks[remote_id].emplace(saved_callback_id, std::move(callback)); |
| } |
| |
| template <class... T, class... X> |
| void PrintBackendServiceManager::ServiceCallbackDone( |
| RemoteSavedCallbacks<T...>& saved_callbacks, |
| const RemoteId& remote_id, |
| const base::UnguessableToken& saved_callback_id, |
| X... data) { |
| auto found_callback_map = saved_callbacks.find(remote_id); |
| CHECK(found_callback_map != saved_callbacks.end()); |
| |
| SavedCallbacks<T...>& callback_map = found_callback_map->second; |
| |
| auto callback_entry = callback_map.find(saved_callback_id); |
| CHECK(callback_entry != callback_map.end()); |
| auto callback = std::move(callback_entry->second); |
| callback_map.erase(callback_entry); |
| |
| // Done disconnect wrapper management, propagate the callback. |
| std::move(callback).Run(std::forward<X>(data)...); |
| } |
| |
| void PrintBackendServiceManager::OnDidEnumeratePrinters( |
| const CallbackContext& context, |
| mojom::PrintBackendService::EnumeratePrintersResult printer_list) { |
| LogCallbackFromRemote("EnumeratePrinters", context); |
| ServiceCallbackDone( |
| GetRemoteSavedEnumeratePrintersCallbacks(context.is_sandboxed), |
| context.remote_id, context.saved_callback_id, std::move(printer_list)); |
| } |
| |
| void PrintBackendServiceManager::OnDidFetchCapabilities( |
| const CallbackContext& context, |
| mojom::PrintBackendService::FetchCapabilitiesResult printer_caps_and_info) { |
| LogCallbackFromRemote("FetchCapabilities", context); |
| ServiceCallbackDone( |
| GetRemoteSavedFetchCapabilitiesCallbacks(context.is_sandboxed), |
| context.remote_id, context.saved_callback_id, |
| std::move(printer_caps_and_info)); |
| } |
| |
| void PrintBackendServiceManager::OnDidGetDefaultPrinterName( |
| const CallbackContext& context, |
| mojom::PrintBackendService::GetDefaultPrinterNameResult printer_name) { |
| LogCallbackFromRemote("GetDefaultPrinterName", context); |
| ServiceCallbackDone( |
| GetRemoteSavedGetDefaultPrinterNameCallbacks(context.is_sandboxed), |
| context.remote_id, context.saved_callback_id, std::move(printer_name)); |
| } |
| |
| #if BUILDFLAG(IS_CHROMEOS) |
| void PrintBackendServiceManager::OnDidGetPrinterSemanticCapsAndDefaults( |
| const CallbackContext& context, |
| mojom::PrintBackendService::GetPrinterSemanticCapsAndDefaultsResult |
| printer_caps) { |
| LogCallbackFromRemote("GetPrinterSemanticCapsAndDefaults", context); |
| ServiceCallbackDone(GetRemoteSavedGetPrinterSemanticCapsAndDefaultsCallbacks( |
| context.is_sandboxed), |
| context.remote_id, context.saved_callback_id, |
| std::move(printer_caps)); |
| } |
| #endif |
| |
| #if BUILDFLAG(IS_WIN) |
| void PrintBackendServiceManager::OnDidGetPaperPrintableArea( |
| const CallbackContext& context, |
| const gfx::Rect& printable_area_um) { |
| LogCallbackFromRemote("GetPaperPrintableArea", context); |
| ServiceCallbackDone( |
| GetRemoteSavedGetPaperPrintableAreaCallbacks(context.is_sandboxed), |
| context.remote_id, context.saved_callback_id, printable_area_um); |
| } |
| #endif |
| |
| void PrintBackendServiceManager::OnDidUseDefaultSettings( |
| const CallbackContext& context, |
| PrintSettingsResult settings) { |
| LogCallbackFromRemote("UseDefaultSettings", context); |
| ServiceCallbackDone( |
| GetRemoteSavedUseDefaultSettingsCallbacks(context.is_sandboxed), |
| context.remote_id, context.saved_callback_id, std::move(settings)); |
| } |
| |
| #if BUILDFLAG(ENABLE_OOP_BASIC_PRINT_DIALOG) |
| void PrintBackendServiceManager::OnDidAskUserForSettings( |
| const CallbackContext& context, |
| PrintSettingsResult settings) { |
| LogCallbackFromRemote("AskUserForSettings", context); |
| ServiceCallbackDone( |
| GetRemoteSavedAskUserForSettingsCallbacks(context.is_sandboxed), |
| context.remote_id, context.saved_callback_id, std::move(settings)); |
| } |
| #endif // BUILDFLAG(ENABLE_OOP_BASIC_PRINT_DIALOG) |
| |
| void PrintBackendServiceManager::OnDidUpdatePrintSettings( |
| const CallbackContext& context, |
| PrintSettingsResult settings) { |
| LogCallbackFromRemote("UpdatePrintSettings", context); |
| ServiceCallbackDone( |
| GetRemoteSavedUpdatePrintSettingsCallbacks(context.is_sandboxed), |
| context.remote_id, context.saved_callback_id, std::move(settings)); |
| } |
| |
| void PrintBackendServiceManager::OnDidStartPrinting( |
| const CallbackContext& context, |
| mojom::ResultCode result, |
| int job_id) { |
| LogCallbackFromRemote("StartPrinting", context); |
| ServiceCallbackDone( |
| GetRemoteSavedStartPrintingCallbacks(context.is_sandboxed), |
| context.remote_id, context.saved_callback_id, result, job_id); |
| } |
| |
| #if BUILDFLAG(IS_WIN) |
| void PrintBackendServiceManager::OnDidRenderPrintedPage( |
| const CallbackContext& context, |
| mojom::ResultCode result) { |
| LogCallbackFromRemote("RenderPrintedPage", context); |
| ServiceCallbackDone( |
| GetRemoteSavedRenderPrintedPageCallbacks(context.is_sandboxed), |
| context.remote_id, context.saved_callback_id, result); |
| } |
| #endif |
| |
| void PrintBackendServiceManager::OnDidRenderPrintedDocument( |
| const CallbackContext& context, |
| mojom::ResultCode result) { |
| LogCallbackFromRemote("RenderPrintedDocument", context); |
| ServiceCallbackDone( |
| GetRemoteSavedRenderPrintedDocumentCallbacks(context.is_sandboxed), |
| context.remote_id, context.saved_callback_id, result); |
| } |
| |
| void PrintBackendServiceManager::OnDidDocumentDone( |
| const CallbackContext& context, |
| mojom::ResultCode result) { |
| LogCallbackFromRemote("DocumentDone", context); |
| ServiceCallbackDone(GetRemoteSavedDocumentDoneCallbacks(context.is_sandboxed), |
| context.remote_id, context.saved_callback_id, result); |
| } |
| |
| void PrintBackendServiceManager::OnDidCancel(const CallbackContext& context) { |
| LogCallbackFromRemote("Cancel", context); |
| ServiceCallbackDone(GetRemoteSavedCancelCallbacks(context.is_sandboxed), |
| context.remote_id, context.saved_callback_id); |
| } |
| |
| // For cases that do not need to clone `result...`. |
| template <class... T> |
| void PrintBackendServiceManager::RunSavedCallbacks( |
| RemoteSavedCallbacks<T...>& saved_callbacks, |
| const RemoteId& remote_id, |
| typename std::remove_reference<T>::type... result) { |
| auto found_callbacks_map = saved_callbacks.find(remote_id); |
| if (found_callbacks_map == saved_callbacks.end()) |
| return; // No callbacks to run. |
| |
| SavedCallbacks<T...>& callbacks_map = found_callbacks_map->second; |
| for (auto& iter : callbacks_map) { |
| const base::UnguessableToken& saved_callback_id = iter.first; |
| DVLOG(1) << "Propagating print backend callback, saved callback ID " |
| << saved_callback_id << " for remote `" << remote_id << "`"; |
| |
| // Don't remove entries from the map while we are iterating through it, |
| // just run the callbacks. |
| base::OnceCallback<void(T...)>& callback = iter.second; |
| std::move(callback).Run(result...); |
| } |
| |
| // Now that we're done iterating we can safely delete all of the callbacks. |
| callbacks_map.clear(); |
| } |
| |
| // For cases that need to copy a single base::expected<T,E> |
| template <class... T> |
| void PrintBackendServiceManager::RunSavedResultCallbacks( |
| RemoteSavedCallbacks<T...>& saved_callbacks, |
| const RemoteId& remote_id, |
| typename std::remove_reference<T>::type... result) { |
| auto found_callbacks_map = saved_callbacks.find(remote_id); |
| if (found_callbacks_map == saved_callbacks.end()) |
| return; // No callbacks to run. |
| |
| SavedCallbacks<T...>& callbacks_map = found_callbacks_map->second; |
| for (auto& iter : callbacks_map) { |
| const base::UnguessableToken& saved_callback_id = iter.first; |
| DVLOG(1) << "Propagating print backend callback, saved callback ID " |
| << saved_callback_id << " for remote `" << remote_id << "`"; |
| |
| // Don't remove entries from the map while we are iterating through it, |
| // just run the callbacks. |
| base::OnceCallback<void(T...)>& callback = iter.second; |
| // Callback parameters that must be moved need cloning first. |
| MaybeCloneOrCopy(std::move(callback), result...); |
| } |
| |
| // Now that we're done iterating we can safely delete all of the callbacks. |
| callbacks_map.clear(); |
| } |
| |
| // static |
| void PrintBackendServiceManager::SetClientsForTesting( |
| const ClientsSet& query_clients, |
| const QueryWithUiClientsMap& query_with_ui_clients, |
| const PrintClientsMap& print_document_clients) { |
| g_print_backend_service_manager_singleton->query_clients_ = query_clients; |
| g_print_backend_service_manager_singleton->query_with_ui_clients_ = |
| query_with_ui_clients; |
| g_print_backend_service_manager_singleton->print_document_clients_ = |
| print_document_clients; |
| } |
| |
| } // namespace printing |