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