• 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 using base::TimeDelta;
21 
22 namespace {
23 
24 // Helper function to ensure |owner| is valid until at least |callback| returns.
HoldRefCallback(const scoped_refptr<printing::PrintJobWorkerOwner> & owner,const base::Closure & callback)25 void HoldRefCallback(const scoped_refptr<printing::PrintJobWorkerOwner>& owner,
26                      const base::Closure& callback) {
27   callback.Run();
28 }
29 
30 }  // namespace
31 
32 namespace printing {
33 
PrintJob()34 PrintJob::PrintJob()
35     : ui_message_loop_(base::MessageLoop::current()),
36       source_(NULL),
37       worker_(),
38       settings_(),
39       is_job_pending_(false),
40       is_canceling_(false),
41       quit_factory_(this) {
42   DCHECK(ui_message_loop_);
43   // This is normally a UI message loop, but in unit tests, the message loop is
44   // of the 'default' type.
45   DCHECK(base::MessageLoopForUI::IsCurrent() ||
46          ui_message_loop_->type() == base::MessageLoop::TYPE_DEFAULT);
47   ui_message_loop_->AddDestructionObserver(this);
48 }
49 
~PrintJob()50 PrintJob::~PrintJob() {
51   ui_message_loop_->RemoveDestructionObserver(this);
52   // The job should be finished (or at least canceled) when it is destroyed.
53   DCHECK(!is_job_pending_);
54   DCHECK(!is_canceling_);
55   if (worker_.get())
56     DCHECK(worker_->message_loop() == NULL);
57   DCHECK_EQ(ui_message_loop_, base::MessageLoop::current());
58 }
59 
Initialize(PrintJobWorkerOwner * job,PrintedPagesSource * source,int page_count)60 void PrintJob::Initialize(PrintJobWorkerOwner* job,
61                           PrintedPagesSource* source,
62                           int page_count) {
63   DCHECK(!source_);
64   DCHECK(!worker_.get());
65   DCHECK(!is_job_pending_);
66   DCHECK(!is_canceling_);
67   DCHECK(!document_.get());
68   source_ = source;
69   worker_.reset(job->DetachWorker(this));
70   settings_ = job->settings();
71 
72   PrintedDocument* new_doc =
73       new PrintedDocument(settings_,
74                           source_,
75                           job->cookie(),
76                           content::BrowserThread::GetBlockingPool());
77   new_doc->set_page_count(page_count);
78   UpdatePrintedDocument(new_doc);
79 
80   // Don't forget to register to our own messages.
81   registrar_.Add(this, chrome::NOTIFICATION_PRINT_JOB_EVENT,
82                  content::Source<PrintJob>(this));
83 }
84 
Observe(int type,const content::NotificationSource & source,const content::NotificationDetails & details)85 void PrintJob::Observe(int type,
86                        const content::NotificationSource& source,
87                        const content::NotificationDetails& details) {
88   DCHECK_EQ(ui_message_loop_, base::MessageLoop::current());
89   switch (type) {
90     case chrome::NOTIFICATION_PRINT_JOB_EVENT: {
91       OnNotifyPrintJobEvent(*content::Details<JobEventDetails>(details).ptr());
92       break;
93     }
94     default: {
95       break;
96     }
97   }
98 }
99 
GetSettingsDone(const PrintSettings & new_settings,PrintingContext::Result result)100 void PrintJob::GetSettingsDone(const PrintSettings& new_settings,
101                                PrintingContext::Result result) {
102   NOTREACHED();
103 }
104 
DetachWorker(PrintJobWorkerOwner * new_owner)105 PrintJobWorker* PrintJob::DetachWorker(PrintJobWorkerOwner* new_owner) {
106   NOTREACHED();
107   return NULL;
108 }
109 
message_loop()110 base::MessageLoop* PrintJob::message_loop() {
111   return ui_message_loop_;
112 }
113 
settings() const114 const PrintSettings& PrintJob::settings() const {
115   return settings_;
116 }
117 
cookie() const118 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 
WillDestroyCurrentMessageLoop()125 void PrintJob::WillDestroyCurrentMessageLoop() {
126   NOTREACHED();
127 }
128 
StartPrinting()129 void PrintJob::StartPrinting() {
130   DCHECK_EQ(ui_message_loop_, base::MessageLoop::current());
131   DCHECK(worker_->message_loop());
132   DCHECK(!is_job_pending_);
133   if (!worker_->message_loop() || is_job_pending_)
134     return;
135 
136   // Real work is done in PrintJobWorker::StartPrinting().
137   worker_->message_loop()->PostTask(
138       FROM_HERE,
139       base::Bind(&HoldRefCallback, make_scoped_refptr(this),
140                  base::Bind(&PrintJobWorker::StartPrinting,
141                             base::Unretained(worker_.get()), document_)));
142   // Set the flag right now.
143   is_job_pending_ = true;
144 
145   // Tell everyone!
146   scoped_refptr<JobEventDetails> details(
147       new JobEventDetails(JobEventDetails::NEW_DOC, document_.get(), NULL));
148   content::NotificationService::current()->Notify(
149       chrome::NOTIFICATION_PRINT_JOB_EVENT,
150       content::Source<PrintJob>(this),
151       content::Details<JobEventDetails>(details.get()));
152 }
153 
Stop()154 void PrintJob::Stop() {
155   DCHECK_EQ(ui_message_loop_, base::MessageLoop::current());
156 
157   if (quit_factory_.HasWeakPtrs()) {
158     // In case we're running a nested message loop to wait for a job to finish,
159     // and we finished before the timeout, quit the nested loop right away.
160     Quit();
161     quit_factory_.InvalidateWeakPtrs();
162   }
163 
164   // Be sure to live long enough.
165   scoped_refptr<PrintJob> handle(this);
166 
167   if (worker_->message_loop()) {
168     ControlledWorkerShutdown();
169   } else {
170     // Flush the cached document.
171     UpdatePrintedDocument(NULL);
172   }
173 }
174 
Cancel()175 void PrintJob::Cancel() {
176   if (is_canceling_)
177     return;
178   is_canceling_ = true;
179 
180   // Be sure to live long enough.
181   scoped_refptr<PrintJob> handle(this);
182 
183   DCHECK_EQ(ui_message_loop_, base::MessageLoop::current());
184   base::MessageLoop* worker_loop =
185       worker_.get() ? worker_->message_loop() : NULL;
186   if (worker_loop) {
187     // Call this right now so it renders the context invalid. Do not use
188     // InvokeLater since it would take too much time.
189     worker_->Cancel();
190   }
191   // Make sure a Cancel() is broadcast.
192   scoped_refptr<JobEventDetails> details(
193       new JobEventDetails(JobEventDetails::FAILED, NULL, NULL));
194   content::NotificationService::current()->Notify(
195       chrome::NOTIFICATION_PRINT_JOB_EVENT,
196       content::Source<PrintJob>(this),
197       content::Details<JobEventDetails>(details.get()));
198   Stop();
199   is_canceling_ = false;
200 }
201 
FlushJob(base::TimeDelta timeout)202 bool PrintJob::FlushJob(base::TimeDelta timeout) {
203   // Make sure the object outlive this message loop.
204   scoped_refptr<PrintJob> handle(this);
205 
206   base::MessageLoop::current()->PostDelayedTask(FROM_HERE,
207       base::Bind(&PrintJob::Quit, quit_factory_.GetWeakPtr()), timeout);
208 
209   base::MessageLoop::ScopedNestableTaskAllower allow(
210       base::MessageLoop::current());
211   base::MessageLoop::current()->Run();
212 
213   return true;
214 }
215 
DisconnectSource()216 void PrintJob::DisconnectSource() {
217   source_ = NULL;
218   if (document_.get())
219     document_->DisconnectSource();
220 }
221 
is_job_pending() const222 bool PrintJob::is_job_pending() const {
223   return is_job_pending_;
224 }
225 
document() const226 PrintedDocument* PrintJob::document() const {
227   return document_.get();
228 }
229 
UpdatePrintedDocument(PrintedDocument * new_document)230 void PrintJob::UpdatePrintedDocument(PrintedDocument* new_document) {
231   if (document_.get() == new_document)
232     return;
233 
234   document_ = new_document;
235 
236   if (document_.get()) {
237     settings_ = document_->settings();
238   }
239 
240   if (worker_.get() && worker_->message_loop()) {
241     DCHECK(!is_job_pending_);
242     // Sync the document with the worker.
243     worker_->message_loop()->PostTask(
244         FROM_HERE,
245         base::Bind(&HoldRefCallback, make_scoped_refptr(this),
246                    base::Bind(&PrintJobWorker::OnDocumentChanged,
247                               base::Unretained(worker_.get()), document_)));
248   }
249 }
250 
OnNotifyPrintJobEvent(const JobEventDetails & event_details)251 void PrintJob::OnNotifyPrintJobEvent(const JobEventDetails& event_details) {
252   switch (event_details.type()) {
253     case JobEventDetails::FAILED: {
254       settings_.Clear();
255       // No need to cancel since the worker already canceled itself.
256       Stop();
257       break;
258     }
259     case JobEventDetails::USER_INIT_DONE:
260     case JobEventDetails::DEFAULT_INIT_DONE:
261     case JobEventDetails::USER_INIT_CANCELED: {
262       DCHECK_EQ(event_details.document(), document_.get());
263       break;
264     }
265     case JobEventDetails::NEW_DOC:
266     case JobEventDetails::NEW_PAGE:
267     case JobEventDetails::PAGE_DONE:
268     case JobEventDetails::JOB_DONE:
269     case JobEventDetails::ALL_PAGES_REQUESTED: {
270       // Don't care.
271       break;
272     }
273     case JobEventDetails::DOC_DONE: {
274       // This will call Stop() and broadcast a JOB_DONE message.
275       base::MessageLoop::current()->PostTask(
276           FROM_HERE, base::Bind(&PrintJob::OnDocumentDone, this));
277       break;
278     }
279     default: {
280       NOTREACHED();
281       break;
282     }
283   }
284 }
285 
OnDocumentDone()286 void PrintJob::OnDocumentDone() {
287   // Be sure to live long enough. The instance could be destroyed by the
288   // JOB_DONE broadcast.
289   scoped_refptr<PrintJob> handle(this);
290 
291   // Stop the worker thread.
292   Stop();
293 
294   scoped_refptr<JobEventDetails> details(
295       new JobEventDetails(JobEventDetails::JOB_DONE, document_.get(), NULL));
296   content::NotificationService::current()->Notify(
297       chrome::NOTIFICATION_PRINT_JOB_EVENT,
298       content::Source<PrintJob>(this),
299       content::Details<JobEventDetails>(details.get()));
300 }
301 
ControlledWorkerShutdown()302 void PrintJob::ControlledWorkerShutdown() {
303   DCHECK_EQ(ui_message_loop_, base::MessageLoop::current());
304 
305   // The deadlock this code works around is specific to window messaging on
306   // Windows, so we aren't likely to need it on any other platforms.
307 #if defined(OS_WIN)
308   // We could easily get into a deadlock case if worker_->Stop() is used; the
309   // printer driver created a window as a child of the browser window. By
310   // canceling the job, the printer driver initiated dialog box is destroyed,
311   // which sends a blocking message to its parent window. If the browser window
312   // thread is not processing messages, a deadlock occurs.
313   //
314   // This function ensures that the dialog box will be destroyed in a timely
315   // manner by the mere fact that the thread will terminate. So the potential
316   // deadlock is eliminated.
317   worker_->StopSoon();
318 
319   // Delay shutdown until the worker terminates.  We want this code path
320   // to wait on the thread to quit before continuing.
321   if (worker_->IsRunning()) {
322     base::MessageLoop::current()->PostDelayedTask(
323         FROM_HERE,
324         base::Bind(&PrintJob::ControlledWorkerShutdown, this),
325         base::TimeDelta::FromMilliseconds(100));
326     return;
327   }
328 #endif
329 
330 
331   // Now make sure the thread object is cleaned up. Do this on a worker
332   // thread because it may block.
333   base::WorkerPool::PostTaskAndReply(
334       FROM_HERE,
335       base::Bind(&PrintJobWorker::Stop, base::Unretained(worker_.get())),
336       base::Bind(&PrintJob::HoldUntilStopIsCalled, this),
337       false);
338 
339   is_job_pending_ = false;
340   registrar_.RemoveAll();
341   UpdatePrintedDocument(NULL);
342 }
343 
HoldUntilStopIsCalled()344 void PrintJob::HoldUntilStopIsCalled() {
345 }
346 
Quit()347 void PrintJob::Quit() {
348   base::MessageLoop::current()->Quit();
349 }
350 
351 // Takes settings_ ownership and will be deleted in the receiving thread.
JobEventDetails(Type type,PrintedDocument * document,PrintedPage * page)352 JobEventDetails::JobEventDetails(Type type,
353                                  PrintedDocument* document,
354                                  PrintedPage* page)
355     : document_(document),
356       page_(page),
357       type_(type) {
358 }
359 
~JobEventDetails()360 JobEventDetails::~JobEventDetails() {
361 }
362 
document() const363 PrintedDocument* JobEventDetails::document() const { return document_.get(); }
364 
page() const365 PrintedPage* JobEventDetails::page() const { return page_.get(); }
366 
367 }  // namespace printing
368