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.h" 6 7 #include "base/bind.h" 8 #include "base/bind_helpers.h" 9 #include "base/message_loop/message_loop.h" 10 #include "base/threading/thread_restrictions.h" 11 #include "base/threading/worker_pool.h" 12 #include "base/timer/timer.h" 13 #include "chrome/browser/chrome_notification_types.h" 14 #include "chrome/browser/printing/print_job_worker.h" 15 #include "content/public/browser/browser_thread.h" 16 #include "content/public/browser/notification_service.h" 17 #include "printing/printed_document.h" 18 #include "printing/printed_page.h" 19 20 #if defined(OS_WIN) 21 #include "chrome/browser/printing/pdf_to_emf_converter.h" 22 #include "printing/pdf_render_settings.h" 23 #endif 24 25 using base::TimeDelta; 26 27 namespace { 28 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) { 32 callback.Run(); 33 } 34 35 } // namespace 36 37 namespace printing { 38 39 PrintJob::PrintJob() 40 : source_(NULL), 41 worker_(), 42 settings_(), 43 is_job_pending_(false), 44 is_canceling_(false), 45 quit_factory_(this) { 46 // This is normally a UI message loop, but in unit tests, the message loop is 47 // of the 'default' type. 48 DCHECK(base::MessageLoopForUI::IsCurrent() || 49 base::MessageLoop::current()->type() == 50 base::MessageLoop::TYPE_DEFAULT); 51 } 52 53 PrintJob::~PrintJob() { 54 // The job should be finished (or at least canceled) when it is destroyed. 55 DCHECK(!is_job_pending_); 56 DCHECK(!is_canceling_); 57 DCHECK(!worker_ || !worker_->IsRunning()); 58 DCHECK(RunsTasksOnCurrentThread()); 59 } 60 61 void PrintJob::Initialize(PrintJobWorkerOwner* job, 62 PrintedPagesSource* source, 63 int page_count) { 64 DCHECK(!source_); 65 DCHECK(!worker_.get()); 66 DCHECK(!is_job_pending_); 67 DCHECK(!is_canceling_); 68 DCHECK(!document_.get()); 69 source_ = source; 70 worker_.reset(job->DetachWorker(this)); 71 settings_ = job->settings(); 72 73 PrintedDocument* new_doc = 74 new PrintedDocument(settings_, 75 source_, 76 job->cookie(), 77 content::BrowserThread::GetBlockingPool()); 78 new_doc->set_page_count(page_count); 79 UpdatePrintedDocument(new_doc); 80 81 // Don't forget to register to our own messages. 82 registrar_.Add(this, chrome::NOTIFICATION_PRINT_JOB_EVENT, 83 content::Source<PrintJob>(this)); 84 } 85 86 void PrintJob::Observe(int type, 87 const content::NotificationSource& source, 88 const content::NotificationDetails& details) { 89 DCHECK(RunsTasksOnCurrentThread()); 90 switch (type) { 91 case chrome::NOTIFICATION_PRINT_JOB_EVENT: { 92 OnNotifyPrintJobEvent(*content::Details<JobEventDetails>(details).ptr()); 93 break; 94 } 95 default: { 96 break; 97 } 98 } 99 } 100 101 void PrintJob::GetSettingsDone(const PrintSettings& new_settings, 102 PrintingContext::Result result) { 103 NOTREACHED(); 104 } 105 106 PrintJobWorker* PrintJob::DetachWorker(PrintJobWorkerOwner* new_owner) { 107 NOTREACHED(); 108 return NULL; 109 } 110 111 const PrintSettings& PrintJob::settings() const { 112 return settings_; 113 } 114 115 int PrintJob::cookie() const { 116 if (!document_.get()) 117 // Always use an invalid cookie in this case. 118 return 0; 119 return document_->cookie(); 120 } 121 122 void PrintJob::StartPrinting() { 123 DCHECK(RunsTasksOnCurrentThread()); 124 DCHECK(worker_->IsRunning()); 125 DCHECK(!is_job_pending_); 126 if (!worker_->IsRunning() || is_job_pending_) 127 return; 128 129 // Real work is done in PrintJobWorker::StartPrinting(). 130 worker_->PostTask(FROM_HERE, 131 base::Bind(&HoldRefCallback, 132 make_scoped_refptr(this), 133 base::Bind(&PrintJobWorker::StartPrinting, 134 base::Unretained(worker_.get()), 135 document_))); 136 // Set the flag right now. 137 is_job_pending_ = true; 138 139 // Tell everyone! 140 scoped_refptr<JobEventDetails> details( 141 new JobEventDetails(JobEventDetails::NEW_DOC, document_.get(), NULL)); 142 content::NotificationService::current()->Notify( 143 chrome::NOTIFICATION_PRINT_JOB_EVENT, 144 content::Source<PrintJob>(this), 145 content::Details<JobEventDetails>(details.get())); 146 } 147 148 void PrintJob::Stop() { 149 DCHECK(RunsTasksOnCurrentThread()); 150 151 if (quit_factory_.HasWeakPtrs()) { 152 // In case we're running a nested message loop to wait for a job to finish, 153 // and we finished before the timeout, quit the nested loop right away. 154 Quit(); 155 quit_factory_.InvalidateWeakPtrs(); 156 } 157 158 // Be sure to live long enough. 159 scoped_refptr<PrintJob> handle(this); 160 161 if (worker_->IsRunning()) { 162 ControlledWorkerShutdown(); 163 } else { 164 // Flush the cached document. 165 UpdatePrintedDocument(NULL); 166 } 167 } 168 169 void PrintJob::Cancel() { 170 if (is_canceling_) 171 return; 172 is_canceling_ = true; 173 174 // Be sure to live long enough. 175 scoped_refptr<PrintJob> handle(this); 176 177 DCHECK(RunsTasksOnCurrentThread()); 178 if (worker_ && worker_->IsRunning()) { 179 // Call this right now so it renders the context invalid. Do not use 180 // InvokeLater since it would take too much time. 181 worker_->Cancel(); 182 } 183 // Make sure a Cancel() is broadcast. 184 scoped_refptr<JobEventDetails> details( 185 new JobEventDetails(JobEventDetails::FAILED, NULL, NULL)); 186 content::NotificationService::current()->Notify( 187 chrome::NOTIFICATION_PRINT_JOB_EVENT, 188 content::Source<PrintJob>(this), 189 content::Details<JobEventDetails>(details.get())); 190 Stop(); 191 is_canceling_ = false; 192 } 193 194 bool PrintJob::FlushJob(base::TimeDelta timeout) { 195 // Make sure the object outlive this message loop. 196 scoped_refptr<PrintJob> handle(this); 197 198 base::MessageLoop::current()->PostDelayedTask(FROM_HERE, 199 base::Bind(&PrintJob::Quit, quit_factory_.GetWeakPtr()), timeout); 200 201 base::MessageLoop::ScopedNestableTaskAllower allow( 202 base::MessageLoop::current()); 203 base::MessageLoop::current()->Run(); 204 205 return true; 206 } 207 208 void PrintJob::DisconnectSource() { 209 source_ = NULL; 210 if (document_.get()) 211 document_->DisconnectSource(); 212 } 213 214 bool PrintJob::is_job_pending() const { 215 return is_job_pending_; 216 } 217 218 PrintedDocument* PrintJob::document() const { 219 return document_.get(); 220 } 221 222 #if defined(OS_WIN) 223 224 class PrintJob::PdfToEmfState { 225 public: 226 PdfToEmfState(const gfx::Size& page_size, const gfx::Rect& content_area) 227 : page_count_(0), 228 current_page_(0), 229 pages_in_progress_(0), 230 page_size_(page_size), 231 content_area_(content_area), 232 converter_(PdfToEmfConverter::CreateDefault()) {} 233 234 void Start(const scoped_refptr<base::RefCountedMemory>& data, 235 const PdfRenderSettings& conversion_settings, 236 const PdfToEmfConverter::StartCallback& start_callback) { 237 converter_->Start(data, conversion_settings, start_callback); 238 } 239 240 void GetMorePages( 241 const PdfToEmfConverter::GetPageCallback& get_page_callback) { 242 const int kMaxNumberOfTempFilesPerDocument = 3; 243 while (pages_in_progress_ < kMaxNumberOfTempFilesPerDocument && 244 current_page_ < page_count_) { 245 ++pages_in_progress_; 246 converter_->GetPage(current_page_++, get_page_callback); 247 } 248 } 249 250 void OnPageProcessed( 251 const PdfToEmfConverter::GetPageCallback& get_page_callback) { 252 --pages_in_progress_; 253 GetMorePages(get_page_callback); 254 // Release converter if we don't need this any more. 255 if (!pages_in_progress_ && current_page_ >= page_count_) 256 converter_.reset(); 257 } 258 259 void set_page_count(int page_count) { page_count_ = page_count; } 260 gfx::Size page_size() const { return page_size_; } 261 gfx::Rect content_area() const { return content_area_; } 262 263 private: 264 int page_count_; 265 int current_page_; 266 int pages_in_progress_; 267 gfx::Size page_size_; 268 gfx::Rect content_area_; 269 scoped_ptr<PdfToEmfConverter> converter_; 270 }; 271 272 void PrintJob::StartPdfToEmfConversion( 273 const scoped_refptr<base::RefCountedMemory>& bytes, 274 const gfx::Size& page_size, 275 const gfx::Rect& content_area) { 276 DCHECK(!ptd_to_emf_state_.get()); 277 ptd_to_emf_state_.reset(new PdfToEmfState(page_size, content_area)); 278 const int kPrinterDpi = settings().dpi(); 279 ptd_to_emf_state_->Start( 280 bytes, 281 printing::PdfRenderSettings(content_area, kPrinterDpi, true), 282 base::Bind(&PrintJob::OnPdfToEmfStarted, this)); 283 } 284 285 void PrintJob::OnPdfToEmfStarted(int page_count) { 286 if (page_count <= 0) { 287 ptd_to_emf_state_.reset(); 288 Cancel(); 289 return; 290 } 291 ptd_to_emf_state_->set_page_count(page_count); 292 ptd_to_emf_state_->GetMorePages( 293 base::Bind(&PrintJob::OnPdfToEmfPageConverted, this)); 294 } 295 296 void PrintJob::OnPdfToEmfPageConverted(int page_number, 297 double scale_factor, 298 scoped_ptr<MetafilePlayer> emf) { 299 DCHECK(ptd_to_emf_state_); 300 if (!document_.get() || !emf) { 301 ptd_to_emf_state_.reset(); 302 Cancel(); 303 return; 304 } 305 306 // Update the rendered document. It will send notifications to the listener. 307 document_->SetPage(page_number, 308 emf.Pass(), 309 scale_factor, 310 ptd_to_emf_state_->page_size(), 311 ptd_to_emf_state_->content_area()); 312 313 ptd_to_emf_state_->GetMorePages( 314 base::Bind(&PrintJob::OnPdfToEmfPageConverted, this)); 315 } 316 317 #endif // OS_WIN 318 319 void PrintJob::UpdatePrintedDocument(PrintedDocument* new_document) { 320 if (document_.get() == new_document) 321 return; 322 323 document_ = new_document; 324 325 if (document_.get()) { 326 settings_ = document_->settings(); 327 } 328 329 if (worker_) { 330 DCHECK(!is_job_pending_); 331 // Sync the document with the worker. 332 worker_->PostTask(FROM_HERE, 333 base::Bind(&HoldRefCallback, 334 make_scoped_refptr(this), 335 base::Bind(&PrintJobWorker::OnDocumentChanged, 336 base::Unretained(worker_.get()), 337 document_))); 338 } 339 } 340 341 void PrintJob::OnNotifyPrintJobEvent(const JobEventDetails& event_details) { 342 switch (event_details.type()) { 343 case JobEventDetails::FAILED: { 344 settings_.Clear(); 345 // No need to cancel since the worker already canceled itself. 346 Stop(); 347 break; 348 } 349 case JobEventDetails::USER_INIT_DONE: 350 case JobEventDetails::DEFAULT_INIT_DONE: 351 case JobEventDetails::USER_INIT_CANCELED: { 352 DCHECK_EQ(event_details.document(), document_.get()); 353 break; 354 } 355 case JobEventDetails::NEW_DOC: 356 case JobEventDetails::NEW_PAGE: 357 case JobEventDetails::JOB_DONE: 358 case JobEventDetails::ALL_PAGES_REQUESTED: { 359 // Don't care. 360 break; 361 } 362 case JobEventDetails::DOC_DONE: { 363 // This will call Stop() and broadcast a JOB_DONE message. 364 base::MessageLoop::current()->PostTask( 365 FROM_HERE, base::Bind(&PrintJob::OnDocumentDone, this)); 366 break; 367 } 368 case JobEventDetails::PAGE_DONE: 369 #if defined(OS_WIN) 370 ptd_to_emf_state_->OnPageProcessed( 371 base::Bind(&PrintJob::OnPdfToEmfPageConverted, this)); 372 #endif // OS_WIN 373 break; 374 default: { 375 NOTREACHED(); 376 break; 377 } 378 } 379 } 380 381 void PrintJob::OnDocumentDone() { 382 // Be sure to live long enough. The instance could be destroyed by the 383 // JOB_DONE broadcast. 384 scoped_refptr<PrintJob> handle(this); 385 386 // Stop the worker thread. 387 Stop(); 388 389 scoped_refptr<JobEventDetails> details( 390 new JobEventDetails(JobEventDetails::JOB_DONE, document_.get(), NULL)); 391 content::NotificationService::current()->Notify( 392 chrome::NOTIFICATION_PRINT_JOB_EVENT, 393 content::Source<PrintJob>(this), 394 content::Details<JobEventDetails>(details.get())); 395 } 396 397 void PrintJob::ControlledWorkerShutdown() { 398 DCHECK(RunsTasksOnCurrentThread()); 399 400 // The deadlock this code works around is specific to window messaging on 401 // Windows, so we aren't likely to need it on any other platforms. 402 #if defined(OS_WIN) 403 // We could easily get into a deadlock case if worker_->Stop() is used; the 404 // printer driver created a window as a child of the browser window. By 405 // canceling the job, the printer driver initiated dialog box is destroyed, 406 // which sends a blocking message to its parent window. If the browser window 407 // thread is not processing messages, a deadlock occurs. 408 // 409 // This function ensures that the dialog box will be destroyed in a timely 410 // manner by the mere fact that the thread will terminate. So the potential 411 // deadlock is eliminated. 412 worker_->StopSoon(); 413 414 // Delay shutdown until the worker terminates. We want this code path 415 // to wait on the thread to quit before continuing. 416 if (worker_->IsRunning()) { 417 base::MessageLoop::current()->PostDelayedTask( 418 FROM_HERE, 419 base::Bind(&PrintJob::ControlledWorkerShutdown, this), 420 base::TimeDelta::FromMilliseconds(100)); 421 return; 422 } 423 #endif 424 425 426 // Now make sure the thread object is cleaned up. Do this on a worker 427 // thread because it may block. 428 base::WorkerPool::PostTaskAndReply( 429 FROM_HERE, 430 base::Bind(&PrintJobWorker::Stop, base::Unretained(worker_.get())), 431 base::Bind(&PrintJob::HoldUntilStopIsCalled, this), 432 false); 433 434 is_job_pending_ = false; 435 registrar_.RemoveAll(); 436 UpdatePrintedDocument(NULL); 437 } 438 439 void PrintJob::HoldUntilStopIsCalled() { 440 } 441 442 void PrintJob::Quit() { 443 base::MessageLoop::current()->Quit(); 444 } 445 446 // Takes settings_ ownership and will be deleted in the receiving thread. 447 JobEventDetails::JobEventDetails(Type type, 448 PrintedDocument* document, 449 PrintedPage* page) 450 : document_(document), 451 page_(page), 452 type_(type) { 453 } 454 455 JobEventDetails::~JobEventDetails() { 456 } 457 458 PrintedDocument* JobEventDetails::document() const { return document_.get(); } 459 460 PrintedPage* JobEventDetails::page() const { return page_.get(); } 461 462 } // namespace printing 463