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