print_view_manager.cc revision 72a454cd3513ac24fbdd0e0cb9ad70b86a99b801
1// Copyright (c) 2011 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.h"
6
7#include "base/scoped_ptr.h"
8#include "base/utf_string_conversions.h"
9#include "chrome/browser/browser_process.h"
10#include "chrome/browser/printing/print_job.h"
11#include "chrome/browser/printing/print_job_manager.h"
12#include "chrome/browser/printing/printer_query.h"
13#include "chrome/browser/renderer_host/render_view_host.h"
14#include "chrome/browser/tab_contents/navigation_entry.h"
15#include "chrome/browser/tab_contents/tab_contents.h"
16#include "chrome/common/notification_details.h"
17#include "chrome/common/notification_source.h"
18#include "chrome/common/render_messages.h"
19#include "chrome/common/render_messages_params.h"
20#include "grit/generated_resources.h"
21#include "printing/native_metafile.h"
22#include "printing/printed_document.h"
23#include "ui/base/l10n/l10n_util.h"
24
25using base::TimeDelta;
26
27namespace printing {
28
29PrintViewManager::PrintViewManager(TabContents& owner)
30    : number_pages_(0),
31      waiting_to_print_(false),
32      printing_succeeded_(false),
33      inside_inner_message_loop_(false),
34      owner_(owner) {
35}
36
37PrintViewManager::~PrintViewManager() {
38  DisconnectFromCurrentPrintJob();
39}
40
41void PrintViewManager::Stop() {
42  // Cancel the current job, wait for the worker to finish.
43  TerminatePrintJob(true);
44}
45
46bool PrintViewManager::OnRenderViewGone(RenderViewHost* render_view_host) {
47  if (!print_job_.get())
48    return true;
49
50  if (render_view_host != owner_.render_view_host())
51    return false;
52
53  scoped_refptr<PrintedDocument> document(print_job_->document());
54  if (document) {
55    // If IsComplete() returns false, the document isn't completely rendered.
56    // Since our renderer is gone, there's nothing to do, cancel it. Otherwise,
57    // the print job may finish without problem.
58    TerminatePrintJob(!document->IsComplete());
59  }
60  return true;
61}
62
63string16 PrintViewManager::RenderSourceName() {
64  string16 name(owner_.GetTitle());
65  if (name.empty())
66    name = l10n_util::GetStringUTF16(IDS_DEFAULT_PRINT_DOCUMENT_TITLE);
67  return name;
68}
69
70GURL PrintViewManager::RenderSourceUrl() {
71  NavigationEntry* entry = owner_.controller().GetActiveEntry();
72  if (entry)
73    return entry->virtual_url();
74  else
75    return GURL();
76}
77
78void PrintViewManager::OnDidGetPrintedPagesCount(int cookie, int number_pages) {
79  DCHECK_GT(cookie, 0);
80  DCHECK_GT(number_pages, 0);
81  number_pages_ = number_pages;
82  if (!OpportunisticallyCreatePrintJob(cookie))
83    return;
84
85  PrintedDocument* document = print_job_->document();
86  if (!document || cookie != document->cookie()) {
87    // Out of sync. It may happens since we are completely asynchronous. Old
88    // spurious message can happen if one of the processes is overloaded.
89    return;
90  }
91}
92
93void PrintViewManager::OnDidPrintPage(
94    const ViewHostMsg_DidPrintPage_Params& params) {
95  if (!OpportunisticallyCreatePrintJob(params.document_cookie))
96    return;
97
98  PrintedDocument* document = print_job_->document();
99  if (!document || params.document_cookie != document->cookie()) {
100    // Out of sync. It may happen since we are completely asynchronous. Old
101    // spurious messages can be received if one of the processes is overloaded.
102    return;
103  }
104
105#if defined(OS_WIN)
106  // http://msdn2.microsoft.com/en-us/library/ms535522.aspx
107  // Windows 2000/XP: When a page in a spooled file exceeds approximately 350
108  // MB, it can fail to print and not send an error message.
109  if (params.data_size && params.data_size >= 350*1024*1024) {
110    NOTREACHED() << "size:" << params.data_size;
111    TerminatePrintJob(true);
112    owner_.Stop();
113    return;
114  }
115#endif
116
117  base::SharedMemory shared_buf(params.metafile_data_handle, true);
118  if (!shared_buf.Map(params.data_size)) {
119    NOTREACHED() << "couldn't map";
120    owner_.Stop();
121    return;
122  }
123
124#if defined(OS_POSIX) && !defined(OS_MACOSX)
125  NOTIMPLEMENTED() << " this printing code doesn't quite work yet.";
126#else
127  scoped_ptr<NativeMetafile> metafile(new NativeMetafile());
128  if (!metafile->Init(shared_buf.memory(), params.data_size)) {
129    NOTREACHED() << "Invalid metafile header";
130    owner_.Stop();
131    return;
132  }
133
134  // Update the rendered document. It will send notifications to the listener.
135  document->SetPage(params.page_number,
136                    metafile.release(),
137                    params.actual_shrink,
138                    params.page_size,
139                    params.content_area,
140                    params.has_visible_overlays);
141#endif
142  ShouldQuitFromInnerMessageLoop();
143}
144
145bool PrintViewManager::OnMessageReceived(const IPC::Message& message) {
146  bool handled = true;
147  IPC_BEGIN_MESSAGE_MAP(PrintViewManager, message)
148    IPC_MESSAGE_HANDLER(ViewHostMsg_DidGetPrintedPagesCount,
149                        OnDidGetPrintedPagesCount)
150    IPC_MESSAGE_HANDLER(ViewHostMsg_DidPrintPage, OnDidPrintPage)
151    IPC_MESSAGE_UNHANDLED(handled = false)
152  IPC_END_MESSAGE_MAP()
153  return handled;
154}
155
156void PrintViewManager::Observe(NotificationType type,
157                               const NotificationSource& source,
158                               const NotificationDetails& details) {
159  switch (type.value) {
160    case NotificationType::PRINT_JOB_EVENT: {
161      OnNotifyPrintJobEvent(*Details<JobEventDetails>(details).ptr());
162      break;
163    }
164    default: {
165      NOTREACHED();
166      break;
167    }
168  }
169}
170
171void PrintViewManager::OnNotifyPrintJobEvent(
172    const JobEventDetails& event_details) {
173  switch (event_details.type()) {
174    case JobEventDetails::FAILED: {
175      TerminatePrintJob(true);
176      break;
177    }
178    case JobEventDetails::USER_INIT_DONE:
179    case JobEventDetails::DEFAULT_INIT_DONE:
180    case JobEventDetails::USER_INIT_CANCELED: {
181      NOTREACHED();
182      break;
183    }
184    case JobEventDetails::ALL_PAGES_REQUESTED: {
185      ShouldQuitFromInnerMessageLoop();
186      break;
187    }
188    case JobEventDetails::NEW_DOC:
189    case JobEventDetails::NEW_PAGE:
190    case JobEventDetails::PAGE_DONE: {
191      // Don't care about the actual printing process.
192      break;
193    }
194    case JobEventDetails::DOC_DONE: {
195      waiting_to_print_ = false;
196      break;
197    }
198    case JobEventDetails::JOB_DONE: {
199      // Printing is done, we don't need it anymore.
200      // print_job_->is_job_pending() may still be true, depending on the order
201      // of object registration.
202      printing_succeeded_ = true;
203      ReleasePrintJob();
204      break;
205    }
206    default: {
207      NOTREACHED();
208      break;
209    }
210  }
211}
212
213bool PrintViewManager::RenderAllMissingPagesNow() {
214  if (!print_job_.get() || !print_job_->is_job_pending()) {
215    DCHECK_EQ(waiting_to_print_, false);
216    return false;
217  }
218
219  // We can't print if there is no renderer.
220  if (!owner_.render_view_host() ||
221      !owner_.render_view_host()->IsRenderViewLive()) {
222    waiting_to_print_ = false;
223    return false;
224  }
225
226  // Is the document already complete?
227  if (print_job_->document() && print_job_->document()->IsComplete()) {
228    waiting_to_print_ = false;
229    printing_succeeded_ = true;
230    return true;
231  }
232
233  // TabContents is either dying or a second consecutive request to print
234  // happened before the first had time to finish. We need to render all the
235  // pages in an hurry if a print_job_ is still pending. No need to wait for it
236  // to actually spool the pages, only to have the renderer generate them. Run
237  // a message loop until we get our signal that the print job is satisfied.
238  // PrintJob will send a ALL_PAGES_REQUESTED after having received all the
239  // pages it needs. MessageLoop::current()->Quit() will be called as soon as
240  // print_job_->document()->IsComplete() is true on either ALL_PAGES_REQUESTED
241  // or in DidPrintPage(). The check is done in
242  // ShouldQuitFromInnerMessageLoop().
243  // BLOCKS until all the pages are received. (Need to enable recursive task)
244  if (!RunInnerMessageLoop()) {
245    // This function is always called from DisconnectFromCurrentPrintJob() so we
246    // know that the job will be stopped/canceled in any case.
247    return false;
248  }
249  return true;
250}
251
252void PrintViewManager::ShouldQuitFromInnerMessageLoop() {
253  // Look at the reason.
254  DCHECK(print_job_->document());
255  if (print_job_->document() &&
256      print_job_->document()->IsComplete() &&
257      inside_inner_message_loop_) {
258    // We are in a message loop created by RenderAllMissingPagesNow. Quit from
259    // it.
260    MessageLoop::current()->Quit();
261    inside_inner_message_loop_ = false;
262    waiting_to_print_ = false;
263  }
264}
265
266bool PrintViewManager::CreateNewPrintJob(PrintJobWorkerOwner* job) {
267  DCHECK(!inside_inner_message_loop_);
268  if (waiting_to_print_) {
269    // We can't help; we are waiting for a print job initialization. The user is
270    // button bashing. The only thing we could do is to batch up the requests.
271    return false;
272  }
273
274  // Disconnect the current print_job_.
275  DisconnectFromCurrentPrintJob();
276
277  // We can't print if there is no renderer.
278  if (!owner_.render_view_host() ||
279      !owner_.render_view_host()->IsRenderViewLive()) {
280    return false;
281  }
282
283  // Ask the renderer to generate the print preview, create the print preview
284  // view and switch to it, initialize the printer and show the print dialog.
285  DCHECK(!print_job_.get());
286  DCHECK(job);
287  if (!job)
288    return false;
289
290  print_job_ = new PrintJob();
291  print_job_->Initialize(job, this, number_pages_);
292  registrar_.Add(this, NotificationType::PRINT_JOB_EVENT,
293                 Source<PrintJob>(print_job_.get()));
294  printing_succeeded_ = false;
295  return true;
296}
297
298void PrintViewManager::DisconnectFromCurrentPrintJob() {
299  // Make sure all the necessary rendered page are done. Don't bother with the
300  // return value.
301  bool result = RenderAllMissingPagesNow();
302
303  // Verify that assertion.
304  if (print_job_.get() &&
305      print_job_->document() &&
306      !print_job_->document()->IsComplete()) {
307    DCHECK(!result);
308    // That failed.
309    TerminatePrintJob(true);
310  } else {
311    // DO NOT wait for the job to finish.
312    ReleasePrintJob();
313  }
314}
315
316void PrintViewManager::PrintingDone(bool success) {
317  if (print_job_.get()) {
318    owner_.PrintingDone(print_job_->cookie(), success);
319  }
320}
321
322void PrintViewManager::TerminatePrintJob(bool cancel) {
323  if (!print_job_.get())
324    return;
325
326  if (cancel) {
327    // We don't need the metafile data anymore because the printing is canceled.
328    print_job_->Cancel();
329    waiting_to_print_ = false;
330    inside_inner_message_loop_ = false;
331  } else {
332    DCHECK(!inside_inner_message_loop_);
333    DCHECK(!print_job_->document() || print_job_->document()->IsComplete() ||
334           !waiting_to_print_);
335
336    // TabContents is either dying or navigating elsewhere. We need to render
337    // all the pages in an hurry if a print job is still pending. This does the
338    // trick since it runs a blocking message loop:
339    print_job_->Stop();
340  }
341  ReleasePrintJob();
342}
343
344void PrintViewManager::ReleasePrintJob() {
345  DCHECK_EQ(waiting_to_print_, false);
346  if (!print_job_.get())
347    return;
348
349  PrintingDone(printing_succeeded_);
350
351  registrar_.Remove(this, NotificationType::PRINT_JOB_EVENT,
352                    Source<PrintJob>(print_job_.get()));
353  print_job_->DisconnectSource();
354  // Don't close the worker thread.
355  print_job_ = NULL;
356}
357
358bool PrintViewManager::RunInnerMessageLoop() {
359  // This value may actually be too low:
360  //
361  // - If we're looping because of printer settings initializaton, the premise
362  // here is that some poor users have their print server away on a VPN over
363  // dialup. In this situation, the simple fact of opening the printer can be
364  // dead slow. On the other side, we don't want to die infinitely for a real
365  // network error. Give the printer 60 seconds to comply.
366  //
367  // - If we're looping because of renderer page generation, the renderer could
368  // be cpu bound, the page overly complex/large or the system just
369  // memory-bound.
370  static const int kPrinterSettingsTimeout = 60000;
371  base::OneShotTimer<MessageLoop> quit_timer;
372  quit_timer.Start(TimeDelta::FromMilliseconds(kPrinterSettingsTimeout),
373                   MessageLoop::current(), &MessageLoop::Quit);
374
375  inside_inner_message_loop_ = true;
376
377  // Need to enable recursive task.
378  bool old_state = MessageLoop::current()->NestableTasksAllowed();
379  MessageLoop::current()->SetNestableTasksAllowed(true);
380  MessageLoop::current()->Run();
381  // Restore task state.
382  MessageLoop::current()->SetNestableTasksAllowed(old_state);
383
384  bool success = true;
385  if (inside_inner_message_loop_) {
386    // Ok we timed out. That's sad.
387    inside_inner_message_loop_ = false;
388    success = false;
389  }
390
391  return success;
392}
393
394bool PrintViewManager::OpportunisticallyCreatePrintJob(int cookie) {
395  if (print_job_.get())
396    return true;
397
398  if (!cookie) {
399    // Out of sync. It may happens since we are completely asynchronous. Old
400    // spurious message can happen if one of the processes is overloaded.
401    return false;
402  }
403
404  // The job was initiated by a script. Time to get the corresponding worker
405  // thread.
406  scoped_refptr<PrinterQuery> queued_query;
407  g_browser_process->print_job_manager()->PopPrinterQuery(cookie,
408                                                          &queued_query);
409  DCHECK(queued_query.get());
410  if (!queued_query.get())
411    return false;
412
413  if (!CreateNewPrintJob(queued_query.get())) {
414    // Don't kill anything.
415    return false;
416  }
417
418  // Settings are already loaded. Go ahead. This will set
419  // print_job_->is_job_pending() to true.
420  print_job_->StartPrinting();
421  return true;
422}
423
424}  // namespace printing
425