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