Home | History | Annotate | Download | only in printing
      1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 #include "chrome/browser/printing/print_job.h"
      6 
      7 #include "base/bind.h"
      8 #include "base/bind_helpers.h"
      9 #include "base/message_loop/message_loop.h"
     10 #include "base/threading/thread_restrictions.h"
     11 #include "base/threading/worker_pool.h"
     12 #include "base/timer/timer.h"
     13 #include "chrome/browser/chrome_notification_types.h"
     14 #include "chrome/browser/printing/print_job_worker.h"
     15 #include "content/public/browser/browser_thread.h"
     16 #include "content/public/browser/notification_service.h"
     17 #include "printing/printed_document.h"
     18 #include "printing/printed_page.h"
     19 
     20 #if defined(OS_WIN)
     21 #include "chrome/browser/printing/pdf_to_emf_converter.h"
     22 #include "printing/pdf_render_settings.h"
     23 #endif
     24 
     25 using base::TimeDelta;
     26 
     27 namespace {
     28 
     29 // Helper function to ensure |owner| is valid until at least |callback| returns.
     30 void HoldRefCallback(const scoped_refptr<printing::PrintJobWorkerOwner>& owner,
     31                      const base::Closure& callback) {
     32   callback.Run();
     33 }
     34 
     35 }  // namespace
     36 
     37 namespace printing {
     38 
     39 PrintJob::PrintJob()
     40     : source_(NULL),
     41       worker_(),
     42       settings_(),
     43       is_job_pending_(false),
     44       is_canceling_(false),
     45       quit_factory_(this) {
     46   // This is normally a UI message loop, but in unit tests, the message loop is
     47   // of the 'default' type.
     48   DCHECK(base::MessageLoopForUI::IsCurrent() ||
     49          base::MessageLoop::current()->type() ==
     50              base::MessageLoop::TYPE_DEFAULT);
     51 }
     52 
     53 PrintJob::~PrintJob() {
     54   // The job should be finished (or at least canceled) when it is destroyed.
     55   DCHECK(!is_job_pending_);
     56   DCHECK(!is_canceling_);
     57   DCHECK(!worker_ || !worker_->IsRunning());
     58   DCHECK(RunsTasksOnCurrentThread());
     59 }
     60 
     61 void PrintJob::Initialize(PrintJobWorkerOwner* job,
     62                           PrintedPagesSource* source,
     63                           int page_count) {
     64   DCHECK(!source_);
     65   DCHECK(!worker_.get());
     66   DCHECK(!is_job_pending_);
     67   DCHECK(!is_canceling_);
     68   DCHECK(!document_.get());
     69   source_ = source;
     70   worker_.reset(job->DetachWorker(this));
     71   settings_ = job->settings();
     72 
     73   PrintedDocument* new_doc =
     74       new PrintedDocument(settings_,
     75                           source_,
     76                           job->cookie(),
     77                           content::BrowserThread::GetBlockingPool());
     78   new_doc->set_page_count(page_count);
     79   UpdatePrintedDocument(new_doc);
     80 
     81   // Don't forget to register to our own messages.
     82   registrar_.Add(this, chrome::NOTIFICATION_PRINT_JOB_EVENT,
     83                  content::Source<PrintJob>(this));
     84 }
     85 
     86 void PrintJob::Observe(int type,
     87                        const content::NotificationSource& source,
     88                        const content::NotificationDetails& details) {
     89   DCHECK(RunsTasksOnCurrentThread());
     90   switch (type) {
     91     case chrome::NOTIFICATION_PRINT_JOB_EVENT: {
     92       OnNotifyPrintJobEvent(*content::Details<JobEventDetails>(details).ptr());
     93       break;
     94     }
     95     default: {
     96       break;
     97     }
     98   }
     99 }
    100 
    101 void PrintJob::GetSettingsDone(const PrintSettings& new_settings,
    102                                PrintingContext::Result result) {
    103   NOTREACHED();
    104 }
    105 
    106 PrintJobWorker* PrintJob::DetachWorker(PrintJobWorkerOwner* new_owner) {
    107   NOTREACHED();
    108   return NULL;
    109 }
    110 
    111 const PrintSettings& PrintJob::settings() const {
    112   return settings_;
    113 }
    114 
    115 int PrintJob::cookie() const {
    116   if (!document_.get())
    117     // Always use an invalid cookie in this case.
    118     return 0;
    119   return document_->cookie();
    120 }
    121 
    122 void PrintJob::StartPrinting() {
    123   DCHECK(RunsTasksOnCurrentThread());
    124   DCHECK(worker_->IsRunning());
    125   DCHECK(!is_job_pending_);
    126   if (!worker_->IsRunning() || is_job_pending_)
    127     return;
    128 
    129   // Real work is done in PrintJobWorker::StartPrinting().
    130   worker_->PostTask(FROM_HERE,
    131                     base::Bind(&HoldRefCallback,
    132                                make_scoped_refptr(this),
    133                                base::Bind(&PrintJobWorker::StartPrinting,
    134                                           base::Unretained(worker_.get()),
    135                                           document_)));
    136   // Set the flag right now.
    137   is_job_pending_ = true;
    138 
    139   // Tell everyone!
    140   scoped_refptr<JobEventDetails> details(
    141       new JobEventDetails(JobEventDetails::NEW_DOC, document_.get(), NULL));
    142   content::NotificationService::current()->Notify(
    143       chrome::NOTIFICATION_PRINT_JOB_EVENT,
    144       content::Source<PrintJob>(this),
    145       content::Details<JobEventDetails>(details.get()));
    146 }
    147 
    148 void PrintJob::Stop() {
    149   DCHECK(RunsTasksOnCurrentThread());
    150 
    151   if (quit_factory_.HasWeakPtrs()) {
    152     // In case we're running a nested message loop to wait for a job to finish,
    153     // and we finished before the timeout, quit the nested loop right away.
    154     Quit();
    155     quit_factory_.InvalidateWeakPtrs();
    156   }
    157 
    158   // Be sure to live long enough.
    159   scoped_refptr<PrintJob> handle(this);
    160 
    161   if (worker_->IsRunning()) {
    162     ControlledWorkerShutdown();
    163   } else {
    164     // Flush the cached document.
    165     UpdatePrintedDocument(NULL);
    166   }
    167 }
    168 
    169 void PrintJob::Cancel() {
    170   if (is_canceling_)
    171     return;
    172   is_canceling_ = true;
    173 
    174   // Be sure to live long enough.
    175   scoped_refptr<PrintJob> handle(this);
    176 
    177   DCHECK(RunsTasksOnCurrentThread());
    178   if (worker_ && worker_->IsRunning()) {
    179     // Call this right now so it renders the context invalid. Do not use
    180     // InvokeLater since it would take too much time.
    181     worker_->Cancel();
    182   }
    183   // Make sure a Cancel() is broadcast.
    184   scoped_refptr<JobEventDetails> details(
    185       new JobEventDetails(JobEventDetails::FAILED, NULL, NULL));
    186   content::NotificationService::current()->Notify(
    187       chrome::NOTIFICATION_PRINT_JOB_EVENT,
    188       content::Source<PrintJob>(this),
    189       content::Details<JobEventDetails>(details.get()));
    190   Stop();
    191   is_canceling_ = false;
    192 }
    193 
    194 bool PrintJob::FlushJob(base::TimeDelta timeout) {
    195   // Make sure the object outlive this message loop.
    196   scoped_refptr<PrintJob> handle(this);
    197 
    198   base::MessageLoop::current()->PostDelayedTask(FROM_HERE,
    199       base::Bind(&PrintJob::Quit, quit_factory_.GetWeakPtr()), timeout);
    200 
    201   base::MessageLoop::ScopedNestableTaskAllower allow(
    202       base::MessageLoop::current());
    203   base::MessageLoop::current()->Run();
    204 
    205   return true;
    206 }
    207 
    208 void PrintJob::DisconnectSource() {
    209   source_ = NULL;
    210   if (document_.get())
    211     document_->DisconnectSource();
    212 }
    213 
    214 bool PrintJob::is_job_pending() const {
    215   return is_job_pending_;
    216 }
    217 
    218 PrintedDocument* PrintJob::document() const {
    219   return document_.get();
    220 }
    221 
    222 #if defined(OS_WIN)
    223 
    224 class PrintJob::PdfToEmfState {
    225  public:
    226   PdfToEmfState(const gfx::Size& page_size, const gfx::Rect& content_area)
    227       : page_count_(0),
    228         current_page_(0),
    229         pages_in_progress_(0),
    230         page_size_(page_size),
    231         content_area_(content_area),
    232         converter_(PdfToEmfConverter::CreateDefault()) {}
    233 
    234   void Start(const scoped_refptr<base::RefCountedMemory>& data,
    235              const PdfRenderSettings& conversion_settings,
    236              const PdfToEmfConverter::StartCallback& start_callback) {
    237     converter_->Start(data, conversion_settings, start_callback);
    238   }
    239 
    240   void GetMorePages(
    241       const PdfToEmfConverter::GetPageCallback& get_page_callback) {
    242     const int kMaxNumberOfTempFilesPerDocument = 3;
    243     while (pages_in_progress_ < kMaxNumberOfTempFilesPerDocument &&
    244            current_page_ < page_count_) {
    245       ++pages_in_progress_;
    246       converter_->GetPage(current_page_++, get_page_callback);
    247     }
    248   }
    249 
    250   void OnPageProcessed(
    251       const PdfToEmfConverter::GetPageCallback& get_page_callback) {
    252     --pages_in_progress_;
    253     GetMorePages(get_page_callback);
    254     // Release converter if we don't need this any more.
    255     if (!pages_in_progress_ && current_page_ >= page_count_)
    256       converter_.reset();
    257   }
    258 
    259   void set_page_count(int page_count) { page_count_ = page_count; }
    260   gfx::Size page_size() const { return page_size_; }
    261   gfx::Rect content_area() const { return content_area_; }
    262 
    263  private:
    264   int page_count_;
    265   int current_page_;
    266   int pages_in_progress_;
    267   gfx::Size page_size_;
    268   gfx::Rect content_area_;
    269   scoped_ptr<PdfToEmfConverter> converter_;
    270 };
    271 
    272 void PrintJob::StartPdfToEmfConversion(
    273     const scoped_refptr<base::RefCountedMemory>& bytes,
    274     const gfx::Size& page_size,
    275     const gfx::Rect& content_area) {
    276   DCHECK(!ptd_to_emf_state_.get());
    277   ptd_to_emf_state_.reset(new PdfToEmfState(page_size, content_area));
    278   const int kPrinterDpi = settings().dpi();
    279   ptd_to_emf_state_->Start(
    280       bytes,
    281       printing::PdfRenderSettings(content_area, kPrinterDpi, true),
    282       base::Bind(&PrintJob::OnPdfToEmfStarted, this));
    283 }
    284 
    285 void PrintJob::OnPdfToEmfStarted(int page_count) {
    286   if (page_count <= 0) {
    287     ptd_to_emf_state_.reset();
    288     Cancel();
    289     return;
    290   }
    291   ptd_to_emf_state_->set_page_count(page_count);
    292   ptd_to_emf_state_->GetMorePages(
    293       base::Bind(&PrintJob::OnPdfToEmfPageConverted, this));
    294 }
    295 
    296 void PrintJob::OnPdfToEmfPageConverted(int page_number,
    297                                        double scale_factor,
    298                                        scoped_ptr<MetafilePlayer> emf) {
    299   DCHECK(ptd_to_emf_state_);
    300   if (!document_.get() || !emf) {
    301     ptd_to_emf_state_.reset();
    302     Cancel();
    303     return;
    304   }
    305 
    306   // Update the rendered document. It will send notifications to the listener.
    307   document_->SetPage(page_number,
    308                      emf.Pass(),
    309                      scale_factor,
    310                      ptd_to_emf_state_->page_size(),
    311                      ptd_to_emf_state_->content_area());
    312 
    313   ptd_to_emf_state_->GetMorePages(
    314       base::Bind(&PrintJob::OnPdfToEmfPageConverted, this));
    315 }
    316 
    317 #endif  // OS_WIN
    318 
    319 void PrintJob::UpdatePrintedDocument(PrintedDocument* new_document) {
    320   if (document_.get() == new_document)
    321     return;
    322 
    323   document_ = new_document;
    324 
    325   if (document_.get()) {
    326     settings_ = document_->settings();
    327   }
    328 
    329   if (worker_) {
    330     DCHECK(!is_job_pending_);
    331     // Sync the document with the worker.
    332     worker_->PostTask(FROM_HERE,
    333                       base::Bind(&HoldRefCallback,
    334                                  make_scoped_refptr(this),
    335                                  base::Bind(&PrintJobWorker::OnDocumentChanged,
    336                                             base::Unretained(worker_.get()),
    337                                             document_)));
    338   }
    339 }
    340 
    341 void PrintJob::OnNotifyPrintJobEvent(const JobEventDetails& event_details) {
    342   switch (event_details.type()) {
    343     case JobEventDetails::FAILED: {
    344       settings_.Clear();
    345       // No need to cancel since the worker already canceled itself.
    346       Stop();
    347       break;
    348     }
    349     case JobEventDetails::USER_INIT_DONE:
    350     case JobEventDetails::DEFAULT_INIT_DONE:
    351     case JobEventDetails::USER_INIT_CANCELED: {
    352       DCHECK_EQ(event_details.document(), document_.get());
    353       break;
    354     }
    355     case JobEventDetails::NEW_DOC:
    356     case JobEventDetails::NEW_PAGE:
    357     case JobEventDetails::JOB_DONE:
    358     case JobEventDetails::ALL_PAGES_REQUESTED: {
    359       // Don't care.
    360       break;
    361     }
    362     case JobEventDetails::DOC_DONE: {
    363       // This will call Stop() and broadcast a JOB_DONE message.
    364       base::MessageLoop::current()->PostTask(
    365           FROM_HERE, base::Bind(&PrintJob::OnDocumentDone, this));
    366       break;
    367     }
    368     case JobEventDetails::PAGE_DONE:
    369 #if defined(OS_WIN)
    370       ptd_to_emf_state_->OnPageProcessed(
    371           base::Bind(&PrintJob::OnPdfToEmfPageConverted, this));
    372 #endif  // OS_WIN
    373       break;
    374     default: {
    375       NOTREACHED();
    376       break;
    377     }
    378   }
    379 }
    380 
    381 void PrintJob::OnDocumentDone() {
    382   // Be sure to live long enough. The instance could be destroyed by the
    383   // JOB_DONE broadcast.
    384   scoped_refptr<PrintJob> handle(this);
    385 
    386   // Stop the worker thread.
    387   Stop();
    388 
    389   scoped_refptr<JobEventDetails> details(
    390       new JobEventDetails(JobEventDetails::JOB_DONE, document_.get(), NULL));
    391   content::NotificationService::current()->Notify(
    392       chrome::NOTIFICATION_PRINT_JOB_EVENT,
    393       content::Source<PrintJob>(this),
    394       content::Details<JobEventDetails>(details.get()));
    395 }
    396 
    397 void PrintJob::ControlledWorkerShutdown() {
    398   DCHECK(RunsTasksOnCurrentThread());
    399 
    400   // The deadlock this code works around is specific to window messaging on
    401   // Windows, so we aren't likely to need it on any other platforms.
    402 #if defined(OS_WIN)
    403   // We could easily get into a deadlock case if worker_->Stop() is used; the
    404   // printer driver created a window as a child of the browser window. By
    405   // canceling the job, the printer driver initiated dialog box is destroyed,
    406   // which sends a blocking message to its parent window. If the browser window
    407   // thread is not processing messages, a deadlock occurs.
    408   //
    409   // This function ensures that the dialog box will be destroyed in a timely
    410   // manner by the mere fact that the thread will terminate. So the potential
    411   // deadlock is eliminated.
    412   worker_->StopSoon();
    413 
    414   // Delay shutdown until the worker terminates.  We want this code path
    415   // to wait on the thread to quit before continuing.
    416   if (worker_->IsRunning()) {
    417     base::MessageLoop::current()->PostDelayedTask(
    418         FROM_HERE,
    419         base::Bind(&PrintJob::ControlledWorkerShutdown, this),
    420         base::TimeDelta::FromMilliseconds(100));
    421     return;
    422   }
    423 #endif
    424 
    425 
    426   // Now make sure the thread object is cleaned up. Do this on a worker
    427   // thread because it may block.
    428   base::WorkerPool::PostTaskAndReply(
    429       FROM_HERE,
    430       base::Bind(&PrintJobWorker::Stop, base::Unretained(worker_.get())),
    431       base::Bind(&PrintJob::HoldUntilStopIsCalled, this),
    432       false);
    433 
    434   is_job_pending_ = false;
    435   registrar_.RemoveAll();
    436   UpdatePrintedDocument(NULL);
    437 }
    438 
    439 void PrintJob::HoldUntilStopIsCalled() {
    440 }
    441 
    442 void PrintJob::Quit() {
    443   base::MessageLoop::current()->Quit();
    444 }
    445 
    446 // Takes settings_ ownership and will be deleted in the receiving thread.
    447 JobEventDetails::JobEventDetails(Type type,
    448                                  PrintedDocument* document,
    449                                  PrintedPage* page)
    450     : document_(document),
    451       page_(page),
    452       type_(type) {
    453 }
    454 
    455 JobEventDetails::~JobEventDetails() {
    456 }
    457 
    458 PrintedDocument* JobEventDetails::document() const { return document_.get(); }
    459 
    460 PrintedPage* JobEventDetails::page() const { return page_.get(); }
    461 
    462 }  // namespace printing
    463