blob: 761aad3ffe877d04484f0ecd1a4eb5a3a7da968e [file] [log] [blame]
// 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 <utility>
#include "base/bind.h"
#include "base/containers/flat_map.h"
#include "base/containers/flat_set.h"
#include "base/logging.h"
#include "base/memory/scoped_refptr.h"
#include "base/metrics/histogram_functions.h"
#include "base/threading/thread_restrictions.h"
#include "base/time/time.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/printing_features.h"
#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
namespace printing {
namespace {
// Histogram name for capturing if any printer drivers were encountered that
// required fallback to workaround an access-denied error. Determining if this
// happens in the wild would be the impetus to pursue further efforts to
// identify and possibly better rectify such cases.
constexpr char kPrintBackendRequiresElevatedPrivilegeHistogramName[] =
"Printing.PrintBackend.DriversRequiringElevatedPrivilegeEncountered";
// For fetching remote IDs when there is no printer name.
constexpr char kEmptyPrinterName[] = "";
PrintBackendServiceManager* g_print_backend_service_manager_singleton = nullptr;
size_t GetClientsCountForRemoteId(
const PrintBackendServiceManager::PrintClientsMap& clients,
const PrintBackendServiceManager::RemoteId& remote_id) {
auto iter = clients.find(remote_id);
if (iter != clients.end()) {
DCHECK(!iter->second.empty());
return iter->second.size();
}
return 0;
}
#if BUILDFLAG(IS_WIN)
// TODO(crbug.com/809738): Update for other platforms as they are made able
// to support modal dialogs from OOP.
uint32_t NativeViewToUint(gfx::NativeView view) {
return base::win::HandleToUint32(views::HWNDForNativeView(view));
}
#endif
} // namespace
PrintBackendServiceManager::CallbackContext::CallbackContext() = default;
PrintBackendServiceManager::CallbackContext::CallbackContext(
PrintBackendServiceManager::CallbackContext&& other) noexcept = default;
PrintBackendServiceManager::CallbackContext::~CallbackContext() = default;
PrintBackendServiceManager::PrintBackendServiceManager() = default;
PrintBackendServiceManager::~PrintBackendServiceManager() = default;
// static
void PrintBackendServiceManager::LogCallToRemote(
base::StringPiece 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(
base::StringPiece 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/1227561) 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>(
print_backend->GetPrinterDriverInfo(printer_name));
}
uint32_t PrintBackendServiceManager::RegisterQueryClient() {
return *RegisterClient(ClientType::kQuery, kEmptyPrinterName);
}
absl::optional<uint32_t>
PrintBackendServiceManager::RegisterQueryWithUiClient() {
return RegisterClient(ClientType::kQueryWithUi, kEmptyPrinterName);
}
uint32_t PrintBackendServiceManager::RegisterPrintDocumentClient(
const std::string& printer_name) {
DCHECK_NE(printer_name, kEmptyPrinterName);
return *RegisterClient(ClientType::kPrintDocument, printer_name);
}
void PrintBackendServiceManager::UnregisterClient(uint32_t id) {
// Determine which client type has this ID, and remove it once found.
absl::optional<ClientType> client_type;
RemoteId remote_id = GetRemoteIdForPrinterName(kEmptyPrinterName);
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
<< " from print backend service.";
absl::optional<base::TimeDelta> new_timeout =
DetermineIdleTimeoutUpdateOnUnregisteredClient(client_type.value(),
remote_id);
if (new_timeout.has_value())
UpdateServiceIdleTimeoutByRemoteId(remote_id, 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) {
CallbackContext context;
auto& service = GetServiceAndCallbackContext(kEmptyPrinterName,
ClientType::kQuery, context);
SaveCallback(GetRemoteSavedEnumeratePrintersCallbacks(context.is_sandboxed),
context.remote_id, context.saved_callback_id,
std::move(callback));
LogCallToRemote("EnumeratePrinters", context);
service->EnumeratePrinters(
base::BindOnce(&PrintBackendServiceManager::OnDidEnumeratePrinters,
base::Unretained(this), std::move(context)));
}
void PrintBackendServiceManager::FetchCapabilities(
const std::string& printer_name,
mojom::PrintBackendService::FetchCapabilitiesCallback callback) {
CallbackContext context;
auto& service =
GetServiceAndCallbackContext(printer_name, ClientType::kQuery, context);
SaveCallback(GetRemoteSavedFetchCapabilitiesCallbacks(context.is_sandboxed),
context.remote_id, context.saved_callback_id,
std::move(callback));
SetCrashKeys(printer_name);
LogCallToRemote("FetchCapabilities", context);
service->FetchCapabilities(
printer_name,
base::BindOnce(&PrintBackendServiceManager::OnDidFetchCapabilities,
base::Unretained(this), std::move(context)));
}
void PrintBackendServiceManager::GetDefaultPrinterName(
mojom::PrintBackendService::GetDefaultPrinterNameCallback callback) {
CallbackContext context;
auto& service = GetServiceAndCallbackContext(kEmptyPrinterName,
ClientType::kQuery, context);
SaveCallback(
GetRemoteSavedGetDefaultPrinterNameCallbacks(context.is_sandboxed),
context.remote_id, context.saved_callback_id, std::move(callback));
LogCallToRemote("GetDefaultPrinterName", context);
service->GetDefaultPrinterName(
base::BindOnce(&PrintBackendServiceManager::OnDidGetDefaultPrinterName,
base::Unretained(this), std::move(context)));
}
void PrintBackendServiceManager::GetPrinterSemanticCapsAndDefaults(
const std::string& printer_name,
mojom::PrintBackendService::GetPrinterSemanticCapsAndDefaultsCallback
callback) {
CallbackContext context;
auto& service =
GetServiceAndCallbackContext(printer_name, ClientType::kQuery, context);
SaveCallback(GetRemoteSavedGetPrinterSemanticCapsAndDefaultsCallbacks(
context.is_sandboxed),
context.remote_id, context.saved_callback_id,
std::move(callback));
SetCrashKeys(printer_name);
LogCallToRemote("GetPrinterSemanticCapsAndDefaults", context);
service->GetPrinterSemanticCapsAndDefaults(
printer_name,
base::BindOnce(
&PrintBackendServiceManager::OnDidGetPrinterSemanticCapsAndDefaults,
base::Unretained(this), std::move(context)));
}
void PrintBackendServiceManager::UseDefaultSettings(
const std::string& printer_name,
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.
CallbackContext context;
auto& service = GetServiceAndCallbackContext(
printer_name, ClientType::kQueryWithUi, context);
SaveCallback(GetRemoteSavedUseDefaultSettingsCallbacks(context.is_sandboxed),
context.remote_id, context.saved_callback_id,
std::move(callback));
SetCrashKeys(printer_name);
LogCallToRemote("UseDefaultSettings", context);
service->UseDefaultSettings(
base::BindOnce(&PrintBackendServiceManager::OnDidUseDefaultSettings,
base::Unretained(this), std::move(context)));
}
#if BUILDFLAG(IS_WIN)
void PrintBackendServiceManager::AskUserForSettings(
const std::string& printer_name,
gfx::NativeView parent_view,
int max_pages,
bool has_selection,
bool is_scripted,
mojom::PrintBackendService::AskUserForSettingsCallback callback) {
CallbackContext context;
auto& service = GetServiceAndCallbackContext(
printer_name, ClientType::kQueryWithUi, context);
SaveCallback(GetRemoteSavedAskUserForSettingsCallbacks(context.is_sandboxed),
context.remote_id, context.saved_callback_id,
std::move(callback));
SetCrashKeys(printer_name);
LogCallToRemote("AskUserForSettings", context);
service->AskUserForSettings(
NativeViewToUint(parent_view), max_pages, has_selection, is_scripted,
base::BindOnce(&PrintBackendServiceManager::OnDidAskUserForSettings,
base::Unretained(this), std::move(context)));
}
#endif // BUILDFLAG(IS_WIN)
void PrintBackendServiceManager::UpdatePrintSettings(
const std::string& printer_name,
base::Value::Dict job_settings,
mojom::PrintBackendService::UpdatePrintSettingsCallback callback) {
CallbackContext context;
auto& service =
GetServiceAndCallbackContext(printer_name, ClientType::kQuery, context);
SaveCallback(GetRemoteSavedUpdatePrintSettingsCallbacks(context.is_sandboxed),
context.remote_id, context.saved_callback_id,
std::move(callback));
SetCrashKeys(printer_name);
LogCallToRemote("UpdatePrintSettings", context);
service->UpdatePrintSettings(
std::move(job_settings),
base::BindOnce(&PrintBackendServiceManager::OnDidUpdatePrintSettings,
base::Unretained(this), std::move(context)));
}
void PrintBackendServiceManager::StartPrinting(
const std::string& printer_name,
int document_cookie,
const std::u16string& document_name,
mojom::PrintTargetType target_type,
const PrintSettings& settings,
mojom::PrintBackendService::StartPrintingCallback callback) {
CallbackContext context;
auto& service = GetServiceAndCallbackContext(
printer_name, ClientType::kPrintDocument, context);
SaveCallback(GetRemoteSavedStartPrintingCallbacks(context.is_sandboxed),
context.remote_id, context.saved_callback_id,
std::move(callback));
SetCrashKeys(printer_name);
LogCallToRemote("StartPrinting", context);
service->StartPrinting(
document_cookie, document_name, target_type, settings,
base::BindOnce(&PrintBackendServiceManager::OnDidStartPrinting,
base::Unretained(this), std::move(context)));
}
#if BUILDFLAG(IS_WIN)
void PrintBackendServiceManager::RenderPrintedPage(
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) {
CallbackContext context;
auto& service = GetServiceAndCallbackContext(
printer_name, ClientType::kPrintDocument, context);
SaveCallback(GetRemoteSavedRenderPrintedPageCallbacks(context.is_sandboxed),
context.remote_id, 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", context);
service->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(context)));
}
#endif // BUILDFLAG(IS_WIN)
void PrintBackendServiceManager::RenderPrintedDocument(
const std::string& printer_name,
int document_cookie,
uint32_t page_count,
mojom::MetafileDataType data_type,
base::ReadOnlySharedMemoryRegion serialized_data,
mojom::PrintBackendService::RenderPrintedDocumentCallback callback) {
CallbackContext context;
auto& service = GetServiceAndCallbackContext(
printer_name, ClientType::kPrintDocument, context);
SaveCallback(
GetRemoteSavedRenderPrintedDocumentCallbacks(context.is_sandboxed),
context.remote_id, context.saved_callback_id, std::move(callback));
SetCrashKeys(printer_name);
LogCallToRemote("RenderPrintedDocument", context);
service->RenderPrintedDocument(
document_cookie, page_count, data_type, std::move(serialized_data),
base::BindOnce(&PrintBackendServiceManager::OnDidRenderPrintedDocument,
base::Unretained(this), std::move(context)));
}
void PrintBackendServiceManager::DocumentDone(
const std::string& printer_name,
int document_cookie,
mojom::PrintBackendService::DocumentDoneCallback callback) {
CallbackContext context;
auto& service = GetServiceAndCallbackContext(
printer_name, ClientType::kPrintDocument, context);
SaveCallback(GetRemoteSavedDocumentDoneCallbacks(context.is_sandboxed),
context.remote_id, context.saved_callback_id,
std::move(callback));
SetCrashKeys(printer_name);
LogCallToRemote("DocumentDone", context);
service->DocumentDone(
document_cookie,
base::BindOnce(&PrintBackendServiceManager::OnDidDocumentDone,
base::Unretained(this), std::move(context)));
}
void PrintBackendServiceManager::Cancel(
const std::string& printer_name,
int document_cookie,
mojom::PrintBackendService::CancelCallback callback) {
CallbackContext context;
auto& service = GetServiceAndCallbackContext(
printer_name, ClientType::kPrintDocument, context);
SaveCallback(GetRemoteSavedCancelCallbacks(context.is_sandboxed),
context.remote_id, context.saved_callback_id,
std::move(callback));
SetCrashKeys(printer_name);
LogCallToRemote("Cancel", context);
service->Cancel(document_cookie,
base::BindOnce(&PrintBackendServiceManager::OnDidCancel,
base::Unretained(this), std::move(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.";
if (drivers_requiring_elevated_privilege_.emplace(printer_name).second &&
drivers_requiring_elevated_privilege_.size() == 1) {
// First time we've detected a problem for any driver.
base::UmaHistogramBoolean(
kPrintBackendRequiresElevatedPrivilegeHistogramName, /*sample=*/true);
}
}
void PrintBackendServiceManager::SetServiceForTesting(
mojo::Remote<mojom::PrintBackendService>* remote) {
sandboxed_service_remote_for_test_ = remote;
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;
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_) {
// 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. RemoteId is only used within
// browse process management code, so a simple incrementing sequence is
// sufficient.
static uint32_t id_sequence = 0;
return remote_id_map_.insert({printer_name, RemoteId(++id_sequence)})
.first->second;
}
#endif
// Non-Windows platforms and the testing environment always just use one
// instance for all printers.
return RemoteId(1);
}
absl::optional<uint32_t> PrintBackendServiceManager::RegisterClient(
ClientType client_type,
const std::string& printer_name) {
uint32_t client_id = ++last_client_id_;
RemoteId remote_id = GetRemoteIdForPrinterName(printer_name);
VLOG(1) << "Registering a client with ID " << client_id
<< " for print backend service.";
switch (client_type) {
case ClientType::kQuery:
query_clients_.insert(client_id);
break;
case ClientType::kQueryWithUi:
#if !BUILDFLAG(IS_LINUX)
if (!query_with_ui_clients_.empty())
return absl::nullopt;
#endif
query_with_ui_clients_.insert(client_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.
auto iter = sandboxed_remotes_bundles_.find(remote_id);
if (iter == sandboxed_remotes_bundles_.end()) {
// 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.
bool is_sandboxed;
GetService(printer_name, client_type, &is_sandboxed);
} else {
// 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.
absl::optional<base::TimeDelta> new_timeout =
DetermineIdleTimeoutUpdateOnRegisteredClient(client_type, remote_id);
if (new_timeout.has_value())
UpdateServiceIdleTimeoutByRemoteId(remote_id, new_timeout.value());
}
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) {
// 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)
const mojo::Remote<mojom::PrintBackendService>&
PrintBackendServiceManager::GetService(const std::string& printer_name,
ClientType client_type,
bool* is_sandboxed) {
// 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
*is_sandboxed = should_sandbox;
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 (!should_sandbox && unsandboxed_service_remote_for_test_)
return *unsandboxed_service_remote_for_test_;
return *sandboxed_service_remote_for_test_;
}
// Performance is improved if a service is launched ahead of the time it will
// be needed by client callers.
DCHECK_GT(GetClientsRegisteredCount(), 0u);
if (should_sandbox) {
// On the first print that will try to use sandboxed service, make note that
// so far no drivers have been discovered to require fallback beyond any
// predetermined known cases.
static bool first_sandboxed_print = true;
if (first_sandboxed_print) {
first_sandboxed_print = false;
base::UmaHistogramBoolean(
kPrintBackendRequiresElevatedPrivilegeHistogramName,
/*sample=*/false);
}
}
RemoteId remote_id = GetRemoteIdForPrinterName(printer_name);
if (should_sandbox) {
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 << "`";
mojo::Remote<T>& host = bundle->host;
content::ServiceProcessHost::Launch(
host.BindNewPipeAndPassReceiver(),
content::ServiceProcessHost::Options()
.WithDisplayName(IDS_UTILITY_PROCESS_PRINT_BACKEND_SERVICE_NAME)
.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;
}
// static
constexpr base::TimeDelta PrintBackendServiceManager::GetClientTypeIdleTimeout(
ClientType client_type) {
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();
}
}
absl::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() ||
GetClientsCountForRemoteId(print_document_clients_, remote_id) > 0) {
return absl::nullopt;
}
if (query_clients_.size() > 1)
return absl::nullopt;
// First client of type and no others will need an update.
break;
case ClientType::kQueryWithUi:
#if BUILDFLAG(IS_LINUX)
// No need to update if there were other query with UI clients.
if (query_with_ui_clients_.size() > 1)
return absl::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 (GetClientsCountForRemoteId(print_document_clients_, remote_id) > 0)
return absl::nullopt;
break;
case ClientType::kPrintDocument:
size_t clients_count =
GetClientsCountForRemoteId(print_document_clients_, 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 absl::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 absl::nullopt;
break;
}
return GetClientTypeIdleTimeout(registered_client_type);
}
absl::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 (!query_with_ui_clients_.empty() ||
GetClientsCountForRemoteId(print_document_clients_, remote_id) > 0) {
return absl::nullopt;
}
if (!query_clients_.empty())
return absl::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 (!query_with_ui_clients_.empty())
return absl::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 (GetClientsCountForRemoteId(print_document_clients_, remote_id) > 0)
return absl::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 for same remote
// ID.
if (GetClientsCountForRemoteId(print_document_clients_, remote_id) > 0)
return absl::nullopt;
// This is the longest timeout, so no need to update if there is still a
// query with UI.
if (!query_with_ui_clients_.empty())
return absl::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;
service.set_idle_handler(
kNoClientsRegisteredResetOnIdleTimeout,
base::BindRepeating(&PrintBackendServiceManager::OnIdleTimeout,
base::Unretained(this), sandboxed, remote_id));
// TODO(crbug.com/1225111) 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);
}
RunSavedCallbacksStructResult(
GetRemoteSavedEnumeratePrintersCallbacks(sandboxed), remote_id,
mojom::PrinterListResult::NewResultCode(mojom::ResultCode::kFailed));
RunSavedCallbacksStructResult(
GetRemoteSavedFetchCapabilitiesCallbacks(sandboxed), remote_id,
mojom::PrinterCapsAndInfoResult::NewResultCode(
mojom::ResultCode::kFailed));
RunSavedCallbacksStructResult(
GetRemoteSavedGetDefaultPrinterNameCallbacks(sandboxed), remote_id,
mojom::DefaultPrinterNameResult::NewResultCode(
mojom::ResultCode::kFailed));
RunSavedCallbacksStructResult(
GetRemoteSavedGetPrinterSemanticCapsAndDefaultsCallbacks(sandboxed),
remote_id,
mojom::PrinterSemanticCapsAndDefaultsResult::NewResultCode(
mojom::ResultCode::kFailed));
RunSavedCallbacksStructResult(
GetRemoteSavedUseDefaultSettingsCallbacks(sandboxed), remote_id,
mojom::PrintSettingsResult::NewResultCode(mojom::ResultCode::kFailed));
#if BUILDFLAG(IS_WIN)
RunSavedCallbacksStructResult(
GetRemoteSavedAskUserForSettingsCallbacks(sandboxed), remote_id,
mojom::PrintSettingsResult::NewResultCode(mojom::ResultCode::kFailed));
#endif
RunSavedCallbacksStructResult(
GetRemoteSavedUpdatePrintSettingsCallbacks(sandboxed), remote_id,
mojom::PrintSettingsResult::NewResultCode(mojom::ResultCode::kFailed));
RunSavedCallbacks(GetRemoteSavedStartPrintingCallbacks(sandboxed), remote_id,
mojom::ResultCode::kFailed);
#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_;
}
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_;
}
PrintBackendServiceManager::RemoteSavedUseDefaultSettingsCallbacks&
PrintBackendServiceManager::GetRemoteSavedUseDefaultSettingsCallbacks(
bool sandboxed) {
return sandboxed ? sandboxed_saved_use_default_settings_callbacks_
: unsandboxed_saved_use_default_settings_callbacks_;
}
#if BUILDFLAG(IS_WIN)
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_;
}
const mojo::Remote<mojom::PrintBackendService>&
PrintBackendServiceManager::GetServiceAndCallbackContext(
const std::string& printer_name,
ClientType client_type,
CallbackContext& context) {
context.remote_id = GetRemoteIdForPrinterName(printer_name);
context.saved_callback_id = base::UnguessableToken::Create();
return GetService(printer_name, client_type, &context.is_sandboxed);
}
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);
DCHECK(found_callback_map != saved_callbacks.end());
SavedCallbacks<T...>& callback_map = found_callback_map->second;
auto callback_entry = callback_map.find(saved_callback_id);
DCHECK(callback_entry != callback_map.end());
base::OnceCallback<void(X...)> 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::PrinterListResultPtr 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::PrinterCapsAndInfoResultPtr 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::DefaultPrinterNameResultPtr printer_name) {
LogCallbackFromRemote("GetDefaultPrinterName", context);
ServiceCallbackDone(
GetRemoteSavedGetDefaultPrinterNameCallbacks(context.is_sandboxed),
context.remote_id, context.saved_callback_id, std::move(printer_name));
}
void PrintBackendServiceManager::OnDidGetPrinterSemanticCapsAndDefaults(
const CallbackContext& context,
mojom::PrinterSemanticCapsAndDefaultsResultPtr printer_caps) {
LogCallbackFromRemote("GetPrinterSemanticCapsAndDefaults", context);
ServiceCallbackDone(GetRemoteSavedGetPrinterSemanticCapsAndDefaultsCallbacks(
context.is_sandboxed),
context.remote_id, context.saved_callback_id,
std::move(printer_caps));
}
void PrintBackendServiceManager::OnDidUseDefaultSettings(
const CallbackContext& context,
mojom::PrintSettingsResultPtr settings) {
LogCallbackFromRemote("UseDefaultSettings", context);
ServiceCallbackDone(
GetRemoteSavedUseDefaultSettingsCallbacks(context.is_sandboxed),
context.remote_id, context.saved_callback_id, std::move(settings));
}
#if BUILDFLAG(IS_WIN)
void PrintBackendServiceManager::OnDidAskUserForSettings(
const CallbackContext& context,
mojom::PrintSettingsResultPtr settings) {
LogCallbackFromRemote("AskUserForSettings", context);
ServiceCallbackDone(
GetRemoteSavedAskUserForSettingsCallbacks(context.is_sandboxed),
context.remote_id, context.saved_callback_id, std::move(settings));
}
#endif // BUILDFLAG(IS_WIN)
void PrintBackendServiceManager::OnDidUpdatePrintSettings(
const CallbackContext& context,
mojom::PrintSettingsResultPtr 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) {
LogCallbackFromRemote("StartPrinting", context);
ServiceCallbackDone(
GetRemoteSavedStartPrintingCallbacks(context.is_sandboxed),
context.remote_id, context.saved_callback_id, result);
}
#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);
}
template <class T>
void PrintBackendServiceManager::RunSavedCallbacksStructResult(
RemoteSavedStructCallbacks<T>& saved_callbacks,
const RemoteId& remote_id,
mojo::StructPtr<T> result_to_clone) {
auto found_callbacks_map = saved_callbacks.find(remote_id);
if (found_callbacks_map == saved_callbacks.end())
return; // No callbacks to run.
SavedCallbacks<mojo::StructPtr<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(mojo::StructPtr<T>)>& callback = iter.second;
std::move(callback).Run(result_to_clone.Clone());
}
// Now that we're done iterating we can safely delete all of the callbacks.
callbacks_map.clear();
}
template <class... T>
void PrintBackendServiceManager::RunSavedCallbacks(
RemoteSavedCallbacks<T...>& saved_callbacks,
const RemoteId& remote_id,
T... 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();
}
// static
void PrintBackendServiceManager::SetClientsForTesting(
const ClientsSet& query_clients,
const ClientsSet& 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