blob: 457749cf31578666304c30a5df1b8428629caafe [file] [log] [blame]
// Copyright 2021 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/printing/print_job_worker_oop.h"
#include "base/notreached.h"
#include "base/strings/utf_string_conversions.h"
#include "base/values.h"
#include "build/build_config.h"
#include "chrome/browser/printing/print_backend_service_manager.h"
#include "chrome/browser/printing/print_error_dialog.h"
#include "chrome/browser/printing/print_job.h"
#include "chrome/services/printing/public/mojom/print_backend_service.mojom.h"
#include "components/device_event_log/device_event_log.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"
#include "printing/metafile.h"
#include "printing/printed_document.h"
#include "printing/printing_features.h"
#include "third_party/abseil-cpp/absl/types/optional.h"
#if BUILDFLAG(IS_WIN)
#include "printing/printed_page_win.h"
#endif
using content::BrowserThread;
namespace printing {
namespace {
mojom::PrintTargetType DeterminePrintTargetType(
const base::Value& job_settings) {
#if BUILDFLAG(IS_MAC)
if (job_settings.FindKey(kSettingOpenPDFInPreview))
return mojom::PrintTargetType::kExternalPreview;
#endif
if (job_settings.FindBoolKey(kSettingShowSystemDialog).value_or(false))
return mojom::PrintTargetType::kSystemDialog;
return mojom::PrintTargetType::kDirectToDevice;
}
} // namespace
PrintJobWorkerOop::PrintJobWorkerOop(int render_process_id, int render_frame_id)
: PrintJobWorker(render_process_id, render_frame_id) {}
PrintJobWorkerOop::~PrintJobWorkerOop() {
DCHECK(!service_manager_client_id_.has_value());
}
void PrintJobWorkerOop::StartPrinting(PrintedDocument* new_document) {
if (!StartPrintingSanityCheck(new_document))
return;
// Do browser-side context setup.
std::u16string document_name = GetDocumentName(new_document);
mojom::ResultCode result = printing_context()->NewDocument(document_name);
if (result != mojom::ResultCode::kSuccess) {
OnFailure();
return;
}
std::string device_name =
base::UTF16ToUTF8(document()->settings().device_name());
VLOG(1) << "Start printing document " << document()->cookie() << " to "
<< device_name;
// `PrintBackendServiceManager` interactions must happen on the UI thread.
content::GetUIThreadTaskRunner({})->PostTask(
FROM_HERE, base::BindOnce(&PrintJobWorkerOop::SendStartPrinting,
ui_weak_factory_.GetWeakPtr(), device_name,
document_name));
}
void PrintJobWorkerOop::OnDidStartPrinting(mojom::ResultCode result) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
if (result != mojom::ResultCode::kSuccess) {
PRINTER_LOG(ERROR) << "Error initiating printing via service for document "
<< document()->cookie() << ": " << result;
if (result != mojom::ResultCode::kAccessDenied || !TryRestartPrinting())
NotifyFailure(result);
return;
}
VLOG(1) << "Printing initiated with service for document "
<< document()->cookie();
#if BUILDFLAG(IS_WIN)
task_runner()->PostTask(FROM_HERE,
base::BindOnce(&PrintJobWorker::OnNewPage,
worker_weak_factory_.GetWeakPtr()));
#else
// TODO(crbug.com/809738) Still need more support for printing pipeline in
// the service (need `RenderPrintedDocument()` support).
task_runner()->PostTask(FROM_HERE,
base::BindOnce(&PrintJobWorkerOop::OnFailure,
worker_weak_factory_.GetWeakPtr()));
#endif
}
#if BUILDFLAG(IS_WIN)
void PrintJobWorkerOop::OnDidRenderPrintedPage(uint32_t page_index,
mojom::ResultCode result) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
if (result != mojom::ResultCode::kSuccess) {
PRINTER_LOG(ERROR)
<< "Error rendering printed page via service for document "
<< document()->cookie() << ": " << result;
NotifyFailure(result);
return;
}
scoped_refptr<PrintedPage> page = document()->GetPage(page_index);
if (!page) {
DLOG(ERROR) << "Unable to get page " << page_index << " for document "
<< document()->cookie();
task_runner()->PostTask(FROM_HERE,
base::BindOnce(&PrintJobWorkerOop::OnFailure,
worker_weak_factory_.GetWeakPtr()));
return;
}
VLOG(1) << "Rendered printed page with service for document "
<< document()->cookie() << " page " << page_index;
// Signal everyone that the page is printed.
print_job()->PostTask(
FROM_HERE,
base::BindOnce(&PrintJob::OnPageDone, base::RetainedRef(print_job()),
base::RetainedRef(page)));
++pages_printed_count_;
if (pages_printed_count_ == document()->page_count()) {
// The last page has printed, can proceed to document done processing.
VLOG(1) << "All pages printed for document";
SendDocumentDone();
}
}
#endif // BUILDFLAG(IS_WIN)
void PrintJobWorkerOop::OnDidDocumentDone(int job_id,
mojom::ResultCode result) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
#if BUILDFLAG(IS_WIN)
DCHECK_EQ(pages_printed_count_, document()->page_count());
#endif
if (result != mojom::ResultCode::kSuccess) {
VLOG(1) << "Error completing printing via service for document "
<< document()->cookie() << ": " << result;
NotifyFailure(result);
return;
}
VLOG(1) << "Printing completed with service for document "
<< document()->cookie();
UnregisterServiceManagerClient();
FinishDocumentDone(job_id);
}
#if BUILDFLAG(IS_WIN)
void PrintJobWorkerOop::SpoolPage(PrintedPage* page) {
DCHECK(task_runner()->RunsTasksInCurrentSequence());
DCHECK_NE(page_number(), PageNumber::npos());
#if !defined(NDEBUG)
DCHECK(document()->IsPageInList(*page));
#endif
const MetafilePlayer* metafile = page->metafile();
DCHECK(metafile);
base::MappedReadOnlyRegion region_mapping =
metafile->GetDataAsSharedMemoryRegion();
if (!region_mapping.IsValid()) {
OnFailure();
return;
}
VLOG(1) << "Spooling page " << page_number() << " to print via service";
content::GetUIThreadTaskRunner({})->PostTask(
FROM_HERE,
base::BindOnce(&PrintJobWorkerOop::SendRenderPrintedPage,
ui_weak_factory_.GetWeakPtr(), base::RetainedRef(page),
metafile->GetDataType(),
std::move(region_mapping.region)));
}
#endif // BUILDFLAG(IS_WIN)
void PrintJobWorkerOop::OnDocumentDone() {
// Can do browser-side checks related to completeness for sending, but must
// wait to do OOP related work until OnDidDocumentDone() is received.
CheckDocumentSpoolingComplete();
// Since this call occurs due to all pages having been sent, do not just call
// `SendDocumentDone()`. That should happen as a result of callbacks from
// PrintBackend service.
}
void PrintJobWorkerOop::UpdatePrintSettings(base::Value new_settings,
SettingsCallback callback) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
// Don't use as a const reference, since that reference into `new_settings`
// isn't safe after TakeDictDeprecated() destroys the internal dictionary for
// it.
std::string device_name = *new_settings.FindStringKey(kSettingDeviceName);
// Save the print target type from the settings, since this will be needed
// later when printing is started.
print_target_type_ = DeterminePrintTargetType(new_settings);
VLOG(1) << "Updating print settings via service for " << device_name;
PrintBackendServiceManager& service_mgr =
PrintBackendServiceManager::GetInstance();
service_mgr.UpdatePrintSettings(
device_name, std::move(new_settings).TakeDictDeprecated(),
base::BindOnce(&PrintJobWorkerOop::OnDidUpdatePrintSettings,
ui_weak_factory_.GetWeakPtr(), device_name,
std::move(callback)));
}
void PrintJobWorkerOop::OnFailure() {
content::GetUIThreadTaskRunner({})->PostTask(
FROM_HERE,
base::BindOnce(&PrintJobWorkerOop::UnregisterServiceManagerClient,
ui_weak_factory_.GetWeakPtr()));
PrintJobWorker::OnFailure();
}
void PrintJobWorkerOop::ShowErrorDialog() {
ShowPrintErrorDialog();
}
void PrintJobWorkerOop::UnregisterServiceManagerClient() {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
if (service_manager_client_id_.has_value()) {
PrintBackendServiceManager::GetInstance().UnregisterClient(
service_manager_client_id_.value());
service_manager_client_id_.reset();
}
}
bool PrintJobWorkerOop::TryRestartPrinting() {
// Safety precaution to avoid any chance of infinite loop for retrying.
if (print_retried_)
return false;
print_retried_ = true;
// Register that this printer requires elevated privileges.
PrintBackendServiceManager& service_mgr =
PrintBackendServiceManager::GetInstance();
service_mgr.SetPrinterDriverRequiresElevatedPrivilege(device_name_);
// Failure from access-denied means we no longer need the prior client ID.
UnregisterServiceManagerClient();
// Retry the operation, which should now happen at a higher privilege
// level.
SendStartPrinting(device_name_, document_name_);
return true;
}
void PrintJobWorkerOop::NotifyFailure(mojom::ResultCode result) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
if (result == mojom::ResultCode::kAccessDenied) {
// An attempt to restart could be undesirable if some pages were able to
// be sent to the destination before the error occurred. If we receive
// an access-denied error in such cases then we just abort this print job
// with an error notification to the user. This is more clear to the user
// what has occurred than if we transparently retry the job and succeed,
// where the user could end up with too many printed pages and not know
// why.
// Register that this printer requires elevated privileges so that any
// further attempts to print should succeed.
PrintBackendServiceManager& service_mgr =
PrintBackendServiceManager::GetInstance();
service_mgr.SetPrinterDriverRequiresElevatedPrivilege(device_name_);
}
ShowErrorDialog();
// Initiate rest of regular failure handling.
task_runner()->PostTask(FROM_HERE,
base::BindOnce(&PrintJobWorkerOop::OnFailure,
worker_weak_factory_.GetWeakPtr()));
}
void PrintJobWorkerOop::OnDidUpdatePrintSettings(
const std::string& device_name,
SettingsCallback callback,
mojom::PrintSettingsResultPtr print_settings) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
mojom::ResultCode result;
if (print_settings->is_result_code()) {
result = print_settings->get_result_code();
DCHECK_NE(result, mojom::ResultCode::kSuccess);
PRINTER_LOG(ERROR) << "Failure to update print settings for " << device_name
<< " - error " << result;
// TODO(crbug.com/809738) Fill in support for handling of access-denied
// result code.
} else {
VLOG(1) << "Update print settings from service complete for "
<< device_name;
result = mojom::ResultCode::kSuccess;
printing_context()->ApplyPrintSettings(print_settings->get_settings());
}
GetSettingsDone(std::move(callback), result);
}
void PrintJobWorkerOop::SendStartPrinting(const std::string& device_name,
const std::u16string& document_name) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
DCHECK(features::kEnableOopPrintDriversJobPrint.Get());
// The device name is needed repeatedly for each call to the service, cache
// that for this print job.
device_name_ = device_name;
// Save the document name in case it is needed for retrying a job after
// failure.
document_name_ = document_name;
const int32_t document_cookie = document()->cookie();
VLOG(1) << "Starting printing via service for to `" << device_name_
<< "` for document " << document_cookie;
PrintBackendServiceManager& service_mgr =
PrintBackendServiceManager::GetInstance();
// Register this worker as a printing client.
service_manager_client_id_ = service_mgr.RegisterClient();
service_mgr.StartPrinting(
device_name_, document_cookie, document_name_, print_target_type_,
document()->settings(),
base::BindOnce(&PrintJobWorkerOop::OnDidStartPrinting,
ui_weak_factory_.GetWeakPtr()));
}
#if BUILDFLAG(IS_WIN)
void PrintJobWorkerOop::SendRenderPrintedPage(
const PrintedPage* page,
mojom::MetafileDataType page_data_type,
base::ReadOnlySharedMemoryRegion serialized_page_data) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
// Page numbers are 0-based for the printing context.
const uint32_t page_index = page->page_number() - 1;
const int32_t document_cookie = document()->cookie();
VLOG(1) << "Sending page " << page_index << " of document " << document_cookie
<< " to `" << device_name_ << "` for printing";
PrintBackendServiceManager& service_mgr =
PrintBackendServiceManager::GetInstance();
service_mgr.RenderPrintedPage(
device_name_, document_cookie, *page, page_data_type,
std::move(serialized_page_data),
base::BindOnce(&PrintJobWorkerOop::OnDidRenderPrintedPage,
ui_weak_factory_.GetWeakPtr(), page_index));
}
#endif // BUILDFLAG(IS_WIN)
void PrintJobWorkerOop::SendDocumentDone() {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
const int32_t document_cookie = document()->cookie();
VLOG(1) << "Sending document done for document " << document_cookie;
PrintBackendServiceManager& service_mgr =
PrintBackendServiceManager::GetInstance();
service_mgr.DocumentDone(device_name_, document_cookie,
base::BindOnce(&PrintJobWorkerOop::OnDidDocumentDone,
ui_weak_factory_.GetWeakPtr(),
printing_context()->job_id()));
}
} // namespace printing