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