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_worker.h"
6
7#include "base/bind.h"
8#include "base/bind_helpers.h"
9#include "base/callback.h"
10#include "base/compiler_specific.h"
11#include "base/message_loop/message_loop.h"
12#include "base/values.h"
13#include "chrome/browser/browser_process.h"
14#include "chrome/browser/chrome_notification_types.h"
15#include "chrome/browser/printing/print_job.h"
16#include "chrome/grit/generated_resources.h"
17#include "content/public/browser/browser_thread.h"
18#include "content/public/browser/notification_service.h"
19#include "content/public/browser/render_view_host.h"
20#include "content/public/browser/web_contents.h"
21#include "printing/print_job_constants.h"
22#include "printing/printed_document.h"
23#include "printing/printed_page.h"
24#include "printing/printing_utils.h"
25#include "ui/base/l10n/l10n_util.h"
26
27using content::BrowserThread;
28
29namespace printing {
30
31namespace {
32
33// Helper function to ensure |owner| is valid until at least |callback| returns.
34void HoldRefCallback(const scoped_refptr<printing::PrintJobWorkerOwner>& owner,
35                     const base::Closure& callback) {
36  callback.Run();
37}
38
39class PrintingContextDelegate : public PrintingContext::Delegate {
40 public:
41  PrintingContextDelegate(int render_process_id, int render_view_id);
42  virtual ~PrintingContextDelegate();
43
44  virtual gfx::NativeView GetParentView() OVERRIDE;
45  virtual std::string GetAppLocale() OVERRIDE;
46
47 private:
48  int render_process_id_;
49  int render_view_id_;
50};
51
52PrintingContextDelegate::PrintingContextDelegate(int render_process_id,
53                                                 int render_view_id)
54    : render_process_id_(render_process_id),
55      render_view_id_(render_view_id) {
56}
57
58PrintingContextDelegate::~PrintingContextDelegate() {
59}
60
61gfx::NativeView PrintingContextDelegate::GetParentView() {
62  DCHECK_CURRENTLY_ON(BrowserThread::UI);
63  content::RenderViewHost* view =
64      content::RenderViewHost::FromID(render_process_id_, render_view_id_);
65  if (!view)
66    return NULL;
67  content::WebContents* wc = content::WebContents::FromRenderViewHost(view);
68  return wc ? wc->GetNativeView() : NULL;
69}
70
71std::string PrintingContextDelegate::GetAppLocale() {
72  return g_browser_process->GetApplicationLocale();
73}
74
75void NotificationCallback(PrintJobWorkerOwner* print_job,
76                          JobEventDetails::Type detail_type,
77                          PrintedDocument* document,
78                          PrintedPage* page) {
79  JobEventDetails* details = new JobEventDetails(detail_type, document, page);
80  content::NotificationService::current()->Notify(
81      chrome::NOTIFICATION_PRINT_JOB_EVENT,
82      // We know that is is a PrintJob object in this circumstance.
83      content::Source<PrintJob>(static_cast<PrintJob*>(print_job)),
84      content::Details<JobEventDetails>(details));
85}
86
87}  // namespace
88
89PrintJobWorker::PrintJobWorker(int render_process_id,
90                               int render_view_id,
91                               PrintJobWorkerOwner* owner)
92    : owner_(owner), thread_("Printing_Worker"), weak_factory_(this) {
93  // The object is created in the IO thread.
94  DCHECK(owner_->RunsTasksOnCurrentThread());
95
96  printing_context_delegate_.reset(
97      new PrintingContextDelegate(render_process_id, render_view_id));
98  printing_context_ = PrintingContext::Create(printing_context_delegate_.get());
99}
100
101PrintJobWorker::~PrintJobWorker() {
102  // The object is normally deleted in the UI thread, but when the user
103  // cancels printing or in the case of print preview, the worker is destroyed
104  // on the I/O thread.
105  DCHECK(owner_->RunsTasksOnCurrentThread());
106  Stop();
107}
108
109void PrintJobWorker::SetNewOwner(PrintJobWorkerOwner* new_owner) {
110  DCHECK(page_number_ == PageNumber::npos());
111  owner_ = new_owner;
112}
113
114void PrintJobWorker::GetSettings(
115    bool ask_user_for_settings,
116    int document_page_count,
117    bool has_selection,
118    MarginType margin_type) {
119  DCHECK(task_runner_->RunsTasksOnCurrentThread());
120  DCHECK_EQ(page_number_, PageNumber::npos());
121
122  // Recursive task processing is needed for the dialog in case it needs to be
123  // destroyed by a task.
124  // TODO(thestig): This code is wrong. SetNestableTasksAllowed(true) is needed
125  // on the thread where the PrintDlgEx is called, and definitely both calls
126  // should happen on the same thread. See http://crbug.com/73466
127  // MessageLoop::current()->SetNestableTasksAllowed(true);
128  printing_context_->set_margin_type(margin_type);
129
130  // When we delegate to a destination, we don't ask the user for settings.
131  // TODO(mad): Ask the destination for settings.
132  if (ask_user_for_settings) {
133    BrowserThread::PostTask(
134        BrowserThread::UI, FROM_HERE,
135        base::Bind(&HoldRefCallback, make_scoped_refptr(owner_),
136                   base::Bind(&PrintJobWorker::GetSettingsWithUI,
137                              base::Unretained(this),
138                              document_page_count,
139                              has_selection)));
140  } else {
141    BrowserThread::PostTask(
142        BrowserThread::UI, FROM_HERE,
143        base::Bind(&HoldRefCallback, make_scoped_refptr(owner_),
144                   base::Bind(&PrintJobWorker::UseDefaultSettings,
145                              base::Unretained(this))));
146  }
147}
148
149void PrintJobWorker::SetSettings(
150    scoped_ptr<base::DictionaryValue> new_settings) {
151  DCHECK(task_runner_->RunsTasksOnCurrentThread());
152
153  BrowserThread::PostTask(
154      BrowserThread::UI,
155      FROM_HERE,
156      base::Bind(&HoldRefCallback,
157                 make_scoped_refptr(owner_),
158                 base::Bind(&PrintJobWorker::UpdatePrintSettings,
159                            base::Unretained(this),
160                            base::Passed(&new_settings))));
161}
162
163void PrintJobWorker::UpdatePrintSettings(
164    scoped_ptr<base::DictionaryValue> new_settings) {
165  DCHECK_CURRENTLY_ON(BrowserThread::UI);
166  PrintingContext::Result result =
167      printing_context_->UpdatePrintSettings(*new_settings);
168  GetSettingsDone(result);
169}
170
171void PrintJobWorker::GetSettingsDone(PrintingContext::Result result) {
172  // Most PrintingContext functions may start a message loop and process
173  // message recursively, so disable recursive task processing.
174  // TODO(thestig): See above comment. SetNestableTasksAllowed(false) needs to
175  // be called on the same thread as the previous call.  See
176  // http://crbug.com/73466
177  // MessageLoop::current()->SetNestableTasksAllowed(false);
178
179  // We can't use OnFailure() here since owner_ may not support notifications.
180
181  // PrintJob will create the new PrintedDocument.
182  owner_->PostTask(FROM_HERE,
183                   base::Bind(&PrintJobWorkerOwner::GetSettingsDone,
184                              make_scoped_refptr(owner_),
185                              printing_context_->settings(),
186                              result));
187}
188
189void PrintJobWorker::GetSettingsWithUI(
190    int document_page_count,
191    bool has_selection) {
192  DCHECK_CURRENTLY_ON(BrowserThread::UI);
193  printing_context_->AskUserForSettings(
194      document_page_count,
195      has_selection,
196      base::Bind(&PrintJobWorker::GetSettingsWithUIDone,
197                 base::Unretained(this)));
198}
199
200void PrintJobWorker::GetSettingsWithUIDone(PrintingContext::Result result) {
201  PostTask(FROM_HERE,
202           base::Bind(&HoldRefCallback,
203                      make_scoped_refptr(owner_),
204                      base::Bind(&PrintJobWorker::GetSettingsDone,
205                                 base::Unretained(this),
206                                 result)));
207}
208
209void PrintJobWorker::UseDefaultSettings() {
210  PrintingContext::Result result = printing_context_->UseDefaultSettings();
211  GetSettingsDone(result);
212}
213
214void PrintJobWorker::StartPrinting(PrintedDocument* new_document) {
215  DCHECK(task_runner_->RunsTasksOnCurrentThread());
216  DCHECK_EQ(page_number_, PageNumber::npos());
217  DCHECK_EQ(document_.get(), new_document);
218  DCHECK(document_.get());
219
220  if (!document_.get() || page_number_ != PageNumber::npos() ||
221      document_.get() != new_document) {
222    return;
223  }
224
225  base::string16 document_name =
226      printing::SimplifyDocumentTitle(document_->name());
227  if (document_name.empty()) {
228    document_name = printing::SimplifyDocumentTitle(
229        l10n_util::GetStringUTF16(IDS_DEFAULT_PRINT_DOCUMENT_TITLE));
230  }
231  PrintingContext::Result result =
232      printing_context_->NewDocument(document_name);
233  if (result != PrintingContext::OK) {
234    OnFailure();
235    return;
236  }
237
238  // Try to print already cached data. It may already have been generated for
239  // the print preview.
240  OnNewPage();
241  // Don't touch this anymore since the instance could be destroyed. It happens
242  // if all the pages are printed a one sweep and the client doesn't have a
243  // handle to us anymore. There's a timing issue involved between the worker
244  // thread and the UI thread. Take no chance.
245}
246
247void PrintJobWorker::OnDocumentChanged(PrintedDocument* new_document) {
248  DCHECK(task_runner_->RunsTasksOnCurrentThread());
249  DCHECK_EQ(page_number_, PageNumber::npos());
250
251  if (page_number_ != PageNumber::npos())
252    return;
253
254  document_ = new_document;
255}
256
257void PrintJobWorker::OnNewPage() {
258  if (!document_.get())  // Spurious message.
259    return;
260
261  // message_loop() could return NULL when the print job is cancelled.
262  DCHECK(task_runner_->RunsTasksOnCurrentThread());
263
264  if (page_number_ == PageNumber::npos()) {
265    // Find first page to print.
266    int page_count = document_->page_count();
267    if (!page_count) {
268      // We still don't know how many pages the document contains. We can't
269      // start to print the document yet since the header/footer may refer to
270      // the document's page count.
271      return;
272    }
273    // We have enough information to initialize page_number_.
274    page_number_.Init(document_->settings(), page_count);
275  }
276  DCHECK_NE(page_number_, PageNumber::npos());
277
278  while (true) {
279    // Is the page available?
280    scoped_refptr<PrintedPage> page = document_->GetPage(page_number_.ToInt());
281    if (!page.get()) {
282      // We need to wait for the page to be available.
283      base::MessageLoop::current()->PostDelayedTask(
284          FROM_HERE,
285          base::Bind(&PrintJobWorker::OnNewPage, weak_factory_.GetWeakPtr()),
286          base::TimeDelta::FromMilliseconds(500));
287      break;
288    }
289    // The page is there, print it.
290    SpoolPage(page.get());
291    ++page_number_;
292    if (page_number_ == PageNumber::npos()) {
293      OnDocumentDone();
294      // Don't touch this anymore since the instance could be destroyed.
295      break;
296    }
297  }
298}
299
300void PrintJobWorker::Cancel() {
301  // This is the only function that can be called from any thread.
302  printing_context_->Cancel();
303  // Cannot touch any member variable since we don't know in which thread
304  // context we run.
305}
306
307bool PrintJobWorker::IsRunning() const {
308  return thread_.IsRunning();
309}
310
311bool PrintJobWorker::PostTask(const tracked_objects::Location& from_here,
312                              const base::Closure& task) {
313  if (task_runner_.get())
314    return task_runner_->PostTask(from_here, task);
315  return false;
316}
317
318void PrintJobWorker::StopSoon() {
319  thread_.StopSoon();
320}
321
322void PrintJobWorker::Stop() {
323  thread_.Stop();
324}
325
326bool PrintJobWorker::Start() {
327  bool result = thread_.Start();
328  task_runner_ = thread_.task_runner();
329  return result;
330}
331
332void PrintJobWorker::OnDocumentDone() {
333  DCHECK(task_runner_->RunsTasksOnCurrentThread());
334  DCHECK_EQ(page_number_, PageNumber::npos());
335  DCHECK(document_.get());
336
337  if (printing_context_->DocumentDone() != PrintingContext::OK) {
338    OnFailure();
339    return;
340  }
341
342  owner_->PostTask(FROM_HERE,
343                   base::Bind(&NotificationCallback,
344                              make_scoped_refptr(owner_),
345                              JobEventDetails::DOC_DONE,
346                              document_,
347                              scoped_refptr<PrintedPage>()));
348
349  // Makes sure the variables are reinitialized.
350  document_ = NULL;
351}
352
353void PrintJobWorker::SpoolPage(PrintedPage* page) {
354  DCHECK(task_runner_->RunsTasksOnCurrentThread());
355  DCHECK_NE(page_number_, PageNumber::npos());
356
357  // Signal everyone that the page is about to be printed.
358  owner_->PostTask(FROM_HERE,
359                   base::Bind(&NotificationCallback,
360                              make_scoped_refptr(owner_),
361                              JobEventDetails::NEW_PAGE,
362                              document_,
363                              make_scoped_refptr(page)));
364
365  // Preprocess.
366  if (printing_context_->NewPage() != PrintingContext::OK) {
367    OnFailure();
368    return;
369  }
370
371  // Actual printing.
372#if defined(OS_WIN) || defined(OS_MACOSX)
373  document_->RenderPrintedPage(*page, printing_context_->context());
374#elif defined(OS_POSIX)
375  document_->RenderPrintedPage(*page, printing_context_.get());
376#endif
377
378  // Postprocess.
379  if (printing_context_->PageDone() != PrintingContext::OK) {
380    OnFailure();
381    return;
382  }
383
384  // Signal everyone that the page is printed.
385  owner_->PostTask(FROM_HERE,
386                   base::Bind(&NotificationCallback,
387                              make_scoped_refptr(owner_),
388                              JobEventDetails::PAGE_DONE,
389                              document_,
390                              make_scoped_refptr(page)));
391}
392
393void PrintJobWorker::OnFailure() {
394  DCHECK(task_runner_->RunsTasksOnCurrentThread());
395
396  // We may loose our last reference by broadcasting the FAILED event.
397  scoped_refptr<PrintJobWorkerOwner> handle(owner_);
398
399  owner_->PostTask(FROM_HERE,
400                   base::Bind(&NotificationCallback,
401                              make_scoped_refptr(owner_),
402                              JobEventDetails::FAILED,
403                              document_,
404                              scoped_refptr<PrintedPage>()));
405  Cancel();
406
407  // Makes sure the variables are reinitialized.
408  document_ = NULL;
409  page_number_ = PageNumber::npos();
410}
411
412}  // namespace printing
413