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