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/notification_service.h" 16 #include "printing/printed_document.h" 17 #include "printing/printed_page.h" 18 19 using base::TimeDelta; 20 21 namespace { 22 23 // Helper function to ensure |owner| is valid until at least |callback| returns. 24 void HoldRefCallback(const scoped_refptr<printing::PrintJobWorkerOwner>& owner, 25 const base::Closure& callback) { 26 callback.Run(); 27 } 28 29 } // namespace 30 31 namespace printing { 32 33 PrintJob::PrintJob() 34 : ui_message_loop_(base::MessageLoop::current()), 35 source_(NULL), 36 worker_(), 37 settings_(), 38 is_job_pending_(false), 39 is_canceling_(false), 40 is_stopping_(false), 41 is_stopped_(false), 42 quit_factory_(this), 43 weak_ptr_factory_(this) { 44 DCHECK(ui_message_loop_); 45 // This is normally a UI message loop, but in unit tests, the message loop is 46 // of the 'default' type. 47 DCHECK(ui_message_loop_->type() == base::MessageLoop::TYPE_UI || 48 ui_message_loop_->type() == base::MessageLoop::TYPE_DEFAULT); 49 ui_message_loop_->AddDestructionObserver(this); 50 } 51 52 PrintJob::~PrintJob() { 53 ui_message_loop_->RemoveDestructionObserver(this); 54 // The job should be finished (or at least canceled) when it is destroyed. 55 DCHECK(!is_job_pending_); 56 DCHECK(!is_canceling_); 57 if (worker_.get()) 58 DCHECK(worker_->message_loop() == NULL); 59 DCHECK_EQ(ui_message_loop_, base::MessageLoop::current()); 60 } 61 62 void PrintJob::Initialize(PrintJobWorkerOwner* job, 63 PrintedPagesSource* source, 64 int page_count) { 65 DCHECK(!source_); 66 DCHECK(!worker_.get()); 67 DCHECK(!is_job_pending_); 68 DCHECK(!is_canceling_); 69 DCHECK(!document_.get()); 70 source_ = source; 71 worker_.reset(job->DetachWorker(this)); 72 settings_ = job->settings(); 73 74 PrintedDocument* new_doc = 75 new PrintedDocument(settings_, source_, job->cookie()); 76 new_doc->set_page_count(page_count); 77 UpdatePrintedDocument(new_doc); 78 79 // Don't forget to register to our own messages. 80 registrar_.Add(this, chrome::NOTIFICATION_PRINT_JOB_EVENT, 81 content::Source<PrintJob>(this)); 82 } 83 84 void PrintJob::Observe(int type, 85 const content::NotificationSource& source, 86 const content::NotificationDetails& details) { 87 DCHECK_EQ(ui_message_loop_, base::MessageLoop::current()); 88 switch (type) { 89 case chrome::NOTIFICATION_PRINT_JOB_EVENT: { 90 OnNotifyPrintJobEvent(*content::Details<JobEventDetails>(details).ptr()); 91 break; 92 } 93 default: { 94 break; 95 } 96 } 97 } 98 99 void PrintJob::GetSettingsDone(const PrintSettings& new_settings, 100 PrintingContext::Result result) { 101 NOTREACHED(); 102 } 103 104 PrintJobWorker* PrintJob::DetachWorker(PrintJobWorkerOwner* new_owner) { 105 NOTREACHED(); 106 return NULL; 107 } 108 109 base::MessageLoop* PrintJob::message_loop() { 110 return ui_message_loop_; 111 } 112 113 const PrintSettings& PrintJob::settings() const { 114 return settings_; 115 } 116 117 int PrintJob::cookie() const { 118 if (!document_.get()) 119 // Always use an invalid cookie in this case. 120 return 0; 121 return document_->cookie(); 122 } 123 124 void PrintJob::WillDestroyCurrentMessageLoop() { 125 NOTREACHED(); 126 } 127 128 void PrintJob::StartPrinting() { 129 DCHECK_EQ(ui_message_loop_, base::MessageLoop::current()); 130 DCHECK(worker_->message_loop()); 131 DCHECK(!is_job_pending_); 132 if (!worker_->message_loop() || is_job_pending_) 133 return; 134 135 // Real work is done in PrintJobWorker::StartPrinting(). 136 worker_->message_loop()->PostTask( 137 FROM_HERE, 138 base::Bind(&HoldRefCallback, make_scoped_refptr(this), 139 base::Bind(&PrintJobWorker::StartPrinting, 140 base::Unretained(worker_.get()), document_))); 141 // Set the flag right now. 142 is_job_pending_ = true; 143 144 // Tell everyone! 145 scoped_refptr<JobEventDetails> details( 146 new JobEventDetails(JobEventDetails::NEW_DOC, document_.get(), NULL)); 147 content::NotificationService::current()->Notify( 148 chrome::NOTIFICATION_PRINT_JOB_EVENT, 149 content::Source<PrintJob>(this), 150 content::Details<JobEventDetails>(details.get())); 151 } 152 153 void PrintJob::Stop() { 154 DCHECK_EQ(ui_message_loop_, base::MessageLoop::current()); 155 156 if (quit_factory_.HasWeakPtrs()) { 157 // In case we're running a nested message loop to wait for a job to finish, 158 // and we finished before the timeout, quit the nested loop right away. 159 Quit(); 160 quit_factory_.InvalidateWeakPtrs(); 161 } 162 163 // Be sure to live long enough. 164 scoped_refptr<PrintJob> handle(this); 165 166 base::MessageLoop* worker_loop = worker_->message_loop(); 167 if (worker_loop) { 168 ControlledWorkerShutdown(); 169 170 is_job_pending_ = false; 171 registrar_.Remove(this, chrome::NOTIFICATION_PRINT_JOB_EVENT, 172 content::Source<PrintJob>(this)); 173 } 174 // Flush the cached document. 175 UpdatePrintedDocument(NULL); 176 } 177 178 void PrintJob::Cancel() { 179 if (is_canceling_) 180 return; 181 is_canceling_ = true; 182 183 // Be sure to live long enough. 184 scoped_refptr<PrintJob> handle(this); 185 186 DCHECK_EQ(ui_message_loop_, base::MessageLoop::current()); 187 base::MessageLoop* worker_loop = 188 worker_.get() ? worker_->message_loop() : NULL; 189 if (worker_loop) { 190 // Call this right now so it renders the context invalid. Do not use 191 // InvokeLater since it would take too much time. 192 worker_->Cancel(); 193 } 194 // Make sure a Cancel() is broadcast. 195 scoped_refptr<JobEventDetails> details( 196 new JobEventDetails(JobEventDetails::FAILED, NULL, NULL)); 197 content::NotificationService::current()->Notify( 198 chrome::NOTIFICATION_PRINT_JOB_EVENT, 199 content::Source<PrintJob>(this), 200 content::Details<JobEventDetails>(details.get())); 201 Stop(); 202 is_canceling_ = false; 203 } 204 205 bool PrintJob::FlushJob(base::TimeDelta timeout) { 206 // Make sure the object outlive this message loop. 207 scoped_refptr<PrintJob> handle(this); 208 209 base::MessageLoop::current()->PostDelayedTask(FROM_HERE, 210 base::Bind(&PrintJob::Quit, quit_factory_.GetWeakPtr()), timeout); 211 212 base::MessageLoop::ScopedNestableTaskAllower allow( 213 base::MessageLoop::current()); 214 base::MessageLoop::current()->Run(); 215 216 return true; 217 } 218 219 void PrintJob::DisconnectSource() { 220 source_ = NULL; 221 if (document_.get()) 222 document_->DisconnectSource(); 223 } 224 225 bool PrintJob::is_job_pending() const { 226 return is_job_pending_; 227 } 228 229 bool PrintJob::is_stopping() const { 230 return is_stopping_; 231 } 232 233 bool PrintJob::is_stopped() const { 234 return is_stopped_; 235 } 236 237 PrintedDocument* PrintJob::document() const { 238 return document_.get(); 239 } 240 241 void PrintJob::UpdatePrintedDocument(PrintedDocument* new_document) { 242 if (document_.get() == new_document) 243 return; 244 245 document_ = new_document; 246 247 if (document_.get()) { 248 settings_ = document_->settings(); 249 } 250 251 if (worker_.get() && worker_->message_loop()) { 252 DCHECK(!is_job_pending_); 253 // Sync the document with the worker. 254 worker_->message_loop()->PostTask( 255 FROM_HERE, 256 base::Bind(&HoldRefCallback, make_scoped_refptr(this), 257 base::Bind(&PrintJobWorker::OnDocumentChanged, 258 base::Unretained(worker_.get()), document_))); 259 } 260 } 261 262 void PrintJob::OnNotifyPrintJobEvent(const JobEventDetails& event_details) { 263 switch (event_details.type()) { 264 case JobEventDetails::FAILED: { 265 settings_.Clear(); 266 // No need to cancel since the worker already canceled itself. 267 Stop(); 268 break; 269 } 270 case JobEventDetails::USER_INIT_DONE: 271 case JobEventDetails::DEFAULT_INIT_DONE: 272 case JobEventDetails::USER_INIT_CANCELED: { 273 DCHECK_EQ(event_details.document(), document_.get()); 274 break; 275 } 276 case JobEventDetails::NEW_DOC: 277 case JobEventDetails::NEW_PAGE: 278 case JobEventDetails::PAGE_DONE: 279 case JobEventDetails::JOB_DONE: 280 case JobEventDetails::ALL_PAGES_REQUESTED: { 281 // Don't care. 282 break; 283 } 284 case JobEventDetails::DOC_DONE: { 285 // This will call Stop() and broadcast a JOB_DONE message. 286 base::MessageLoop::current()->PostTask( 287 FROM_HERE, base::Bind(&PrintJob::OnDocumentDone, this)); 288 break; 289 } 290 default: { 291 NOTREACHED(); 292 break; 293 } 294 } 295 } 296 297 void PrintJob::OnDocumentDone() { 298 // Be sure to live long enough. The instance could be destroyed by the 299 // JOB_DONE broadcast. 300 scoped_refptr<PrintJob> handle(this); 301 302 // Stop the worker thread. 303 Stop(); 304 305 scoped_refptr<JobEventDetails> details( 306 new JobEventDetails(JobEventDetails::JOB_DONE, document_.get(), NULL)); 307 content::NotificationService::current()->Notify( 308 chrome::NOTIFICATION_PRINT_JOB_EVENT, 309 content::Source<PrintJob>(this), 310 content::Details<JobEventDetails>(details.get())); 311 } 312 313 void PrintJob::ControlledWorkerShutdown() { 314 DCHECK_EQ(ui_message_loop_, base::MessageLoop::current()); 315 316 // The deadlock this code works around is specific to window messaging on 317 // Windows, so we aren't likely to need it on any other platforms. 318 #if defined(OS_WIN) 319 // We could easily get into a deadlock case if worker_->Stop() is used; the 320 // printer driver created a window as a child of the browser window. By 321 // canceling the job, the printer driver initiated dialog box is destroyed, 322 // which sends a blocking message to its parent window. If the browser window 323 // thread is not processing messages, a deadlock occurs. 324 // 325 // This function ensures that the dialog box will be destroyed in a timely 326 // manner by the mere fact that the thread will terminate. So the potential 327 // deadlock is eliminated. 328 worker_->StopSoon(); 329 330 // Run a tight message loop until the worker terminates. It may seems like a 331 // hack but I see no other way to get it to work flawlessly. The issues here 332 // are: 333 // - We don't want to run tasks while the thread is quitting. 334 // - We want this code path to wait on the thread to quit before continuing. 335 MSG msg; 336 HANDLE thread_handle = worker_->thread_handle().platform_handle(); 337 for (; thread_handle;) { 338 // Note that we don't do any kind of message prioritization since we don't 339 // execute any pending task or timer. 340 DWORD result = MsgWaitForMultipleObjects(1, &thread_handle, 341 FALSE, INFINITE, QS_ALLINPUT); 342 if (result == WAIT_OBJECT_0 + 1) { 343 while (PeekMessage(&msg, NULL, 0, 0, TRUE) > 0) { 344 TranslateMessage(&msg); 345 DispatchMessage(&msg); 346 } 347 // Continue looping. 348 } else if (result == WAIT_OBJECT_0) { 349 // The thread quit. 350 break; 351 } else { 352 // An error occurred. Assume the thread quit. 353 NOTREACHED(); 354 break; 355 } 356 } 357 #endif 358 359 360 // Now make sure the thread object is cleaned up. Do this on a worker 361 // thread because it may block. 362 is_stopping_ = true; 363 364 base::WorkerPool::PostTaskAndReply( 365 FROM_HERE, 366 base::Bind(&PrintJobWorker::Stop, 367 base::Unretained(worker_.get())), 368 base::Bind(&PrintJob::HoldUntilStopIsCalled, 369 weak_ptr_factory_.GetWeakPtr(), 370 scoped_refptr<PrintJob>(this)), 371 false); 372 } 373 374 void PrintJob::HoldUntilStopIsCalled(const scoped_refptr<PrintJob>&) { 375 is_stopped_ = true; 376 is_stopping_ = false; 377 } 378 379 void PrintJob::Quit() { 380 base::MessageLoop::current()->Quit(); 381 } 382 383 // Takes settings_ ownership and will be deleted in the receiving thread. 384 JobEventDetails::JobEventDetails(Type type, 385 PrintedDocument* document, 386 PrintedPage* page) 387 : document_(document), 388 page_(page), 389 type_(type) { 390 } 391 392 JobEventDetails::~JobEventDetails() { 393 } 394 395 PrintedDocument* JobEventDetails::document() const { return document_.get(); } 396 397 PrintedPage* JobEventDetails::page() const { return page_.get(); } 398 399 } // namespace printing 400