1 // Copyright (c) 2012 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_job.h"
8 #include "base/bind_helpers.h"
9 #include "base/location.h"
10 #include "base/message_loop/message_loop.h"
11 #include "base/single_thread_task_runner.h"
12 #include "base/thread_task_runner_handle.h"
13 #include "base/threading/thread_restrictions.h"
14 #include "base/threading/worker_pool.h"
15 #include "chrome/browser/chrome_notification_types.h"
16 #include "chrome/browser/printing/print_job_worker.h"
17 #include "content/public/browser/browser_thread.h"
18 #include "content/public/browser/notification_service.h"
19 #include "printing/printed_document.h"
20 #include "printing/printed_page.h"
23 #include "chrome/browser/printing/pdf_to_emf_converter.h"
24 #include "printing/pdf_render_settings.h"
27 using base::TimeDelta
;
31 // Helper function to ensure |owner| is valid until at least |callback| returns.
32 void HoldRefCallback(const scoped_refptr
<printing::PrintJobWorkerOwner
>& owner
,
33 const base::Closure
& callback
) {
45 is_job_pending_(false),
48 // This is normally a UI message loop, but in unit tests, the message loop is
49 // of the 'default' type.
50 DCHECK(base::MessageLoopForUI::IsCurrent() ||
51 base::MessageLoop::current()->type() ==
52 base::MessageLoop::TYPE_DEFAULT
);
55 PrintJob::~PrintJob() {
56 // The job should be finished (or at least canceled) when it is destroyed.
57 DCHECK(!is_job_pending_
);
58 DCHECK(!is_canceling_
);
59 DCHECK(!worker_
|| !worker_
->IsRunning());
60 DCHECK(RunsTasksOnCurrentThread());
63 void PrintJob::Initialize(PrintJobWorkerOwner
* job
,
64 PrintedPagesSource
* source
,
67 DCHECK(!worker_
.get());
68 DCHECK(!is_job_pending_
);
69 DCHECK(!is_canceling_
);
70 DCHECK(!document_
.get());
72 worker_
.reset(job
->DetachWorker(this));
73 settings_
= job
->settings();
75 PrintedDocument
* new_doc
=
76 new PrintedDocument(settings_
,
79 content::BrowserThread::GetBlockingPool());
80 new_doc
->set_page_count(page_count
);
81 UpdatePrintedDocument(new_doc
);
83 // Don't forget to register to our own messages.
84 registrar_
.Add(this, chrome::NOTIFICATION_PRINT_JOB_EVENT
,
85 content::Source
<PrintJob
>(this));
88 void PrintJob::Observe(int type
,
89 const content::NotificationSource
& source
,
90 const content::NotificationDetails
& details
) {
91 DCHECK(RunsTasksOnCurrentThread());
93 case chrome::NOTIFICATION_PRINT_JOB_EVENT
: {
94 OnNotifyPrintJobEvent(*content::Details
<JobEventDetails
>(details
).ptr());
103 void PrintJob::GetSettingsDone(const PrintSettings
& new_settings
,
104 PrintingContext::Result result
) {
108 PrintJobWorker
* PrintJob::DetachWorker(PrintJobWorkerOwner
* new_owner
) {
113 const PrintSettings
& PrintJob::settings() const {
117 int PrintJob::cookie() const {
118 if (!document_
.get())
119 // Always use an invalid cookie in this case.
121 return document_
->cookie();
124 void PrintJob::StartPrinting() {
125 DCHECK(RunsTasksOnCurrentThread());
126 DCHECK(worker_
->IsRunning());
127 DCHECK(!is_job_pending_
);
128 if (!worker_
->IsRunning() || is_job_pending_
)
131 // Real work is done in PrintJobWorker::StartPrinting().
132 worker_
->PostTask(FROM_HERE
,
133 base::Bind(&HoldRefCallback
,
134 make_scoped_refptr(this),
135 base::Bind(&PrintJobWorker::StartPrinting
,
136 base::Unretained(worker_
.get()),
138 // Set the flag right now.
139 is_job_pending_
= true;
142 scoped_refptr
<JobEventDetails
> details(
143 new JobEventDetails(JobEventDetails::NEW_DOC
, document_
.get(), NULL
));
144 content::NotificationService::current()->Notify(
145 chrome::NOTIFICATION_PRINT_JOB_EVENT
,
146 content::Source
<PrintJob
>(this),
147 content::Details
<JobEventDetails
>(details
.get()));
150 void PrintJob::Stop() {
151 DCHECK(RunsTasksOnCurrentThread());
153 if (quit_factory_
.HasWeakPtrs()) {
154 // In case we're running a nested message loop to wait for a job to finish,
155 // and we finished before the timeout, quit the nested loop right away.
157 quit_factory_
.InvalidateWeakPtrs();
160 // Be sure to live long enough.
161 scoped_refptr
<PrintJob
> handle(this);
163 if (worker_
->IsRunning()) {
164 ControlledWorkerShutdown();
166 // Flush the cached document.
167 UpdatePrintedDocument(NULL
);
171 void PrintJob::Cancel() {
174 is_canceling_
= true;
176 // Be sure to live long enough.
177 scoped_refptr
<PrintJob
> handle(this);
179 DCHECK(RunsTasksOnCurrentThread());
180 if (worker_
&& worker_
->IsRunning()) {
181 // Call this right now so it renders the context invalid. Do not use
182 // InvokeLater since it would take too much time.
185 // Make sure a Cancel() is broadcast.
186 scoped_refptr
<JobEventDetails
> details(
187 new JobEventDetails(JobEventDetails::FAILED
, NULL
, NULL
));
188 content::NotificationService::current()->Notify(
189 chrome::NOTIFICATION_PRINT_JOB_EVENT
,
190 content::Source
<PrintJob
>(this),
191 content::Details
<JobEventDetails
>(details
.get()));
193 is_canceling_
= false;
196 bool PrintJob::FlushJob(base::TimeDelta timeout
) {
197 // Make sure the object outlive this message loop.
198 scoped_refptr
<PrintJob
> handle(this);
200 base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
201 FROM_HERE
, base::Bind(&PrintJob::Quit
, quit_factory_
.GetWeakPtr()),
204 base::MessageLoop::ScopedNestableTaskAllower
allow(
205 base::MessageLoop::current());
206 base::MessageLoop::current()->Run();
211 void PrintJob::DisconnectSource() {
214 document_
->DisconnectSource();
217 bool PrintJob::is_job_pending() const {
218 return is_job_pending_
;
221 PrintedDocument
* PrintJob::document() const {
222 return document_
.get();
227 class PrintJob::PdfToEmfState
{
229 PdfToEmfState(const gfx::Size
& page_size
, const gfx::Rect
& content_area
)
232 pages_in_progress_(0),
233 page_size_(page_size
),
234 content_area_(content_area
),
235 converter_(PdfToEmfConverter::CreateDefault()) {}
237 void Start(const scoped_refptr
<base::RefCountedMemory
>& data
,
238 const PdfRenderSettings
& conversion_settings
,
239 const PdfToEmfConverter::StartCallback
& start_callback
) {
240 converter_
->Start(data
, conversion_settings
, start_callback
);
244 const PdfToEmfConverter::GetPageCallback
& get_page_callback
) {
245 const int kMaxNumberOfTempFilesPerDocument
= 3;
246 while (pages_in_progress_
< kMaxNumberOfTempFilesPerDocument
&&
247 current_page_
< page_count_
) {
248 ++pages_in_progress_
;
249 converter_
->GetPage(current_page_
++, get_page_callback
);
253 void OnPageProcessed(
254 const PdfToEmfConverter::GetPageCallback
& get_page_callback
) {
255 --pages_in_progress_
;
256 GetMorePages(get_page_callback
);
257 // Release converter if we don't need this any more.
258 if (!pages_in_progress_
&& current_page_
>= page_count_
)
262 void set_page_count(int page_count
) { page_count_
= page_count
; }
263 gfx::Size
page_size() const { return page_size_
; }
264 gfx::Rect
content_area() const { return content_area_
; }
269 int pages_in_progress_
;
270 gfx::Size page_size_
;
271 gfx::Rect content_area_
;
272 scoped_ptr
<PdfToEmfConverter
> converter_
;
275 void PrintJob::StartPdfToEmfConversion(
276 const scoped_refptr
<base::RefCountedMemory
>& bytes
,
277 const gfx::Size
& page_size
,
278 const gfx::Rect
& content_area
) {
279 DCHECK(!ptd_to_emf_state_
.get());
280 ptd_to_emf_state_
.reset(new PdfToEmfState(page_size
, content_area
));
281 const int kPrinterDpi
= settings().dpi();
282 ptd_to_emf_state_
->Start(
284 printing::PdfRenderSettings(content_area
, kPrinterDpi
, true),
285 base::Bind(&PrintJob::OnPdfToEmfStarted
, this));
288 void PrintJob::OnPdfToEmfStarted(int page_count
) {
289 if (page_count
<= 0) {
290 ptd_to_emf_state_
.reset();
294 ptd_to_emf_state_
->set_page_count(page_count
);
295 ptd_to_emf_state_
->GetMorePages(
296 base::Bind(&PrintJob::OnPdfToEmfPageConverted
, this));
299 void PrintJob::OnPdfToEmfPageConverted(int page_number
,
301 scoped_ptr
<MetafilePlayer
> emf
) {
302 DCHECK(ptd_to_emf_state_
);
303 if (!document_
.get() || !emf
) {
304 ptd_to_emf_state_
.reset();
309 // Update the rendered document. It will send notifications to the listener.
310 document_
->SetPage(page_number
,
313 ptd_to_emf_state_
->page_size(),
314 ptd_to_emf_state_
->content_area());
316 ptd_to_emf_state_
->GetMorePages(
317 base::Bind(&PrintJob::OnPdfToEmfPageConverted
, this));
322 void PrintJob::UpdatePrintedDocument(PrintedDocument
* new_document
) {
323 if (document_
.get() == new_document
)
326 document_
= new_document
;
328 if (document_
.get()) {
329 settings_
= document_
->settings();
333 DCHECK(!is_job_pending_
);
334 // Sync the document with the worker.
335 worker_
->PostTask(FROM_HERE
,
336 base::Bind(&HoldRefCallback
,
337 make_scoped_refptr(this),
338 base::Bind(&PrintJobWorker::OnDocumentChanged
,
339 base::Unretained(worker_
.get()),
344 void PrintJob::OnNotifyPrintJobEvent(const JobEventDetails
& event_details
) {
345 switch (event_details
.type()) {
346 case JobEventDetails::FAILED
: {
348 // No need to cancel since the worker already canceled itself.
352 case JobEventDetails::USER_INIT_DONE
:
353 case JobEventDetails::DEFAULT_INIT_DONE
:
354 case JobEventDetails::USER_INIT_CANCELED
: {
355 DCHECK_EQ(event_details
.document(), document_
.get());
358 case JobEventDetails::NEW_DOC
:
359 case JobEventDetails::NEW_PAGE
:
360 case JobEventDetails::JOB_DONE
:
361 case JobEventDetails::ALL_PAGES_REQUESTED
: {
365 case JobEventDetails::DOC_DONE
: {
366 // This will call Stop() and broadcast a JOB_DONE message.
367 base::ThreadTaskRunnerHandle::Get()->PostTask(
368 FROM_HERE
, base::Bind(&PrintJob::OnDocumentDone
, this));
371 case JobEventDetails::PAGE_DONE
:
373 ptd_to_emf_state_
->OnPageProcessed(
374 base::Bind(&PrintJob::OnPdfToEmfPageConverted
, this));
384 void PrintJob::OnDocumentDone() {
385 // Be sure to live long enough. The instance could be destroyed by the
386 // JOB_DONE broadcast.
387 scoped_refptr
<PrintJob
> handle(this);
389 // Stop the worker thread.
392 scoped_refptr
<JobEventDetails
> details(
393 new JobEventDetails(JobEventDetails::JOB_DONE
, document_
.get(), NULL
));
394 content::NotificationService::current()->Notify(
395 chrome::NOTIFICATION_PRINT_JOB_EVENT
,
396 content::Source
<PrintJob
>(this),
397 content::Details
<JobEventDetails
>(details
.get()));
400 void PrintJob::ControlledWorkerShutdown() {
401 DCHECK(RunsTasksOnCurrentThread());
403 // The deadlock this code works around is specific to window messaging on
404 // Windows, so we aren't likely to need it on any other platforms.
406 // We could easily get into a deadlock case if worker_->Stop() is used; the
407 // printer driver created a window as a child of the browser window. By
408 // canceling the job, the printer driver initiated dialog box is destroyed,
409 // which sends a blocking message to its parent window. If the browser window
410 // thread is not processing messages, a deadlock occurs.
412 // This function ensures that the dialog box will be destroyed in a timely
413 // manner by the mere fact that the thread will terminate. So the potential
414 // deadlock is eliminated.
417 // Delay shutdown until the worker terminates. We want this code path
418 // to wait on the thread to quit before continuing.
419 if (worker_
->IsRunning()) {
420 base::MessageLoop::current()->PostDelayedTask(
422 base::Bind(&PrintJob::ControlledWorkerShutdown
, this),
423 base::TimeDelta::FromMilliseconds(100));
429 // Now make sure the thread object is cleaned up. Do this on a worker
430 // thread because it may block.
431 base::WorkerPool::PostTaskAndReply(
433 base::Bind(&PrintJobWorker::Stop
, base::Unretained(worker_
.get())),
434 base::Bind(&PrintJob::HoldUntilStopIsCalled
, this),
437 is_job_pending_
= false;
438 registrar_
.RemoveAll();
439 UpdatePrintedDocument(NULL
);
442 void PrintJob::HoldUntilStopIsCalled() {
445 void PrintJob::Quit() {
446 base::MessageLoop::current()->Quit();
449 // Takes settings_ ownership and will be deleted in the receiving thread.
450 JobEventDetails::JobEventDetails(Type type
,
451 PrintedDocument
* document
,
453 : document_(document
),
458 JobEventDetails::~JobEventDetails() {
461 PrintedDocument
* JobEventDetails::document() const { return document_
.get(); }
463 PrintedPage
* JobEventDetails::page() const { return page_
.get(); }
465 } // namespace printing