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"
7 #include "base/auto_reset.h"
9 #include "base/location.h"
10 #include "base/memory/scoped_ptr.h"
11 #include "base/prefs/pref_service.h"
12 #include "base/single_thread_task_runner.h"
13 #include "base/strings/utf_string_conversions.h"
14 #include "base/thread_task_runner_handle.h"
15 #include "base/timer/timer.h"
16 #include "chrome/browser/browser_process.h"
17 #include "chrome/browser/chrome_notification_types.h"
18 #include "chrome/browser/printing/print_job.h"
19 #include "chrome/browser/printing/print_job_manager.h"
20 #include "chrome/browser/printing/printer_query.h"
21 #include "chrome/browser/profiles/profile.h"
22 #include "chrome/browser/ui/simple_message_box.h"
23 #include "chrome/common/pref_names.h"
24 #include "chrome/grit/generated_resources.h"
25 #include "components/printing/common/print_messages.h"
26 #include "content/public/browser/browser_thread.h"
27 #include "content/public/browser/notification_details.h"
28 #include "content/public/browser/notification_service.h"
29 #include "content/public/browser/notification_source.h"
30 #include "content/public/browser/render_view_host.h"
31 #include "content/public/browser/web_contents.h"
32 #include "printing/pdf_metafile_skia.h"
33 #include "printing/printed_document.h"
34 #include "ui/base/l10n/l10n_util.h"
36 #if defined(ENABLE_PRINT_PREVIEW)
37 #include "chrome/browser/printing/print_error_dialog.h"
40 using base::TimeDelta
;
41 using content::BrowserThread
;
47 void ShowWarningMessageBox(const base::string16
& message
) {
48 // Runs always on the UI thread.
49 static bool is_dialog_shown
= false;
52 // Block opening dialog from nested task.
53 base::AutoReset
<bool> auto_reset(&is_dialog_shown
, true);
55 chrome::ShowMessageBox(nullptr, base::string16(), message
,
56 chrome::MESSAGE_BOX_TYPE_WARNING
);
61 PrintViewManagerBase::PrintViewManagerBase(content::WebContents
* web_contents
)
62 : PrintManager(web_contents
),
63 printing_succeeded_(false),
64 inside_inner_message_loop_(false),
65 queue_(g_browser_process
->print_job_manager()->queue()) {
67 #if !defined(OS_MACOSX)
68 expecting_first_page_
= true;
71 Profile::FromBrowserContext(web_contents
->GetBrowserContext());
72 printing_enabled_
.Init(
73 prefs::kPrintingEnabled
,
75 base::Bind(&PrintViewManagerBase::UpdateScriptedPrintingBlocked
,
76 base::Unretained(this)));
79 PrintViewManagerBase::~PrintViewManagerBase() {
80 ReleasePrinterQuery();
81 DisconnectFromCurrentPrintJob();
84 #if defined(ENABLE_BASIC_PRINTING)
85 bool PrintViewManagerBase::PrintNow() {
86 return PrintNowInternal(new PrintMsg_PrintPages(routing_id()));
88 #endif // ENABLE_BASIC_PRINTING
90 void PrintViewManagerBase::UpdateScriptedPrintingBlocked() {
91 Send(new PrintMsg_SetScriptedPrintingBlocked(
93 !printing_enabled_
.GetValue()));
96 void PrintViewManagerBase::NavigationStopped() {
97 // Cancel the current job, wait for the worker to finish.
98 TerminatePrintJob(true);
101 void PrintViewManagerBase::RenderProcessGone(base::TerminationStatus status
) {
102 PrintManager::RenderProcessGone(status
);
103 ReleasePrinterQuery();
105 if (!print_job_
.get())
108 scoped_refptr
<PrintedDocument
> document(print_job_
->document());
109 if (document
.get()) {
110 // If IsComplete() returns false, the document isn't completely rendered.
111 // Since our renderer is gone, there's nothing to do, cancel it. Otherwise,
112 // the print job may finish without problem.
113 TerminatePrintJob(!document
->IsComplete());
117 base::string16
PrintViewManagerBase::RenderSourceName() {
118 base::string16
name(web_contents()->GetTitle());
120 name
= l10n_util::GetStringUTF16(IDS_DEFAULT_PRINT_DOCUMENT_TITLE
);
124 void PrintViewManagerBase::OnDidGetPrintedPagesCount(int cookie
,
126 PrintManager::OnDidGetPrintedPagesCount(cookie
, number_pages
);
127 OpportunisticallyCreatePrintJob(cookie
);
130 void PrintViewManagerBase::OnDidPrintPage(
131 const PrintHostMsg_DidPrintPage_Params
& params
) {
132 if (!OpportunisticallyCreatePrintJob(params
.document_cookie
))
135 PrintedDocument
* document
= print_job_
->document();
136 if (!document
|| params
.document_cookie
!= document
->cookie()) {
137 // Out of sync. It may happen since we are completely asynchronous. Old
138 // spurious messages can be received if one of the processes is overloaded.
142 #if defined(OS_MACOSX)
143 const bool metafile_must_be_valid
= true;
145 const bool metafile_must_be_valid
= expecting_first_page_
;
146 expecting_first_page_
= false;
149 base::SharedMemory
shared_buf(params
.metafile_data_handle
, true);
150 if (metafile_must_be_valid
) {
151 if (!shared_buf
.Map(params
.data_size
)) {
152 NOTREACHED() << "couldn't map";
153 web_contents()->Stop();
158 scoped_ptr
<PdfMetafileSkia
> metafile(new PdfMetafileSkia
);
159 if (metafile_must_be_valid
) {
160 if (!metafile
->InitFromData(shared_buf
.memory(), params
.data_size
)) {
161 NOTREACHED() << "Invalid metafile header";
162 web_contents()->Stop();
168 // Update the rendered document. It will send notifications to the listener.
169 document
->SetPage(params
.page_number
,
172 params
.content_area
);
174 ShouldQuitFromInnerMessageLoop();
176 if (metafile_must_be_valid
) {
177 scoped_refptr
<base::RefCountedBytes
> bytes
= new base::RefCountedBytes(
178 reinterpret_cast<const unsigned char*>(shared_buf
.memory()),
181 document
->DebugDumpData(bytes
.get(), FILE_PATH_LITERAL(".pdf"));
182 print_job_
->StartPdfToEmfConversion(
183 bytes
, params
.page_size
, params
.content_area
);
188 void PrintViewManagerBase::OnPrintingFailed(int cookie
) {
189 PrintManager::OnPrintingFailed(cookie
);
191 #if defined(ENABLE_PRINT_PREVIEW)
192 chrome::ShowPrintErrorDialog();
195 ReleasePrinterQuery();
197 content::NotificationService::current()->Notify(
198 chrome::NOTIFICATION_PRINT_JOB_RELEASED
,
199 content::Source
<content::WebContents
>(web_contents()),
200 content::NotificationService::NoDetails());
203 void PrintViewManagerBase::OnShowInvalidPrinterSettingsError() {
204 base::ThreadTaskRunnerHandle::Get()->PostTask(
205 FROM_HERE
, base::Bind(&ShowWarningMessageBox
,
206 l10n_util::GetStringUTF16(
207 IDS_PRINT_INVALID_PRINTER_SETTINGS
)));
210 void PrintViewManagerBase::DidStartLoading() {
211 UpdateScriptedPrintingBlocked();
214 bool PrintViewManagerBase::OnMessageReceived(const IPC::Message
& message
) {
216 IPC_BEGIN_MESSAGE_MAP(PrintViewManagerBase
, message
)
217 IPC_MESSAGE_HANDLER(PrintHostMsg_DidPrintPage
, OnDidPrintPage
)
218 IPC_MESSAGE_HANDLER(PrintHostMsg_ShowInvalidPrinterSettingsError
,
219 OnShowInvalidPrinterSettingsError
);
220 IPC_MESSAGE_UNHANDLED(handled
= false)
221 IPC_END_MESSAGE_MAP()
222 return handled
|| PrintManager::OnMessageReceived(message
);
225 void PrintViewManagerBase::Observe(
227 const content::NotificationSource
& source
,
228 const content::NotificationDetails
& details
) {
230 case chrome::NOTIFICATION_PRINT_JOB_EVENT
: {
231 OnNotifyPrintJobEvent(*content::Details
<JobEventDetails
>(details
).ptr());
241 void PrintViewManagerBase::OnNotifyPrintJobEvent(
242 const JobEventDetails
& event_details
) {
243 switch (event_details
.type()) {
244 case JobEventDetails::FAILED
: {
245 TerminatePrintJob(true);
247 content::NotificationService::current()->Notify(
248 chrome::NOTIFICATION_PRINT_JOB_RELEASED
,
249 content::Source
<content::WebContents
>(web_contents()),
250 content::NotificationService::NoDetails());
253 case JobEventDetails::USER_INIT_DONE
:
254 case JobEventDetails::DEFAULT_INIT_DONE
:
255 case JobEventDetails::USER_INIT_CANCELED
: {
259 case JobEventDetails::ALL_PAGES_REQUESTED
: {
260 ShouldQuitFromInnerMessageLoop();
263 case JobEventDetails::NEW_DOC
:
264 case JobEventDetails::NEW_PAGE
:
265 case JobEventDetails::PAGE_DONE
:
266 case JobEventDetails::DOC_DONE
: {
267 // Don't care about the actual printing process.
270 case JobEventDetails::JOB_DONE
: {
271 // Printing is done, we don't need it anymore.
272 // print_job_->is_job_pending() may still be true, depending on the order
273 // of object registration.
274 printing_succeeded_
= true;
277 content::NotificationService::current()->Notify(
278 chrome::NOTIFICATION_PRINT_JOB_RELEASED
,
279 content::Source
<content::WebContents
>(web_contents()),
280 content::NotificationService::NoDetails());
290 bool PrintViewManagerBase::RenderAllMissingPagesNow() {
291 if (!print_job_
.get() || !print_job_
->is_job_pending())
294 // We can't print if there is no renderer.
295 if (!web_contents() ||
296 !web_contents()->GetRenderViewHost() ||
297 !web_contents()->GetRenderViewHost()->IsRenderViewLive()) {
301 // Is the document already complete?
302 if (print_job_
->document() && print_job_
->document()->IsComplete()) {
303 printing_succeeded_
= true;
307 // WebContents is either dying or a second consecutive request to print
308 // happened before the first had time to finish. We need to render all the
309 // pages in an hurry if a print_job_ is still pending. No need to wait for it
310 // to actually spool the pages, only to have the renderer generate them. Run
311 // a message loop until we get our signal that the print job is satisfied.
312 // PrintJob will send a ALL_PAGES_REQUESTED after having received all the
313 // pages it needs. MessageLoop::current()->Quit() will be called as soon as
314 // print_job_->document()->IsComplete() is true on either ALL_PAGES_REQUESTED
315 // or in DidPrintPage(). The check is done in
316 // ShouldQuitFromInnerMessageLoop().
317 // BLOCKS until all the pages are received. (Need to enable recursive task)
318 if (!RunInnerMessageLoop()) {
319 // This function is always called from DisconnectFromCurrentPrintJob() so we
320 // know that the job will be stopped/canceled in any case.
326 void PrintViewManagerBase::ShouldQuitFromInnerMessageLoop() {
327 // Look at the reason.
328 DCHECK(print_job_
->document());
329 if (print_job_
->document() &&
330 print_job_
->document()->IsComplete() &&
331 inside_inner_message_loop_
) {
332 // We are in a message loop created by RenderAllMissingPagesNow. Quit from
334 base::MessageLoop::current()->Quit();
335 inside_inner_message_loop_
= false;
339 bool PrintViewManagerBase::CreateNewPrintJob(PrintJobWorkerOwner
* job
) {
340 DCHECK(!inside_inner_message_loop_
);
342 // Disconnect the current print_job_.
343 DisconnectFromCurrentPrintJob();
345 // We can't print if there is no renderer.
346 if (!web_contents()->GetRenderViewHost() ||
347 !web_contents()->GetRenderViewHost()->IsRenderViewLive()) {
351 // Ask the renderer to generate the print preview, create the print preview
352 // view and switch to it, initialize the printer and show the print dialog.
353 DCHECK(!print_job_
.get());
358 print_job_
= new PrintJob();
359 print_job_
->Initialize(job
, this, number_pages_
);
360 registrar_
.Add(this, chrome::NOTIFICATION_PRINT_JOB_EVENT
,
361 content::Source
<PrintJob
>(print_job_
.get()));
362 printing_succeeded_
= false;
366 void PrintViewManagerBase::DisconnectFromCurrentPrintJob() {
367 // Make sure all the necessary rendered page are done. Don't bother with the
369 bool result
= RenderAllMissingPagesNow();
371 // Verify that assertion.
372 if (print_job_
.get() &&
373 print_job_
->document() &&
374 !print_job_
->document()->IsComplete()) {
377 TerminatePrintJob(true);
379 // DO NOT wait for the job to finish.
382 #if !defined(OS_MACOSX)
383 expecting_first_page_
= true;
387 void PrintViewManagerBase::PrintingDone(bool success
) {
388 if (!print_job_
.get())
390 Send(new PrintMsg_PrintingDone(routing_id(), success
));
393 void PrintViewManagerBase::TerminatePrintJob(bool cancel
) {
394 if (!print_job_
.get())
398 // We don't need the metafile data anymore because the printing is canceled.
399 print_job_
->Cancel();
400 inside_inner_message_loop_
= false;
402 DCHECK(!inside_inner_message_loop_
);
403 DCHECK(!print_job_
->document() || print_job_
->document()->IsComplete());
405 // WebContents is either dying or navigating elsewhere. We need to render
406 // all the pages in an hurry if a print job is still pending. This does the
407 // trick since it runs a blocking message loop:
413 void PrintViewManagerBase::ReleasePrintJob() {
414 if (!print_job_
.get())
417 PrintingDone(printing_succeeded_
);
419 registrar_
.Remove(this, chrome::NOTIFICATION_PRINT_JOB_EVENT
,
420 content::Source
<PrintJob
>(print_job_
.get()));
421 print_job_
->DisconnectSource();
422 // Don't close the worker thread.
426 bool PrintViewManagerBase::RunInnerMessageLoop() {
427 // This value may actually be too low:
429 // - If we're looping because of printer settings initialization, the premise
430 // here is that some poor users have their print server away on a VPN over a
431 // slow connection. In this situation, the simple fact of opening the printer
432 // can be dead slow. On the other side, we don't want to die infinitely for a
433 // real network error. Give the printer 60 seconds to comply.
435 // - If we're looping because of renderer page generation, the renderer could
436 // be CPU bound, the page overly complex/large or the system just
438 static const int kPrinterSettingsTimeout
= 60000;
439 base::OneShotTimer
<base::MessageLoop
> quit_timer
;
440 quit_timer
.Start(FROM_HERE
,
441 TimeDelta::FromMilliseconds(kPrinterSettingsTimeout
),
442 base::MessageLoop::current(), &base::MessageLoop::Quit
);
444 inside_inner_message_loop_
= true;
446 // Need to enable recursive task.
448 base::MessageLoop::ScopedNestableTaskAllower
allow(
449 base::MessageLoop::current());
450 base::MessageLoop::current()->Run();
454 if (inside_inner_message_loop_
) {
455 // Ok we timed out. That's sad.
456 inside_inner_message_loop_
= false;
463 bool PrintViewManagerBase::OpportunisticallyCreatePrintJob(int cookie
) {
464 if (print_job_
.get())
468 // Out of sync. It may happens since we are completely asynchronous. Old
469 // spurious message can happen if one of the processes is overloaded.
473 // The job was initiated by a script. Time to get the corresponding worker
475 scoped_refptr
<PrinterQuery
> queued_query
= queue_
->PopPrinterQuery(cookie
);
476 if (!queued_query
.get()) {
481 if (!CreateNewPrintJob(queued_query
.get())) {
482 // Don't kill anything.
486 // Settings are already loaded. Go ahead. This will set
487 // print_job_->is_job_pending() to true.
488 print_job_
->StartPrinting();
492 bool PrintViewManagerBase::PrintNowInternal(IPC::Message
* message
) {
493 // Don't print / print preview interstitials.
494 if (web_contents()->ShowingInterstitialPage()) {
498 return Send(message
);
501 void PrintViewManagerBase::ReleasePrinterQuery() {
505 int cookie
= cookie_
;
508 printing::PrintJobManager
* print_job_manager
=
509 g_browser_process
->print_job_manager();
510 // May be NULL in tests.
511 if (!print_job_manager
)
514 scoped_refptr
<printing::PrinterQuery
> printer_query
;
515 printer_query
= queue_
->PopPrinterQuery(cookie
);
516 if (!printer_query
.get())
518 BrowserThread::PostTask(
519 BrowserThread::IO
, FROM_HERE
,
520 base::Bind(&PrinterQuery::StopWorker
, printer_query
.get()));
523 } // namespace printing