print_job.cc revision ddb351dbec246cf1fab5ec20d2d5520909041de1
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
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_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
34PrintJob::~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
44void 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
66void 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
81void PrintJob::GetSettingsDone(const PrintSettings& new_settings,
82                               PrintingContext::Result result) {
83  NOTREACHED();
84}
85
86PrintJobWorker* PrintJob::DetachWorker(PrintJobWorkerOwner* new_owner) {
87  NOTREACHED();
88  return NULL;
89}
90
91MessageLoop* PrintJob::message_loop() {
92  return ui_message_loop_;
93}
94
95const PrintSettings& PrintJob::settings() const {
96  return settings_;
97}
98
99int 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
106void PrintJob::WillDestroyCurrentMessageLoop() {
107  NOTREACHED();
108}
109
110void 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
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    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
150void 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
176bool 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
198void PrintJob::DisconnectSource() {
199  source_ = NULL;
200  if (document_.get())
201    document_->DisconnectSource();
202}
203
204bool PrintJob::is_job_pending() const {
205  return is_job_pending_;
206}
207
208PrintedDocument* PrintJob::document() const {
209  return document_.get();
210}
211
212void 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
230void 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
265void 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
281void 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.
336JobEventDetails::JobEventDetails(Type type,
337                                 PrintedDocument* document,
338                                 PrintedPage* page)
339    : document_(document),
340      page_(page),
341      type_(type) {
342}
343
344JobEventDetails::~JobEventDetails() {
345}
346
347PrintedDocument* JobEventDetails::document() const {
348  return document_;
349}
350
351PrintedPage* JobEventDetails::page() const {
352  return page_;
353}
354
355}  // namespace printing
356