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