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