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