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