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