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