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
27 using content::BrowserThread;
28
29 namespace printing {
30
31 namespace {
32
33 // Helper function to ensure |owner| is valid until at least |callback| returns.
HoldRefCallback(const scoped_refptr<printing::PrintJobWorkerOwner> & owner,const base::Closure & callback)34 void HoldRefCallback(const scoped_refptr<printing::PrintJobWorkerOwner>& owner,
35 const base::Closure& callback) {
36 callback.Run();
37 }
38
39 class 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
PrintingContextDelegate(int render_process_id,int render_view_id)52 PrintingContextDelegate::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
~PrintingContextDelegate()58 PrintingContextDelegate::~PrintingContextDelegate() {
59 }
60
GetParentView()61 gfx::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
GetAppLocale()71 std::string PrintingContextDelegate::GetAppLocale() {
72 return g_browser_process->GetApplicationLocale();
73 }
74
NotificationCallback(PrintJobWorkerOwner * print_job,JobEventDetails::Type detail_type,PrintedDocument * document,PrintedPage * page)75 void 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
PrintJobWorker(int render_process_id,int render_view_id,PrintJobWorkerOwner * owner)89 PrintJobWorker::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
~PrintJobWorker()101 PrintJobWorker::~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
SetNewOwner(PrintJobWorkerOwner * new_owner)109 void PrintJobWorker::SetNewOwner(PrintJobWorkerOwner* new_owner) {
110 DCHECK(page_number_ == PageNumber::npos());
111 owner_ = new_owner;
112 }
113
GetSettings(bool ask_user_for_settings,int document_page_count,bool has_selection,MarginType margin_type)114 void 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
SetSettings(scoped_ptr<base::DictionaryValue> new_settings)149 void 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
UpdatePrintSettings(scoped_ptr<base::DictionaryValue> new_settings)163 void 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
GetSettingsDone(PrintingContext::Result result)171 void 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
GetSettingsWithUI(int document_page_count,bool has_selection)189 void 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
GetSettingsWithUIDone(PrintingContext::Result result)200 void 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
UseDefaultSettings()209 void PrintJobWorker::UseDefaultSettings() {
210 PrintingContext::Result result = printing_context_->UseDefaultSettings();
211 GetSettingsDone(result);
212 }
213
StartPrinting(PrintedDocument * new_document)214 void 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
OnDocumentChanged(PrintedDocument * new_document)247 void 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
OnNewPage()257 void 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
Cancel()300 void 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
IsRunning() const307 bool PrintJobWorker::IsRunning() const {
308 return thread_.IsRunning();
309 }
310
PostTask(const tracked_objects::Location & from_here,const base::Closure & task)311 bool 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
StopSoon()318 void PrintJobWorker::StopSoon() {
319 thread_.StopSoon();
320 }
321
Stop()322 void PrintJobWorker::Stop() {
323 thread_.Stop();
324 }
325
Start()326 bool PrintJobWorker::Start() {
327 bool result = thread_.Start();
328 task_runner_ = thread_.task_runner();
329 return result;
330 }
331
OnDocumentDone()332 void 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
SpoolPage(PrintedPage * page)353 void 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
OnFailure()393 void 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