Home | History | Annotate | Download | only in printing
      1 // Copyright 2013 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_view_manager_base.h"
      6 
      7 #include <map>
      8 
      9 #include "base/bind.h"
     10 #include "base/memory/scoped_ptr.h"
     11 #include "base/prefs/pref_service.h"
     12 #include "base/strings/utf_string_conversions.h"
     13 #include "base/timer/timer.h"
     14 #include "chrome/browser/browser_process.h"
     15 #include "chrome/browser/chrome_notification_types.h"
     16 #include "chrome/browser/printing/print_job.h"
     17 #include "chrome/browser/printing/print_job_manager.h"
     18 #include "chrome/browser/printing/printer_query.h"
     19 #include "chrome/browser/profiles/profile.h"
     20 #include "chrome/common/pref_names.h"
     21 #include "chrome/common/print_messages.h"
     22 #include "content/public/browser/browser_thread.h"
     23 #include "content/public/browser/notification_details.h"
     24 #include "content/public/browser/notification_service.h"
     25 #include "content/public/browser/notification_source.h"
     26 #include "content/public/browser/render_view_host.h"
     27 #include "content/public/browser/web_contents.h"
     28 #include "content/public/browser/web_contents_view.h"
     29 #include "grit/generated_resources.h"
     30 #include "printing/metafile_impl.h"
     31 #include "printing/printed_document.h"
     32 #include "ui/base/l10n/l10n_util.h"
     33 
     34 #if defined(OS_WIN)
     35 #include "base/command_line.h"
     36 #include "chrome/common/chrome_switches.h"
     37 #endif
     38 
     39 #if defined(ENABLE_FULL_PRINTING)
     40 #include "chrome/browser/printing/print_error_dialog.h"
     41 #endif
     42 
     43 using base::TimeDelta;
     44 using content::BrowserThread;
     45 
     46 #if defined(OS_WIN)
     47 // Limits memory usage by raster to 64 MiB.
     48 const int kMaxRasterSizeInPixels = 16*1024*1024;
     49 #endif
     50 
     51 namespace printing {
     52 
     53 PrintViewManagerBase::PrintViewManagerBase(content::WebContents* web_contents)
     54     : content::WebContentsObserver(web_contents),
     55       number_pages_(0),
     56       printing_succeeded_(false),
     57       inside_inner_message_loop_(false),
     58       cookie_(0),
     59       queue_(g_browser_process->print_job_manager()->queue()) {
     60   DCHECK(queue_);
     61 #if defined(OS_POSIX) && !defined(OS_MACOSX)
     62   expecting_first_page_ = true;
     63 #endif
     64   Profile* profile =
     65       Profile::FromBrowserContext(web_contents->GetBrowserContext());
     66   printing_enabled_.Init(
     67       prefs::kPrintingEnabled,
     68       profile->GetPrefs(),
     69       base::Bind(&PrintViewManagerBase::UpdateScriptedPrintingBlocked,
     70                  base::Unretained(this)));
     71 }
     72 
     73 PrintViewManagerBase::~PrintViewManagerBase() {
     74   ReleasePrinterQuery();
     75   DisconnectFromCurrentPrintJob();
     76 }
     77 
     78 bool PrintViewManagerBase::PrintNow() {
     79   return PrintNowInternal(new PrintMsg_PrintPages(routing_id()));
     80 }
     81 
     82 void PrintViewManagerBase::UpdateScriptedPrintingBlocked() {
     83   Send(new PrintMsg_SetScriptedPrintingBlocked(
     84        routing_id(),
     85        !printing_enabled_.GetValue()));
     86 }
     87 
     88 void PrintViewManagerBase::NavigationStopped() {
     89   // Cancel the current job, wait for the worker to finish.
     90   TerminatePrintJob(true);
     91 }
     92 
     93 void PrintViewManagerBase::RenderProcessGone(base::TerminationStatus status) {
     94   ReleasePrinterQuery();
     95 
     96   if (!print_job_.get())
     97     return;
     98 
     99   scoped_refptr<PrintedDocument> document(print_job_->document());
    100   if (document.get()) {
    101     // If IsComplete() returns false, the document isn't completely rendered.
    102     // Since our renderer is gone, there's nothing to do, cancel it. Otherwise,
    103     // the print job may finish without problem.
    104     TerminatePrintJob(!document->IsComplete());
    105   }
    106 }
    107 
    108 base::string16 PrintViewManagerBase::RenderSourceName() {
    109   base::string16 name(web_contents()->GetTitle());
    110   if (name.empty())
    111     name = l10n_util::GetStringUTF16(IDS_DEFAULT_PRINT_DOCUMENT_TITLE);
    112   return name;
    113 }
    114 
    115 void PrintViewManagerBase::OnDidGetPrintedPagesCount(int cookie,
    116                                                      int number_pages) {
    117   DCHECK_GT(cookie, 0);
    118   DCHECK_GT(number_pages, 0);
    119   number_pages_ = number_pages;
    120   OpportunisticallyCreatePrintJob(cookie);
    121 }
    122 
    123 void PrintViewManagerBase::OnDidGetDocumentCookie(int cookie) {
    124   cookie_ = cookie;
    125 }
    126 
    127 void PrintViewManagerBase::OnDidPrintPage(
    128     const PrintHostMsg_DidPrintPage_Params& params) {
    129   if (!OpportunisticallyCreatePrintJob(params.document_cookie))
    130     return;
    131 
    132   PrintedDocument* document = print_job_->document();
    133   if (!document || params.document_cookie != document->cookie()) {
    134     // Out of sync. It may happen since we are completely asynchronous. Old
    135     // spurious messages can be received if one of the processes is overloaded.
    136     return;
    137   }
    138 
    139 #if defined(OS_WIN) || defined(OS_MACOSX)
    140   const bool metafile_must_be_valid = true;
    141 #elif defined(OS_POSIX)
    142   const bool metafile_must_be_valid = expecting_first_page_;
    143   expecting_first_page_ = false;
    144 #endif
    145 
    146   base::SharedMemory shared_buf(params.metafile_data_handle, true);
    147   if (metafile_must_be_valid) {
    148     if (!shared_buf.Map(params.data_size)) {
    149       NOTREACHED() << "couldn't map";
    150       web_contents()->Stop();
    151       return;
    152     }
    153   }
    154 
    155   scoped_ptr<NativeMetafile> metafile(new NativeMetafile);
    156   if (metafile_must_be_valid) {
    157     if (!metafile->InitFromData(shared_buf.memory(), params.data_size)) {
    158       NOTREACHED() << "Invalid metafile header";
    159       web_contents()->Stop();
    160       return;
    161     }
    162   }
    163 
    164 #if defined(OS_WIN)
    165   bool big_emf = (params.data_size && params.data_size >= kMetafileMaxSize);
    166   const CommandLine* cmdline = CommandLine::ForCurrentProcess();
    167   int raster_size = std::min(params.page_size.GetArea(),
    168                              kMaxRasterSizeInPixels);
    169   if (big_emf || (cmdline && cmdline->HasSwitch(switches::kPrintRaster))) {
    170     scoped_ptr<NativeMetafile> raster_metafile(
    171         metafile->RasterizeMetafile(raster_size));
    172     if (raster_metafile.get()) {
    173       metafile.swap(raster_metafile);
    174     } else if (big_emf) {
    175       // Don't fall back to emf here.
    176       NOTREACHED() << "size:" << params.data_size;
    177       TerminatePrintJob(true);
    178       web_contents()->Stop();
    179       return;
    180     }
    181   }
    182 #endif
    183 
    184   // Update the rendered document. It will send notifications to the listener.
    185   document->SetPage(params.page_number,
    186                     metafile.release(),
    187                     params.actual_shrink,
    188                     params.page_size,
    189                     params.content_area);
    190 
    191   ShouldQuitFromInnerMessageLoop();
    192 }
    193 
    194 void PrintViewManagerBase::OnPrintingFailed(int cookie) {
    195   if (cookie != cookie_) {
    196     NOTREACHED();
    197     return;
    198   }
    199 
    200 #if defined(ENABLE_FULL_PRINTING)
    201   chrome::ShowPrintErrorDialog(
    202       web_contents()->GetView()->GetTopLevelNativeWindow());
    203 #endif
    204 
    205   ReleasePrinterQuery();
    206 
    207   content::NotificationService::current()->Notify(
    208       chrome::NOTIFICATION_PRINT_JOB_RELEASED,
    209       content::Source<content::WebContents>(web_contents()),
    210       content::NotificationService::NoDetails());
    211 }
    212 
    213 void PrintViewManagerBase::DidStartLoading(
    214     content::RenderViewHost* render_view_host) {
    215   UpdateScriptedPrintingBlocked();
    216 }
    217 
    218 bool PrintViewManagerBase::OnMessageReceived(const IPC::Message& message) {
    219   bool handled = true;
    220   IPC_BEGIN_MESSAGE_MAP(PrintViewManagerBase, message)
    221     IPC_MESSAGE_HANDLER(PrintHostMsg_DidGetPrintedPagesCount,
    222                         OnDidGetPrintedPagesCount)
    223     IPC_MESSAGE_HANDLER(PrintHostMsg_DidGetDocumentCookie,
    224                         OnDidGetDocumentCookie)
    225     IPC_MESSAGE_HANDLER(PrintHostMsg_DidPrintPage, OnDidPrintPage)
    226     IPC_MESSAGE_HANDLER(PrintHostMsg_PrintingFailed, OnPrintingFailed)
    227     IPC_MESSAGE_UNHANDLED(handled = false)
    228   IPC_END_MESSAGE_MAP()
    229   return handled;
    230 }
    231 
    232 void PrintViewManagerBase::Observe(
    233     int type,
    234     const content::NotificationSource& source,
    235     const content::NotificationDetails& details) {
    236   switch (type) {
    237     case chrome::NOTIFICATION_PRINT_JOB_EVENT: {
    238       OnNotifyPrintJobEvent(*content::Details<JobEventDetails>(details).ptr());
    239       break;
    240     }
    241     default: {
    242       NOTREACHED();
    243       break;
    244     }
    245   }
    246 }
    247 
    248 void PrintViewManagerBase::OnNotifyPrintJobEvent(
    249     const JobEventDetails& event_details) {
    250   switch (event_details.type()) {
    251     case JobEventDetails::FAILED: {
    252       TerminatePrintJob(true);
    253 
    254       content::NotificationService::current()->Notify(
    255           chrome::NOTIFICATION_PRINT_JOB_RELEASED,
    256           content::Source<content::WebContents>(web_contents()),
    257           content::NotificationService::NoDetails());
    258       break;
    259     }
    260     case JobEventDetails::USER_INIT_DONE:
    261     case JobEventDetails::DEFAULT_INIT_DONE:
    262     case JobEventDetails::USER_INIT_CANCELED: {
    263       NOTREACHED();
    264       break;
    265     }
    266     case JobEventDetails::ALL_PAGES_REQUESTED: {
    267       ShouldQuitFromInnerMessageLoop();
    268       break;
    269     }
    270     case JobEventDetails::NEW_DOC:
    271     case JobEventDetails::NEW_PAGE:
    272     case JobEventDetails::PAGE_DONE:
    273     case JobEventDetails::DOC_DONE: {
    274       // Don't care about the actual printing process.
    275       break;
    276     }
    277     case JobEventDetails::JOB_DONE: {
    278       // Printing is done, we don't need it anymore.
    279       // print_job_->is_job_pending() may still be true, depending on the order
    280       // of object registration.
    281       printing_succeeded_ = true;
    282       ReleasePrintJob();
    283 
    284       content::NotificationService::current()->Notify(
    285           chrome::NOTIFICATION_PRINT_JOB_RELEASED,
    286           content::Source<content::WebContents>(web_contents()),
    287           content::NotificationService::NoDetails());
    288       break;
    289     }
    290     default: {
    291       NOTREACHED();
    292       break;
    293     }
    294   }
    295 }
    296 
    297 bool PrintViewManagerBase::RenderAllMissingPagesNow() {
    298   if (!print_job_.get() || !print_job_->is_job_pending())
    299     return false;
    300 
    301   // We can't print if there is no renderer.
    302   if (!web_contents() ||
    303       !web_contents()->GetRenderViewHost() ||
    304       !web_contents()->GetRenderViewHost()->IsRenderViewLive()) {
    305     return false;
    306   }
    307 
    308   // Is the document already complete?
    309   if (print_job_->document() && print_job_->document()->IsComplete()) {
    310     printing_succeeded_ = true;
    311     return true;
    312   }
    313 
    314   // WebContents is either dying or a second consecutive request to print
    315   // happened before the first had time to finish. We need to render all the
    316   // pages in an hurry if a print_job_ is still pending. No need to wait for it
    317   // to actually spool the pages, only to have the renderer generate them. Run
    318   // a message loop until we get our signal that the print job is satisfied.
    319   // PrintJob will send a ALL_PAGES_REQUESTED after having received all the
    320   // pages it needs. MessageLoop::current()->Quit() will be called as soon as
    321   // print_job_->document()->IsComplete() is true on either ALL_PAGES_REQUESTED
    322   // or in DidPrintPage(). The check is done in
    323   // ShouldQuitFromInnerMessageLoop().
    324   // BLOCKS until all the pages are received. (Need to enable recursive task)
    325   if (!RunInnerMessageLoop()) {
    326     // This function is always called from DisconnectFromCurrentPrintJob() so we
    327     // know that the job will be stopped/canceled in any case.
    328     return false;
    329   }
    330   return true;
    331 }
    332 
    333 void PrintViewManagerBase::ShouldQuitFromInnerMessageLoop() {
    334   // Look at the reason.
    335   DCHECK(print_job_->document());
    336   if (print_job_->document() &&
    337       print_job_->document()->IsComplete() &&
    338       inside_inner_message_loop_) {
    339     // We are in a message loop created by RenderAllMissingPagesNow. Quit from
    340     // it.
    341     base::MessageLoop::current()->Quit();
    342     inside_inner_message_loop_ = false;
    343   }
    344 }
    345 
    346 bool PrintViewManagerBase::CreateNewPrintJob(PrintJobWorkerOwner* job) {
    347   DCHECK(!inside_inner_message_loop_);
    348 
    349   // Disconnect the current print_job_.
    350   DisconnectFromCurrentPrintJob();
    351 
    352   // We can't print if there is no renderer.
    353   if (!web_contents()->GetRenderViewHost() ||
    354       !web_contents()->GetRenderViewHost()->IsRenderViewLive()) {
    355     return false;
    356   }
    357 
    358   // Ask the renderer to generate the print preview, create the print preview
    359   // view and switch to it, initialize the printer and show the print dialog.
    360   DCHECK(!print_job_.get());
    361   DCHECK(job);
    362   if (!job)
    363     return false;
    364 
    365   print_job_ = new PrintJob();
    366   print_job_->Initialize(job, this, number_pages_);
    367   registrar_.Add(this, chrome::NOTIFICATION_PRINT_JOB_EVENT,
    368                  content::Source<PrintJob>(print_job_.get()));
    369   printing_succeeded_ = false;
    370   return true;
    371 }
    372 
    373 void PrintViewManagerBase::DisconnectFromCurrentPrintJob() {
    374   // Make sure all the necessary rendered page are done. Don't bother with the
    375   // return value.
    376   bool result = RenderAllMissingPagesNow();
    377 
    378   // Verify that assertion.
    379   if (print_job_.get() &&
    380       print_job_->document() &&
    381       !print_job_->document()->IsComplete()) {
    382     DCHECK(!result);
    383     // That failed.
    384     TerminatePrintJob(true);
    385   } else {
    386     // DO NOT wait for the job to finish.
    387     ReleasePrintJob();
    388   }
    389 #if defined(OS_POSIX) && !defined(OS_MACOSX)
    390   expecting_first_page_ = true;
    391 #endif
    392 }
    393 
    394 void PrintViewManagerBase::PrintingDone(bool success) {
    395   if (!print_job_.get())
    396     return;
    397   Send(new PrintMsg_PrintingDone(routing_id(), success));
    398 }
    399 
    400 void PrintViewManagerBase::TerminatePrintJob(bool cancel) {
    401   if (!print_job_.get())
    402     return;
    403 
    404   if (cancel) {
    405     // We don't need the metafile data anymore because the printing is canceled.
    406     print_job_->Cancel();
    407     inside_inner_message_loop_ = false;
    408   } else {
    409     DCHECK(!inside_inner_message_loop_);
    410     DCHECK(!print_job_->document() || print_job_->document()->IsComplete());
    411 
    412     // WebContents is either dying or navigating elsewhere. We need to render
    413     // all the pages in an hurry if a print job is still pending. This does the
    414     // trick since it runs a blocking message loop:
    415     print_job_->Stop();
    416   }
    417   ReleasePrintJob();
    418 }
    419 
    420 void PrintViewManagerBase::ReleasePrintJob() {
    421   if (!print_job_.get())
    422     return;
    423 
    424   PrintingDone(printing_succeeded_);
    425 
    426   registrar_.Remove(this, chrome::NOTIFICATION_PRINT_JOB_EVENT,
    427                     content::Source<PrintJob>(print_job_.get()));
    428   print_job_->DisconnectSource();
    429   // Don't close the worker thread.
    430   print_job_ = NULL;
    431 }
    432 
    433 bool PrintViewManagerBase::RunInnerMessageLoop() {
    434   // This value may actually be too low:
    435   //
    436   // - If we're looping because of printer settings initialization, the premise
    437   // here is that some poor users have their print server away on a VPN over a
    438   // slow connection. In this situation, the simple fact of opening the printer
    439   // can be dead slow. On the other side, we don't want to die infinitely for a
    440   // real network error. Give the printer 60 seconds to comply.
    441   //
    442   // - If we're looping because of renderer page generation, the renderer could
    443   // be CPU bound, the page overly complex/large or the system just
    444   // memory-bound.
    445   static const int kPrinterSettingsTimeout = 60000;
    446   base::OneShotTimer<base::MessageLoop> quit_timer;
    447   quit_timer.Start(FROM_HERE,
    448                    TimeDelta::FromMilliseconds(kPrinterSettingsTimeout),
    449                    base::MessageLoop::current(), &base::MessageLoop::Quit);
    450 
    451   inside_inner_message_loop_ = true;
    452 
    453   // Need to enable recursive task.
    454   {
    455     base::MessageLoop::ScopedNestableTaskAllower allow(
    456         base::MessageLoop::current());
    457     base::MessageLoop::current()->Run();
    458   }
    459 
    460   bool success = true;
    461   if (inside_inner_message_loop_) {
    462     // Ok we timed out. That's sad.
    463     inside_inner_message_loop_ = false;
    464     success = false;
    465   }
    466 
    467   return success;
    468 }
    469 
    470 bool PrintViewManagerBase::OpportunisticallyCreatePrintJob(int cookie) {
    471   if (print_job_.get())
    472     return true;
    473 
    474   if (!cookie) {
    475     // Out of sync. It may happens since we are completely asynchronous. Old
    476     // spurious message can happen if one of the processes is overloaded.
    477     return false;
    478   }
    479 
    480   // The job was initiated by a script. Time to get the corresponding worker
    481   // thread.
    482   scoped_refptr<PrinterQuery> queued_query = queue_->PopPrinterQuery(cookie);
    483   if (!queued_query) {
    484     NOTREACHED();
    485     return false;
    486   }
    487 
    488   if (!CreateNewPrintJob(queued_query)) {
    489     // Don't kill anything.
    490     return false;
    491   }
    492 
    493   // Settings are already loaded. Go ahead. This will set
    494   // print_job_->is_job_pending() to true.
    495   print_job_->StartPrinting();
    496   return true;
    497 }
    498 
    499 bool PrintViewManagerBase::PrintNowInternal(IPC::Message* message) {
    500   // Don't print / print preview interstitials.
    501   if (web_contents()->ShowingInterstitialPage()) {
    502     delete message;
    503     return false;
    504   }
    505   return Send(message);
    506 }
    507 
    508 void PrintViewManagerBase::ReleasePrinterQuery() {
    509   if (!cookie_)
    510     return;
    511 
    512   int cookie = cookie_;
    513   cookie_ = 0;
    514   queue_->SetDestination(NULL);
    515 
    516 
    517   printing::PrintJobManager* print_job_manager =
    518       g_browser_process->print_job_manager();
    519   // May be NULL in tests.
    520   if (!print_job_manager)
    521     return;
    522 
    523   scoped_refptr<printing::PrinterQuery> printer_query;
    524   printer_query = queue_->PopPrinterQuery(cookie);
    525   if (!printer_query)
    526     return;
    527   BrowserThread::PostTask(
    528       BrowserThread::IO, FROM_HERE,
    529       base::Bind(&PrinterQuery::StopWorker, printer_query.get()));
    530 }
    531 
    532 }  // namespace printing
    533