• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright (c) 2011 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/message_loop.h"
8 #include "base/threading/thread_restrictions.h"
9 #include "base/timer.h"
10 #include "chrome/browser/printing/print_job_worker.h"
11 #include "content/common/notification_service.h"
12 #include "printing/printed_document.h"
13 #include "printing/printed_page.h"
14 
15 using base::TimeDelta;
16 
17 namespace printing {
18 
PrintJob()19 PrintJob::PrintJob()
20     : ui_message_loop_(MessageLoop::current()),
21       source_(NULL),
22       worker_(),
23       settings_(),
24       is_job_pending_(false),
25       is_canceling_(false) {
26   DCHECK(ui_message_loop_);
27   // This is normally a UI message loop, but in unit tests, the message loop is
28   // of the 'default' type.
29   DCHECK(ui_message_loop_->type() == MessageLoop::TYPE_UI ||
30          ui_message_loop_->type() == MessageLoop::TYPE_DEFAULT);
31   ui_message_loop_->AddDestructionObserver(this);
32 }
33 
~PrintJob()34 PrintJob::~PrintJob() {
35   ui_message_loop_->RemoveDestructionObserver(this);
36   // The job should be finished (or at least canceled) when it is destroyed.
37   DCHECK(!is_job_pending_);
38   DCHECK(!is_canceling_);
39   if (worker_.get())
40     DCHECK(worker_->message_loop() == NULL);
41   DCHECK_EQ(ui_message_loop_, MessageLoop::current());
42 }
43 
Initialize(PrintJobWorkerOwner * job,PrintedPagesSource * source,int page_count)44 void PrintJob::Initialize(PrintJobWorkerOwner* job,
45                           PrintedPagesSource* source,
46                           int page_count) {
47   DCHECK(!source_);
48   DCHECK(!worker_.get());
49   DCHECK(!is_job_pending_);
50   DCHECK(!is_canceling_);
51   DCHECK(!document_.get());
52   source_ = source;
53   worker_.reset(job->DetachWorker(this));
54   settings_ = job->settings();
55 
56   PrintedDocument* new_doc =
57       new PrintedDocument(settings_, source_, job->cookie());
58   new_doc->set_page_count(page_count);
59   UpdatePrintedDocument(new_doc);
60 
61   // Don't forget to register to our own messages.
62   registrar_.Add(this, NotificationType::PRINT_JOB_EVENT,
63                  Source<PrintJob>(this));
64 }
65 
Observe(NotificationType type,const NotificationSource & source,const NotificationDetails & details)66 void PrintJob::Observe(NotificationType type,
67                        const NotificationSource& source,
68                        const NotificationDetails& details) {
69   DCHECK_EQ(ui_message_loop_, MessageLoop::current());
70   switch (type.value) {
71     case NotificationType::PRINT_JOB_EVENT: {
72       OnNotifyPrintJobEvent(*Details<JobEventDetails>(details).ptr());
73       break;
74     }
75     default: {
76       break;
77     }
78   }
79 }
80 
GetSettingsDone(const PrintSettings & new_settings,PrintingContext::Result result)81 void PrintJob::GetSettingsDone(const PrintSettings& new_settings,
82                                PrintingContext::Result result) {
83   NOTREACHED();
84 }
85 
DetachWorker(PrintJobWorkerOwner * new_owner)86 PrintJobWorker* PrintJob::DetachWorker(PrintJobWorkerOwner* new_owner) {
87   NOTREACHED();
88   return NULL;
89 }
90 
message_loop()91 MessageLoop* PrintJob::message_loop() {
92   return ui_message_loop_;
93 }
94 
settings() const95 const PrintSettings& PrintJob::settings() const {
96   return settings_;
97 }
98 
cookie() const99 int PrintJob::cookie() const {
100   if (!document_.get())
101     // Always use an invalid cookie in this case.
102     return 0;
103   return document_->cookie();
104 }
105 
WillDestroyCurrentMessageLoop()106 void PrintJob::WillDestroyCurrentMessageLoop() {
107   NOTREACHED();
108 }
109 
StartPrinting()110 void PrintJob::StartPrinting() {
111   DCHECK_EQ(ui_message_loop_, MessageLoop::current());
112   DCHECK(worker_->message_loop());
113   DCHECK(!is_job_pending_);
114   if (!worker_->message_loop() || is_job_pending_)
115     return;
116 
117   // Real work is done in PrintJobWorker::StartPrinting().
118   worker_->message_loop()->PostTask(FROM_HERE, NewRunnableMethod(
119       worker_.get(), &PrintJobWorker::StartPrinting, document_));
120   // Set the flag right now.
121   is_job_pending_ = true;
122 
123   // Tell everyone!
124   scoped_refptr<JobEventDetails> details(
125       new JobEventDetails(JobEventDetails::NEW_DOC, document_.get(), NULL));
126   NotificationService::current()->Notify(
127       NotificationType::PRINT_JOB_EVENT,
128       Source<PrintJob>(this),
129       Details<JobEventDetails>(details.get()));
130 }
131 
Stop()132 void PrintJob::Stop() {
133   DCHECK_EQ(ui_message_loop_, MessageLoop::current());
134 
135   // Be sure to live long enough.
136   scoped_refptr<PrintJob> handle(this);
137 
138   MessageLoop* worker_loop = worker_->message_loop();
139   if (worker_loop) {
140     ControlledWorkerShutdown();
141 
142     is_job_pending_ = false;
143     registrar_.Remove(this, NotificationType::PRINT_JOB_EVENT,
144                       Source<PrintJob>(this));
145   }
146   // Flush the cached document.
147   UpdatePrintedDocument(NULL);
148 }
149 
Cancel()150 void PrintJob::Cancel() {
151   if (is_canceling_)
152     return;
153   is_canceling_ = true;
154 
155   // Be sure to live long enough.
156   scoped_refptr<PrintJob> handle(this);
157 
158   DCHECK_EQ(ui_message_loop_, MessageLoop::current());
159   MessageLoop* worker_loop = worker_.get() ? worker_->message_loop() : NULL;
160   if (worker_loop) {
161     // Call this right now so it renders the context invalid. Do not use
162     // InvokeLater since it would take too much time.
163     worker_->Cancel();
164   }
165   // Make sure a Cancel() is broadcast.
166   scoped_refptr<JobEventDetails> details(
167       new JobEventDetails(JobEventDetails::FAILED, NULL, NULL));
168   NotificationService::current()->Notify(
169       NotificationType::PRINT_JOB_EVENT,
170       Source<PrintJob>(this),
171       Details<JobEventDetails>(details.get()));
172   Stop();
173   is_canceling_ = false;
174 }
175 
FlushJob(int timeout_ms)176 bool PrintJob::FlushJob(int timeout_ms) {
177   // Make sure the object outlive this message loop.
178   scoped_refptr<PrintJob> handle(this);
179 
180   // Stop() will eventually be called, which will get out of the inner message
181   // loop. But, don't take it for granted and set a timer in case something goes
182   // wrong.
183   base::OneShotTimer<MessageLoop> quit_task;
184   if (timeout_ms) {
185     quit_task.Start(TimeDelta::FromMilliseconds(timeout_ms),
186                     MessageLoop::current(), &MessageLoop::Quit);
187   }
188 
189   bool old_state = MessageLoop::current()->NestableTasksAllowed();
190   MessageLoop::current()->SetNestableTasksAllowed(true);
191   MessageLoop::current()->Run();
192   // Restore task state.
193   MessageLoop::current()->SetNestableTasksAllowed(old_state);
194 
195   return true;
196 }
197 
DisconnectSource()198 void PrintJob::DisconnectSource() {
199   source_ = NULL;
200   if (document_.get())
201     document_->DisconnectSource();
202 }
203 
is_job_pending() const204 bool PrintJob::is_job_pending() const {
205   return is_job_pending_;
206 }
207 
document() const208 PrintedDocument* PrintJob::document() const {
209   return document_.get();
210 }
211 
UpdatePrintedDocument(PrintedDocument * new_document)212 void PrintJob::UpdatePrintedDocument(PrintedDocument* new_document) {
213   if (document_.get() == new_document)
214     return;
215 
216   document_ = new_document;
217 
218   if (document_.get()) {
219     settings_ = document_->settings();
220   }
221 
222   if (worker_.get() && worker_->message_loop()) {
223     DCHECK(!is_job_pending_);
224     // Sync the document with the worker.
225     worker_->message_loop()->PostTask(FROM_HERE, NewRunnableMethod(
226         worker_.get(), &PrintJobWorker::OnDocumentChanged, document_));
227   }
228 }
229 
OnNotifyPrintJobEvent(const JobEventDetails & event_details)230 void PrintJob::OnNotifyPrintJobEvent(const JobEventDetails& event_details) {
231   switch (event_details.type()) {
232     case JobEventDetails::FAILED: {
233       settings_.Clear();
234       // No need to cancel since the worker already canceled itself.
235       Stop();
236       break;
237     }
238     case JobEventDetails::USER_INIT_DONE:
239     case JobEventDetails::DEFAULT_INIT_DONE:
240     case JobEventDetails::USER_INIT_CANCELED: {
241       DCHECK_EQ(event_details.document(), document_.get());
242       break;
243     }
244     case JobEventDetails::NEW_DOC:
245     case JobEventDetails::NEW_PAGE:
246     case JobEventDetails::PAGE_DONE:
247     case JobEventDetails::JOB_DONE:
248     case JobEventDetails::ALL_PAGES_REQUESTED: {
249       // Don't care.
250       break;
251     }
252     case JobEventDetails::DOC_DONE: {
253       // This will call Stop() and broadcast a JOB_DONE message.
254       MessageLoop::current()->PostTask(FROM_HERE, NewRunnableMethod(
255           this, &PrintJob::OnDocumentDone));
256       break;
257     }
258     default: {
259       NOTREACHED();
260       break;
261     }
262   }
263 }
264 
OnDocumentDone()265 void PrintJob::OnDocumentDone() {
266   // Be sure to live long enough. The instance could be destroyed by the
267   // JOB_DONE broadcast.
268   scoped_refptr<PrintJob> handle(this);
269 
270   // Stop the worker thread.
271   Stop();
272 
273   scoped_refptr<JobEventDetails> details(
274       new JobEventDetails(JobEventDetails::JOB_DONE, document_.get(), NULL));
275   NotificationService::current()->Notify(
276       NotificationType::PRINT_JOB_EVENT,
277       Source<PrintJob>(this),
278       Details<JobEventDetails>(details.get()));
279 }
280 
ControlledWorkerShutdown()281 void PrintJob::ControlledWorkerShutdown() {
282   DCHECK_EQ(ui_message_loop_, MessageLoop::current());
283 
284   // The deadlock this code works around is specific to window messaging on
285   // Windows, so we aren't likely to need it on any other platforms.
286 #if defined(OS_WIN)
287   // We could easily get into a deadlock case if worker_->Stop() is used; the
288   // printer driver created a window as a child of the browser window. By
289   // canceling the job, the printer driver initiated dialog box is destroyed,
290   // which sends a blocking message to its parent window. If the browser window
291   // thread is not processing messages, a deadlock occurs.
292   //
293   // This function ensures that the dialog box will be destroyed in a timely
294   // manner by the mere fact that the thread will terminate. So the potential
295   // deadlock is eliminated.
296   worker_->StopSoon();
297 
298   // Run a tight message loop until the worker terminates. It may seems like a
299   // hack but I see no other way to get it to work flawlessly. The issues here
300   // are:
301   // - We don't want to run tasks while the thread is quitting.
302   // - We want this code path to wait on the thread to quit before continuing.
303   MSG msg;
304   HANDLE thread_handle = worker_->thread_handle();
305   for (; thread_handle;) {
306     // Note that we don't do any kind of message priorization since we don't
307     // execute any pending task or timer.
308     DWORD result = MsgWaitForMultipleObjects(1, &thread_handle,
309                                              FALSE, INFINITE, QS_ALLINPUT);
310     if (result == WAIT_OBJECT_0 + 1) {
311       while (PeekMessage(&msg, NULL, 0, 0, TRUE) > 0) {
312         TranslateMessage(&msg);
313         DispatchMessage(&msg);
314       }
315       // Continue looping.
316     } else if (result == WAIT_OBJECT_0) {
317       // The thread quit.
318       break;
319     } else {
320       // An error occured. Assume the thread quit.
321       NOTREACHED();
322       break;
323     }
324   }
325 #endif
326 
327   // Temporarily allow it until we fix
328   // http://code.google.com/p/chromium/issues/detail?id=67044
329   base::ThreadRestrictions::ScopedAllowIO allow_io;
330 
331   // Now make sure the thread object is cleaned up.
332   worker_->Stop();
333 }
334 
335 // Takes settings_ ownership and will be deleted in the receiving thread.
JobEventDetails(Type type,PrintedDocument * document,PrintedPage * page)336 JobEventDetails::JobEventDetails(Type type,
337                                  PrintedDocument* document,
338                                  PrintedPage* page)
339     : document_(document),
340       page_(page),
341       type_(type) {
342 }
343 
~JobEventDetails()344 JobEventDetails::~JobEventDetails() {
345 }
346 
document() const347 PrintedDocument* JobEventDetails::document() const {
348   return document_;
349 }
350 
page() const351 PrintedPage* JobEventDetails::page() const {
352   return page_;
353 }
354 
355 }  // namespace printing
356