• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
16 namespace printing {
17 
18 class PrintJobWorker::NotificationTask : public Task {
19  public:
NotificationTask()20   NotificationTask() : print_job_(NULL), details_(NULL) {
21   }
~NotificationTask()22   ~NotificationTask() {
23   }
24 
25   // Initializes the object. This object can't be initialized in the constructor
26   // since it is not created directly.
Init(PrintJobWorkerOwner * print_job,JobEventDetails::Type detail_type,PrintedDocument * document,PrintedPage * page)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 
Run()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 
PrintJobWorker(PrintJobWorkerOwner * owner)52 PrintJobWorker::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 
~PrintJobWorker()62 PrintJobWorker::~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 
SetNewOwner(PrintJobWorkerOwner * new_owner)69 void PrintJobWorker::SetNewOwner(PrintJobWorkerOwner* new_owner) {
70   DCHECK(page_number_ == PageNumber::npos());
71   owner_ = new_owner;
72 }
73 
GetSettings(bool ask_user_for_settings,gfx::NativeView parent_view,int document_page_count,bool has_selection,bool use_overlays)74 void 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 
SetSettings(const DictionaryValue * const new_settings)102 void 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 
UpdatePrintSettings(const DictionaryValue * const new_settings)110 void 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 
GetSettingsDone(PrintingContext::Result result)136 void 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 
GetSettingsWithUI(gfx::NativeView parent_view,int document_page_count,bool has_selection)154 void 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 
GetSettingsWithUIDone(PrintingContext::Result result)166 void PrintJobWorker::GetSettingsWithUIDone(PrintingContext::Result result) {
167   message_loop()->PostTask(FROM_HERE, NewRunnableMethod(
168       this, &PrintJobWorker::GetSettingsDone, result));
169 }
170 
UseDefaultSettings()171 void PrintJobWorker::UseDefaultSettings() {
172   PrintingContext::Result result = printing_context_->UseDefaultSettings();
173   GetSettingsDone(result);
174 }
175 
StartPrinting(PrintedDocument * new_document)176 void 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 
OnDocumentChanged(PrintedDocument * new_document)204 void 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 
OnNewPage()216 void 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 
Cancel()260 void 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 
OnDocumentDone()267 void 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 
SpoolPage(PrintedPage & page)289 void 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 
OnFailure()329 void 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 
RetainCallee(printing::PrintJobWorker * obj)350 void RunnableMethodTraits<printing::PrintJobWorker>::RetainCallee(
351     printing::PrintJobWorker* obj) {
352   DCHECK(!owner_.get());
353   owner_ = obj->owner_;
354 }
355 
ReleaseCallee(printing::PrintJobWorker * obj)356 void RunnableMethodTraits<printing::PrintJobWorker>::ReleaseCallee(
357     printing::PrintJobWorker* obj) {
358   DCHECK_EQ(owner_, obj->owner_);
359   owner_ = NULL;
360 }
361