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/browser/printing/printing_ui_web_contents_observer.h"
17 #include "content/public/browser/browser_thread.h"
18 #include "content/public/browser/notification_service.h"
19 #include "grit/generated_resources.h"
20 #include "printing/print_job_constants.h"
21 #include "printing/printed_document.h"
22 #include "printing/printed_page.h"
23 #include "printing/printing_utils.h"
24 #include "ui/base/l10n/l10n_util.h"
26 using content::BrowserThread
;
30 // Helper function to ensure |owner| is valid until at least |callback| returns.
31 void HoldRefCallback(const scoped_refptr
<printing::PrintJobWorkerOwner
>& owner
,
32 const base::Closure
& callback
) {
40 void NotificationCallback(PrintJobWorkerOwner
* print_job
,
41 JobEventDetails::Type detail_type
,
42 PrintedDocument
* document
,
44 JobEventDetails
* details
= new JobEventDetails(detail_type
, document
, page
);
45 content::NotificationService::current()->Notify(
46 chrome::NOTIFICATION_PRINT_JOB_EVENT
,
47 // We know that is is a PrintJob object in this circumstance.
48 content::Source
<PrintJob
>(static_cast<PrintJob
*>(print_job
)),
49 content::Details
<JobEventDetails
>(details
));
52 PrintJobWorker::PrintJobWorker(PrintJobWorkerOwner
* owner
)
53 : Thread("Printing_Worker"),
56 // The object is created in the IO thread.
57 DCHECK_EQ(owner_
->message_loop(), base::MessageLoop::current());
59 printing_context_
.reset(PrintingContext::Create(
60 g_browser_process
->GetApplicationLocale()));
63 PrintJobWorker::~PrintJobWorker() {
64 // The object is normally deleted in the UI thread, but when the user
65 // cancels printing or in the case of print preview, the worker is destroyed
67 DCHECK_EQ(owner_
->message_loop(), base::MessageLoop::current());
71 void PrintJobWorker::SetNewOwner(PrintJobWorkerOwner
* new_owner
) {
72 DCHECK(page_number_
== PageNumber::npos());
76 void PrintJobWorker::SetPrintDestination(
77 PrintDestinationInterface
* destination
) {
78 destination_
= destination
;
81 void PrintJobWorker::GetSettings(
82 bool ask_user_for_settings
,
83 scoped_ptr
<PrintingUIWebContentsObserver
> web_contents_observer
,
84 int document_page_count
,
86 MarginType margin_type
) {
87 DCHECK_EQ(message_loop(), base::MessageLoop::current());
88 DCHECK_EQ(page_number_
, PageNumber::npos());
90 // Recursive task processing is needed for the dialog in case it needs to be
91 // destroyed by a task.
92 // TODO(thestig): This code is wrong. SetNestableTasksAllowed(true) is needed
93 // on the thread where the PrintDlgEx is called, and definitely both calls
94 // should happen on the same thread. See http://crbug.com/73466
95 // MessageLoop::current()->SetNestableTasksAllowed(true);
96 printing_context_
->set_margin_type(margin_type
);
98 // When we delegate to a destination, we don't ask the user for settings.
99 // TODO(mad): Ask the destination for settings.
100 if (ask_user_for_settings
&& destination_
.get() == NULL
) {
101 BrowserThread::PostTask(
102 BrowserThread::UI
, FROM_HERE
,
103 base::Bind(&HoldRefCallback
, make_scoped_refptr(owner_
),
104 base::Bind(&PrintJobWorker::GetSettingsWithUI
,
105 base::Unretained(this),
106 base::Passed(&web_contents_observer
),
110 BrowserThread::PostTask(
111 BrowserThread::UI
, FROM_HERE
,
112 base::Bind(&HoldRefCallback
, make_scoped_refptr(owner_
),
113 base::Bind(&PrintJobWorker::UseDefaultSettings
,
114 base::Unretained(this))));
118 void PrintJobWorker::SetSettings(
119 const base::DictionaryValue
* const new_settings
) {
120 DCHECK_EQ(message_loop(), base::MessageLoop::current());
122 BrowserThread::PostTask(
123 BrowserThread::UI
, FROM_HERE
,
124 base::Bind(&HoldRefCallback
, make_scoped_refptr(owner_
),
125 base::Bind(&PrintJobWorker::UpdatePrintSettings
,
126 base::Unretained(this), new_settings
)));
129 void PrintJobWorker::UpdatePrintSettings(
130 const base::DictionaryValue
* const new_settings
) {
131 // Create new PageRanges based on |new_settings|.
132 PageRanges new_ranges
;
133 const base::ListValue
* page_range_array
;
134 if (new_settings
->GetList(kSettingPageRange
, &page_range_array
)) {
135 for (size_t index
= 0; index
< page_range_array
->GetSize(); ++index
) {
136 const base::DictionaryValue
* dict
;
137 if (!page_range_array
->GetDictionary(index
, &dict
))
141 if (!dict
->GetInteger(kSettingPageRangeFrom
, &range
.from
) ||
142 !dict
->GetInteger(kSettingPageRangeTo
, &range
.to
)) {
146 // Page numbers are 1-based in the dictionary.
147 // Page numbers are 0-based for the printing context.
150 new_ranges
.push_back(range
);
153 PrintingContext::Result result
=
154 printing_context_
->UpdatePrintSettings(*new_settings
, new_ranges
);
156 GetSettingsDone(result
);
159 void PrintJobWorker::GetSettingsDone(PrintingContext::Result result
) {
160 // Most PrintingContext functions may start a message loop and process
161 // message recursively, so disable recursive task processing.
162 // TODO(thestig): See above comment. SetNestableTasksAllowed(false) needs to
163 // be called on the same thread as the previous call. See
164 // http://crbug.com/73466
165 // MessageLoop::current()->SetNestableTasksAllowed(false);
167 // We can't use OnFailure() here since owner_ may not support notifications.
169 // PrintJob will create the new PrintedDocument.
170 owner_
->message_loop()->PostTask(
172 base::Bind(&PrintJobWorkerOwner::GetSettingsDone
,
173 make_scoped_refptr(owner_
), printing_context_
->settings(),
177 void PrintJobWorker::GetSettingsWithUI(
178 scoped_ptr
<PrintingUIWebContentsObserver
> web_contents_observer
,
179 int document_page_count
,
180 bool has_selection
) {
181 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
183 gfx::NativeView parent_view
= web_contents_observer
->GetParentView();
185 GetSettingsWithUIDone(printing::PrintingContext::FAILED
);
188 printing_context_
->AskUserForSettings(
189 parent_view
, document_page_count
, has_selection
,
190 base::Bind(&PrintJobWorker::GetSettingsWithUIDone
,
191 base::Unretained(this)));
194 void PrintJobWorker::GetSettingsWithUIDone(PrintingContext::Result result
) {
195 message_loop()->PostTask(
197 base::Bind(&HoldRefCallback
, make_scoped_refptr(owner_
),
198 base::Bind(&PrintJobWorker::GetSettingsDone
,
199 base::Unretained(this), result
)));
202 void PrintJobWorker::UseDefaultSettings() {
203 PrintingContext::Result result
= printing_context_
->UseDefaultSettings();
204 GetSettingsDone(result
);
207 void PrintJobWorker::StartPrinting(PrintedDocument
* new_document
) {
208 DCHECK_EQ(message_loop(), base::MessageLoop::current());
209 DCHECK_EQ(page_number_
, PageNumber::npos());
210 DCHECK_EQ(document_
, new_document
);
211 DCHECK(document_
.get());
213 if (!document_
.get() || page_number_
!= PageNumber::npos() ||
214 document_
.get() != new_document
) {
218 base::string16 document_name
=
219 printing::SimplifyDocumentTitle(document_
->name());
220 if (document_name
.empty()) {
221 document_name
= printing::SimplifyDocumentTitle(
222 l10n_util::GetStringUTF16(IDS_DEFAULT_PRINT_DOCUMENT_TITLE
));
224 PrintingContext::Result result
=
225 printing_context_
->NewDocument(document_name
);
226 if (result
!= PrintingContext::OK
) {
231 // Try to print already cached data. It may already have been generated for
232 // the print preview.
234 // Don't touch this anymore since the instance could be destroyed. It happens
235 // if all the pages are printed a one sweep and the client doesn't have a
236 // handle to us anymore. There's a timing issue involved between the worker
237 // thread and the UI thread. Take no chance.
240 void PrintJobWorker::OnDocumentChanged(PrintedDocument
* new_document
) {
241 DCHECK_EQ(message_loop(), base::MessageLoop::current());
242 DCHECK_EQ(page_number_
, PageNumber::npos());
244 if (page_number_
!= PageNumber::npos())
247 document_
= new_document
;
250 void PrintJobWorker::OnNewPage() {
251 if (!document_
.get()) // Spurious message.
254 // message_loop() could return NULL when the print job is cancelled.
255 DCHECK_EQ(message_loop(), base::MessageLoop::current());
257 if (page_number_
== PageNumber::npos()) {
258 // Find first page to print.
259 int page_count
= document_
->page_count();
261 // We still don't know how many pages the document contains. We can't
262 // start to print the document yet since the header/footer may refer to
263 // the document's page count.
266 // We have enough information to initialize page_number_.
267 page_number_
.Init(document_
->settings(), page_count
);
268 if (destination_
.get() != NULL
)
269 destination_
->SetPageCount(page_count
);
271 DCHECK_NE(page_number_
, PageNumber::npos());
274 // Is the page available?
275 scoped_refptr
<PrintedPage
> page
;
276 if (!document_
->GetPage(page_number_
.ToInt(), &page
)) {
277 // We need to wait for the page to be available.
278 base::MessageLoop::current()->PostDelayedTask(
280 base::Bind(&PrintJobWorker::OnNewPage
, weak_factory_
.GetWeakPtr()),
281 base::TimeDelta::FromMilliseconds(500));
284 // The page is there, print it.
285 SpoolPage(page
.get());
287 if (page_number_
== PageNumber::npos()) {
289 // Don't touch this anymore since the instance could be destroyed.
295 void PrintJobWorker::Cancel() {
296 // This is the only function that can be called from any thread.
297 printing_context_
->Cancel();
298 // Cannot touch any member variable since we don't know in which thread
302 void PrintJobWorker::OnDocumentDone() {
303 DCHECK_EQ(message_loop(), base::MessageLoop::current());
304 DCHECK_EQ(page_number_
, PageNumber::npos());
305 DCHECK(document_
.get());
307 if (printing_context_
->DocumentDone() != PrintingContext::OK
) {
312 owner_
->message_loop()->PostTask(
313 FROM_HERE
, base::Bind(NotificationCallback
, make_scoped_refptr(owner_
),
314 JobEventDetails::DOC_DONE
, document_
,
315 scoped_refptr
<PrintedPage
>()));
317 // Makes sure the variables are reinitialized.
321 void PrintJobWorker::SpoolPage(PrintedPage
* page
) {
322 DCHECK_EQ(message_loop(), base::MessageLoop::current());
323 DCHECK_NE(page_number_
, PageNumber::npos());
325 // Signal everyone that the page is about to be printed.
326 owner_
->message_loop()->PostTask(
327 FROM_HERE
, base::Bind(NotificationCallback
, make_scoped_refptr(owner_
),
328 JobEventDetails::NEW_PAGE
, document_
,
329 make_scoped_refptr(page
)));
332 if (printing_context_
->NewPage() != PrintingContext::OK
) {
337 if (destination_
.get() != NULL
) {
338 std::vector
<uint8
> metabytes(page
->metafile()->GetDataSize());
339 bool success
= page
->metafile()->GetData(
340 reinterpret_cast<void*>(&metabytes
[0]), metabytes
.size());
341 DCHECK(success
) << "Failed to get metafile data.";
342 destination_
->SetPageContent(
344 reinterpret_cast<void*>(&metabytes
[0]),
350 #if defined(OS_WIN) || defined(OS_MACOSX)
351 document_
->RenderPrintedPage(*page
, printing_context_
->context());
352 #elif defined(OS_POSIX)
353 document_
->RenderPrintedPage(*page
, printing_context_
.get());
357 if (printing_context_
->PageDone() != PrintingContext::OK
) {
362 // Signal everyone that the page is printed.
363 owner_
->message_loop()->PostTask(
365 base::Bind(NotificationCallback
, make_scoped_refptr(owner_
),
366 JobEventDetails::PAGE_DONE
, document_
,
367 make_scoped_refptr(page
)));
370 void PrintJobWorker::OnFailure() {
371 DCHECK_EQ(message_loop(), base::MessageLoop::current());
373 // We may loose our last reference by broadcasting the FAILED event.
374 scoped_refptr
<PrintJobWorkerOwner
> handle(owner_
);
376 owner_
->message_loop()->PostTask(
377 FROM_HERE
, base::Bind(NotificationCallback
, make_scoped_refptr(owner_
),
378 JobEventDetails::FAILED
, document_
,
379 scoped_refptr
<PrintedPage
>()));
382 // Makes sure the variables are reinitialized.
384 page_number_
= PageNumber::npos();
387 } // namespace printing