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.
4
5 #include "chrome/browser/printing/print_job.h"
6
7 #include "base/bind.h"
8 #include "base/bind_helpers.h"
9 #include "base/message_loop/message_loop.h"
10 #include "base/threading/thread_restrictions.h"
11 #include "base/threading/worker_pool.h"
12 #include "base/timer/timer.h"
13 #include "chrome/browser/chrome_notification_types.h"
14 #include "chrome/browser/printing/print_job_worker.h"
15 #include "content/public/browser/browser_thread.h"
16 #include "content/public/browser/notification_service.h"
17 #include "printing/printed_document.h"
18 #include "printing/printed_page.h"
19
20 #if defined(OS_WIN)
21 #include "chrome/browser/printing/pdf_to_emf_converter.h"
22 #include "printing/pdf_render_settings.h"
23 #endif
24
25 using base::TimeDelta;
26
27 namespace {
28
29 // Helper function to ensure |owner| is valid until at least |callback| returns.
HoldRefCallback(const scoped_refptr<printing::PrintJobWorkerOwner> & owner,const base::Closure & callback)30 void HoldRefCallback(const scoped_refptr<printing::PrintJobWorkerOwner>& owner,
31 const base::Closure& callback) {
32 callback.Run();
33 }
34
35 } // namespace
36
37 namespace printing {
38
PrintJob()39 PrintJob::PrintJob()
40 : source_(NULL),
41 worker_(),
42 settings_(),
43 is_job_pending_(false),
44 is_canceling_(false),
45 quit_factory_(this) {
46 // This is normally a UI message loop, but in unit tests, the message loop is
47 // of the 'default' type.
48 DCHECK(base::MessageLoopForUI::IsCurrent() ||
49 base::MessageLoop::current()->type() ==
50 base::MessageLoop::TYPE_DEFAULT);
51 }
52
~PrintJob()53 PrintJob::~PrintJob() {
54 // The job should be finished (or at least canceled) when it is destroyed.
55 DCHECK(!is_job_pending_);
56 DCHECK(!is_canceling_);
57 DCHECK(!worker_ || !worker_->IsRunning());
58 DCHECK(RunsTasksOnCurrentThread());
59 }
60
Initialize(PrintJobWorkerOwner * job,PrintedPagesSource * source,int page_count)61 void PrintJob::Initialize(PrintJobWorkerOwner* job,
62 PrintedPagesSource* source,
63 int page_count) {
64 DCHECK(!source_);
65 DCHECK(!worker_.get());
66 DCHECK(!is_job_pending_);
67 DCHECK(!is_canceling_);
68 DCHECK(!document_.get());
69 source_ = source;
70 worker_.reset(job->DetachWorker(this));
71 settings_ = job->settings();
72
73 PrintedDocument* new_doc =
74 new PrintedDocument(settings_,
75 source_,
76 job->cookie(),
77 content::BrowserThread::GetBlockingPool());
78 new_doc->set_page_count(page_count);
79 UpdatePrintedDocument(new_doc);
80
81 // Don't forget to register to our own messages.
82 registrar_.Add(this, chrome::NOTIFICATION_PRINT_JOB_EVENT,
83 content::Source<PrintJob>(this));
84 }
85
Observe(int type,const content::NotificationSource & source,const content::NotificationDetails & details)86 void PrintJob::Observe(int type,
87 const content::NotificationSource& source,
88 const content::NotificationDetails& details) {
89 DCHECK(RunsTasksOnCurrentThread());
90 switch (type) {
91 case chrome::NOTIFICATION_PRINT_JOB_EVENT: {
92 OnNotifyPrintJobEvent(*content::Details<JobEventDetails>(details).ptr());
93 break;
94 }
95 default: {
96 break;
97 }
98 }
99 }
100
GetSettingsDone(const PrintSettings & new_settings,PrintingContext::Result result)101 void PrintJob::GetSettingsDone(const PrintSettings& new_settings,
102 PrintingContext::Result result) {
103 NOTREACHED();
104 }
105
DetachWorker(PrintJobWorkerOwner * new_owner)106 PrintJobWorker* PrintJob::DetachWorker(PrintJobWorkerOwner* new_owner) {
107 NOTREACHED();
108 return NULL;
109 }
110
settings() const111 const PrintSettings& PrintJob::settings() const {
112 return settings_;
113 }
114
cookie() const115 int PrintJob::cookie() const {
116 if (!document_.get())
117 // Always use an invalid cookie in this case.
118 return 0;
119 return document_->cookie();
120 }
121
StartPrinting()122 void PrintJob::StartPrinting() {
123 DCHECK(RunsTasksOnCurrentThread());
124 DCHECK(worker_->IsRunning());
125 DCHECK(!is_job_pending_);
126 if (!worker_->IsRunning() || is_job_pending_)
127 return;
128
129 // Real work is done in PrintJobWorker::StartPrinting().
130 worker_->PostTask(FROM_HERE,
131 base::Bind(&HoldRefCallback,
132 make_scoped_refptr(this),
133 base::Bind(&PrintJobWorker::StartPrinting,
134 base::Unretained(worker_.get()),
135 document_)));
136 // Set the flag right now.
137 is_job_pending_ = true;
138
139 // Tell everyone!
140 scoped_refptr<JobEventDetails> details(
141 new JobEventDetails(JobEventDetails::NEW_DOC, document_.get(), NULL));
142 content::NotificationService::current()->Notify(
143 chrome::NOTIFICATION_PRINT_JOB_EVENT,
144 content::Source<PrintJob>(this),
145 content::Details<JobEventDetails>(details.get()));
146 }
147
Stop()148 void PrintJob::Stop() {
149 DCHECK(RunsTasksOnCurrentThread());
150
151 if (quit_factory_.HasWeakPtrs()) {
152 // In case we're running a nested message loop to wait for a job to finish,
153 // and we finished before the timeout, quit the nested loop right away.
154 Quit();
155 quit_factory_.InvalidateWeakPtrs();
156 }
157
158 // Be sure to live long enough.
159 scoped_refptr<PrintJob> handle(this);
160
161 if (worker_->IsRunning()) {
162 ControlledWorkerShutdown();
163 } else {
164 // Flush the cached document.
165 UpdatePrintedDocument(NULL);
166 }
167 }
168
Cancel()169 void PrintJob::Cancel() {
170 if (is_canceling_)
171 return;
172 is_canceling_ = true;
173
174 // Be sure to live long enough.
175 scoped_refptr<PrintJob> handle(this);
176
177 DCHECK(RunsTasksOnCurrentThread());
178 if (worker_ && worker_->IsRunning()) {
179 // Call this right now so it renders the context invalid. Do not use
180 // InvokeLater since it would take too much time.
181 worker_->Cancel();
182 }
183 // Make sure a Cancel() is broadcast.
184 scoped_refptr<JobEventDetails> details(
185 new JobEventDetails(JobEventDetails::FAILED, NULL, NULL));
186 content::NotificationService::current()->Notify(
187 chrome::NOTIFICATION_PRINT_JOB_EVENT,
188 content::Source<PrintJob>(this),
189 content::Details<JobEventDetails>(details.get()));
190 Stop();
191 is_canceling_ = false;
192 }
193
FlushJob(base::TimeDelta timeout)194 bool PrintJob::FlushJob(base::TimeDelta timeout) {
195 // Make sure the object outlive this message loop.
196 scoped_refptr<PrintJob> handle(this);
197
198 base::MessageLoop::current()->PostDelayedTask(FROM_HERE,
199 base::Bind(&PrintJob::Quit, quit_factory_.GetWeakPtr()), timeout);
200
201 base::MessageLoop::ScopedNestableTaskAllower allow(
202 base::MessageLoop::current());
203 base::MessageLoop::current()->Run();
204
205 return true;
206 }
207
DisconnectSource()208 void PrintJob::DisconnectSource() {
209 source_ = NULL;
210 if (document_.get())
211 document_->DisconnectSource();
212 }
213
is_job_pending() const214 bool PrintJob::is_job_pending() const {
215 return is_job_pending_;
216 }
217
document() const218 PrintedDocument* PrintJob::document() const {
219 return document_.get();
220 }
221
222 #if defined(OS_WIN)
223
224 class PrintJob::PdfToEmfState {
225 public:
PdfToEmfState(const gfx::Size & page_size,const gfx::Rect & content_area)226 PdfToEmfState(const gfx::Size& page_size, const gfx::Rect& content_area)
227 : page_count_(0),
228 current_page_(0),
229 pages_in_progress_(0),
230 page_size_(page_size),
231 content_area_(content_area),
232 converter_(PdfToEmfConverter::CreateDefault()) {}
233
Start(const scoped_refptr<base::RefCountedMemory> & data,const PdfRenderSettings & conversion_settings,const PdfToEmfConverter::StartCallback & start_callback)234 void Start(const scoped_refptr<base::RefCountedMemory>& data,
235 const PdfRenderSettings& conversion_settings,
236 const PdfToEmfConverter::StartCallback& start_callback) {
237 converter_->Start(data, conversion_settings, start_callback);
238 }
239
GetMorePages(const PdfToEmfConverter::GetPageCallback & get_page_callback)240 void GetMorePages(
241 const PdfToEmfConverter::GetPageCallback& get_page_callback) {
242 const int kMaxNumberOfTempFilesPerDocument = 3;
243 while (pages_in_progress_ < kMaxNumberOfTempFilesPerDocument &&
244 current_page_ < page_count_) {
245 ++pages_in_progress_;
246 converter_->GetPage(current_page_++, get_page_callback);
247 }
248 }
249
OnPageProcessed(const PdfToEmfConverter::GetPageCallback & get_page_callback)250 void OnPageProcessed(
251 const PdfToEmfConverter::GetPageCallback& get_page_callback) {
252 --pages_in_progress_;
253 GetMorePages(get_page_callback);
254 // Release converter if we don't need this any more.
255 if (!pages_in_progress_ && current_page_ >= page_count_)
256 converter_.reset();
257 }
258
set_page_count(int page_count)259 void set_page_count(int page_count) { page_count_ = page_count; }
page_size() const260 gfx::Size page_size() const { return page_size_; }
content_area() const261 gfx::Rect content_area() const { return content_area_; }
262
263 private:
264 int page_count_;
265 int current_page_;
266 int pages_in_progress_;
267 gfx::Size page_size_;
268 gfx::Rect content_area_;
269 scoped_ptr<PdfToEmfConverter> converter_;
270 };
271
StartPdfToEmfConversion(const scoped_refptr<base::RefCountedMemory> & bytes,const gfx::Size & page_size,const gfx::Rect & content_area)272 void PrintJob::StartPdfToEmfConversion(
273 const scoped_refptr<base::RefCountedMemory>& bytes,
274 const gfx::Size& page_size,
275 const gfx::Rect& content_area) {
276 DCHECK(!ptd_to_emf_state_.get());
277 ptd_to_emf_state_.reset(new PdfToEmfState(page_size, content_area));
278 const int kPrinterDpi = settings().dpi();
279 ptd_to_emf_state_->Start(
280 bytes,
281 printing::PdfRenderSettings(content_area, kPrinterDpi, true),
282 base::Bind(&PrintJob::OnPdfToEmfStarted, this));
283 }
284
OnPdfToEmfStarted(int page_count)285 void PrintJob::OnPdfToEmfStarted(int page_count) {
286 if (page_count <= 0) {
287 ptd_to_emf_state_.reset();
288 Cancel();
289 return;
290 }
291 ptd_to_emf_state_->set_page_count(page_count);
292 ptd_to_emf_state_->GetMorePages(
293 base::Bind(&PrintJob::OnPdfToEmfPageConverted, this));
294 }
295
OnPdfToEmfPageConverted(int page_number,double scale_factor,scoped_ptr<MetafilePlayer> emf)296 void PrintJob::OnPdfToEmfPageConverted(int page_number,
297 double scale_factor,
298 scoped_ptr<MetafilePlayer> emf) {
299 DCHECK(ptd_to_emf_state_);
300 if (!document_.get() || !emf) {
301 ptd_to_emf_state_.reset();
302 Cancel();
303 return;
304 }
305
306 // Update the rendered document. It will send notifications to the listener.
307 document_->SetPage(page_number,
308 emf.Pass(),
309 scale_factor,
310 ptd_to_emf_state_->page_size(),
311 ptd_to_emf_state_->content_area());
312
313 ptd_to_emf_state_->GetMorePages(
314 base::Bind(&PrintJob::OnPdfToEmfPageConverted, this));
315 }
316
317 #endif // OS_WIN
318
UpdatePrintedDocument(PrintedDocument * new_document)319 void PrintJob::UpdatePrintedDocument(PrintedDocument* new_document) {
320 if (document_.get() == new_document)
321 return;
322
323 document_ = new_document;
324
325 if (document_.get()) {
326 settings_ = document_->settings();
327 }
328
329 if (worker_) {
330 DCHECK(!is_job_pending_);
331 // Sync the document with the worker.
332 worker_->PostTask(FROM_HERE,
333 base::Bind(&HoldRefCallback,
334 make_scoped_refptr(this),
335 base::Bind(&PrintJobWorker::OnDocumentChanged,
336 base::Unretained(worker_.get()),
337 document_)));
338 }
339 }
340
OnNotifyPrintJobEvent(const JobEventDetails & event_details)341 void PrintJob::OnNotifyPrintJobEvent(const JobEventDetails& event_details) {
342 switch (event_details.type()) {
343 case JobEventDetails::FAILED: {
344 settings_.Clear();
345 // No need to cancel since the worker already canceled itself.
346 Stop();
347 break;
348 }
349 case JobEventDetails::USER_INIT_DONE:
350 case JobEventDetails::DEFAULT_INIT_DONE:
351 case JobEventDetails::USER_INIT_CANCELED: {
352 DCHECK_EQ(event_details.document(), document_.get());
353 break;
354 }
355 case JobEventDetails::NEW_DOC:
356 case JobEventDetails::NEW_PAGE:
357 case JobEventDetails::JOB_DONE:
358 case JobEventDetails::ALL_PAGES_REQUESTED: {
359 // Don't care.
360 break;
361 }
362 case JobEventDetails::DOC_DONE: {
363 // This will call Stop() and broadcast a JOB_DONE message.
364 base::MessageLoop::current()->PostTask(
365 FROM_HERE, base::Bind(&PrintJob::OnDocumentDone, this));
366 break;
367 }
368 case JobEventDetails::PAGE_DONE:
369 #if defined(OS_WIN)
370 ptd_to_emf_state_->OnPageProcessed(
371 base::Bind(&PrintJob::OnPdfToEmfPageConverted, this));
372 #endif // OS_WIN
373 break;
374 default: {
375 NOTREACHED();
376 break;
377 }
378 }
379 }
380
OnDocumentDone()381 void PrintJob::OnDocumentDone() {
382 // Be sure to live long enough. The instance could be destroyed by the
383 // JOB_DONE broadcast.
384 scoped_refptr<PrintJob> handle(this);
385
386 // Stop the worker thread.
387 Stop();
388
389 scoped_refptr<JobEventDetails> details(
390 new JobEventDetails(JobEventDetails::JOB_DONE, document_.get(), NULL));
391 content::NotificationService::current()->Notify(
392 chrome::NOTIFICATION_PRINT_JOB_EVENT,
393 content::Source<PrintJob>(this),
394 content::Details<JobEventDetails>(details.get()));
395 }
396
ControlledWorkerShutdown()397 void PrintJob::ControlledWorkerShutdown() {
398 DCHECK(RunsTasksOnCurrentThread());
399
400 // The deadlock this code works around is specific to window messaging on
401 // Windows, so we aren't likely to need it on any other platforms.
402 #if defined(OS_WIN)
403 // We could easily get into a deadlock case if worker_->Stop() is used; the
404 // printer driver created a window as a child of the browser window. By
405 // canceling the job, the printer driver initiated dialog box is destroyed,
406 // which sends a blocking message to its parent window. If the browser window
407 // thread is not processing messages, a deadlock occurs.
408 //
409 // This function ensures that the dialog box will be destroyed in a timely
410 // manner by the mere fact that the thread will terminate. So the potential
411 // deadlock is eliminated.
412 worker_->StopSoon();
413
414 // Delay shutdown until the worker terminates. We want this code path
415 // to wait on the thread to quit before continuing.
416 if (worker_->IsRunning()) {
417 base::MessageLoop::current()->PostDelayedTask(
418 FROM_HERE,
419 base::Bind(&PrintJob::ControlledWorkerShutdown, this),
420 base::TimeDelta::FromMilliseconds(100));
421 return;
422 }
423 #endif
424
425
426 // Now make sure the thread object is cleaned up. Do this on a worker
427 // thread because it may block.
428 base::WorkerPool::PostTaskAndReply(
429 FROM_HERE,
430 base::Bind(&PrintJobWorker::Stop, base::Unretained(worker_.get())),
431 base::Bind(&PrintJob::HoldUntilStopIsCalled, this),
432 false);
433
434 is_job_pending_ = false;
435 registrar_.RemoveAll();
436 UpdatePrintedDocument(NULL);
437 }
438
HoldUntilStopIsCalled()439 void PrintJob::HoldUntilStopIsCalled() {
440 }
441
Quit()442 void PrintJob::Quit() {
443 base::MessageLoop::current()->Quit();
444 }
445
446 // Takes settings_ ownership and will be deleted in the receiving thread.
JobEventDetails(Type type,PrintedDocument * document,PrintedPage * page)447 JobEventDetails::JobEventDetails(Type type,
448 PrintedDocument* document,
449 PrintedPage* page)
450 : document_(document),
451 page_(page),
452 type_(type) {
453 }
454
~JobEventDetails()455 JobEventDetails::~JobEventDetails() {
456 }
457
document() const458 PrintedDocument* JobEventDetails::document() const { return document_.get(); }
459
page() const460 PrintedPage* JobEventDetails::page() const { return page_.get(); }
461
462 } // namespace printing
463