blob: 7f44a89164ed960902ccd67cc9c9d395b782cbcf [file] [log] [blame]
// Copyright (c) 2012 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.h"
#include <memory>
#include <utility>
#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/location.h"
#include "base/message_loop/message_loop.h"
#include "base/run_loop.h"
#include "base/single_thread_task_runner.h"
#include "base/task/post_task.h"
#include "base/threading/thread_restrictions.h"
#include "base/threading/thread_task_runner_handle.h"
#include "build/build_config.h"
#include "chrome/browser/chrome_notification_types.h"
#include "chrome/browser/printing/print_job_worker.h"
#include "content/public/browser/notification_service.h"
#include "printing/printed_document.h"
#if defined(OS_WIN)
#include "base/command_line.h"
#include "chrome/browser/printing/pdf_to_emf_converter.h"
#include "chrome/common/chrome_features.h"
#include "printing/pdf_render_settings.h"
#include "printing/printed_page_win.h"
#endif
using base::TimeDelta;
namespace printing {
// Helper function to ensure |job| is valid until at least |callback| returns.
void HoldRefCallback(scoped_refptr<PrintJob> job, base::OnceClosure callback) {
std::move(callback).Run();
}
PrintJob::PrintJob()
: is_job_pending_(false),
is_canceling_(false),
task_runner_(base::ThreadTaskRunnerHandle::Get()) {
DCHECK(base::MessageLoopForUI::IsCurrent());
}
PrintJob::~PrintJob() {
// The job should be finished (or at least canceled) when it is destroyed.
DCHECK(!is_job_pending_);
DCHECK(!is_canceling_);
DCHECK(!worker_ || !worker_->IsRunning());
DCHECK(RunsTasksInCurrentSequence());
}
void PrintJob::Initialize(PrinterQuery* query,
const base::string16& name,
int page_count) {
DCHECK(!worker_);
DCHECK(!is_job_pending_);
DCHECK(!is_canceling_);
DCHECK(!document_);
worker_ = query->DetachWorker();
worker_->SetPrintJob(this);
settings_ = query->settings();
auto new_doc =
base::MakeRefCounted<PrintedDocument>(settings_, name, query->cookie());
new_doc->set_page_count(page_count);
UpdatePrintedDocument(new_doc);
#if defined(OS_WIN)
pdf_page_mapping_ = PageRange::GetPages(settings_.ranges());
if (pdf_page_mapping_.empty()) {
for (int i = 0; i < page_count; i++)
pdf_page_mapping_.push_back(i);
}
#endif
// Don't forget to register to our own messages.
registrar_.Add(this, chrome::NOTIFICATION_PRINT_JOB_EVENT,
content::Source<PrintJob>(this));
}
#if defined(OS_WIN)
// static
std::vector<int> PrintJob::GetFullPageMapping(const std::vector<int>& pages,
int total_page_count) {
std::vector<int> mapping(total_page_count, -1);
for (int page_number : pages) {
// Make sure the page is in range.
if (page_number >= 0 && page_number < total_page_count)
mapping[page_number] = page_number;
}
return mapping;
}
void PrintJob::StartConversionToNativeFormat(
const scoped_refptr<base::RefCountedMemory>& print_data,
const gfx::Size& page_size,
const gfx::Rect& content_area,
const gfx::Point& physical_offsets) {
if (PrintedDocument::HasDebugDumpPath())
document()->DebugDumpData(print_data.get(), FILE_PATH_LITERAL(".pdf"));
if (settings_.printer_is_textonly()) {
StartPdfToTextConversion(print_data, page_size);
} else if ((settings_.printer_is_ps2() || settings_.printer_is_ps3()) &&
!base::FeatureList::IsEnabled(
features::kDisablePostScriptPrinting)) {
StartPdfToPostScriptConversion(print_data, content_area, physical_offsets,
settings_.printer_is_ps2());
} else {
StartPdfToEmfConversion(print_data, page_size, content_area);
}
// Indicate that the PDF is fully rendered and we no longer need the renderer
// and web contents, so the print job does not need to be cancelled if they
// die. This is needed on Windows because the PrintedDocument will not be
// considered complete until PDF conversion finishes.
document()->SetConvertingPdf();
}
void PrintJob::ResetPageMapping() {
pdf_page_mapping_ =
GetFullPageMapping(pdf_page_mapping_, document_->page_count());
}
#endif // defined(OS_WIN)
void PrintJob::Observe(int type,
const content::NotificationSource& source,
const content::NotificationDetails& details) {
DCHECK(RunsTasksInCurrentSequence());
DCHECK_EQ(chrome::NOTIFICATION_PRINT_JOB_EVENT, type);
OnNotifyPrintJobEvent(*content::Details<JobEventDetails>(details).ptr());
}
void PrintJob::StartPrinting() {
DCHECK(RunsTasksInCurrentSequence());
if (!worker_->IsRunning() || is_job_pending_) {
NOTREACHED();
return;
}
// Real work is done in PrintJobWorker::StartPrinting().
worker_->PostTask(
FROM_HERE, base::BindOnce(&HoldRefCallback, base::WrapRefCounted(this),
base::BindOnce(&PrintJobWorker::StartPrinting,
base::Unretained(worker_.get()),
base::RetainedRef(document_))));
// Set the flag right now.
is_job_pending_ = true;
// Tell everyone!
auto details = base::MakeRefCounted<JobEventDetails>(JobEventDetails::NEW_DOC,
0, document_.get());
content::NotificationService::current()->Notify(
chrome::NOTIFICATION_PRINT_JOB_EVENT,
content::Source<PrintJob>(this),
content::Details<JobEventDetails>(details.get()));
}
void PrintJob::Stop() {
DCHECK(RunsTasksInCurrentSequence());
if (quit_closure_) {
// In case we're running a nested run loop to wait for a job to finish,
// and we finished before the timeout, quit the nested loop right away.
std::move(quit_closure_).Run();
}
// Be sure to live long enough.
scoped_refptr<PrintJob> handle(this);
if (worker_->IsRunning()) {
ControlledWorkerShutdown();
} else {
// Flush the cached document.
is_job_pending_ = false;
ClearPrintedDocument();
}
}
void PrintJob::Cancel() {
if (is_canceling_)
return;
is_canceling_ = true;
DCHECK(RunsTasksInCurrentSequence());
if (worker_ && worker_->IsRunning()) {
// Call this right now so it renders the context invalid. Do not use
// InvokeLater since it would take too much time.
worker_->Cancel();
}
// Make sure a Cancel() is broadcast.
auto details = base::MakeRefCounted<JobEventDetails>(JobEventDetails::FAILED,
0, nullptr);
content::NotificationService::current()->Notify(
chrome::NOTIFICATION_PRINT_JOB_EVENT,
content::Source<PrintJob>(this),
content::Details<JobEventDetails>(details.get()));
Stop();
is_canceling_ = false;
}
bool PrintJob::FlushJob(base::TimeDelta timeout) {
// Make sure the object outlive this message loop.
scoped_refptr<PrintJob> handle(this);
base::RunLoop loop(base::RunLoop::Type::kNestableTasksAllowed);
quit_closure_ = loop.QuitClosure();
base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
FROM_HERE, loop.QuitClosure(), timeout);
loop.Run();
return true;
}
bool PrintJob::is_job_pending() const {
return is_job_pending_;
}
PrintedDocument* PrintJob::document() const {
return document_.get();
}
#if defined(OS_WIN)
class PrintJob::PdfConversionState {
public:
PdfConversionState(const gfx::Size& page_size, const gfx::Rect& content_area)
: page_count_(0),
current_page_(0),
pages_in_progress_(0),
page_size_(page_size),
content_area_(content_area) {}
void Start(const scoped_refptr<base::RefCountedMemory>& data,
const PdfRenderSettings& conversion_settings,
PdfConverter::StartCallback start_callback) {
converter_ = PdfConverter::StartPdfConverter(data, conversion_settings,
std::move(start_callback));
}
void GetMorePages(const PdfConverter::GetPageCallback& get_page_callback) {
const int kMaxNumberOfTempFilesPerDocument = 3;
while (pages_in_progress_ < kMaxNumberOfTempFilesPerDocument &&
current_page_ < page_count_) {
++pages_in_progress_;
converter_->GetPage(current_page_++, get_page_callback);
}
}
void OnPageProcessed(const PdfConverter::GetPageCallback& get_page_callback) {
--pages_in_progress_;
GetMorePages(get_page_callback);
// Release converter if we don't need this any more.
if (!pages_in_progress_ && current_page_ >= page_count_)
converter_.reset();
}
void set_page_count(int page_count) { page_count_ = page_count; }
const gfx::Size& page_size() const { return page_size_; }
const gfx::Rect& content_area() const { return content_area_; }
private:
int page_count_;
int current_page_;
int pages_in_progress_;
gfx::Size page_size_;
gfx::Rect content_area_;
std::unique_ptr<PdfConverter> converter_;
};
void PrintJob::StartPdfToEmfConversion(
const scoped_refptr<base::RefCountedMemory>& bytes,
const gfx::Size& page_size,
const gfx::Rect& content_area) {
DCHECK(!pdf_conversion_state_);
pdf_conversion_state_ =
std::make_unique<PdfConversionState>(page_size, content_area);
// TODO(thestig): Figure out why rendering text with GDI results in random
// missing characters for some users. https://crbug.com/658606
// Update : The missing letters seem to have been caused by the same
// problem as https://crbug.com/659604 which was resolved. GDI printing
// seems to work with the fix for this bug applied.
bool print_text_with_gdi =
settings_.print_text_with_gdi() && !settings_.printer_is_xps() &&
base::FeatureList::IsEnabled(features::kGdiTextPrinting);
PdfRenderSettings render_settings(
content_area, gfx::Point(0, 0), settings_.dpi_size(),
/*autorotate=*/true, settings_.color() == COLOR,
print_text_with_gdi ? PdfRenderSettings::Mode::GDI_TEXT
: PdfRenderSettings::Mode::NORMAL);
pdf_conversion_state_->Start(
bytes, render_settings,
base::BindOnce(&PrintJob::OnPdfConversionStarted, this));
}
void PrintJob::OnPdfConversionStarted(int page_count) {
if (page_count <= 0) {
// Be sure to live long enough.
scoped_refptr<PrintJob> handle(this);
pdf_conversion_state_.reset();
Cancel();
return;
}
pdf_conversion_state_->set_page_count(page_count);
pdf_conversion_state_->GetMorePages(
base::BindRepeating(&PrintJob::OnPdfPageConverted, this));
}
void PrintJob::OnPdfPageConverted(int page_number,
float scale_factor,
std::unique_ptr<MetafilePlayer> metafile) {
DCHECK(pdf_conversion_state_);
if (!document_ || !metafile || page_number < 0 ||
static_cast<size_t>(page_number) >= pdf_page_mapping_.size()) {
// Be sure to live long enough.
scoped_refptr<PrintJob> handle(this);
pdf_conversion_state_.reset();
Cancel();
return;
}
// Add the page to the document if it is one of the pages requested by the
// user. If it is not, ignore it.
if (pdf_page_mapping_[page_number] != -1) {
// Update the rendered document. It will send notifications to the listener.
document_->SetPage(pdf_page_mapping_[page_number], std::move(metafile),
scale_factor, pdf_conversion_state_->page_size(),
pdf_conversion_state_->content_area());
}
pdf_conversion_state_->GetMorePages(
base::Bind(&PrintJob::OnPdfPageConverted, this));
}
void PrintJob::StartPdfToTextConversion(
const scoped_refptr<base::RefCountedMemory>& bytes,
const gfx::Size& page_size) {
DCHECK(!pdf_conversion_state_);
pdf_conversion_state_ =
std::make_unique<PdfConversionState>(gfx::Size(), gfx::Rect());
gfx::Rect page_area = gfx::Rect(0, 0, page_size.width(), page_size.height());
PdfRenderSettings render_settings(
page_area, gfx::Point(0, 0), settings_.dpi_size(),
/*autorotate=*/true,
/*use_color=*/true, PdfRenderSettings::Mode::TEXTONLY);
pdf_conversion_state_->Start(
bytes, render_settings,
base::BindOnce(&PrintJob::OnPdfConversionStarted, this));
}
void PrintJob::StartPdfToPostScriptConversion(
const scoped_refptr<base::RefCountedMemory>& bytes,
const gfx::Rect& content_area,
const gfx::Point& physical_offsets,
bool ps_level2) {
DCHECK(!pdf_conversion_state_);
pdf_conversion_state_ = std::make_unique<PdfConversionState>(
gfx::Size(), gfx::Rect());
PdfRenderSettings render_settings(
content_area, physical_offsets, settings_.dpi_size(),
/*autorotate=*/true, settings_.color() == COLOR,
ps_level2 ? PdfRenderSettings::Mode::POSTSCRIPT_LEVEL2
: PdfRenderSettings::Mode::POSTSCRIPT_LEVEL3);
pdf_conversion_state_->Start(
bytes, render_settings,
base::BindOnce(&PrintJob::OnPdfConversionStarted, this));
}
#endif // defined(OS_WIN)
void PrintJob::UpdatePrintedDocument(
scoped_refptr<PrintedDocument> new_document) {
DCHECK(new_document);
document_ = new_document;
if (worker_)
SyncPrintedDocumentToWorker();
}
void PrintJob::ClearPrintedDocument() {
if (!document_)
return;
document_ = nullptr;
if (worker_)
SyncPrintedDocumentToWorker();
}
void PrintJob::SyncPrintedDocumentToWorker() {
DCHECK(worker_);
DCHECK(!is_job_pending_);
worker_->PostTask(
FROM_HERE,
base::BindOnce(&HoldRefCallback, base::WrapRefCounted(this),
base::BindOnce(&PrintJobWorker::OnDocumentChanged,
base::Unretained(worker_.get()),
base::RetainedRef(document_))));
}
void PrintJob::OnNotifyPrintJobEvent(const JobEventDetails& event_details) {
switch (event_details.type()) {
case JobEventDetails::FAILED: {
settings_.Clear();
// No need to cancel since the worker already canceled itself.
Stop();
break;
}
case JobEventDetails::USER_INIT_DONE:
case JobEventDetails::DEFAULT_INIT_DONE:
case JobEventDetails::USER_INIT_CANCELED: {
DCHECK_EQ(event_details.document(), document_.get());
break;
}
case JobEventDetails::NEW_DOC:
case JobEventDetails::JOB_DONE:
case JobEventDetails::ALL_PAGES_REQUESTED: {
// Don't care.
break;
}
case JobEventDetails::DOC_DONE: {
// This will call Stop() and broadcast a JOB_DONE message.
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(&PrintJob::OnDocumentDone, this));
break;
}
#if defined(OS_WIN)
case JobEventDetails::PAGE_DONE:
if (pdf_conversion_state_) {
pdf_conversion_state_->OnPageProcessed(
base::Bind(&PrintJob::OnPdfPageConverted, this));
}
break;
#endif // defined(OS_WIN)
default: {
NOTREACHED();
break;
}
}
}
void PrintJob::OnDocumentDone() {
// Be sure to live long enough. The instance could be destroyed by the
// JOB_DONE broadcast.
scoped_refptr<PrintJob> handle(this);
// Stop the worker thread.
Stop();
auto details = base::MakeRefCounted<JobEventDetails>(
JobEventDetails::JOB_DONE, 0, document_.get());
content::NotificationService::current()->Notify(
chrome::NOTIFICATION_PRINT_JOB_EVENT,
content::Source<PrintJob>(this),
content::Details<JobEventDetails>(details.get()));
}
void PrintJob::ControlledWorkerShutdown() {
DCHECK(RunsTasksInCurrentSequence());
// The deadlock this code works around is specific to window messaging on
// Windows, so we aren't likely to need it on any other platforms.
#if defined(OS_WIN)
// We could easily get into a deadlock case if worker_->Stop() is used; the
// printer driver created a window as a child of the browser window. By
// canceling the job, the printer driver initiated dialog box is destroyed,
// which sends a blocking message to its parent window. If the browser window
// thread is not processing messages, a deadlock occurs.
//
// This function ensures that the dialog box will be destroyed in a timely
// manner by the mere fact that the thread will terminate. So the potential
// deadlock is eliminated.
worker_->StopSoon();
// Delay shutdown until the worker terminates. We want this code path
// to wait on the thread to quit before continuing.
if (worker_->IsRunning()) {
base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
FROM_HERE, base::Bind(&PrintJob::ControlledWorkerShutdown, this),
base::TimeDelta::FromMilliseconds(100));
return;
}
#endif
// Now make sure the thread object is cleaned up. Do this on a worker
// thread because it may block.
base::PostTaskWithTraitsAndReply(
FROM_HERE,
{base::MayBlock(), base::WithBaseSyncPrimitives(),
base::TaskPriority::BEST_EFFORT,
base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN},
base::BindOnce(&PrintJobWorker::Stop, base::Unretained(worker_.get())),
base::BindOnce(&PrintJob::HoldUntilStopIsCalled, this));
is_job_pending_ = false;
registrar_.RemoveAll();
ClearPrintedDocument();
}
bool PrintJob::RunsTasksInCurrentSequence() const {
return task_runner_->RunsTasksInCurrentSequence();
}
bool PrintJob::PostTask(const base::Location& from_here,
base::OnceClosure task) {
return task_runner_->PostTask(from_here, std::move(task));
}
void PrintJob::HoldUntilStopIsCalled() {
}
void PrintJob::set_settings(const PrintSettings& settings) {
settings_ = settings;
}
void PrintJob::set_job_pending(bool pending) {
is_job_pending_ = pending;
}
#if defined(OS_WIN)
JobEventDetails::JobEventDetails(Type type,
int job_id,
PrintedDocument* document,
PrintedPage* page)
: document_(document), page_(page), type_(type), job_id_(job_id) {}
#endif
JobEventDetails::JobEventDetails(Type type,
int job_id,
PrintedDocument* document)
: document_(document), type_(type), job_id_(job_id) {}
JobEventDetails::~JobEventDetails() {
}
PrintedDocument* JobEventDetails::document() const { return document_.get(); }
#if defined(OS_WIN)
PrintedPage* JobEventDetails::page() const { return page_.get(); }
#endif
} // namespace printing