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_worker.h" 6 7 #include "base/message_loop.h" 8 #include "base/values.h" 9 #include "chrome/browser/browser_process.h" 10 #include "chrome/browser/printing/print_job.h" 11 #include "content/browser/browser_thread.h" 12 #include "content/common/notification_service.h" 13 #include "printing/printed_document.h" 14 #include "printing/printed_page.h" 15 16 namespace printing { 17 18 class PrintJobWorker::NotificationTask : public Task { 19 public: 20 NotificationTask() : print_job_(NULL), details_(NULL) { 21 } 22 ~NotificationTask() { 23 } 24 25 // Initializes the object. This object can't be initialized in the constructor 26 // since it is not created directly. 27 void Init(PrintJobWorkerOwner* print_job, 28 JobEventDetails::Type detail_type, 29 PrintedDocument* document, 30 PrintedPage* page) { 31 DCHECK(!print_job_); 32 DCHECK(!details_); 33 print_job_ = print_job; 34 details_ = new JobEventDetails(detail_type, document, page); 35 } 36 37 virtual void Run() { 38 // Send the notification in the right thread. 39 NotificationService::current()->Notify( 40 NotificationType::PRINT_JOB_EVENT, 41 // We know that is is a PrintJob object in this circumstance. 42 Source<PrintJob>(static_cast<PrintJob*>(print_job_.get())), 43 Details<JobEventDetails>(details_)); 44 } 45 46 // The job which originates this notification. 47 scoped_refptr<PrintJobWorkerOwner> print_job_; 48 scoped_refptr<JobEventDetails> details_; 49 }; 50 51 52 PrintJobWorker::PrintJobWorker(PrintJobWorkerOwner* owner) 53 : Thread("Printing_Worker"), 54 owner_(owner) { 55 // The object is created in the IO thread. 56 DCHECK_EQ(owner_->message_loop(), MessageLoop::current()); 57 58 printing_context_.reset(PrintingContext::Create( 59 g_browser_process->GetApplicationLocale())); 60 } 61 62 PrintJobWorker::~PrintJobWorker() { 63 // The object is normally deleted in the UI thread, but when the user 64 // cancels printing or in the case of print preview, the worker is destroyed 65 // on the I/O thread. 66 DCHECK_EQ(owner_->message_loop(), MessageLoop::current()); 67 } 68 69 void PrintJobWorker::SetNewOwner(PrintJobWorkerOwner* new_owner) { 70 DCHECK(page_number_ == PageNumber::npos()); 71 owner_ = new_owner; 72 } 73 74 void PrintJobWorker::GetSettings(bool ask_user_for_settings, 75 gfx::NativeView parent_view, 76 int document_page_count, 77 bool has_selection, 78 bool use_overlays) { 79 DCHECK_EQ(message_loop(), MessageLoop::current()); 80 DCHECK_EQ(page_number_, PageNumber::npos()); 81 82 // Recursive task processing is needed for the dialog in case it needs to be 83 // destroyed by a task. 84 // TODO(thestig): this code is wrong, SetNestableTasksAllowed(true) is needed 85 // on the thread where the PrintDlgEx is called, and definitely both calls 86 // should happen on the same thread. See http://crbug.com/73466 87 // MessageLoop::current()->SetNestableTasksAllowed(true); 88 printing_context_->set_use_overlays(use_overlays); 89 90 if (ask_user_for_settings) { 91 BrowserThread::PostTask( 92 BrowserThread::UI, FROM_HERE, 93 NewRunnableMethod(this, &PrintJobWorker::GetSettingsWithUI, 94 parent_view, document_page_count, 95 has_selection)); 96 } else { 97 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, 98 NewRunnableMethod(this, &PrintJobWorker::UseDefaultSettings)); 99 } 100 } 101 102 void PrintJobWorker::SetSettings(const DictionaryValue* const new_settings) { 103 DCHECK_EQ(message_loop(), MessageLoop::current()); 104 105 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, 106 NewRunnableMethod(this, &PrintJobWorker::UpdatePrintSettings, 107 new_settings)); 108 } 109 110 void PrintJobWorker::UpdatePrintSettings( 111 const DictionaryValue* const new_settings) { 112 // Create new PageRanges based on |new_settings|. 113 PageRanges new_ranges; 114 ListValue* page_range_array; 115 if (new_settings->GetList("pageRange", &page_range_array)) { 116 for (size_t index = 0; index < page_range_array->GetSize(); ++index) { 117 DictionaryValue* dict; 118 if (page_range_array->GetDictionary(index, &dict)) { 119 PageRange range; 120 if (dict->GetInteger("from", &range.from) && 121 dict->GetInteger("to", &range.to)) { 122 // Page numbers are 0-based. 123 range.from--; 124 range.to--; 125 new_ranges.push_back(range); 126 } 127 } 128 } 129 } 130 PrintingContext::Result result = 131 printing_context_->UpdatePrintSettings(*new_settings, new_ranges); 132 delete new_settings; 133 GetSettingsDone(result); 134 } 135 136 void PrintJobWorker::GetSettingsDone(PrintingContext::Result result) { 137 // Most PrintingContext functions may start a message loop and process 138 // message recursively, so disable recursive task processing. 139 // TODO(thestig): see above comment. SetNestableTasksAllowed(false) needs to 140 // be called on the same thread as the previous call. See 141 // http://crbug.com/73466 142 // MessageLoop::current()->SetNestableTasksAllowed(false); 143 144 // We can't use OnFailure() here since owner_ may not support notifications. 145 146 // PrintJob will create the new PrintedDocument. 147 owner_->message_loop()->PostTask(FROM_HERE, NewRunnableMethod( 148 owner_, 149 &PrintJobWorkerOwner::GetSettingsDone, 150 printing_context_->settings(), 151 result)); 152 } 153 154 void PrintJobWorker::GetSettingsWithUI(gfx::NativeView parent_view, 155 int document_page_count, 156 bool has_selection) { 157 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 158 159 printing_context_->AskUserForSettings( 160 parent_view, 161 document_page_count, 162 has_selection, 163 NewCallback(this, &PrintJobWorker::GetSettingsWithUIDone)); 164 } 165 166 void PrintJobWorker::GetSettingsWithUIDone(PrintingContext::Result result) { 167 message_loop()->PostTask(FROM_HERE, NewRunnableMethod( 168 this, &PrintJobWorker::GetSettingsDone, result)); 169 } 170 171 void PrintJobWorker::UseDefaultSettings() { 172 PrintingContext::Result result = printing_context_->UseDefaultSettings(); 173 GetSettingsDone(result); 174 } 175 176 void PrintJobWorker::StartPrinting(PrintedDocument* new_document) { 177 DCHECK_EQ(message_loop(), MessageLoop::current()); 178 DCHECK_EQ(page_number_, PageNumber::npos()); 179 DCHECK_EQ(document_, new_document); 180 DCHECK(document_.get()); 181 DCHECK(new_document->settings().Equals(printing_context_->settings())); 182 183 if (!document_.get() || page_number_ != PageNumber::npos() || 184 document_ != new_document) { 185 return; 186 } 187 188 PrintingContext::Result result = 189 printing_context_->NewDocument(document_->name()); 190 if (result != PrintingContext::OK) { 191 OnFailure(); 192 return; 193 } 194 195 // Try to print already cached data. It may already have been generated for 196 // the print preview. 197 OnNewPage(); 198 // Don't touch this anymore since the instance could be destroyed. It happens 199 // if all the pages are printed a one sweep and the client doesn't have a 200 // handle to us anymore. There's a timing issue involved between the worker 201 // thread and the UI thread. Take no chance. 202 } 203 204 void PrintJobWorker::OnDocumentChanged(PrintedDocument* new_document) { 205 DCHECK_EQ(message_loop(), MessageLoop::current()); 206 DCHECK_EQ(page_number_, PageNumber::npos()); 207 DCHECK(!new_document || 208 new_document->settings().Equals(printing_context_->settings())); 209 210 if (page_number_ != PageNumber::npos()) 211 return; 212 213 document_ = new_document; 214 } 215 216 void PrintJobWorker::OnNewPage() { 217 if (!document_.get()) { 218 // Spurious message. 219 return; 220 } 221 // message_loop() could return NULL when the print job is cancelled. 222 DCHECK_EQ(message_loop(), MessageLoop::current()); 223 224 if (page_number_ == PageNumber::npos()) { 225 // Find first page to print. 226 int page_count = document_->page_count(); 227 if (!page_count) { 228 // We still don't know how many pages the document contains. We can't 229 // start to print the document yet since the header/footer may refer to 230 // the document's page count. 231 return; 232 } 233 // We have enough information to initialize page_number_. 234 page_number_.Init(document_->settings(), page_count); 235 } 236 DCHECK_NE(page_number_, PageNumber::npos()); 237 238 for (;;) { 239 // Is the page available? 240 scoped_refptr<PrintedPage> page; 241 if (!document_->GetPage(page_number_.ToInt(), &page)) { 242 // We need to wait for the page to be available. 243 MessageLoop::current()->PostDelayedTask( 244 FROM_HERE, 245 NewRunnableMethod(this, &PrintJobWorker::OnNewPage), 246 500); 247 break; 248 } 249 // The page is there, print it. 250 SpoolPage(*page); 251 ++page_number_; 252 if (page_number_ == PageNumber::npos()) { 253 OnDocumentDone(); 254 // Don't touch this anymore since the instance could be destroyed. 255 break; 256 } 257 } 258 } 259 260 void PrintJobWorker::Cancel() { 261 // This is the only function that can be called from any thread. 262 printing_context_->Cancel(); 263 // Cannot touch any member variable since we don't know in which thread 264 // context we run. 265 } 266 267 void PrintJobWorker::OnDocumentDone() { 268 DCHECK_EQ(message_loop(), MessageLoop::current()); 269 DCHECK_EQ(page_number_, PageNumber::npos()); 270 DCHECK(document_.get()); 271 272 if (printing_context_->DocumentDone() != PrintingContext::OK) { 273 OnFailure(); 274 return; 275 } 276 277 // Tell everyone! 278 NotificationTask* task = new NotificationTask(); 279 task->Init(owner_, 280 JobEventDetails::DOC_DONE, 281 document_.get(), 282 NULL); 283 owner_->message_loop()->PostTask(FROM_HERE, task); 284 285 // Makes sure the variables are reinitialized. 286 document_ = NULL; 287 } 288 289 void PrintJobWorker::SpoolPage(PrintedPage& page) { 290 DCHECK_EQ(message_loop(), MessageLoop::current()); 291 DCHECK_NE(page_number_, PageNumber::npos()); 292 293 // Signal everyone that the page is about to be printed. 294 NotificationTask* task = new NotificationTask(); 295 task->Init(owner_, 296 JobEventDetails::NEW_PAGE, 297 document_.get(), 298 &page); 299 owner_->message_loop()->PostTask(FROM_HERE, task); 300 301 // Preprocess. 302 if (printing_context_->NewPage() != PrintingContext::OK) { 303 OnFailure(); 304 return; 305 } 306 307 // Actual printing. 308 #if defined(OS_WIN) || defined(OS_MACOSX) 309 document_->RenderPrintedPage(page, printing_context_->context()); 310 #elif defined(OS_POSIX) 311 document_->RenderPrintedPage(page, printing_context_.get()); 312 #endif 313 314 // Postprocess. 315 if (printing_context_->PageDone() != PrintingContext::OK) { 316 OnFailure(); 317 return; 318 } 319 320 // Signal everyone that the page is printed. 321 task = new NotificationTask(); 322 task->Init(owner_, 323 JobEventDetails::PAGE_DONE, 324 document_.get(), 325 &page); 326 owner_->message_loop()->PostTask(FROM_HERE, task); 327 } 328 329 void PrintJobWorker::OnFailure() { 330 DCHECK_EQ(message_loop(), MessageLoop::current()); 331 332 // We may loose our last reference by broadcasting the FAILED event. 333 scoped_refptr<PrintJobWorkerOwner> handle(owner_); 334 335 NotificationTask* task = new NotificationTask(); 336 task->Init(owner_, 337 JobEventDetails::FAILED, 338 document_.get(), 339 NULL); 340 owner_->message_loop()->PostTask(FROM_HERE, task); 341 Cancel(); 342 343 // Makes sure the variables are reinitialized. 344 document_ = NULL; 345 page_number_ = PageNumber::npos(); 346 } 347 348 } // namespace printing 349 350 void RunnableMethodTraits<printing::PrintJobWorker>::RetainCallee( 351 printing::PrintJobWorker* obj) { 352 DCHECK(!owner_.get()); 353 owner_ = obj->owner_; 354 } 355 356 void RunnableMethodTraits<printing::PrintJobWorker>::ReleaseCallee( 357 printing::PrintJobWorker* obj) { 358 DCHECK_EQ(owner_, obj->owner_); 359 owner_ = NULL; 360 } 361