print_job_worker.cc revision 21d179b334e59e9a3bfcaed4c4430bef1bc5759d
1// Copyright (c) 2006-2008 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_worker.h"
6
7#include "base/message_loop.h"
8#include "chrome/browser/browser_process.h"
9#include "chrome/browser/browser_thread.h"
10#include "chrome/browser/printing/print_job.h"
11#include "chrome/common/notification_service.h"
12#include "printing/printed_document.h"
13#include "printing/printed_page.h"
14
15namespace printing {
16
17class PrintJobWorker::NotificationTask : public Task {
18 public:
19  NotificationTask() : print_job_(NULL), details_(NULL) {
20  }
21  ~NotificationTask() {
22  }
23
24  // Initializes the object. This object can't be initialized in the constructor
25  // since it is not created directly.
26  void Init(PrintJobWorkerOwner* print_job,
27            JobEventDetails::Type detail_type,
28            PrintedDocument* document,
29            PrintedPage* page) {
30    DCHECK(!print_job_);
31    DCHECK(!details_);
32    print_job_ = print_job;
33    details_ = new JobEventDetails(detail_type, document, page);
34  }
35
36  virtual void Run() {
37    // Send the notification in the right thread.
38    NotificationService::current()->Notify(
39        NotificationType::PRINT_JOB_EVENT,
40        // We know that is is a PrintJob object in this circumstance.
41        Source<PrintJob>(static_cast<PrintJob*>(print_job_.get())),
42        Details<JobEventDetails>(details_));
43  }
44
45  // The job which originates this notification.
46  scoped_refptr<PrintJobWorkerOwner> print_job_;
47  scoped_refptr<JobEventDetails> details_;
48};
49
50
51PrintJobWorker::PrintJobWorker(PrintJobWorkerOwner* owner)
52    : Thread("Printing_Worker"),
53      owner_(owner) {
54  // The object is created in the IO thread.
55  DCHECK_EQ(owner_->message_loop(), MessageLoop::current());
56
57  printing_context_.reset(PrintingContext::Create(
58      g_browser_process->GetApplicationLocale()));
59}
60
61PrintJobWorker::~PrintJobWorker() {
62  // The object is normally deleted in the UI thread, but when the user
63  // cancels printing or in the case of print preview, the worker is destroyed
64  // on the I/O thread.
65  DCHECK_EQ(owner_->message_loop(), MessageLoop::current());
66}
67
68void PrintJobWorker::SetNewOwner(PrintJobWorkerOwner* new_owner) {
69  DCHECK(page_number_ == PageNumber::npos());
70  owner_ = new_owner;
71}
72
73void PrintJobWorker::GetSettings(bool ask_user_for_settings,
74                                 gfx::NativeView parent_view,
75                                 int document_page_count,
76                                 bool has_selection,
77                                 bool use_overlays) {
78  DCHECK_EQ(message_loop(), MessageLoop::current());
79  DCHECK_EQ(page_number_, PageNumber::npos());
80
81  // Recursive task processing is needed for the dialog in case it needs to be
82  // destroyed by a task.
83  MessageLoop::current()->SetNestableTasksAllowed(true);
84  printing_context_->set_use_overlays(use_overlays);
85
86  if (ask_user_for_settings) {
87#if defined(OS_POSIX)
88    BrowserThread::PostTask(
89        BrowserThread::UI, FROM_HERE,
90        NewRunnableMethod(this, &PrintJobWorker::GetSettingsWithUI,
91                          parent_view, document_page_count,
92                          has_selection));
93#else
94    printing_context_->AskUserForSettings(
95        parent_view,
96        document_page_count,
97        has_selection,
98        NewCallback(this, &PrintJobWorker::GetSettingsDone));
99#endif  // defined(OS_POSIX)
100  } else {
101#if defined(OS_POSIX)
102    BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
103        NewRunnableMethod(this, &PrintJobWorker::UseDefaultSettings));
104#else
105    UseDefaultSettings();
106#endif  // defined(OS_POSIX)
107  }
108}
109
110void PrintJobWorker::GetSettingsDone(PrintingContext::Result result) {
111  // Most PrintingContext functions may start a message loop and process
112  // message recursively, so disable recursive task processing.
113  MessageLoop::current()->SetNestableTasksAllowed(false);
114
115  // We can't use OnFailure() here since owner_ may not support notifications.
116
117  // PrintJob will create the new PrintedDocument.
118  owner_->message_loop()->PostTask(FROM_HERE, NewRunnableMethod(
119      owner_,
120      &PrintJobWorkerOwner::GetSettingsDone,
121      printing_context_->settings(),
122      result));
123}
124
125#if defined(OS_POSIX)
126void PrintJobWorker::GetSettingsWithUI(gfx::NativeView parent_view,
127                                       int document_page_count,
128                                       bool has_selection) {
129  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
130
131  printing_context_->AskUserForSettings(
132      parent_view,
133      document_page_count,
134      has_selection,
135      NewCallback(this, &PrintJobWorker::GetSettingsWithUIDone));
136}
137
138void PrintJobWorker::GetSettingsWithUIDone(PrintingContext::Result result) {
139  message_loop()->PostTask(FROM_HERE, NewRunnableMethod(
140      this, &PrintJobWorker::GetSettingsDone, result));
141}
142#endif  // defined(OS_POSIX)
143
144void PrintJobWorker::UseDefaultSettings() {
145  PrintingContext::Result result = printing_context_->UseDefaultSettings();
146  GetSettingsDone(result);
147}
148
149void PrintJobWorker::StartPrinting(PrintedDocument* new_document) {
150  DCHECK_EQ(message_loop(), MessageLoop::current());
151  DCHECK_EQ(page_number_, PageNumber::npos());
152  DCHECK_EQ(document_, new_document);
153  DCHECK(document_.get());
154  DCHECK(new_document->settings().Equals(printing_context_->settings()));
155
156  if (!document_.get() || page_number_ != PageNumber::npos() ||
157      document_ != new_document) {
158    return;
159  }
160
161  PrintingContext::Result result =
162      printing_context_->NewDocument(document_->name());
163  if (result != PrintingContext::OK) {
164    OnFailure();
165    return;
166  }
167
168  // Try to print already cached data. It may already have been generated for
169  // the print preview.
170  OnNewPage();
171  // Don't touch this anymore since the instance could be destroyed. It happens
172  // if all the pages are printed a one sweep and the client doesn't have a
173  // handle to us anymore. There's a timing issue involved between the worker
174  // thread and the UI thread. Take no chance.
175}
176
177void PrintJobWorker::OnDocumentChanged(PrintedDocument* new_document) {
178  DCHECK_EQ(message_loop(), MessageLoop::current());
179  DCHECK_EQ(page_number_, PageNumber::npos());
180  DCHECK(!new_document ||
181         new_document->settings().Equals(printing_context_->settings()));
182
183  if (page_number_ != PageNumber::npos())
184    return;
185
186  document_ = new_document;
187}
188
189void PrintJobWorker::OnNewPage() {
190  if (!document_.get()) {
191    // Spurious message.
192    return;
193  }
194  // message_loop() could return NULL when the print job is cancelled.
195  DCHECK_EQ(message_loop(), MessageLoop::current());
196
197  if (page_number_ == PageNumber::npos()) {
198    // Find first page to print.
199    int page_count = document_->page_count();
200    if (!page_count) {
201      // We still don't know how many pages the document contains. We can't
202      // start to print the document yet since the header/footer may refer to
203      // the document's page count.
204      return;
205    }
206    // We have enough information to initialize page_number_.
207    page_number_.Init(document_->settings(), page_count);
208  }
209  DCHECK_NE(page_number_, PageNumber::npos());
210
211  for (;;) {
212    // Is the page available?
213    scoped_refptr<PrintedPage> page;
214    if (!document_->GetPage(page_number_.ToInt(), &page)) {
215      // We need to wait for the page to be available.
216      MessageLoop::current()->PostDelayedTask(
217          FROM_HERE,
218          NewRunnableMethod(this, &PrintJobWorker::OnNewPage),
219          500);
220      break;
221    }
222    // The page is there, print it.
223    SpoolPage(*page);
224    ++page_number_;
225    if (page_number_ == PageNumber::npos()) {
226      OnDocumentDone();
227      // Don't touch this anymore since the instance could be destroyed.
228      break;
229    }
230  }
231}
232
233void PrintJobWorker::Cancel() {
234  // This is the only function that can be called from any thread.
235  printing_context_->Cancel();
236  // Cannot touch any member variable since we don't know in which thread
237  // context we run.
238}
239
240void PrintJobWorker::DismissDialog() {
241  printing_context_->DismissDialog();
242}
243
244void PrintJobWorker::OnDocumentDone() {
245  DCHECK_EQ(message_loop(), MessageLoop::current());
246  DCHECK_EQ(page_number_, PageNumber::npos());
247  DCHECK(document_.get());
248
249  if (printing_context_->DocumentDone() != PrintingContext::OK) {
250    OnFailure();
251    return;
252  }
253
254  // Tell everyone!
255  NotificationTask* task = new NotificationTask();
256  task->Init(owner_,
257             JobEventDetails::DOC_DONE,
258             document_.get(),
259             NULL);
260  owner_->message_loop()->PostTask(FROM_HERE, task);
261
262  // Makes sure the variables are reinitialized.
263  document_ = NULL;
264}
265
266void PrintJobWorker::SpoolPage(PrintedPage& page) {
267  DCHECK_EQ(message_loop(), MessageLoop::current());
268  DCHECK_NE(page_number_, PageNumber::npos());
269
270  // Signal everyone that the page is about to be printed.
271  NotificationTask* task = new NotificationTask();
272  task->Init(owner_,
273             JobEventDetails::NEW_PAGE,
274             document_.get(),
275             &page);
276  owner_->message_loop()->PostTask(FROM_HERE, task);
277
278  // Preprocess.
279  if (printing_context_->NewPage() != PrintingContext::OK) {
280    OnFailure();
281    return;
282  }
283
284  // Actual printing.
285  document_->RenderPrintedPage(page, printing_context_->context());
286
287  // Postprocess.
288  if (printing_context_->PageDone() != PrintingContext::OK) {
289    OnFailure();
290    return;
291  }
292
293  // Signal everyone that the page is printed.
294  task = new NotificationTask();
295  task->Init(owner_,
296             JobEventDetails::PAGE_DONE,
297             document_.get(),
298             &page);
299  owner_->message_loop()->PostTask(FROM_HERE, task);
300}
301
302void PrintJobWorker::OnFailure() {
303  DCHECK_EQ(message_loop(), MessageLoop::current());
304
305  // We may loose our last reference by broadcasting the FAILED event.
306  scoped_refptr<PrintJobWorkerOwner> handle(owner_);
307
308  NotificationTask* task = new NotificationTask();
309  task->Init(owner_,
310             JobEventDetails::FAILED,
311             document_.get(),
312             NULL);
313  owner_->message_loop()->PostTask(FROM_HERE, task);
314  Cancel();
315
316  // Makes sure the variables are reinitialized.
317  document_ = NULL;
318  page_number_ = PageNumber::npos();
319}
320
321}  // namespace printing
322
323void RunnableMethodTraits<printing::PrintJobWorker>::RetainCallee(
324    printing::PrintJobWorker* obj) {
325  DCHECK(!owner_.get());
326  owner_ = obj->owner_;
327}
328
329void RunnableMethodTraits<printing::PrintJobWorker>::ReleaseCallee(
330    printing::PrintJobWorker* obj) {
331  DCHECK_EQ(owner_, obj->owner_);
332  owner_ = NULL;
333}
334