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/notification_service.h"
     16 #include "printing/printed_document.h"
     17 #include "printing/printed_page.h"
     18 
     19 using base::TimeDelta;
     20 
     21 namespace {
     22 
     23 // Helper function to ensure |owner| is valid until at least |callback| returns.
     24 void HoldRefCallback(const scoped_refptr<printing::PrintJobWorkerOwner>& owner,
     25                      const base::Closure& callback) {
     26   callback.Run();
     27 }
     28 
     29 }  // namespace
     30 
     31 namespace printing {
     32 
     33 PrintJob::PrintJob()
     34     : ui_message_loop_(base::MessageLoop::current()),
     35       source_(NULL),
     36       worker_(),
     37       settings_(),
     38       is_job_pending_(false),
     39       is_canceling_(false),
     40       is_stopping_(false),
     41       is_stopped_(false),
     42       quit_factory_(this),
     43       weak_ptr_factory_(this) {
     44   DCHECK(ui_message_loop_);
     45   // This is normally a UI message loop, but in unit tests, the message loop is
     46   // of the 'default' type.
     47   DCHECK(ui_message_loop_->type() == base::MessageLoop::TYPE_UI ||
     48          ui_message_loop_->type() == base::MessageLoop::TYPE_DEFAULT);
     49   ui_message_loop_->AddDestructionObserver(this);
     50 }
     51 
     52 PrintJob::~PrintJob() {
     53   ui_message_loop_->RemoveDestructionObserver(this);
     54   // The job should be finished (or at least canceled) when it is destroyed.
     55   DCHECK(!is_job_pending_);
     56   DCHECK(!is_canceling_);
     57   if (worker_.get())
     58     DCHECK(worker_->message_loop() == NULL);
     59   DCHECK_EQ(ui_message_loop_, base::MessageLoop::current());
     60 }
     61 
     62 void PrintJob::Initialize(PrintJobWorkerOwner* job,
     63                           PrintedPagesSource* source,
     64                           int page_count) {
     65   DCHECK(!source_);
     66   DCHECK(!worker_.get());
     67   DCHECK(!is_job_pending_);
     68   DCHECK(!is_canceling_);
     69   DCHECK(!document_.get());
     70   source_ = source;
     71   worker_.reset(job->DetachWorker(this));
     72   settings_ = job->settings();
     73 
     74   PrintedDocument* new_doc =
     75       new PrintedDocument(settings_, source_, job->cookie());
     76   new_doc->set_page_count(page_count);
     77   UpdatePrintedDocument(new_doc);
     78 
     79   // Don't forget to register to our own messages.
     80   registrar_.Add(this, chrome::NOTIFICATION_PRINT_JOB_EVENT,
     81                  content::Source<PrintJob>(this));
     82 }
     83 
     84 void PrintJob::Observe(int type,
     85                        const content::NotificationSource& source,
     86                        const content::NotificationDetails& details) {
     87   DCHECK_EQ(ui_message_loop_, base::MessageLoop::current());
     88   switch (type) {
     89     case chrome::NOTIFICATION_PRINT_JOB_EVENT: {
     90       OnNotifyPrintJobEvent(*content::Details<JobEventDetails>(details).ptr());
     91       break;
     92     }
     93     default: {
     94       break;
     95     }
     96   }
     97 }
     98 
     99 void PrintJob::GetSettingsDone(const PrintSettings& new_settings,
    100                                PrintingContext::Result result) {
    101   NOTREACHED();
    102 }
    103 
    104 PrintJobWorker* PrintJob::DetachWorker(PrintJobWorkerOwner* new_owner) {
    105   NOTREACHED();
    106   return NULL;
    107 }
    108 
    109 base::MessageLoop* PrintJob::message_loop() {
    110   return ui_message_loop_;
    111 }
    112 
    113 const PrintSettings& PrintJob::settings() const {
    114   return settings_;
    115 }
    116 
    117 int PrintJob::cookie() const {
    118   if (!document_.get())
    119     // Always use an invalid cookie in this case.
    120     return 0;
    121   return document_->cookie();
    122 }
    123 
    124 void PrintJob::WillDestroyCurrentMessageLoop() {
    125   NOTREACHED();
    126 }
    127 
    128 void PrintJob::StartPrinting() {
    129   DCHECK_EQ(ui_message_loop_, base::MessageLoop::current());
    130   DCHECK(worker_->message_loop());
    131   DCHECK(!is_job_pending_);
    132   if (!worker_->message_loop() || is_job_pending_)
    133     return;
    134 
    135   // Real work is done in PrintJobWorker::StartPrinting().
    136   worker_->message_loop()->PostTask(
    137       FROM_HERE,
    138       base::Bind(&HoldRefCallback, make_scoped_refptr(this),
    139                  base::Bind(&PrintJobWorker::StartPrinting,
    140                             base::Unretained(worker_.get()), document_)));
    141   // Set the flag right now.
    142   is_job_pending_ = true;
    143 
    144   // Tell everyone!
    145   scoped_refptr<JobEventDetails> details(
    146       new JobEventDetails(JobEventDetails::NEW_DOC, document_.get(), NULL));
    147   content::NotificationService::current()->Notify(
    148       chrome::NOTIFICATION_PRINT_JOB_EVENT,
    149       content::Source<PrintJob>(this),
    150       content::Details<JobEventDetails>(details.get()));
    151 }
    152 
    153 void PrintJob::Stop() {
    154   DCHECK_EQ(ui_message_loop_, base::MessageLoop::current());
    155 
    156   if (quit_factory_.HasWeakPtrs()) {
    157     // In case we're running a nested message loop to wait for a job to finish,
    158     // and we finished before the timeout, quit the nested loop right away.
    159     Quit();
    160     quit_factory_.InvalidateWeakPtrs();
    161   }
    162 
    163   // Be sure to live long enough.
    164   scoped_refptr<PrintJob> handle(this);
    165 
    166   base::MessageLoop* worker_loop = worker_->message_loop();
    167   if (worker_loop) {
    168     ControlledWorkerShutdown();
    169 
    170     is_job_pending_ = false;
    171     registrar_.Remove(this, chrome::NOTIFICATION_PRINT_JOB_EVENT,
    172                       content::Source<PrintJob>(this));
    173   }
    174   // Flush the cached document.
    175   UpdatePrintedDocument(NULL);
    176 }
    177 
    178 void PrintJob::Cancel() {
    179   if (is_canceling_)
    180     return;
    181   is_canceling_ = true;
    182 
    183   // Be sure to live long enough.
    184   scoped_refptr<PrintJob> handle(this);
    185 
    186   DCHECK_EQ(ui_message_loop_, base::MessageLoop::current());
    187   base::MessageLoop* worker_loop =
    188       worker_.get() ? worker_->message_loop() : NULL;
    189   if (worker_loop) {
    190     // Call this right now so it renders the context invalid. Do not use
    191     // InvokeLater since it would take too much time.
    192     worker_->Cancel();
    193   }
    194   // Make sure a Cancel() is broadcast.
    195   scoped_refptr<JobEventDetails> details(
    196       new JobEventDetails(JobEventDetails::FAILED, NULL, NULL));
    197   content::NotificationService::current()->Notify(
    198       chrome::NOTIFICATION_PRINT_JOB_EVENT,
    199       content::Source<PrintJob>(this),
    200       content::Details<JobEventDetails>(details.get()));
    201   Stop();
    202   is_canceling_ = false;
    203 }
    204 
    205 bool PrintJob::FlushJob(base::TimeDelta timeout) {
    206   // Make sure the object outlive this message loop.
    207   scoped_refptr<PrintJob> handle(this);
    208 
    209   base::MessageLoop::current()->PostDelayedTask(FROM_HERE,
    210       base::Bind(&PrintJob::Quit, quit_factory_.GetWeakPtr()), timeout);
    211 
    212   base::MessageLoop::ScopedNestableTaskAllower allow(
    213       base::MessageLoop::current());
    214   base::MessageLoop::current()->Run();
    215 
    216   return true;
    217 }
    218 
    219 void PrintJob::DisconnectSource() {
    220   source_ = NULL;
    221   if (document_.get())
    222     document_->DisconnectSource();
    223 }
    224 
    225 bool PrintJob::is_job_pending() const {
    226   return is_job_pending_;
    227 }
    228 
    229 bool PrintJob::is_stopping() const {
    230   return is_stopping_;
    231 }
    232 
    233 bool PrintJob::is_stopped() const {
    234   return is_stopped_;
    235 }
    236 
    237 PrintedDocument* PrintJob::document() const {
    238   return document_.get();
    239 }
    240 
    241 void PrintJob::UpdatePrintedDocument(PrintedDocument* new_document) {
    242   if (document_.get() == new_document)
    243     return;
    244 
    245   document_ = new_document;
    246 
    247   if (document_.get()) {
    248     settings_ = document_->settings();
    249   }
    250 
    251   if (worker_.get() && worker_->message_loop()) {
    252     DCHECK(!is_job_pending_);
    253     // Sync the document with the worker.
    254     worker_->message_loop()->PostTask(
    255         FROM_HERE,
    256         base::Bind(&HoldRefCallback, make_scoped_refptr(this),
    257                    base::Bind(&PrintJobWorker::OnDocumentChanged,
    258                               base::Unretained(worker_.get()), document_)));
    259   }
    260 }
    261 
    262 void PrintJob::OnNotifyPrintJobEvent(const JobEventDetails& event_details) {
    263   switch (event_details.type()) {
    264     case JobEventDetails::FAILED: {
    265       settings_.Clear();
    266       // No need to cancel since the worker already canceled itself.
    267       Stop();
    268       break;
    269     }
    270     case JobEventDetails::USER_INIT_DONE:
    271     case JobEventDetails::DEFAULT_INIT_DONE:
    272     case JobEventDetails::USER_INIT_CANCELED: {
    273       DCHECK_EQ(event_details.document(), document_.get());
    274       break;
    275     }
    276     case JobEventDetails::NEW_DOC:
    277     case JobEventDetails::NEW_PAGE:
    278     case JobEventDetails::PAGE_DONE:
    279     case JobEventDetails::JOB_DONE:
    280     case JobEventDetails::ALL_PAGES_REQUESTED: {
    281       // Don't care.
    282       break;
    283     }
    284     case JobEventDetails::DOC_DONE: {
    285       // This will call Stop() and broadcast a JOB_DONE message.
    286       base::MessageLoop::current()->PostTask(
    287           FROM_HERE, base::Bind(&PrintJob::OnDocumentDone, this));
    288       break;
    289     }
    290     default: {
    291       NOTREACHED();
    292       break;
    293     }
    294   }
    295 }
    296 
    297 void PrintJob::OnDocumentDone() {
    298   // Be sure to live long enough. The instance could be destroyed by the
    299   // JOB_DONE broadcast.
    300   scoped_refptr<PrintJob> handle(this);
    301 
    302   // Stop the worker thread.
    303   Stop();
    304 
    305   scoped_refptr<JobEventDetails> details(
    306       new JobEventDetails(JobEventDetails::JOB_DONE, document_.get(), NULL));
    307   content::NotificationService::current()->Notify(
    308       chrome::NOTIFICATION_PRINT_JOB_EVENT,
    309       content::Source<PrintJob>(this),
    310       content::Details<JobEventDetails>(details.get()));
    311 }
    312 
    313 void PrintJob::ControlledWorkerShutdown() {
    314   DCHECK_EQ(ui_message_loop_, base::MessageLoop::current());
    315 
    316   // The deadlock this code works around is specific to window messaging on
    317   // Windows, so we aren't likely to need it on any other platforms.
    318 #if defined(OS_WIN)
    319   // We could easily get into a deadlock case if worker_->Stop() is used; the
    320   // printer driver created a window as a child of the browser window. By
    321   // canceling the job, the printer driver initiated dialog box is destroyed,
    322   // which sends a blocking message to its parent window. If the browser window
    323   // thread is not processing messages, a deadlock occurs.
    324   //
    325   // This function ensures that the dialog box will be destroyed in a timely
    326   // manner by the mere fact that the thread will terminate. So the potential
    327   // deadlock is eliminated.
    328   worker_->StopSoon();
    329 
    330   // Run a tight message loop until the worker terminates. It may seems like a
    331   // hack but I see no other way to get it to work flawlessly. The issues here
    332   // are:
    333   // - We don't want to run tasks while the thread is quitting.
    334   // - We want this code path to wait on the thread to quit before continuing.
    335   MSG msg;
    336   HANDLE thread_handle = worker_->thread_handle().platform_handle();
    337   for (; thread_handle;) {
    338     // Note that we don't do any kind of message prioritization since we don't
    339     // execute any pending task or timer.
    340     DWORD result = MsgWaitForMultipleObjects(1, &thread_handle,
    341                                              FALSE, INFINITE, QS_ALLINPUT);
    342     if (result == WAIT_OBJECT_0 + 1) {
    343       while (PeekMessage(&msg, NULL, 0, 0, TRUE) > 0) {
    344         TranslateMessage(&msg);
    345         DispatchMessage(&msg);
    346       }
    347       // Continue looping.
    348     } else if (result == WAIT_OBJECT_0) {
    349       // The thread quit.
    350       break;
    351     } else {
    352       // An error occurred. Assume the thread quit.
    353       NOTREACHED();
    354       break;
    355     }
    356   }
    357 #endif
    358 
    359 
    360   // Now make sure the thread object is cleaned up. Do this on a worker
    361   // thread because it may block.
    362   is_stopping_ = true;
    363 
    364   base::WorkerPool::PostTaskAndReply(
    365       FROM_HERE,
    366       base::Bind(&PrintJobWorker::Stop,
    367                  base::Unretained(worker_.get())),
    368       base::Bind(&PrintJob::HoldUntilStopIsCalled,
    369                  weak_ptr_factory_.GetWeakPtr(),
    370                  scoped_refptr<PrintJob>(this)),
    371       false);
    372 }
    373 
    374 void PrintJob::HoldUntilStopIsCalled(const scoped_refptr<PrintJob>&) {
    375   is_stopped_ = true;
    376   is_stopping_ = false;
    377 }
    378 
    379 void PrintJob::Quit() {
    380   base::MessageLoop::current()->Quit();
    381 }
    382 
    383 // Takes settings_ ownership and will be deleted in the receiving thread.
    384 JobEventDetails::JobEventDetails(Type type,
    385                                  PrintedDocument* document,
    386                                  PrintedPage* page)
    387     : document_(document),
    388       page_(page),
    389       type_(type) {
    390 }
    391 
    392 JobEventDetails::~JobEventDetails() {
    393 }
    394 
    395 PrintedDocument* JobEventDetails::document() const { return document_.get(); }
    396 
    397 PrintedPage* JobEventDetails::page() const { return page_.get(); }
    398 
    399 }  // namespace printing
    400