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/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" 25 26 using content::BrowserThread; 27 28 namespace { 29 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) { 33 callback.Run(); 34 } 35 36 } // namespace 37 38 namespace printing { 39 40 void NotificationCallback(PrintJobWorkerOwner* print_job, 41 JobEventDetails::Type detail_type, 42 PrintedDocument* document, 43 PrintedPage* page) { 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)); 50 } 51 52 PrintJobWorker::PrintJobWorker(PrintJobWorkerOwner* owner) 53 : Thread("Printing_Worker"), 54 owner_(owner), 55 weak_factory_(this) { 56 // The object is created in the IO thread. 57 DCHECK_EQ(owner_->message_loop(), base::MessageLoop::current()); 58 59 printing_context_.reset(PrintingContext::Create( 60 g_browser_process->GetApplicationLocale())); 61 } 62 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 66 // on the I/O thread. 67 DCHECK_EQ(owner_->message_loop(), base::MessageLoop::current()); 68 Stop(); 69 } 70 71 void PrintJobWorker::SetNewOwner(PrintJobWorkerOwner* new_owner) { 72 DCHECK(page_number_ == PageNumber::npos()); 73 owner_ = new_owner; 74 } 75 76 void PrintJobWorker::SetPrintDestination( 77 PrintDestinationInterface* destination) { 78 destination_ = destination; 79 } 80 81 void PrintJobWorker::GetSettings( 82 bool ask_user_for_settings, 83 scoped_ptr<PrintingUIWebContentsObserver> web_contents_observer, 84 int document_page_count, 85 bool has_selection, 86 MarginType margin_type) { 87 DCHECK_EQ(message_loop(), base::MessageLoop::current()); 88 DCHECK_EQ(page_number_, PageNumber::npos()); 89 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); 97 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), 107 document_page_count, 108 has_selection))); 109 } else { 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)))); 115 } 116 } 117 118 void PrintJobWorker::SetSettings(const DictionaryValue* const new_settings) { 119 DCHECK_EQ(message_loop(), base::MessageLoop::current()); 120 121 BrowserThread::PostTask( 122 BrowserThread::UI, FROM_HERE, 123 base::Bind(&HoldRefCallback, make_scoped_refptr(owner_), 124 base::Bind(&PrintJobWorker::UpdatePrintSettings, 125 base::Unretained(this), new_settings))); 126 } 127 128 void PrintJobWorker::UpdatePrintSettings( 129 const DictionaryValue* const new_settings) { 130 // Create new PageRanges based on |new_settings|. 131 PageRanges new_ranges; 132 const ListValue* page_range_array; 133 if (new_settings->GetList(kSettingPageRange, &page_range_array)) { 134 for (size_t index = 0; index < page_range_array->GetSize(); ++index) { 135 const DictionaryValue* dict; 136 if (!page_range_array->GetDictionary(index, &dict)) 137 continue; 138 139 PageRange range; 140 if (!dict->GetInteger(kSettingPageRangeFrom, &range.from) || 141 !dict->GetInteger(kSettingPageRangeTo, &range.to)) { 142 continue; 143 } 144 145 // Page numbers are 1-based in the dictionary. 146 // Page numbers are 0-based for the printing context. 147 range.from--; 148 range.to--; 149 new_ranges.push_back(range); 150 } 151 } 152 PrintingContext::Result result = 153 printing_context_->UpdatePrintSettings(*new_settings, new_ranges); 154 delete new_settings; 155 GetSettingsDone(result); 156 } 157 158 void PrintJobWorker::GetSettingsDone(PrintingContext::Result result) { 159 // Most PrintingContext functions may start a message loop and process 160 // message recursively, so disable recursive task processing. 161 // TODO(thestig): See above comment. SetNestableTasksAllowed(false) needs to 162 // be called on the same thread as the previous call. See 163 // http://crbug.com/73466 164 // MessageLoop::current()->SetNestableTasksAllowed(false); 165 166 // We can't use OnFailure() here since owner_ may not support notifications. 167 168 // PrintJob will create the new PrintedDocument. 169 owner_->message_loop()->PostTask( 170 FROM_HERE, 171 base::Bind(&PrintJobWorkerOwner::GetSettingsDone, 172 make_scoped_refptr(owner_), printing_context_->settings(), 173 result)); 174 } 175 176 void PrintJobWorker::GetSettingsWithUI( 177 scoped_ptr<PrintingUIWebContentsObserver> web_contents_observer, 178 int document_page_count, 179 bool has_selection) { 180 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 181 182 gfx::NativeView parent_view = web_contents_observer->GetParentView(); 183 if (!parent_view) { 184 GetSettingsWithUIDone(printing::PrintingContext::FAILED); 185 return; 186 } 187 printing_context_->AskUserForSettings( 188 parent_view, document_page_count, has_selection, 189 base::Bind(&PrintJobWorker::GetSettingsWithUIDone, 190 base::Unretained(this))); 191 } 192 193 void PrintJobWorker::GetSettingsWithUIDone(PrintingContext::Result result) { 194 message_loop()->PostTask( 195 FROM_HERE, 196 base::Bind(&HoldRefCallback, make_scoped_refptr(owner_), 197 base::Bind(&PrintJobWorker::GetSettingsDone, 198 base::Unretained(this), result))); 199 } 200 201 void PrintJobWorker::UseDefaultSettings() { 202 PrintingContext::Result result = printing_context_->UseDefaultSettings(); 203 GetSettingsDone(result); 204 } 205 206 void PrintJobWorker::StartPrinting(PrintedDocument* new_document) { 207 DCHECK_EQ(message_loop(), base::MessageLoop::current()); 208 DCHECK_EQ(page_number_, PageNumber::npos()); 209 DCHECK_EQ(document_, new_document); 210 DCHECK(document_.get()); 211 212 if (!document_.get() || page_number_ != PageNumber::npos() || 213 document_.get() != new_document) { 214 return; 215 } 216 217 base::string16 document_name = 218 printing::SimplifyDocumentTitle(document_->name()); 219 if (document_name.empty()) { 220 document_name = printing::SimplifyDocumentTitle( 221 l10n_util::GetStringUTF16(IDS_DEFAULT_PRINT_DOCUMENT_TITLE)); 222 } 223 PrintingContext::Result result = 224 printing_context_->NewDocument(document_name); 225 if (result != PrintingContext::OK) { 226 OnFailure(); 227 return; 228 } 229 230 // Try to print already cached data. It may already have been generated for 231 // the print preview. 232 OnNewPage(); 233 // Don't touch this anymore since the instance could be destroyed. It happens 234 // if all the pages are printed a one sweep and the client doesn't have a 235 // handle to us anymore. There's a timing issue involved between the worker 236 // thread and the UI thread. Take no chance. 237 } 238 239 void PrintJobWorker::OnDocumentChanged(PrintedDocument* new_document) { 240 DCHECK_EQ(message_loop(), base::MessageLoop::current()); 241 DCHECK_EQ(page_number_, PageNumber::npos()); 242 243 if (page_number_ != PageNumber::npos()) 244 return; 245 246 document_ = new_document; 247 } 248 249 void PrintJobWorker::OnNewPage() { 250 if (!document_.get()) // Spurious message. 251 return; 252 253 // message_loop() could return NULL when the print job is cancelled. 254 DCHECK_EQ(message_loop(), base::MessageLoop::current()); 255 256 if (page_number_ == PageNumber::npos()) { 257 // Find first page to print. 258 int page_count = document_->page_count(); 259 if (!page_count) { 260 // We still don't know how many pages the document contains. We can't 261 // start to print the document yet since the header/footer may refer to 262 // the document's page count. 263 return; 264 } 265 // We have enough information to initialize page_number_. 266 page_number_.Init(document_->settings(), page_count); 267 if (destination_.get() != NULL) 268 destination_->SetPageCount(page_count); 269 } 270 DCHECK_NE(page_number_, PageNumber::npos()); 271 272 while (true) { 273 // Is the page available? 274 scoped_refptr<PrintedPage> page; 275 if (!document_->GetPage(page_number_.ToInt(), &page)) { 276 // We need to wait for the page to be available. 277 base::MessageLoop::current()->PostDelayedTask( 278 FROM_HERE, 279 base::Bind(&PrintJobWorker::OnNewPage, weak_factory_.GetWeakPtr()), 280 base::TimeDelta::FromMilliseconds(500)); 281 break; 282 } 283 // The page is there, print it. 284 SpoolPage(page.get()); 285 ++page_number_; 286 if (page_number_ == PageNumber::npos()) { 287 OnDocumentDone(); 288 // Don't touch this anymore since the instance could be destroyed. 289 break; 290 } 291 } 292 } 293 294 void PrintJobWorker::Cancel() { 295 // This is the only function that can be called from any thread. 296 printing_context_->Cancel(); 297 // Cannot touch any member variable since we don't know in which thread 298 // context we run. 299 } 300 301 void PrintJobWorker::OnDocumentDone() { 302 DCHECK_EQ(message_loop(), base::MessageLoop::current()); 303 DCHECK_EQ(page_number_, PageNumber::npos()); 304 DCHECK(document_.get()); 305 306 if (printing_context_->DocumentDone() != PrintingContext::OK) { 307 OnFailure(); 308 return; 309 } 310 311 owner_->message_loop()->PostTask( 312 FROM_HERE, base::Bind(NotificationCallback, make_scoped_refptr(owner_), 313 JobEventDetails::DOC_DONE, document_, 314 scoped_refptr<PrintedPage>())); 315 316 // Makes sure the variables are reinitialized. 317 document_ = NULL; 318 } 319 320 void PrintJobWorker::SpoolPage(PrintedPage* page) { 321 DCHECK_EQ(message_loop(), base::MessageLoop::current()); 322 DCHECK_NE(page_number_, PageNumber::npos()); 323 324 // Signal everyone that the page is about to be printed. 325 owner_->message_loop()->PostTask( 326 FROM_HERE, base::Bind(NotificationCallback, make_scoped_refptr(owner_), 327 JobEventDetails::NEW_PAGE, document_, 328 make_scoped_refptr(page))); 329 330 // Preprocess. 331 if (printing_context_->NewPage() != PrintingContext::OK) { 332 OnFailure(); 333 return; 334 } 335 336 if (destination_.get() != NULL) { 337 std::vector<uint8> metabytes(page->metafile()->GetDataSize()); 338 bool success = page->metafile()->GetData( 339 reinterpret_cast<void*>(&metabytes[0]), metabytes.size()); 340 DCHECK(success) << "Failed to get metafile data."; 341 destination_->SetPageContent( 342 page->page_number(), 343 reinterpret_cast<void*>(&metabytes[0]), 344 metabytes.size()); 345 return; 346 } 347 348 // Actual printing. 349 #if defined(OS_WIN) || defined(OS_MACOSX) 350 document_->RenderPrintedPage(*page, printing_context_->context()); 351 #elif defined(OS_POSIX) 352 document_->RenderPrintedPage(*page, printing_context_.get()); 353 #endif 354 355 // Postprocess. 356 if (printing_context_->PageDone() != PrintingContext::OK) { 357 OnFailure(); 358 return; 359 } 360 361 // Signal everyone that the page is printed. 362 owner_->message_loop()->PostTask( 363 FROM_HERE, 364 base::Bind(NotificationCallback, make_scoped_refptr(owner_), 365 JobEventDetails::PAGE_DONE, document_, 366 make_scoped_refptr(page))); 367 } 368 369 void PrintJobWorker::OnFailure() { 370 DCHECK_EQ(message_loop(), base::MessageLoop::current()); 371 372 // We may loose our last reference by broadcasting the FAILED event. 373 scoped_refptr<PrintJobWorkerOwner> handle(owner_); 374 375 owner_->message_loop()->PostTask( 376 FROM_HERE, base::Bind(NotificationCallback, make_scoped_refptr(owner_), 377 JobEventDetails::FAILED, document_, 378 scoped_refptr<PrintedPage>())); 379 Cancel(); 380 381 // Makes sure the variables are reinitialized. 382 document_ = NULL; 383 page_number_ = PageNumber::npos(); 384 } 385 386 } // namespace printing 387