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
20using base::TimeDelta;
21
22namespace {
23
24// Helper function to ensure |owner| is valid until at least |callback| returns.
25void HoldRefCallback(const scoped_refptr<printing::PrintJobWorkerOwner>& owner,
26                     const base::Closure& callback) {
27  callback.Run();
28}
29
30}  // namespace
31
32namespace printing {
33
34PrintJob::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
50PrintJob::~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
60void 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
85void 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
100void PrintJob::GetSettingsDone(const PrintSettings& new_settings,
101                               PrintingContext::Result result) {
102  NOTREACHED();
103}
104
105PrintJobWorker* PrintJob::DetachWorker(PrintJobWorkerOwner* new_owner) {
106  NOTREACHED();
107  return NULL;
108}
109
110base::MessageLoop* PrintJob::message_loop() {
111  return ui_message_loop_;
112}
113
114const PrintSettings& PrintJob::settings() const {
115  return settings_;
116}
117
118int 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
125void PrintJob::WillDestroyCurrentMessageLoop() {
126  NOTREACHED();
127}
128
129void 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
154void 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
175void 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
202bool 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
216void PrintJob::DisconnectSource() {
217  source_ = NULL;
218  if (document_.get())
219    document_->DisconnectSource();
220}
221
222bool PrintJob::is_job_pending() const {
223  return is_job_pending_;
224}
225
226PrintedDocument* PrintJob::document() const {
227  return document_.get();
228}
229
230void 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
251void 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
286void 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
302void 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
344void PrintJob::HoldUntilStopIsCalled() {
345}
346
347void PrintJob::Quit() {
348  base::MessageLoop::current()->Quit();
349}
350
351// Takes settings_ ownership and will be deleted in the receiving thread.
352JobEventDetails::JobEventDetails(Type type,
353                                 PrintedDocument* document,
354                                 PrintedPage* page)
355    : document_(document),
356      page_(page),
357      type_(type) {
358}
359
360JobEventDetails::~JobEventDetails() {
361}
362
363PrintedDocument* JobEventDetails::document() const { return document_.get(); }
364
365PrintedPage* JobEventDetails::page() const { return page_.get(); }
366
367}  // namespace printing
368