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.h"
12 #include "base/values.h"
13 #include "chrome/browser/browser_process.h"
14 #include "chrome/browser/printing/print_job.h"
15 #include "chrome/common/chrome_notification_types.h"
16 #include "content/public/browser/browser_thread.h"
17 #include "content/public/browser/notification_service.h"
18 #include "grit/generated_resources.h"
19 #include "printing/backend/print_backend.h"
20 #include "printing/print_job_constants.h"
21 #include "printing/printed_document.h"
22 #include "printing/printed_page.h"
23 #include "ui/base/l10n/l10n_util.h"
25 using content::BrowserThread
;
29 // Helper function to ensure |owner| is valid until at least |callback| returns.
30 void HoldRefCallback(const scoped_refptr
<printing::PrintJobWorkerOwner
>& owner
,
31 const base::Closure
& callback
) {
39 void NotificationCallback(PrintJobWorkerOwner
* print_job
,
40 JobEventDetails::Type detail_type
,
41 PrintedDocument
* document
,
43 JobEventDetails
* details
= new JobEventDetails(detail_type
, document
, page
);
44 content::NotificationService::current()->Notify(
45 chrome::NOTIFICATION_PRINT_JOB_EVENT
,
46 // We know that is is a PrintJob object in this circumstance.
47 content::Source
<PrintJob
>(static_cast<PrintJob
*>(print_job
)),
48 content::Details
<JobEventDetails
>(details
));
51 PrintJobWorker::PrintJobWorker(PrintJobWorkerOwner
* owner
)
52 : Thread("Printing_Worker"),
54 ALLOW_THIS_IN_INITIALIZER_LIST(weak_factory_(this)) {
55 // The object is created in the IO thread.
56 DCHECK_EQ(owner_
->message_loop(), MessageLoop::current());
58 printing_context_
.reset(PrintingContext::Create(
59 g_browser_process
->GetApplicationLocale()));
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
66 DCHECK_EQ(owner_
->message_loop(), MessageLoop::current());
70 void PrintJobWorker::SetNewOwner(PrintJobWorkerOwner
* new_owner
) {
71 DCHECK(page_number_
== PageNumber::npos());
75 void PrintJobWorker::SetPrintDestination(
76 PrintDestinationInterface
* destination
) {
77 destination_
= destination
;
80 void PrintJobWorker::GetSettings(bool ask_user_for_settings
,
81 gfx::NativeView parent_view
,
82 int document_page_count
,
84 MarginType margin_type
) {
85 DCHECK_EQ(message_loop(), MessageLoop::current());
86 DCHECK_EQ(page_number_
, PageNumber::npos());
88 // Recursive task processing is needed for the dialog in case it needs to be
89 // destroyed by a task.
90 // TODO(thestig): This code is wrong. SetNestableTasksAllowed(true) is needed
91 // on the thread where the PrintDlgEx is called, and definitely both calls
92 // should happen on the same thread. See http://crbug.com/73466
93 // MessageLoop::current()->SetNestableTasksAllowed(true);
94 printing_context_
->set_margin_type(margin_type
);
96 // When we delegate to a destination, we don't ask the user for settings.
97 // TODO(mad): Ask the destination for settings.
98 if (ask_user_for_settings
&& destination_
.get() == NULL
) {
99 BrowserThread::PostTask(
100 BrowserThread::UI
, FROM_HERE
,
101 base::Bind(&HoldRefCallback
, make_scoped_refptr(owner_
),
102 base::Bind(&PrintJobWorker::GetSettingsWithUI
,
103 base::Unretained(this), parent_view
,
104 document_page_count
, has_selection
)));
106 BrowserThread::PostTask(
107 BrowserThread::UI
, FROM_HERE
,
108 base::Bind(&HoldRefCallback
, make_scoped_refptr(owner_
),
109 base::Bind(&PrintJobWorker::UseDefaultSettings
,
110 base::Unretained(this))));
114 void PrintJobWorker::SetSettings(const DictionaryValue
* const new_settings
) {
115 DCHECK_EQ(message_loop(), MessageLoop::current());
117 BrowserThread::PostTask(
118 BrowserThread::UI
, FROM_HERE
,
119 base::Bind(&HoldRefCallback
, make_scoped_refptr(owner_
),
120 base::Bind(&PrintJobWorker::UpdatePrintSettings
,
121 base::Unretained(this), new_settings
)));
124 void PrintJobWorker::UpdatePrintSettings(
125 const DictionaryValue
* const new_settings
) {
126 // Create new PageRanges based on |new_settings|.
127 PageRanges new_ranges
;
128 const ListValue
* page_range_array
;
129 if (new_settings
->GetList(kSettingPageRange
, &page_range_array
)) {
130 for (size_t index
= 0; index
< page_range_array
->GetSize(); ++index
) {
131 const DictionaryValue
* dict
;
132 if (!page_range_array
->GetDictionary(index
, &dict
))
136 if (!dict
->GetInteger(kSettingPageRangeFrom
, &range
.from
) ||
137 !dict
->GetInteger(kSettingPageRangeTo
, &range
.to
)) {
141 // Page numbers are 1-based in the dictionary.
142 // Page numbers are 0-based for the printing context.
145 new_ranges
.push_back(range
);
148 PrintingContext::Result result
=
149 printing_context_
->UpdatePrintSettings(*new_settings
, new_ranges
);
151 GetSettingsDone(result
);
154 void PrintJobWorker::GetSettingsDone(PrintingContext::Result result
) {
155 // Most PrintingContext functions may start a message loop and process
156 // message recursively, so disable recursive task processing.
157 // TODO(thestig): See above comment. SetNestableTasksAllowed(false) needs to
158 // be called on the same thread as the previous call. See
159 // http://crbug.com/73466
160 // MessageLoop::current()->SetNestableTasksAllowed(false);
162 // We can't use OnFailure() here since owner_ may not support notifications.
164 // PrintJob will create the new PrintedDocument.
165 owner_
->message_loop()->PostTask(
167 base::Bind(&PrintJobWorkerOwner::GetSettingsDone
,
168 make_scoped_refptr(owner_
), printing_context_
->settings(),
172 void PrintJobWorker::GetSettingsWithUI(gfx::NativeView parent_view
,
173 int document_page_count
,
174 bool has_selection
) {
175 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
177 printing_context_
->AskUserForSettings(
178 parent_view
, document_page_count
, has_selection
,
179 base::Bind(&PrintJobWorker::GetSettingsWithUIDone
,
180 base::Unretained(this)));
183 void PrintJobWorker::GetSettingsWithUIDone(PrintingContext::Result result
) {
184 message_loop()->PostTask(
186 base::Bind(&HoldRefCallback
, make_scoped_refptr(owner_
),
187 base::Bind(&PrintJobWorker::GetSettingsDone
,
188 base::Unretained(this), result
)));
191 void PrintJobWorker::UseDefaultSettings() {
192 PrintingContext::Result result
= printing_context_
->UseDefaultSettings();
193 GetSettingsDone(result
);
196 void PrintJobWorker::StartPrinting(PrintedDocument
* new_document
) {
197 DCHECK_EQ(message_loop(), MessageLoop::current());
198 DCHECK_EQ(page_number_
, PageNumber::npos());
199 DCHECK_EQ(document_
, new_document
);
200 DCHECK(document_
.get());
201 DCHECK(new_document
->settings().Equals(printing_context_
->settings()));
203 if (!document_
.get() || page_number_
!= PageNumber::npos() ||
204 document_
!= new_document
) {
208 string16 document_name
=
209 printing::PrintBackend::SimplifyDocumentTitle(document_
->name());
210 if (document_name
.empty()) {
211 document_name
= printing::PrintBackend::SimplifyDocumentTitle(
212 l10n_util::GetStringUTF16(IDS_DEFAULT_PRINT_DOCUMENT_TITLE
));
214 PrintingContext::Result result
=
215 printing_context_
->NewDocument(document_name
);
216 if (result
!= PrintingContext::OK
) {
221 // Try to print already cached data. It may already have been generated for
222 // the print preview.
224 // Don't touch this anymore since the instance could be destroyed. It happens
225 // if all the pages are printed a one sweep and the client doesn't have a
226 // handle to us anymore. There's a timing issue involved between the worker
227 // thread and the UI thread. Take no chance.
230 void PrintJobWorker::OnDocumentChanged(PrintedDocument
* new_document
) {
231 DCHECK_EQ(message_loop(), MessageLoop::current());
232 DCHECK_EQ(page_number_
, PageNumber::npos());
233 DCHECK(!new_document
||
234 new_document
->settings().Equals(printing_context_
->settings()));
236 if (page_number_
!= PageNumber::npos())
239 document_
= new_document
;
242 void PrintJobWorker::OnNewPage() {
243 if (!document_
.get()) // Spurious message.
246 // message_loop() could return NULL when the print job is cancelled.
247 DCHECK_EQ(message_loop(), MessageLoop::current());
249 if (page_number_
== PageNumber::npos()) {
250 // Find first page to print.
251 int page_count
= document_
->page_count();
253 // We still don't know how many pages the document contains. We can't
254 // start to print the document yet since the header/footer may refer to
255 // the document's page count.
258 // We have enough information to initialize page_number_.
259 page_number_
.Init(document_
->settings(), page_count
);
260 if (destination_
.get() != NULL
)
261 destination_
->SetPageCount(page_count
);
263 DCHECK_NE(page_number_
, PageNumber::npos());
266 // Is the page available?
267 scoped_refptr
<PrintedPage
> page
;
268 if (!document_
->GetPage(page_number_
.ToInt(), &page
)) {
269 // We need to wait for the page to be available.
270 MessageLoop::current()->PostDelayedTask(
272 base::Bind(&PrintJobWorker::OnNewPage
, weak_factory_
.GetWeakPtr()),
273 base::TimeDelta::FromMilliseconds(500));
276 // The page is there, print it.
279 if (page_number_
== PageNumber::npos()) {
281 // Don't touch this anymore since the instance could be destroyed.
287 void PrintJobWorker::Cancel() {
288 // This is the only function that can be called from any thread.
289 printing_context_
->Cancel();
290 // Cannot touch any member variable since we don't know in which thread
294 void PrintJobWorker::OnDocumentDone() {
295 DCHECK_EQ(message_loop(), MessageLoop::current());
296 DCHECK_EQ(page_number_
, PageNumber::npos());
297 DCHECK(document_
.get());
299 if (printing_context_
->DocumentDone() != PrintingContext::OK
) {
304 owner_
->message_loop()->PostTask(
305 FROM_HERE
, base::Bind(NotificationCallback
, make_scoped_refptr(owner_
),
306 JobEventDetails::DOC_DONE
, document_
,
307 scoped_refptr
<PrintedPage
>()));
309 // Makes sure the variables are reinitialized.
313 void PrintJobWorker::SpoolPage(PrintedPage
* page
) {
314 DCHECK_EQ(message_loop(), MessageLoop::current());
315 DCHECK_NE(page_number_
, PageNumber::npos());
317 // Signal everyone that the page is about to be printed.
318 owner_
->message_loop()->PostTask(
319 FROM_HERE
, base::Bind(NotificationCallback
, make_scoped_refptr(owner_
),
320 JobEventDetails::NEW_PAGE
, document_
,
321 make_scoped_refptr(page
)));
324 if (printing_context_
->NewPage() != PrintingContext::OK
) {
329 if (destination_
.get() != NULL
) {
330 std::vector
<uint8
> metabytes(page
->metafile()->GetDataSize());
331 bool success
= page
->metafile()->GetData(
332 reinterpret_cast<void*>(&metabytes
[0]), metabytes
.size());
333 DCHECK(success
) << "Failed to get metafile data.";
334 destination_
->SetPageContent(
336 reinterpret_cast<void*>(&metabytes
[0]),
342 #if defined(OS_WIN) || defined(OS_MACOSX)
343 document_
->RenderPrintedPage(*page
, printing_context_
->context());
344 #elif defined(OS_POSIX)
345 document_
->RenderPrintedPage(*page
, printing_context_
.get());
349 if (printing_context_
->PageDone() != PrintingContext::OK
) {
354 // Signal everyone that the page is printed.
355 owner_
->message_loop()->PostTask(
357 base::Bind(NotificationCallback
, make_scoped_refptr(owner_
),
358 JobEventDetails::PAGE_DONE
, document_
,
359 make_scoped_refptr(page
)));
362 void PrintJobWorker::OnFailure() {
363 DCHECK_EQ(message_loop(), MessageLoop::current());
365 // We may loose our last reference by broadcasting the FAILED event.
366 scoped_refptr
<PrintJobWorkerOwner
> handle(owner_
);
368 owner_
->message_loop()->PostTask(
369 FROM_HERE
, base::Bind(NotificationCallback
, make_scoped_refptr(owner_
),
370 JobEventDetails::FAILED
, document_
,
371 scoped_refptr
<PrintedPage
>()));
374 // Makes sure the variables are reinitialized.
376 page_number_
= PageNumber::npos();
379 } // namespace printing