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