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