blob: b670d5a8071140d22ebd54f768f4dd5dc88a8621 [file] [log] [blame]
// Copyright 2012 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_job_worker.h"
#include <memory>
#include <string>
#include <utility>
#include "base/compiler_specific.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/functional/callback_helpers.h"
#include "base/location.h"
#include "base/numerics/safe_conversions.h"
#include "base/task/single_thread_task_runner.h"
#include "base/values.h"
#include "build/build_config.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/printing/print_job.h"
#include "chrome/grit/generated_resources.h"
#include "components/device_event_log/device_event_log.h"
#include "components/enterprise/buildflags/buildflags.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/global_routing_id.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/web_contents.h"
#include "printing/mojom/print.mojom.h"
#include "printing/print_job_constants.h"
#include "printing/printed_document.h"
#include "printing/printing_context.h"
#include "printing/printing_utils.h"
#include "ui/base/l10n/l10n_util.h"
#if BUILDFLAG(IS_WIN)
#include "base/threading/thread_restrictions.h"
#include "chrome/browser/printing/xps_features.h"
#include "printing/printed_page_win.h"
#endif
using content::BrowserThread;
namespace printing {
namespace {
void DocDoneNotificationCallback(PrintJob* print_job,
int job_id,
PrintedDocument* document) {
print_job->OnDocDone(job_id, document);
}
void FailedNotificationCallback(PrintJob* print_job) {
print_job->OnFailed();
}
} // namespace
PrintJobWorker::PrintJobWorker(
std::unique_ptr<PrintingContext::Delegate> printing_context_delegate,
std::unique_ptr<PrintingContext> printing_context,
PrintJob* print_job)
: printing_context_delegate_(std::move(printing_context_delegate)),
printing_context_(std::move(printing_context)),
print_job_(print_job),
thread_("Printing_Worker") {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
}
PrintJobWorker::~PrintJobWorker() {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
Stop();
}
bool PrintJobWorker::StartPrintingSanityCheck(
const PrintedDocument* new_document) const {
DCHECK(task_runner_->RunsTasksInCurrentSequence());
if (page_number_ != PageNumber::npos()) {
NOTREACHED();
}
if (!document_) {
NOTREACHED();
}
if (document_.get() != new_document) {
NOTREACHED();
}
return true;
}
std::u16string PrintJobWorker::GetDocumentName(
const PrintedDocument* new_document) const {
DCHECK(task_runner_->RunsTasksInCurrentSequence());
std::u16string document_name = SimplifyDocumentTitle(document_->name());
if (document_name.empty()) {
document_name = SimplifyDocumentTitle(
l10n_util::GetStringUTF16(IDS_DEFAULT_PRINT_DOCUMENT_TITLE));
}
return document_name;
}
bool PrintJobWorker::SetupDocument(const std::u16string& document_name) {
mojom::ResultCode result = printing_context_->NewDocument(document_name);
switch (result) {
case mojom::ResultCode::kSuccess:
return true;
case mojom::ResultCode::kCanceled:
OnCancel();
return false;
default:
OnFailure();
return false;
}
}
void PrintJobWorker::StartPrinting(PrintedDocument* new_document) {
if (!StartPrintingSanityCheck(new_document))
return;
if (!SetupDocument(GetDocumentName(new_document))) {
return;
}
// This will start a loop to wait for the page data.
OnNewPage();
// Don't touch this anymore since the instance could be destroyed. It happens
// if all the pages are printed a one sweep and the client doesn't have a
// handle to us anymore. There's a timing issue involved between the worker
// thread and the UI thread. Take no chance.
}
void PrintJobWorker::OnDocumentChanged(PrintedDocument* new_document) {
DCHECK(task_runner_->RunsTasksInCurrentSequence());
if (page_number_ != PageNumber::npos()) {
NOTREACHED();
}
document_ = new_document;
}
void PrintJobWorker::PostWaitForPage() {
// We need to wait for the page to be available.
base::SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask(
FROM_HERE,
base::BindOnce(&PrintJobWorker::OnNewPage, weak_factory_.GetWeakPtr()),
base::Milliseconds(500));
}
void PrintJobWorker::OnNewPage() {
DCHECK(task_runner_->RunsTasksInCurrentSequence());
if (!document_)
return;
bool do_spool_document = true;
#if BUILDFLAG(IS_WIN)
const bool source_is_pdf =
!print_job_->document()->settings().is_modifiable();
if (!ShouldPrintUsingXps(source_is_pdf)) {
// Using the Windows GDI print API.
if (!OnNewPageHelperGdi())
return;
do_spool_document = false;
}
#endif // BUILDFLAG(IS_WIN)
if (do_spool_document) {
if (!document_->GetMetafile()) {
PostWaitForPage();
return;
}
if (!SpoolDocument())
return;
}
OnDocumentDone();
// Don't touch `this` anymore since the instance could be destroyed.
}
#if BUILDFLAG(IS_WIN)
bool PrintJobWorker::OnNewPageHelperGdi() {
if (page_number_ == PageNumber::npos()) {
// Find first page to print.
int page_count = document_->page_count();
if (!page_count) {
// We still don't know how many pages the document contains.
return false;
}
// We have enough information to initialize `page_number_`.
page_number_.Init(document_->settings().ranges(), page_count);
}
while (true) {
scoped_refptr<PrintedPage> page = document_->GetPage(page_number_.ToUint());
if (!page) {
PostWaitForPage();
return false;
}
// The page is there, print it.
if (!SpoolPage(page.get()))
return false;
++page_number_;
if (page_number_ == PageNumber::npos())
break;
}
return true;
}
#endif // BUILDFLAG(IS_WIN)
void PrintJobWorker::Cancel() {
// This is the only function that can be called from any thread.
printing_context_->Cancel();
// Cannot touch any member variable since we don't know in which thread
// context we run.
}
#if BUILDFLAG(ENTERPRISE_CONTENT_ANALYSIS)
void PrintJobWorker::CleanupAfterContentAnalysisDenial() {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
DVLOG(1) << "Canceling job due to content analysis";
}
#endif
bool PrintJobWorker::IsRunning() const {
return thread_.IsRunning();
}
bool PrintJobWorker::PostTask(const base::Location& from_here,
base::OnceClosure task) {
return task_runner_ && task_runner_->PostTask(from_here, std::move(task));
}
void PrintJobWorker::StopSoon() {
thread_.StopSoon();
}
void PrintJobWorker::Stop() {
thread_.Stop();
}
bool PrintJobWorker::Start() {
bool result = thread_.Start();
task_runner_ = thread_.task_runner();
return result;
}
void PrintJobWorker::CheckDocumentSpoolingComplete() {
DCHECK(task_runner_->RunsTasksInCurrentSequence());
DCHECK_EQ(page_number_, PageNumber::npos());
// PrintJob must own this, because only PrintJob can send notifications.
DCHECK(print_job_);
}
void PrintJobWorker::OnDocumentDone() {
CheckDocumentSpoolingComplete();
int job_id = printing_context_->job_id();
if (printing_context_->DocumentDone() != mojom::ResultCode::kSuccess) {
OnFailure();
return;
}
FinishDocumentDone(job_id);
}
void PrintJobWorker::FinishDocumentDone(int job_id) {
DCHECK(task_runner_->RunsTasksInCurrentSequence());
DCHECK(document_);
print_job_->PostTask(
FROM_HERE, base::BindOnce(&DocDoneNotificationCallback,
base::RetainedRef(print_job_.get()), job_id,
base::RetainedRef(document_)));
// Makes sure the variables are reinitialized.
document_ = nullptr;
}
#if BUILDFLAG(IS_WIN)
bool PrintJobWorker::SpoolPage(PrintedPage* page) {
DCHECK(task_runner_->RunsTasksInCurrentSequence());
DCHECK_NE(page_number_, PageNumber::npos());
// Actual printing.
if (document_->RenderPrintedPage(*page, printing_context_.get()) !=
mojom::ResultCode::kSuccess) {
OnFailure();
return false;
}
// Signal everyone that the page is printed.
DCHECK(print_job_);
print_job_->PostTask(FROM_HERE,
base::BindOnce(&PrintJob::OnPageDone, print_job_,
base::RetainedRef(page)));
return true;
}
#endif // BUILDFLAG(IS_WIN)
bool PrintJobWorker::SpoolDocument() {
DCHECK(task_runner_->RunsTasksInCurrentSequence());
mojom::ResultCode result =
document_->RenderPrintedDocument(printing_context_.get());
if (result != mojom::ResultCode::kSuccess) {
PRINTER_LOG(ERROR) << "Failure to render printed document - error "
<< result;
OnFailure();
return false;
}
return true;
}
void PrintJobWorker::OnCancel() {
DCHECK(task_runner_->RunsTasksInCurrentSequence());
DCHECK(print_job_);
print_job_->PostTask(
FROM_HERE,
base::BindOnce(&PrintJob::Cancel, base::RetainedRef(print_job_.get())));
}
void PrintJobWorker::OnFailure() {
DCHECK(task_runner_->RunsTasksInCurrentSequence());
DCHECK(print_job_);
// We may loose our last reference by broadcasting the FAILED event.
scoped_refptr<PrintJob> handle(print_job_.get());
print_job_->PostTask(FROM_HERE,
base::BindOnce(&FailedNotificationCallback,
base::RetainedRef(print_job_.get())));
Cancel();
// Makes sure the variables are reinitialized.
document_ = nullptr;
page_number_ = PageNumber::npos();
}
} // namespace printing