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