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