123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464 |
- // 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_scheduler/post_task.h"
- #include "base/threading/sequenced_worker_pool.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"
- #include "printing/printed_page.h"
- #if defined(OS_WIN)
- #include "chrome/browser/printing/pdf_to_emf_converter.h"
- #include "printing/pdf_render_settings.h"
- #endif
- using base::TimeDelta;
- namespace printing {
- namespace {
- // Helper function to ensure |owner| is valid until at least |callback| returns.
- void HoldRefCallback(const scoped_refptr<PrintJobWorkerOwner>& owner,
- const base::Closure& callback) {
- callback.Run();
- }
- } // namespace
- PrintJob::PrintJob()
- : is_job_pending_(false), is_canceling_(false), quit_factory_(this) {
- // This is normally a UI message loop, but in unit tests, the message loop is
- // of the 'default' type.
- DCHECK(base::MessageLoopForUI::IsCurrent() ||
- base::MessageLoop::current()->type() ==
- base::MessageLoop::TYPE_DEFAULT);
- }
- 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(PrintJobWorkerOwner* job,
- const base::string16& name,
- int page_count) {
- DCHECK(!worker_);
- DCHECK(!is_job_pending_);
- DCHECK(!is_canceling_);
- DCHECK(!document_.get());
- worker_ = job->DetachWorker(this);
- settings_ = job->settings();
- PrintedDocument* new_doc =
- new PrintedDocument(settings_, name, job->cookie());
- new_doc->set_page_count(page_count);
- UpdatePrintedDocument(new_doc);
- // Don't forget to register to our own messages.
- registrar_.Add(this, chrome::NOTIFICATION_PRINT_JOB_EVENT,
- content::Source<PrintJob>(this));
- }
- 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::GetSettingsDone(const PrintSettings& new_settings,
- PrintingContext::Result result) {
- NOTREACHED();
- }
- std::unique_ptr<PrintJobWorker> PrintJob::DetachWorker(
- PrintJobWorkerOwner* new_owner) {
- NOTREACHED();
- return nullptr;
- }
- const PrintSettings& PrintJob::settings() const {
- return settings_;
- }
- int PrintJob::cookie() const {
- // Always use an invalid cookie in this case.
- if (!document_.get())
- return 0;
- return document_->cookie();
- }
- 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::Bind(&HoldRefCallback, WrapRefCounted(this),
- base::Bind(&PrintJobWorker::StartPrinting,
- base::Unretained(worker_.get()),
- base::RetainedRef(document_))));
- // Set the flag right now.
- is_job_pending_ = true;
- // Tell everyone!
- scoped_refptr<JobEventDetails> details(
- new JobEventDetails(JobEventDetails::NEW_DOC, document_.get(), nullptr));
- 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_factory_.HasWeakPtrs()) {
- // In case we're running a nested message loop to wait for a job to finish,
- // and we finished before the timeout, quit the nested loop right away.
- Quit();
- quit_factory_.InvalidateWeakPtrs();
- }
- // Be sure to live long enough.
- scoped_refptr<PrintJob> handle(this);
- if (worker_->IsRunning()) {
- ControlledWorkerShutdown();
- } else {
- // Flush the cached document.
- is_job_pending_ = false;
- UpdatePrintedDocument(nullptr);
- }
- }
- void PrintJob::Cancel() {
- if (is_canceling_)
- return;
- is_canceling_ = true;
- // Be sure to live long enough.
- scoped_refptr<PrintJob> handle(this);
- 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.
- scoped_refptr<JobEventDetails> details(
- new JobEventDetails(JobEventDetails::FAILED, nullptr, 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::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
- FROM_HERE, base::Bind(&PrintJob::Quit, quit_factory_.GetWeakPtr()),
- timeout);
- base::MessageLoop::ScopedNestableTaskAllower allow(
- base::MessageLoop::current());
- base::RunLoop().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(gfx::Size page_size, 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,
- const PdfConverter::StartCallback& start_callback) {
- converter_ = PdfConverter::StartPdfConverter(data, conversion_settings,
- 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; }
- gfx::Size page_size() const { return page_size_; }
- 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::AppendPrintedPage(int page_number) {
- pdf_page_mapping_.push_back(page_number);
- }
- void PrintJob::StartPdfToEmfConversion(
- const scoped_refptr<base::RefCountedMemory>& bytes,
- const gfx::Size& page_size,
- const gfx::Rect& content_area,
- bool print_text_with_gdi) {
- DCHECK(!pdf_conversion_state_);
- pdf_conversion_state_ =
- std::make_unique<PdfConversionState>(page_size, content_area);
- const int kPrinterDpi = settings().dpi();
- PdfRenderSettings settings(
- content_area, gfx::Point(0, 0), kPrinterDpi, /*autorotate=*/true,
- print_text_with_gdi ? PdfRenderSettings::Mode::GDI_TEXT
- : PdfRenderSettings::Mode::NORMAL);
- pdf_conversion_state_->Start(
- bytes, settings, base::Bind(&PrintJob::OnPdfConversionStarted, this));
- }
- void PrintJob::OnPdfConversionStarted(int page_count) {
- if (page_count <= 0) {
- pdf_conversion_state_.reset();
- Cancel();
- return;
- }
- pdf_conversion_state_->set_page_count(page_count);
- pdf_conversion_state_->GetMorePages(
- base::Bind(&PrintJob::OnPdfPageConverted, this));
- }
- void PrintJob::OnPdfPageConverted(int page_number,
- float scale_factor,
- std::unique_ptr<MetafilePlayer> metafile) {
- DCHECK(pdf_conversion_state_);
- if (!document_.get() || !metafile || page_number < 0 ||
- static_cast<size_t>(page_number) >= pdf_page_mapping_.size()) {
- pdf_conversion_state_.reset();
- Cancel();
- return;
- }
- // 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::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());
- const int kPrinterDpi = settings().dpi();
- PdfRenderSettings settings(
- content_area, physical_offsets, kPrinterDpi, true /* autorotate? */,
- ps_level2 ? PdfRenderSettings::Mode::POSTSCRIPT_LEVEL2
- : PdfRenderSettings::Mode::POSTSCRIPT_LEVEL3);
- pdf_conversion_state_->Start(
- bytes, settings, base::Bind(&PrintJob::OnPdfConversionStarted, this));
- }
- #endif // defined(OS_WIN)
- void PrintJob::UpdatePrintedDocument(PrintedDocument* new_document) {
- if (document_.get() == new_document)
- return;
- document_ = new_document;
- if (document_.get())
- settings_ = document_->settings();
- if (worker_) {
- DCHECK(!is_job_pending_);
- // Sync the document with the worker.
- worker_->PostTask(FROM_HERE,
- base::Bind(&HoldRefCallback, WrapRefCounted(this),
- base::Bind(&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::NEW_PAGE:
- 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::Bind(&PrintJob::OnDocumentDone, this));
- break;
- }
- case JobEventDetails::PAGE_DONE:
- #if defined(OS_WIN)
- if (pdf_conversion_state_) {
- pdf_conversion_state_->OnPageProcessed(
- base::Bind(&PrintJob::OnPdfPageConverted, this));
- }
- #endif // defined(OS_WIN)
- break;
- 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();
- scoped_refptr<JobEventDetails> details(
- new JobEventDetails(JobEventDetails::JOB_DONE, document_.get(), nullptr));
- 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::PostTaskAndReply(
- FROM_HERE,
- base::Bind(&PrintJobWorker::Stop, base::Unretained(worker_.get())),
- base::Bind(&PrintJob::HoldUntilStopIsCalled, this));
- is_job_pending_ = false;
- registrar_.RemoveAll();
- UpdatePrintedDocument(nullptr);
- }
- void PrintJob::HoldUntilStopIsCalled() {}
- void PrintJob::Quit() {
- base::RunLoop::QuitCurrentWhenIdleDeprecated();
- }
- // Takes settings_ ownership and will be deleted in the receiving thread.
- JobEventDetails::JobEventDetails(Type type,
- PrintedDocument* document,
- PrintedPage* page)
- : document_(document), page_(page), type_(type) {}
- JobEventDetails::~JobEventDetails() {}
- PrintedDocument* JobEventDetails::document() const {
- return document_.get();
- }
- PrintedPage* JobEventDetails::page() const {
- return page_.get();
- }
- } // namespace printing
|