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