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_worker.h"
      6 
      7 #include "base/message_loop.h"
      8 #include "base/values.h"
      9 #include "chrome/browser/browser_process.h"
     10 #include "chrome/browser/printing/print_job.h"
     11 #include "content/browser/browser_thread.h"
     12 #include "content/common/notification_service.h"
     13 #include "printing/printed_document.h"
     14 #include "printing/printed_page.h"
     15 
     16 namespace printing {
     17 
     18 class PrintJobWorker::NotificationTask : public Task {
     19  public:
     20   NotificationTask() : print_job_(NULL), details_(NULL) {
     21   }
     22   ~NotificationTask() {
     23   }
     24 
     25   // Initializes the object. This object can't be initialized in the constructor
     26   // since it is not created directly.
     27   void Init(PrintJobWorkerOwner* print_job,
     28             JobEventDetails::Type detail_type,
     29             PrintedDocument* document,
     30             PrintedPage* page) {
     31     DCHECK(!print_job_);
     32     DCHECK(!details_);
     33     print_job_ = print_job;
     34     details_ = new JobEventDetails(detail_type, document, page);
     35   }
     36 
     37   virtual void Run() {
     38     // Send the notification in the right thread.
     39     NotificationService::current()->Notify(
     40         NotificationType::PRINT_JOB_EVENT,
     41         // We know that is is a PrintJob object in this circumstance.
     42         Source<PrintJob>(static_cast<PrintJob*>(print_job_.get())),
     43         Details<JobEventDetails>(details_));
     44   }
     45 
     46   // The job which originates this notification.
     47   scoped_refptr<PrintJobWorkerOwner> print_job_;
     48   scoped_refptr<JobEventDetails> details_;
     49 };
     50 
     51 
     52 PrintJobWorker::PrintJobWorker(PrintJobWorkerOwner* owner)
     53     : Thread("Printing_Worker"),
     54       owner_(owner) {
     55   // The object is created in the IO thread.
     56   DCHECK_EQ(owner_->message_loop(), MessageLoop::current());
     57 
     58   printing_context_.reset(PrintingContext::Create(
     59       g_browser_process->GetApplicationLocale()));
     60 }
     61 
     62 PrintJobWorker::~PrintJobWorker() {
     63   // The object is normally deleted in the UI thread, but when the user
     64   // cancels printing or in the case of print preview, the worker is destroyed
     65   // on the I/O thread.
     66   DCHECK_EQ(owner_->message_loop(), MessageLoop::current());
     67 }
     68 
     69 void PrintJobWorker::SetNewOwner(PrintJobWorkerOwner* new_owner) {
     70   DCHECK(page_number_ == PageNumber::npos());
     71   owner_ = new_owner;
     72 }
     73 
     74 void PrintJobWorker::GetSettings(bool ask_user_for_settings,
     75                                  gfx::NativeView parent_view,
     76                                  int document_page_count,
     77                                  bool has_selection,
     78                                  bool use_overlays) {
     79   DCHECK_EQ(message_loop(), MessageLoop::current());
     80   DCHECK_EQ(page_number_, PageNumber::npos());
     81 
     82   // Recursive task processing is needed for the dialog in case it needs to be
     83   // destroyed by a task.
     84   // TODO(thestig): this code is wrong, SetNestableTasksAllowed(true) is needed
     85   // on the thread where the PrintDlgEx is called, and definitely both calls
     86   // should happen on the same thread. See http://crbug.com/73466
     87   // MessageLoop::current()->SetNestableTasksAllowed(true);
     88   printing_context_->set_use_overlays(use_overlays);
     89 
     90   if (ask_user_for_settings) {
     91     BrowserThread::PostTask(
     92         BrowserThread::UI, FROM_HERE,
     93         NewRunnableMethod(this, &PrintJobWorker::GetSettingsWithUI,
     94                           parent_view, document_page_count,
     95                           has_selection));
     96   } else {
     97     BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
     98         NewRunnableMethod(this, &PrintJobWorker::UseDefaultSettings));
     99   }
    100 }
    101 
    102 void PrintJobWorker::SetSettings(const DictionaryValue* const new_settings) {
    103   DCHECK_EQ(message_loop(), MessageLoop::current());
    104 
    105   BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
    106       NewRunnableMethod(this, &PrintJobWorker::UpdatePrintSettings,
    107                         new_settings));
    108 }
    109 
    110 void PrintJobWorker::UpdatePrintSettings(
    111     const DictionaryValue* const new_settings) {
    112   // Create new PageRanges based on |new_settings|.
    113   PageRanges new_ranges;
    114   ListValue* page_range_array;
    115   if (new_settings->GetList("pageRange", &page_range_array)) {
    116     for (size_t index = 0; index < page_range_array->GetSize(); ++index) {
    117       DictionaryValue* dict;
    118       if (page_range_array->GetDictionary(index, &dict)) {
    119         PageRange range;
    120         if (dict->GetInteger("from", &range.from) &&
    121             dict->GetInteger("to", &range.to)) {
    122           // Page numbers are 0-based.
    123           range.from--;
    124           range.to--;
    125           new_ranges.push_back(range);
    126         }
    127       }
    128     }
    129   }
    130   PrintingContext::Result result =
    131       printing_context_->UpdatePrintSettings(*new_settings, new_ranges);
    132   delete new_settings;
    133   GetSettingsDone(result);
    134 }
    135 
    136 void PrintJobWorker::GetSettingsDone(PrintingContext::Result result) {
    137   // Most PrintingContext functions may start a message loop and process
    138   // message recursively, so disable recursive task processing.
    139   // TODO(thestig): see above comment.  SetNestableTasksAllowed(false) needs to
    140   // be called on the same thread as the previous call.  See
    141   // http://crbug.com/73466
    142   // MessageLoop::current()->SetNestableTasksAllowed(false);
    143 
    144   // We can't use OnFailure() here since owner_ may not support notifications.
    145 
    146   // PrintJob will create the new PrintedDocument.
    147   owner_->message_loop()->PostTask(FROM_HERE, NewRunnableMethod(
    148       owner_,
    149       &PrintJobWorkerOwner::GetSettingsDone,
    150       printing_context_->settings(),
    151       result));
    152 }
    153 
    154 void PrintJobWorker::GetSettingsWithUI(gfx::NativeView parent_view,
    155                                        int document_page_count,
    156                                        bool has_selection) {
    157   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    158 
    159   printing_context_->AskUserForSettings(
    160       parent_view,
    161       document_page_count,
    162       has_selection,
    163       NewCallback(this, &PrintJobWorker::GetSettingsWithUIDone));
    164 }
    165 
    166 void PrintJobWorker::GetSettingsWithUIDone(PrintingContext::Result result) {
    167   message_loop()->PostTask(FROM_HERE, NewRunnableMethod(
    168       this, &PrintJobWorker::GetSettingsDone, result));
    169 }
    170 
    171 void PrintJobWorker::UseDefaultSettings() {
    172   PrintingContext::Result result = printing_context_->UseDefaultSettings();
    173   GetSettingsDone(result);
    174 }
    175 
    176 void PrintJobWorker::StartPrinting(PrintedDocument* new_document) {
    177   DCHECK_EQ(message_loop(), MessageLoop::current());
    178   DCHECK_EQ(page_number_, PageNumber::npos());
    179   DCHECK_EQ(document_, new_document);
    180   DCHECK(document_.get());
    181   DCHECK(new_document->settings().Equals(printing_context_->settings()));
    182 
    183   if (!document_.get() || page_number_ != PageNumber::npos() ||
    184       document_ != new_document) {
    185     return;
    186   }
    187 
    188   PrintingContext::Result result =
    189       printing_context_->NewDocument(document_->name());
    190   if (result != PrintingContext::OK) {
    191     OnFailure();
    192     return;
    193   }
    194 
    195   // Try to print already cached data. It may already have been generated for
    196   // the print preview.
    197   OnNewPage();
    198   // Don't touch this anymore since the instance could be destroyed. It happens
    199   // if all the pages are printed a one sweep and the client doesn't have a
    200   // handle to us anymore. There's a timing issue involved between the worker
    201   // thread and the UI thread. Take no chance.
    202 }
    203 
    204 void PrintJobWorker::OnDocumentChanged(PrintedDocument* new_document) {
    205   DCHECK_EQ(message_loop(), MessageLoop::current());
    206   DCHECK_EQ(page_number_, PageNumber::npos());
    207   DCHECK(!new_document ||
    208          new_document->settings().Equals(printing_context_->settings()));
    209 
    210   if (page_number_ != PageNumber::npos())
    211     return;
    212 
    213   document_ = new_document;
    214 }
    215 
    216 void PrintJobWorker::OnNewPage() {
    217   if (!document_.get()) {
    218     // Spurious message.
    219     return;
    220   }
    221   // message_loop() could return NULL when the print job is cancelled.
    222   DCHECK_EQ(message_loop(), MessageLoop::current());
    223 
    224   if (page_number_ == PageNumber::npos()) {
    225     // Find first page to print.
    226     int page_count = document_->page_count();
    227     if (!page_count) {
    228       // We still don't know how many pages the document contains. We can't
    229       // start to print the document yet since the header/footer may refer to
    230       // the document's page count.
    231       return;
    232     }
    233     // We have enough information to initialize page_number_.
    234     page_number_.Init(document_->settings(), page_count);
    235   }
    236   DCHECK_NE(page_number_, PageNumber::npos());
    237 
    238   for (;;) {
    239     // Is the page available?
    240     scoped_refptr<PrintedPage> page;
    241     if (!document_->GetPage(page_number_.ToInt(), &page)) {
    242       // We need to wait for the page to be available.
    243       MessageLoop::current()->PostDelayedTask(
    244           FROM_HERE,
    245           NewRunnableMethod(this, &PrintJobWorker::OnNewPage),
    246           500);
    247       break;
    248     }
    249     // The page is there, print it.
    250     SpoolPage(*page);
    251     ++page_number_;
    252     if (page_number_ == PageNumber::npos()) {
    253       OnDocumentDone();
    254       // Don't touch this anymore since the instance could be destroyed.
    255       break;
    256     }
    257   }
    258 }
    259 
    260 void PrintJobWorker::Cancel() {
    261   // This is the only function that can be called from any thread.
    262   printing_context_->Cancel();
    263   // Cannot touch any member variable since we don't know in which thread
    264   // context we run.
    265 }
    266 
    267 void PrintJobWorker::OnDocumentDone() {
    268   DCHECK_EQ(message_loop(), MessageLoop::current());
    269   DCHECK_EQ(page_number_, PageNumber::npos());
    270   DCHECK(document_.get());
    271 
    272   if (printing_context_->DocumentDone() != PrintingContext::OK) {
    273     OnFailure();
    274     return;
    275   }
    276 
    277   // Tell everyone!
    278   NotificationTask* task = new NotificationTask();
    279   task->Init(owner_,
    280              JobEventDetails::DOC_DONE,
    281              document_.get(),
    282              NULL);
    283   owner_->message_loop()->PostTask(FROM_HERE, task);
    284 
    285   // Makes sure the variables are reinitialized.
    286   document_ = NULL;
    287 }
    288 
    289 void PrintJobWorker::SpoolPage(PrintedPage& page) {
    290   DCHECK_EQ(message_loop(), MessageLoop::current());
    291   DCHECK_NE(page_number_, PageNumber::npos());
    292 
    293   // Signal everyone that the page is about to be printed.
    294   NotificationTask* task = new NotificationTask();
    295   task->Init(owner_,
    296              JobEventDetails::NEW_PAGE,
    297              document_.get(),
    298              &page);
    299   owner_->message_loop()->PostTask(FROM_HERE, task);
    300 
    301   // Preprocess.
    302   if (printing_context_->NewPage() != PrintingContext::OK) {
    303     OnFailure();
    304     return;
    305   }
    306 
    307   // Actual printing.
    308 #if defined(OS_WIN) || defined(OS_MACOSX)
    309   document_->RenderPrintedPage(page, printing_context_->context());
    310 #elif defined(OS_POSIX)
    311   document_->RenderPrintedPage(page, printing_context_.get());
    312 #endif
    313 
    314   // Postprocess.
    315   if (printing_context_->PageDone() != PrintingContext::OK) {
    316     OnFailure();
    317     return;
    318   }
    319 
    320   // Signal everyone that the page is printed.
    321   task = new NotificationTask();
    322   task->Init(owner_,
    323              JobEventDetails::PAGE_DONE,
    324              document_.get(),
    325              &page);
    326   owner_->message_loop()->PostTask(FROM_HERE, task);
    327 }
    328 
    329 void PrintJobWorker::OnFailure() {
    330   DCHECK_EQ(message_loop(), MessageLoop::current());
    331 
    332   // We may loose our last reference by broadcasting the FAILED event.
    333   scoped_refptr<PrintJobWorkerOwner> handle(owner_);
    334 
    335   NotificationTask* task = new NotificationTask();
    336   task->Init(owner_,
    337              JobEventDetails::FAILED,
    338              document_.get(),
    339              NULL);
    340   owner_->message_loop()->PostTask(FROM_HERE, task);
    341   Cancel();
    342 
    343   // Makes sure the variables are reinitialized.
    344   document_ = NULL;
    345   page_number_ = PageNumber::npos();
    346 }
    347 
    348 }  // namespace printing
    349 
    350 void RunnableMethodTraits<printing::PrintJobWorker>::RetainCallee(
    351     printing::PrintJobWorker* obj) {
    352   DCHECK(!owner_.get());
    353   owner_ = obj->owner_;
    354 }
    355 
    356 void RunnableMethodTraits<printing::PrintJobWorker>::ReleaseCallee(
    357     printing::PrintJobWorker* obj) {
    358   DCHECK_EQ(owner_, obj->owner_);
    359   owner_ = NULL;
    360 }
    361