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