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.
5 #include "chrome/browser/printing/print_job_worker.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"
27 using content::BrowserThread
;
33 // Helper function to ensure |owner| is valid until at least |callback| returns.
34 void HoldRefCallback(const scoped_refptr
<printing::PrintJobWorkerOwner
>& owner
,
35 const base::Closure
& callback
) {
39 class PrintingContextDelegate
: public PrintingContext::Delegate
{
41 PrintingContextDelegate(int render_process_id
, int render_view_id
);
42 ~PrintingContextDelegate() override
;
44 gfx::NativeView
GetParentView() override
;
45 std::string
GetAppLocale() override
;
48 int render_process_id_
;
52 PrintingContextDelegate::PrintingContextDelegate(int render_process_id
,
54 : render_process_id_(render_process_id
),
55 render_view_id_(render_view_id
) {
58 PrintingContextDelegate::~PrintingContextDelegate() {
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_
);
67 content::WebContents
* wc
= content::WebContents::FromRenderViewHost(view
);
68 return wc
? wc
->GetNativeView() : NULL
;
71 std::string
PrintingContextDelegate::GetAppLocale() {
72 return g_browser_process
->GetApplicationLocale();
75 void NotificationCallback(PrintJobWorkerOwner
* print_job
,
76 JobEventDetails::Type detail_type
,
77 PrintedDocument
* document
,
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
));
89 PrintJobWorker::PrintJobWorker(int render_process_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());
96 printing_context_delegate_
.reset(
97 new PrintingContextDelegate(render_process_id
, render_view_id
));
98 printing_context_
= PrintingContext::Create(printing_context_delegate_
.get());
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());
109 void PrintJobWorker::SetNewOwner(PrintJobWorkerOwner
* new_owner
) {
110 DCHECK(page_number_
== PageNumber::npos());
114 void PrintJobWorker::GetSettings(
115 bool ask_user_for_settings
,
116 int document_page_count
,
118 MarginType margin_type
) {
119 DCHECK(task_runner_
->RunsTasksOnCurrentThread());
120 DCHECK_EQ(page_number_
, PageNumber::npos());
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
);
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),
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))));
149 void PrintJobWorker::SetSettings(
150 scoped_ptr
<base::DictionaryValue
> new_settings
) {
151 DCHECK(task_runner_
->RunsTasksOnCurrentThread());
153 BrowserThread::PostTask(
156 base::Bind(&HoldRefCallback
,
157 make_scoped_refptr(owner_
),
158 base::Bind(&PrintJobWorker::UpdatePrintSettings
,
159 base::Unretained(this),
160 base::Passed(&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
);
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);
179 // We can't use OnFailure() here since owner_ may not support notifications.
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(),
189 void PrintJobWorker::GetSettingsWithUI(
190 int document_page_count
,
191 bool has_selection
) {
192 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
193 printing_context_
->AskUserForSettings(
196 base::Bind(&PrintJobWorker::GetSettingsWithUIDone
,
197 base::Unretained(this)));
200 void PrintJobWorker::GetSettingsWithUIDone(PrintingContext::Result result
) {
202 base::Bind(&HoldRefCallback
,
203 make_scoped_refptr(owner_
),
204 base::Bind(&PrintJobWorker::GetSettingsDone
,
205 base::Unretained(this),
209 void PrintJobWorker::UseDefaultSettings() {
210 PrintingContext::Result result
= printing_context_
->UseDefaultSettings();
211 GetSettingsDone(result
);
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());
220 if (!document_
.get() || page_number_
!= PageNumber::npos() ||
221 document_
.get() != new_document
) {
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
));
231 PrintingContext::Result result
=
232 printing_context_
->NewDocument(document_name
);
233 if (result
!= PrintingContext::OK
) {
238 // Try to print already cached data. It may already have been generated for
239 // the print preview.
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.
247 void PrintJobWorker::OnDocumentChanged(PrintedDocument
* new_document
) {
248 DCHECK(task_runner_
->RunsTasksOnCurrentThread());
249 DCHECK_EQ(page_number_
, PageNumber::npos());
251 if (page_number_
!= PageNumber::npos())
254 document_
= new_document
;
257 void PrintJobWorker::OnNewPage() {
258 if (!document_
.get()) // Spurious message.
261 // message_loop() could return NULL when the print job is cancelled.
262 DCHECK(task_runner_
->RunsTasksOnCurrentThread());
264 if (page_number_
== PageNumber::npos()) {
265 // Find first page to print.
266 int page_count
= document_
->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.
273 // We have enough information to initialize page_number_.
274 page_number_
.Init(document_
->settings(), page_count
);
276 DCHECK_NE(page_number_
, PageNumber::npos());
279 // Is the page available?
280 scoped_refptr
<PrintedPage
> page
= document_
->GetPage(page_number_
.ToInt());
282 // We need to wait for the page to be available.
283 base::MessageLoop::current()->PostDelayedTask(
285 base::Bind(&PrintJobWorker::OnNewPage
, weak_factory_
.GetWeakPtr()),
286 base::TimeDelta::FromMilliseconds(500));
289 // The page is there, print it.
290 SpoolPage(page
.get());
292 if (page_number_
== PageNumber::npos()) {
294 // Don't touch this anymore since the instance could be destroyed.
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
307 bool PrintJobWorker::IsRunning() const {
308 return thread_
.IsRunning();
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
);
318 void PrintJobWorker::StopSoon() {
322 void PrintJobWorker::Stop() {
326 bool PrintJobWorker::Start() {
327 bool result
= thread_
.Start();
328 task_runner_
= thread_
.task_runner();
332 void PrintJobWorker::OnDocumentDone() {
333 DCHECK(task_runner_
->RunsTasksOnCurrentThread());
334 DCHECK_EQ(page_number_
, PageNumber::npos());
335 DCHECK(document_
.get());
337 if (printing_context_
->DocumentDone() != PrintingContext::OK
) {
342 owner_
->PostTask(FROM_HERE
,
343 base::Bind(&NotificationCallback
,
344 make_scoped_refptr(owner_
),
345 JobEventDetails::DOC_DONE
,
347 scoped_refptr
<PrintedPage
>()));
349 // Makes sure the variables are reinitialized.
353 void PrintJobWorker::SpoolPage(PrintedPage
* page
) {
354 DCHECK(task_runner_
->RunsTasksOnCurrentThread());
355 DCHECK_NE(page_number_
, PageNumber::npos());
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
,
363 make_scoped_refptr(page
)));
366 if (printing_context_
->NewPage() != PrintingContext::OK
) {
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());
379 if (printing_context_
->PageDone() != PrintingContext::OK
) {
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
,
390 make_scoped_refptr(page
)));
393 void PrintJobWorker::OnFailure() {
394 DCHECK(task_runner_
->RunsTasksOnCurrentThread());
396 // We may loose our last reference by broadcasting the FAILED event.
397 scoped_refptr
<PrintJobWorkerOwner
> handle(owner_
);
399 owner_
->PostTask(FROM_HERE
,
400 base::Bind(&NotificationCallback
,
401 make_scoped_refptr(owner_
),
402 JobEventDetails::FAILED
,
404 scoped_refptr
<PrintedPage
>()));
407 // Makes sure the variables are reinitialized.
409 page_number_
= PageNumber::npos();
412 } // namespace printing