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.
5 #include "chrome/browser/printing/print_view_manager_base.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"
35 #include "base/command_line.h"
36 #include "chrome/common/chrome_switches.h"
39 #if defined(ENABLE_FULL_PRINTING)
40 #include "chrome/browser/printing/print_error_dialog.h"
43 using base::TimeDelta;
44 using content::BrowserThread;
47 // Limits memory usage by raster to 64 MiB.
48 const int kMaxRasterSizeInPixels = 16*1024*1024;
53 PrintViewManagerBase::PrintViewManagerBase(content::WebContents* web_contents)
54 : content::WebContentsObserver(web_contents),
56 printing_succeeded_(false),
57 inside_inner_message_loop_(false),
59 queue_(g_browser_process->print_job_manager()->queue()) {
61 #if defined(OS_POSIX) && !defined(OS_MACOSX)
62 expecting_first_page_ = true;
65 Profile::FromBrowserContext(web_contents->GetBrowserContext());
66 printing_enabled_.Init(
67 prefs::kPrintingEnabled,
69 base::Bind(&PrintViewManagerBase::UpdateScriptedPrintingBlocked,
70 base::Unretained(this)));
73 PrintViewManagerBase::~PrintViewManagerBase() {
74 ReleasePrinterQuery();
75 DisconnectFromCurrentPrintJob();
78 bool PrintViewManagerBase::PrintNow() {
79 return PrintNowInternal(new PrintMsg_PrintPages(routing_id()));
82 void PrintViewManagerBase::UpdateScriptedPrintingBlocked() {
83 Send(new PrintMsg_SetScriptedPrintingBlocked(
85 !printing_enabled_.GetValue()));
88 void PrintViewManagerBase::NavigationStopped() {
89 // Cancel the current job, wait for the worker to finish.
90 TerminatePrintJob(true);
93 void PrintViewManagerBase::RenderProcessGone(base::TerminationStatus status) {
94 ReleasePrinterQuery();
96 if (!print_job_.get())
99 scoped_refptr<PrintedDocument> document(print_job_->document());
100 if (document.get()) {
101 // If IsComplete() returns false, the document isn't completely rendered.
102 // Since our renderer is gone, there's nothing to do, cancel it. Otherwise,
103 // the print job may finish without problem.
104 TerminatePrintJob(!document->IsComplete());
108 base::string16 PrintViewManagerBase::RenderSourceName() {
109 base::string16 name(web_contents()->GetTitle());
111 name = l10n_util::GetStringUTF16(IDS_DEFAULT_PRINT_DOCUMENT_TITLE);
115 void PrintViewManagerBase::OnDidGetPrintedPagesCount(int cookie,
117 DCHECK_GT(cookie, 0);
118 DCHECK_GT(number_pages, 0);
119 number_pages_ = number_pages;
120 OpportunisticallyCreatePrintJob(cookie);
123 void PrintViewManagerBase::OnDidGetDocumentCookie(int cookie) {
127 void PrintViewManagerBase::OnDidPrintPage(
128 const PrintHostMsg_DidPrintPage_Params& params) {
129 if (!OpportunisticallyCreatePrintJob(params.document_cookie))
132 PrintedDocument* document = print_job_->document();
133 if (!document || params.document_cookie != document->cookie()) {
134 // Out of sync. It may happen since we are completely asynchronous. Old
135 // spurious messages can be received if one of the processes is overloaded.
139 #if defined(OS_WIN) || defined(OS_MACOSX)
140 const bool metafile_must_be_valid = true;
141 #elif defined(OS_POSIX)
142 const bool metafile_must_be_valid = expecting_first_page_;
143 expecting_first_page_ = false;
146 base::SharedMemory shared_buf(params.metafile_data_handle, true);
147 if (metafile_must_be_valid) {
148 if (!shared_buf.Map(params.data_size)) {
149 NOTREACHED() << "couldn't map";
150 web_contents()->Stop();
155 scoped_ptr<NativeMetafile> metafile(new NativeMetafile);
156 if (metafile_must_be_valid) {
157 if (!metafile->InitFromData(shared_buf.memory(), params.data_size)) {
158 NOTREACHED() << "Invalid metafile header";
159 web_contents()->Stop();
165 bool big_emf = (params.data_size && params.data_size >= kMetafileMaxSize);
166 int raster_size = std::min(params.page_size.GetArea(),
167 kMaxRasterSizeInPixels);
169 scoped_ptr<NativeMetafile> raster_metafile(
170 metafile->RasterizeMetafile(raster_size));
171 if (raster_metafile.get()) {
172 metafile.swap(raster_metafile);
173 } else if (big_emf) {
174 // Don't fall back to emf here.
175 NOTREACHED() << "size:" << params.data_size;
176 TerminatePrintJob(true);
177 web_contents()->Stop();
183 // Update the rendered document. It will send notifications to the listener.
184 document->SetPage(params.page_number,
186 params.actual_shrink,
188 params.content_area);
190 ShouldQuitFromInnerMessageLoop();
193 void PrintViewManagerBase::OnPrintingFailed(int cookie) {
194 if (cookie != cookie_) {
199 #if defined(ENABLE_FULL_PRINTING)
200 chrome::ShowPrintErrorDialog();
203 ReleasePrinterQuery();
205 content::NotificationService::current()->Notify(
206 chrome::NOTIFICATION_PRINT_JOB_RELEASED,
207 content::Source<content::WebContents>(web_contents()),
208 content::NotificationService::NoDetails());
211 void PrintViewManagerBase::OnShowInvalidPrinterSettingsError() {
212 chrome::ShowMessageBox(NULL,
214 l10n_util::GetStringUTF16(
215 IDS_PRINT_INVALID_PRINTER_SETTINGS),
216 chrome::MESSAGE_BOX_TYPE_WARNING);
219 void PrintViewManagerBase::DidStartLoading(
220 content::RenderViewHost* render_view_host) {
221 UpdateScriptedPrintingBlocked();
224 bool PrintViewManagerBase::OnMessageReceived(const IPC::Message& message) {
226 IPC_BEGIN_MESSAGE_MAP(PrintViewManagerBase, message)
227 IPC_MESSAGE_HANDLER(PrintHostMsg_DidGetPrintedPagesCount,
228 OnDidGetPrintedPagesCount)
229 IPC_MESSAGE_HANDLER(PrintHostMsg_DidGetDocumentCookie,
230 OnDidGetDocumentCookie)
231 IPC_MESSAGE_HANDLER(PrintHostMsg_DidPrintPage, OnDidPrintPage)
232 IPC_MESSAGE_HANDLER(PrintHostMsg_PrintingFailed, OnPrintingFailed)
233 IPC_MESSAGE_HANDLER(PrintHostMsg_ShowInvalidPrinterSettingsError,
234 OnShowInvalidPrinterSettingsError);
235 IPC_MESSAGE_UNHANDLED(handled = false)
236 IPC_END_MESSAGE_MAP()
240 void PrintViewManagerBase::Observe(
242 const content::NotificationSource& source,
243 const content::NotificationDetails& details) {
245 case chrome::NOTIFICATION_PRINT_JOB_EVENT: {
246 OnNotifyPrintJobEvent(*content::Details<JobEventDetails>(details).ptr());
256 void PrintViewManagerBase::OnNotifyPrintJobEvent(
257 const JobEventDetails& event_details) {
258 switch (event_details.type()) {
259 case JobEventDetails::FAILED: {
260 TerminatePrintJob(true);
262 content::NotificationService::current()->Notify(
263 chrome::NOTIFICATION_PRINT_JOB_RELEASED,
264 content::Source<content::WebContents>(web_contents()),
265 content::NotificationService::NoDetails());
268 case JobEventDetails::USER_INIT_DONE:
269 case JobEventDetails::DEFAULT_INIT_DONE:
270 case JobEventDetails::USER_INIT_CANCELED: {
274 case JobEventDetails::ALL_PAGES_REQUESTED: {
275 ShouldQuitFromInnerMessageLoop();
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.
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;
292 content::NotificationService::current()->Notify(
293 chrome::NOTIFICATION_PRINT_JOB_RELEASED,
294 content::Source<content::WebContents>(web_contents()),
295 content::NotificationService::NoDetails());
305 bool PrintViewManagerBase::RenderAllMissingPagesNow() {
306 if (!print_job_.get() || !print_job_->is_job_pending())
309 // We can't print if there is no renderer.
310 if (!web_contents() ||
311 !web_contents()->GetRenderViewHost() ||
312 !web_contents()->GetRenderViewHost()->IsRenderViewLive()) {
316 // Is the document already complete?
317 if (print_job_->document() && print_job_->document()->IsComplete()) {
318 printing_succeeded_ = true;
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.
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
349 base::MessageLoop::current()->Quit();
350 inside_inner_message_loop_ = false;
354 bool PrintViewManagerBase::CreateNewPrintJob(PrintJobWorkerOwner* job) {
355 DCHECK(!inside_inner_message_loop_);
357 // Disconnect the current print_job_.
358 DisconnectFromCurrentPrintJob();
360 // We can't print if there is no renderer.
361 if (!web_contents()->GetRenderViewHost() ||
362 !web_contents()->GetRenderViewHost()->IsRenderViewLive()) {
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());
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;
381 void PrintViewManagerBase::DisconnectFromCurrentPrintJob() {
382 // Make sure all the necessary rendered page are done. Don't bother with the
384 bool result = RenderAllMissingPagesNow();
386 // Verify that assertion.
387 if (print_job_.get() &&
388 print_job_->document() &&
389 !print_job_->document()->IsComplete()) {
392 TerminatePrintJob(true);
394 // DO NOT wait for the job to finish.
397 #if defined(OS_POSIX) && !defined(OS_MACOSX)
398 expecting_first_page_ = true;
402 void PrintViewManagerBase::PrintingDone(bool success) {
403 if (!print_job_.get())
405 Send(new PrintMsg_PrintingDone(routing_id(), success));
408 void PrintViewManagerBase::TerminatePrintJob(bool cancel) {
409 if (!print_job_.get())
413 // We don't need the metafile data anymore because the printing is canceled.
414 print_job_->Cancel();
415 inside_inner_message_loop_ = false;
417 DCHECK(!inside_inner_message_loop_);
418 DCHECK(!print_job_->document() || print_job_->document()->IsComplete());
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:
428 void PrintViewManagerBase::ReleasePrintJob() {
429 if (!print_job_.get())
432 PrintingDone(printing_succeeded_);
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.
441 bool PrintViewManagerBase::RunInnerMessageLoop() {
442 // This value may actually be too low:
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.
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
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);
459 inside_inner_message_loop_ = true;
461 // Need to enable recursive task.
463 base::MessageLoop::ScopedNestableTaskAllower allow(
464 base::MessageLoop::current());
465 base::MessageLoop::current()->Run();
469 if (inside_inner_message_loop_) {
470 // Ok we timed out. That's sad.
471 inside_inner_message_loop_ = false;
478 bool PrintViewManagerBase::OpportunisticallyCreatePrintJob(int cookie) {
479 if (print_job_.get())
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.
488 // The job was initiated by a script. Time to get the corresponding worker
490 scoped_refptr<PrinterQuery> queued_query = queue_->PopPrinterQuery(cookie);
496 if (!CreateNewPrintJob(queued_query)) {
497 // Don't kill anything.
501 // Settings are already loaded. Go ahead. This will set
502 // print_job_->is_job_pending() to true.
503 print_job_->StartPrinting();
507 bool PrintViewManagerBase::PrintNowInternal(IPC::Message* message) {
508 // Don't print / print preview interstitials.
509 if (web_contents()->ShowingInterstitialPage()) {
513 return Send(message);
516 void PrintViewManagerBase::ReleasePrinterQuery() {
520 int cookie = cookie_;
522 queue_->SetDestination(NULL);
525 printing::PrintJobManager* print_job_manager =
526 g_browser_process->print_job_manager();
527 // May be NULL in tests.
528 if (!print_job_manager)
531 scoped_refptr<printing::PrinterQuery> printer_query;
532 printer_query = queue_->PopPrinterQuery(cookie);
535 BrowserThread::PostTask(
536 BrowserThread::IO, FROM_HERE,
537 base::Bind(&PrinterQuery::StopWorker, printer_query.get()));
540 } // namespace printing