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