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_view_manager.h" 6 7 #include "base/memory/scoped_ptr.h" 8 #include "base/utf_string_conversions.h" 9 #include "chrome/browser/browser_process.h" 10 #include "chrome/browser/printing/print_job.h" 11 #include "chrome/browser/printing/print_job_manager.h" 12 #include "chrome/browser/printing/print_preview_tab_controller.h" 13 #include "chrome/browser/printing/printer_query.h" 14 #include "chrome/browser/ui/webui/print_preview_ui.h" 15 #include "chrome/common/print_messages.h" 16 #include "content/browser/renderer_host/render_view_host.h" 17 #include "content/browser/tab_contents/navigation_entry.h" 18 #include "content/browser/tab_contents/tab_contents.h" 19 #include "content/common/notification_details.h" 20 #include "content/common/notification_source.h" 21 #include "grit/generated_resources.h" 22 #include "printing/metafile.h" 23 #include "printing/metafile_impl.h" 24 #include "printing/printed_document.h" 25 #include "ui/base/l10n/l10n_util.h" 26 27 using base::TimeDelta; 28 29 namespace { 30 31 string16 GenerateRenderSourceName(TabContents* tab_contents) { 32 string16 name(tab_contents->GetTitle()); 33 if (name.empty()) 34 name = l10n_util::GetStringUTF16(IDS_DEFAULT_PRINT_DOCUMENT_TITLE); 35 return name; 36 } 37 38 } // namespace 39 40 namespace printing { 41 42 PrintViewManager::PrintViewManager(TabContents* tab_contents) 43 : TabContentsObserver(tab_contents), 44 number_pages_(0), 45 waiting_to_print_(false), 46 printing_succeeded_(false), 47 inside_inner_message_loop_(false), 48 is_title_overridden_(false) { 49 #if defined(OS_POSIX) && !defined(OS_MACOSX) 50 expecting_first_page_ = true; 51 #endif 52 } 53 54 PrintViewManager::~PrintViewManager() { 55 DisconnectFromCurrentPrintJob(); 56 } 57 58 bool PrintViewManager::PrintNow() { 59 // Don't print interstitials. 60 if (tab_contents()->showing_interstitial_page()) 61 return false; 62 63 return Send(new PrintMsg_PrintPages(routing_id())); 64 } 65 66 void PrintViewManager::StopNavigation() { 67 // Cancel the current job, wait for the worker to finish. 68 TerminatePrintJob(true); 69 } 70 71 void PrintViewManager::RenderViewGone() { 72 if (!print_job_.get()) 73 return; 74 75 scoped_refptr<PrintedDocument> document(print_job_->document()); 76 if (document) { 77 // If IsComplete() returns false, the document isn't completely rendered. 78 // Since our renderer is gone, there's nothing to do, cancel it. Otherwise, 79 // the print job may finish without problem. 80 TerminatePrintJob(!document->IsComplete()); 81 } 82 } 83 84 void PrintViewManager::OverrideTitle(TabContents* tab_contents) { 85 is_title_overridden_ = true; 86 overridden_title_ = GenerateRenderSourceName(tab_contents); 87 } 88 89 string16 PrintViewManager::RenderSourceName() { 90 if (is_title_overridden_) 91 return overridden_title_; 92 return GenerateRenderSourceName(tab_contents()); 93 } 94 95 GURL PrintViewManager::RenderSourceUrl() { 96 NavigationEntry* entry = tab_contents()->controller().GetActiveEntry(); 97 if (entry) 98 return entry->virtual_url(); 99 return GURL(); 100 } 101 102 void PrintViewManager::OnDidGetPrintedPagesCount(int cookie, int number_pages) { 103 DCHECK_GT(cookie, 0); 104 DCHECK_GT(number_pages, 0); 105 number_pages_ = number_pages; 106 if (!OpportunisticallyCreatePrintJob(cookie)) 107 return; 108 109 PrintedDocument* document = print_job_->document(); 110 if (!document || cookie != document->cookie()) { 111 // Out of sync. It may happens since we are completely asynchronous. Old 112 // spurious message can happen if one of the processes is overloaded. 113 return; 114 } 115 } 116 117 void PrintViewManager::OnDidPrintPage( 118 const PrintHostMsg_DidPrintPage_Params& params) { 119 if (!OpportunisticallyCreatePrintJob(params.document_cookie)) 120 return; 121 122 PrintedDocument* document = print_job_->document(); 123 if (!document || params.document_cookie != document->cookie()) { 124 // Out of sync. It may happen since we are completely asynchronous. Old 125 // spurious messages can be received if one of the processes is overloaded. 126 return; 127 } 128 129 #if defined(OS_WIN) 130 // http://msdn2.microsoft.com/en-us/library/ms535522.aspx 131 // Windows 2000/XP: When a page in a spooled file exceeds approximately 350 132 // MB, it can fail to print and not send an error message. 133 if (params.data_size && params.data_size >= 350*1024*1024) { 134 NOTREACHED() << "size:" << params.data_size; 135 TerminatePrintJob(true); 136 tab_contents()->Stop(); 137 return; 138 } 139 #endif 140 141 #if defined(OS_WIN) || defined(OS_MACOSX) 142 const bool metafile_must_be_valid = true; 143 #elif defined(OS_POSIX) 144 const bool metafile_must_be_valid = expecting_first_page_; 145 expecting_first_page_ = false; 146 #endif 147 148 base::SharedMemory shared_buf(params.metafile_data_handle, true); 149 if (metafile_must_be_valid) { 150 if (!shared_buf.Map(params.data_size)) { 151 NOTREACHED() << "couldn't map"; 152 tab_contents()->Stop(); 153 return; 154 } 155 } 156 157 scoped_ptr<Metafile> metafile(new NativeMetafile); 158 if (metafile_must_be_valid) { 159 if (!metafile->InitFromData(shared_buf.memory(), params.data_size)) { 160 NOTREACHED() << "Invalid metafile header"; 161 tab_contents()->Stop(); 162 return; 163 } 164 } 165 166 // Update the rendered document. It will send notifications to the listener. 167 document->SetPage(params.page_number, 168 metafile.release(), 169 params.actual_shrink, 170 params.page_size, 171 params.content_area, 172 params.has_visible_overlays); 173 174 ShouldQuitFromInnerMessageLoop(); 175 } 176 177 bool PrintViewManager::OnMessageReceived(const IPC::Message& message) { 178 bool handled = true; 179 IPC_BEGIN_MESSAGE_MAP(PrintViewManager, message) 180 IPC_MESSAGE_HANDLER(PrintHostMsg_DidGetPrintedPagesCount, 181 OnDidGetPrintedPagesCount) 182 IPC_MESSAGE_HANDLER(PrintHostMsg_DidPrintPage, OnDidPrintPage) 183 IPC_MESSAGE_UNHANDLED(handled = false) 184 IPC_END_MESSAGE_MAP() 185 return handled; 186 } 187 188 void PrintViewManager::Observe(NotificationType type, 189 const NotificationSource& source, 190 const NotificationDetails& details) { 191 switch (type.value) { 192 case NotificationType::PRINT_JOB_EVENT: { 193 OnNotifyPrintJobEvent(*Details<JobEventDetails>(details).ptr()); 194 break; 195 } 196 default: { 197 NOTREACHED(); 198 break; 199 } 200 } 201 } 202 203 void PrintViewManager::OnNotifyPrintJobEvent( 204 const JobEventDetails& event_details) { 205 switch (event_details.type()) { 206 case JobEventDetails::FAILED: { 207 TerminatePrintJob(true); 208 break; 209 } 210 case JobEventDetails::USER_INIT_DONE: 211 case JobEventDetails::DEFAULT_INIT_DONE: 212 case JobEventDetails::USER_INIT_CANCELED: { 213 NOTREACHED(); 214 break; 215 } 216 case JobEventDetails::ALL_PAGES_REQUESTED: { 217 ShouldQuitFromInnerMessageLoop(); 218 break; 219 } 220 case JobEventDetails::NEW_DOC: 221 case JobEventDetails::NEW_PAGE: 222 case JobEventDetails::PAGE_DONE: { 223 // Don't care about the actual printing process. 224 break; 225 } 226 case JobEventDetails::DOC_DONE: { 227 waiting_to_print_ = false; 228 break; 229 } 230 case JobEventDetails::JOB_DONE: { 231 // Printing is done, we don't need it anymore. 232 // print_job_->is_job_pending() may still be true, depending on the order 233 // of object registration. 234 printing_succeeded_ = true; 235 ReleasePrintJob(); 236 break; 237 } 238 default: { 239 NOTREACHED(); 240 break; 241 } 242 } 243 } 244 245 bool PrintViewManager::RenderAllMissingPagesNow() { 246 if (!print_job_.get() || !print_job_->is_job_pending()) { 247 DCHECK_EQ(waiting_to_print_, false); 248 return false; 249 } 250 251 // We can't print if there is no renderer. 252 if (!tab_contents() || 253 !tab_contents()->render_view_host() || 254 !tab_contents()->render_view_host()->IsRenderViewLive()) { 255 waiting_to_print_ = false; 256 return false; 257 } 258 259 // Is the document already complete? 260 if (print_job_->document() && print_job_->document()->IsComplete()) { 261 waiting_to_print_ = false; 262 printing_succeeded_ = true; 263 return true; 264 } 265 266 // TabContents is either dying or a second consecutive request to print 267 // happened before the first had time to finish. We need to render all the 268 // pages in an hurry if a print_job_ is still pending. No need to wait for it 269 // to actually spool the pages, only to have the renderer generate them. Run 270 // a message loop until we get our signal that the print job is satisfied. 271 // PrintJob will send a ALL_PAGES_REQUESTED after having received all the 272 // pages it needs. MessageLoop::current()->Quit() will be called as soon as 273 // print_job_->document()->IsComplete() is true on either ALL_PAGES_REQUESTED 274 // or in DidPrintPage(). The check is done in 275 // ShouldQuitFromInnerMessageLoop(). 276 // BLOCKS until all the pages are received. (Need to enable recursive task) 277 if (!RunInnerMessageLoop()) { 278 // This function is always called from DisconnectFromCurrentPrintJob() so we 279 // know that the job will be stopped/canceled in any case. 280 return false; 281 } 282 return true; 283 } 284 285 void PrintViewManager::ShouldQuitFromInnerMessageLoop() { 286 // Look at the reason. 287 DCHECK(print_job_->document()); 288 if (print_job_->document() && 289 print_job_->document()->IsComplete() && 290 inside_inner_message_loop_) { 291 // We are in a message loop created by RenderAllMissingPagesNow. Quit from 292 // it. 293 MessageLoop::current()->Quit(); 294 inside_inner_message_loop_ = false; 295 waiting_to_print_ = false; 296 } 297 } 298 299 bool PrintViewManager::CreateNewPrintJob(PrintJobWorkerOwner* job) { 300 DCHECK(!inside_inner_message_loop_); 301 if (waiting_to_print_) { 302 // We can't help; we are waiting for a print job initialization. The user is 303 // button bashing. The only thing we could do is to batch up the requests. 304 return false; 305 } 306 307 // Disconnect the current print_job_. 308 DisconnectFromCurrentPrintJob(); 309 310 // We can't print if there is no renderer. 311 if (!tab_contents()->render_view_host() || 312 !tab_contents()->render_view_host()->IsRenderViewLive()) { 313 return false; 314 } 315 316 // Ask the renderer to generate the print preview, create the print preview 317 // view and switch to it, initialize the printer and show the print dialog. 318 DCHECK(!print_job_.get()); 319 DCHECK(job); 320 if (!job) 321 return false; 322 323 print_job_ = new PrintJob(); 324 print_job_->Initialize(job, this, number_pages_); 325 registrar_.Add(this, NotificationType::PRINT_JOB_EVENT, 326 Source<PrintJob>(print_job_.get())); 327 printing_succeeded_ = false; 328 return true; 329 } 330 331 void PrintViewManager::DisconnectFromCurrentPrintJob() { 332 // Make sure all the necessary rendered page are done. Don't bother with the 333 // return value. 334 bool result = RenderAllMissingPagesNow(); 335 336 // Verify that assertion. 337 if (print_job_.get() && 338 print_job_->document() && 339 !print_job_->document()->IsComplete()) { 340 DCHECK(!result); 341 // That failed. 342 TerminatePrintJob(true); 343 } else { 344 // DO NOT wait for the job to finish. 345 ReleasePrintJob(); 346 } 347 #if defined(OS_POSIX) && !defined(OS_MACOSX) 348 expecting_first_page_ = true; 349 #endif 350 } 351 352 void PrintViewManager::PrintingDone(bool success) { 353 if (!print_job_.get() || !tab_contents()) 354 return; 355 RenderViewHost* rvh = tab_contents()->render_view_host(); 356 rvh->Send(new PrintMsg_PrintingDone(rvh->routing_id(), 357 print_job_->cookie(), 358 success)); 359 } 360 361 void PrintViewManager::TerminatePrintJob(bool cancel) { 362 if (!print_job_.get()) 363 return; 364 365 if (cancel) { 366 // We don't need the metafile data anymore because the printing is canceled. 367 print_job_->Cancel(); 368 waiting_to_print_ = false; 369 inside_inner_message_loop_ = false; 370 } else { 371 DCHECK(!inside_inner_message_loop_); 372 DCHECK(!print_job_->document() || print_job_->document()->IsComplete() || 373 !waiting_to_print_); 374 375 // TabContents is either dying or navigating elsewhere. We need to render 376 // all the pages in an hurry if a print job is still pending. This does the 377 // trick since it runs a blocking message loop: 378 print_job_->Stop(); 379 } 380 ReleasePrintJob(); 381 } 382 383 void PrintViewManager::ReleasePrintJob() { 384 DCHECK_EQ(waiting_to_print_, false); 385 if (!print_job_.get()) 386 return; 387 388 PrintingDone(printing_succeeded_); 389 390 registrar_.Remove(this, NotificationType::PRINT_JOB_EVENT, 391 Source<PrintJob>(print_job_.get())); 392 print_job_->DisconnectSource(); 393 // Don't close the worker thread. 394 print_job_ = NULL; 395 } 396 397 bool PrintViewManager::RunInnerMessageLoop() { 398 // This value may actually be too low: 399 // 400 // - If we're looping because of printer settings initializaton, the premise 401 // here is that some poor users have their print server away on a VPN over 402 // dialup. In this situation, the simple fact of opening the printer can be 403 // dead slow. On the other side, we don't want to die infinitely for a real 404 // network error. Give the printer 60 seconds to comply. 405 // 406 // - If we're looping because of renderer page generation, the renderer could 407 // be cpu bound, the page overly complex/large or the system just 408 // memory-bound. 409 static const int kPrinterSettingsTimeout = 60000; 410 base::OneShotTimer<MessageLoop> quit_timer; 411 quit_timer.Start(TimeDelta::FromMilliseconds(kPrinterSettingsTimeout), 412 MessageLoop::current(), &MessageLoop::Quit); 413 414 inside_inner_message_loop_ = true; 415 416 // Need to enable recursive task. 417 bool old_state = MessageLoop::current()->NestableTasksAllowed(); 418 MessageLoop::current()->SetNestableTasksAllowed(true); 419 MessageLoop::current()->Run(); 420 // Restore task state. 421 MessageLoop::current()->SetNestableTasksAllowed(old_state); 422 423 bool success = true; 424 if (inside_inner_message_loop_) { 425 // Ok we timed out. That's sad. 426 inside_inner_message_loop_ = false; 427 success = false; 428 } 429 430 return success; 431 } 432 433 bool PrintViewManager::OpportunisticallyCreatePrintJob(int cookie) { 434 if (print_job_.get()) 435 return true; 436 437 if (!cookie) { 438 // Out of sync. It may happens since we are completely asynchronous. Old 439 // spurious message can happen if one of the processes is overloaded. 440 return false; 441 } 442 443 // The job was initiated by a script. Time to get the corresponding worker 444 // thread. 445 scoped_refptr<PrinterQuery> queued_query; 446 g_browser_process->print_job_manager()->PopPrinterQuery(cookie, 447 &queued_query); 448 DCHECK(queued_query.get()); 449 if (!queued_query.get()) 450 return false; 451 452 if (!CreateNewPrintJob(queued_query.get())) { 453 // Don't kill anything. 454 return false; 455 } 456 457 // Settings are already loaded. Go ahead. This will set 458 // print_job_->is_job_pending() to true. 459 print_job_->StartPrinting(); 460 return true; 461 } 462 463 } // namespace printing 464