• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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