1 // Copyright 2013 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_base.h" 6 7 #include <map> 8 9 #include "base/bind.h" 10 #include "base/memory/scoped_ptr.h" 11 #include "base/prefs/pref_service.h" 12 #include "base/strings/utf_string_conversions.h" 13 #include "base/timer/timer.h" 14 #include "chrome/browser/browser_process.h" 15 #include "chrome/browser/chrome_notification_types.h" 16 #include "chrome/browser/printing/print_job.h" 17 #include "chrome/browser/printing/print_job_manager.h" 18 #include "chrome/browser/printing/printer_query.h" 19 #include "chrome/browser/profiles/profile.h" 20 #include "chrome/common/pref_names.h" 21 #include "chrome/common/print_messages.h" 22 #include "content/public/browser/browser_thread.h" 23 #include "content/public/browser/notification_details.h" 24 #include "content/public/browser/notification_service.h" 25 #include "content/public/browser/notification_source.h" 26 #include "content/public/browser/render_view_host.h" 27 #include "content/public/browser/web_contents.h" 28 #include "content/public/browser/web_contents_view.h" 29 #include "grit/generated_resources.h" 30 #include "printing/metafile_impl.h" 31 #include "printing/printed_document.h" 32 #include "ui/base/l10n/l10n_util.h" 33 34 #if defined(OS_WIN) 35 #include "base/command_line.h" 36 #include "chrome/common/chrome_switches.h" 37 #endif 38 39 #if defined(ENABLE_FULL_PRINTING) 40 #include "chrome/browser/printing/print_error_dialog.h" 41 #endif 42 43 using base::TimeDelta; 44 using content::BrowserThread; 45 46 #if defined(OS_WIN) 47 // Limits memory usage by raster to 64 MiB. 48 const int kMaxRasterSizeInPixels = 16*1024*1024; 49 #endif 50 51 namespace printing { 52 53 PrintViewManagerBase::PrintViewManagerBase(content::WebContents* web_contents) 54 : content::WebContentsObserver(web_contents), 55 number_pages_(0), 56 printing_succeeded_(false), 57 inside_inner_message_loop_(false), 58 cookie_(0), 59 queue_(g_browser_process->print_job_manager()->queue()) { 60 DCHECK(queue_); 61 #if defined(OS_POSIX) && !defined(OS_MACOSX) 62 expecting_first_page_ = true; 63 #endif 64 Profile* profile = 65 Profile::FromBrowserContext(web_contents->GetBrowserContext()); 66 printing_enabled_.Init( 67 prefs::kPrintingEnabled, 68 profile->GetPrefs(), 69 base::Bind(&PrintViewManagerBase::UpdateScriptedPrintingBlocked, 70 base::Unretained(this))); 71 } 72 73 PrintViewManagerBase::~PrintViewManagerBase() { 74 ReleasePrinterQuery(); 75 DisconnectFromCurrentPrintJob(); 76 } 77 78 bool PrintViewManagerBase::PrintNow() { 79 return PrintNowInternal(new PrintMsg_PrintPages(routing_id())); 80 } 81 82 void PrintViewManagerBase::UpdateScriptedPrintingBlocked() { 83 Send(new PrintMsg_SetScriptedPrintingBlocked( 84 routing_id(), 85 !printing_enabled_.GetValue())); 86 } 87 88 void PrintViewManagerBase::NavigationStopped() { 89 // Cancel the current job, wait for the worker to finish. 90 TerminatePrintJob(true); 91 } 92 93 void PrintViewManagerBase::RenderProcessGone(base::TerminationStatus status) { 94 ReleasePrinterQuery(); 95 96 if (!print_job_.get()) 97 return; 98 99 scoped_refptr<PrintedDocument> document(print_job_->document()); 100 if (document.get()) { 101 // If IsComplete() returns false, the document isn't completely rendered. 102 // Since our renderer is gone, there's nothing to do, cancel it. Otherwise, 103 // the print job may finish without problem. 104 TerminatePrintJob(!document->IsComplete()); 105 } 106 } 107 108 base::string16 PrintViewManagerBase::RenderSourceName() { 109 base::string16 name(web_contents()->GetTitle()); 110 if (name.empty()) 111 name = l10n_util::GetStringUTF16(IDS_DEFAULT_PRINT_DOCUMENT_TITLE); 112 return name; 113 } 114 115 void PrintViewManagerBase::OnDidGetPrintedPagesCount(int cookie, 116 int number_pages) { 117 DCHECK_GT(cookie, 0); 118 DCHECK_GT(number_pages, 0); 119 number_pages_ = number_pages; 120 OpportunisticallyCreatePrintJob(cookie); 121 } 122 123 void PrintViewManagerBase::OnDidGetDocumentCookie(int cookie) { 124 cookie_ = cookie; 125 } 126 127 void PrintViewManagerBase::OnDidPrintPage( 128 const PrintHostMsg_DidPrintPage_Params& params) { 129 if (!OpportunisticallyCreatePrintJob(params.document_cookie)) 130 return; 131 132 PrintedDocument* document = print_job_->document(); 133 if (!document || params.document_cookie != document->cookie()) { 134 // Out of sync. It may happen since we are completely asynchronous. Old 135 // spurious messages can be received if one of the processes is overloaded. 136 return; 137 } 138 139 #if defined(OS_WIN) || defined(OS_MACOSX) 140 const bool metafile_must_be_valid = true; 141 #elif defined(OS_POSIX) 142 const bool metafile_must_be_valid = expecting_first_page_; 143 expecting_first_page_ = false; 144 #endif 145 146 base::SharedMemory shared_buf(params.metafile_data_handle, true); 147 if (metafile_must_be_valid) { 148 if (!shared_buf.Map(params.data_size)) { 149 NOTREACHED() << "couldn't map"; 150 web_contents()->Stop(); 151 return; 152 } 153 } 154 155 scoped_ptr<NativeMetafile> metafile(new NativeMetafile); 156 if (metafile_must_be_valid) { 157 if (!metafile->InitFromData(shared_buf.memory(), params.data_size)) { 158 NOTREACHED() << "Invalid metafile header"; 159 web_contents()->Stop(); 160 return; 161 } 162 } 163 164 #if defined(OS_WIN) 165 bool big_emf = (params.data_size && params.data_size >= kMetafileMaxSize); 166 const CommandLine* cmdline = CommandLine::ForCurrentProcess(); 167 int raster_size = std::min(params.page_size.GetArea(), 168 kMaxRasterSizeInPixels); 169 if (big_emf || (cmdline && cmdline->HasSwitch(switches::kPrintRaster))) { 170 scoped_ptr<NativeMetafile> raster_metafile( 171 metafile->RasterizeMetafile(raster_size)); 172 if (raster_metafile.get()) { 173 metafile.swap(raster_metafile); 174 } else if (big_emf) { 175 // Don't fall back to emf here. 176 NOTREACHED() << "size:" << params.data_size; 177 TerminatePrintJob(true); 178 web_contents()->Stop(); 179 return; 180 } 181 } 182 #endif 183 184 // Update the rendered document. It will send notifications to the listener. 185 document->SetPage(params.page_number, 186 metafile.release(), 187 params.actual_shrink, 188 params.page_size, 189 params.content_area); 190 191 ShouldQuitFromInnerMessageLoop(); 192 } 193 194 void PrintViewManagerBase::OnPrintingFailed(int cookie) { 195 if (cookie != cookie_) { 196 NOTREACHED(); 197 return; 198 } 199 200 #if defined(ENABLE_FULL_PRINTING) 201 chrome::ShowPrintErrorDialog( 202 web_contents()->GetView()->GetTopLevelNativeWindow()); 203 #endif 204 205 ReleasePrinterQuery(); 206 207 content::NotificationService::current()->Notify( 208 chrome::NOTIFICATION_PRINT_JOB_RELEASED, 209 content::Source<content::WebContents>(web_contents()), 210 content::NotificationService::NoDetails()); 211 } 212 213 void PrintViewManagerBase::DidStartLoading( 214 content::RenderViewHost* render_view_host) { 215 UpdateScriptedPrintingBlocked(); 216 } 217 218 bool PrintViewManagerBase::OnMessageReceived(const IPC::Message& message) { 219 bool handled = true; 220 IPC_BEGIN_MESSAGE_MAP(PrintViewManagerBase, message) 221 IPC_MESSAGE_HANDLER(PrintHostMsg_DidGetPrintedPagesCount, 222 OnDidGetPrintedPagesCount) 223 IPC_MESSAGE_HANDLER(PrintHostMsg_DidGetDocumentCookie, 224 OnDidGetDocumentCookie) 225 IPC_MESSAGE_HANDLER(PrintHostMsg_DidPrintPage, OnDidPrintPage) 226 IPC_MESSAGE_HANDLER(PrintHostMsg_PrintingFailed, OnPrintingFailed) 227 IPC_MESSAGE_UNHANDLED(handled = false) 228 IPC_END_MESSAGE_MAP() 229 return handled; 230 } 231 232 void PrintViewManagerBase::Observe( 233 int type, 234 const content::NotificationSource& source, 235 const content::NotificationDetails& details) { 236 switch (type) { 237 case chrome::NOTIFICATION_PRINT_JOB_EVENT: { 238 OnNotifyPrintJobEvent(*content::Details<JobEventDetails>(details).ptr()); 239 break; 240 } 241 default: { 242 NOTREACHED(); 243 break; 244 } 245 } 246 } 247 248 void PrintViewManagerBase::OnNotifyPrintJobEvent( 249 const JobEventDetails& event_details) { 250 switch (event_details.type()) { 251 case JobEventDetails::FAILED: { 252 TerminatePrintJob(true); 253 254 content::NotificationService::current()->Notify( 255 chrome::NOTIFICATION_PRINT_JOB_RELEASED, 256 content::Source<content::WebContents>(web_contents()), 257 content::NotificationService::NoDetails()); 258 break; 259 } 260 case JobEventDetails::USER_INIT_DONE: 261 case JobEventDetails::DEFAULT_INIT_DONE: 262 case JobEventDetails::USER_INIT_CANCELED: { 263 NOTREACHED(); 264 break; 265 } 266 case JobEventDetails::ALL_PAGES_REQUESTED: { 267 ShouldQuitFromInnerMessageLoop(); 268 break; 269 } 270 case JobEventDetails::NEW_DOC: 271 case JobEventDetails::NEW_PAGE: 272 case JobEventDetails::PAGE_DONE: 273 case JobEventDetails::DOC_DONE: { 274 // Don't care about the actual printing process. 275 break; 276 } 277 case JobEventDetails::JOB_DONE: { 278 // Printing is done, we don't need it anymore. 279 // print_job_->is_job_pending() may still be true, depending on the order 280 // of object registration. 281 printing_succeeded_ = true; 282 ReleasePrintJob(); 283 284 content::NotificationService::current()->Notify( 285 chrome::NOTIFICATION_PRINT_JOB_RELEASED, 286 content::Source<content::WebContents>(web_contents()), 287 content::NotificationService::NoDetails()); 288 break; 289 } 290 default: { 291 NOTREACHED(); 292 break; 293 } 294 } 295 } 296 297 bool PrintViewManagerBase::RenderAllMissingPagesNow() { 298 if (!print_job_.get() || !print_job_->is_job_pending()) 299 return false; 300 301 // We can't print if there is no renderer. 302 if (!web_contents() || 303 !web_contents()->GetRenderViewHost() || 304 !web_contents()->GetRenderViewHost()->IsRenderViewLive()) { 305 return false; 306 } 307 308 // Is the document already complete? 309 if (print_job_->document() && print_job_->document()->IsComplete()) { 310 printing_succeeded_ = true; 311 return true; 312 } 313 314 // WebContents is either dying or a second consecutive request to print 315 // happened before the first had time to finish. We need to render all the 316 // pages in an hurry if a print_job_ is still pending. No need to wait for it 317 // to actually spool the pages, only to have the renderer generate them. Run 318 // a message loop until we get our signal that the print job is satisfied. 319 // PrintJob will send a ALL_PAGES_REQUESTED after having received all the 320 // pages it needs. MessageLoop::current()->Quit() will be called as soon as 321 // print_job_->document()->IsComplete() is true on either ALL_PAGES_REQUESTED 322 // or in DidPrintPage(). The check is done in 323 // ShouldQuitFromInnerMessageLoop(). 324 // BLOCKS until all the pages are received. (Need to enable recursive task) 325 if (!RunInnerMessageLoop()) { 326 // This function is always called from DisconnectFromCurrentPrintJob() so we 327 // know that the job will be stopped/canceled in any case. 328 return false; 329 } 330 return true; 331 } 332 333 void PrintViewManagerBase::ShouldQuitFromInnerMessageLoop() { 334 // Look at the reason. 335 DCHECK(print_job_->document()); 336 if (print_job_->document() && 337 print_job_->document()->IsComplete() && 338 inside_inner_message_loop_) { 339 // We are in a message loop created by RenderAllMissingPagesNow. Quit from 340 // it. 341 base::MessageLoop::current()->Quit(); 342 inside_inner_message_loop_ = false; 343 } 344 } 345 346 bool PrintViewManagerBase::CreateNewPrintJob(PrintJobWorkerOwner* job) { 347 DCHECK(!inside_inner_message_loop_); 348 349 // Disconnect the current print_job_. 350 DisconnectFromCurrentPrintJob(); 351 352 // We can't print if there is no renderer. 353 if (!web_contents()->GetRenderViewHost() || 354 !web_contents()->GetRenderViewHost()->IsRenderViewLive()) { 355 return false; 356 } 357 358 // Ask the renderer to generate the print preview, create the print preview 359 // view and switch to it, initialize the printer and show the print dialog. 360 DCHECK(!print_job_.get()); 361 DCHECK(job); 362 if (!job) 363 return false; 364 365 print_job_ = new PrintJob(); 366 print_job_->Initialize(job, this, number_pages_); 367 registrar_.Add(this, chrome::NOTIFICATION_PRINT_JOB_EVENT, 368 content::Source<PrintJob>(print_job_.get())); 369 printing_succeeded_ = false; 370 return true; 371 } 372 373 void PrintViewManagerBase::DisconnectFromCurrentPrintJob() { 374 // Make sure all the necessary rendered page are done. Don't bother with the 375 // return value. 376 bool result = RenderAllMissingPagesNow(); 377 378 // Verify that assertion. 379 if (print_job_.get() && 380 print_job_->document() && 381 !print_job_->document()->IsComplete()) { 382 DCHECK(!result); 383 // That failed. 384 TerminatePrintJob(true); 385 } else { 386 // DO NOT wait for the job to finish. 387 ReleasePrintJob(); 388 } 389 #if defined(OS_POSIX) && !defined(OS_MACOSX) 390 expecting_first_page_ = true; 391 #endif 392 } 393 394 void PrintViewManagerBase::PrintingDone(bool success) { 395 if (!print_job_.get()) 396 return; 397 Send(new PrintMsg_PrintingDone(routing_id(), success)); 398 } 399 400 void PrintViewManagerBase::TerminatePrintJob(bool cancel) { 401 if (!print_job_.get()) 402 return; 403 404 if (cancel) { 405 // We don't need the metafile data anymore because the printing is canceled. 406 print_job_->Cancel(); 407 inside_inner_message_loop_ = false; 408 } else { 409 DCHECK(!inside_inner_message_loop_); 410 DCHECK(!print_job_->document() || print_job_->document()->IsComplete()); 411 412 // WebContents is either dying or navigating elsewhere. We need to render 413 // all the pages in an hurry if a print job is still pending. This does the 414 // trick since it runs a blocking message loop: 415 print_job_->Stop(); 416 } 417 ReleasePrintJob(); 418 } 419 420 void PrintViewManagerBase::ReleasePrintJob() { 421 if (!print_job_.get()) 422 return; 423 424 PrintingDone(printing_succeeded_); 425 426 registrar_.Remove(this, chrome::NOTIFICATION_PRINT_JOB_EVENT, 427 content::Source<PrintJob>(print_job_.get())); 428 print_job_->DisconnectSource(); 429 // Don't close the worker thread. 430 print_job_ = NULL; 431 } 432 433 bool PrintViewManagerBase::RunInnerMessageLoop() { 434 // This value may actually be too low: 435 // 436 // - If we're looping because of printer settings initialization, the premise 437 // here is that some poor users have their print server away on a VPN over a 438 // slow connection. In this situation, the simple fact of opening the printer 439 // can be dead slow. On the other side, we don't want to die infinitely for a 440 // real network error. Give the printer 60 seconds to comply. 441 // 442 // - If we're looping because of renderer page generation, the renderer could 443 // be CPU bound, the page overly complex/large or the system just 444 // memory-bound. 445 static const int kPrinterSettingsTimeout = 60000; 446 base::OneShotTimer<base::MessageLoop> quit_timer; 447 quit_timer.Start(FROM_HERE, 448 TimeDelta::FromMilliseconds(kPrinterSettingsTimeout), 449 base::MessageLoop::current(), &base::MessageLoop::Quit); 450 451 inside_inner_message_loop_ = true; 452 453 // Need to enable recursive task. 454 { 455 base::MessageLoop::ScopedNestableTaskAllower allow( 456 base::MessageLoop::current()); 457 base::MessageLoop::current()->Run(); 458 } 459 460 bool success = true; 461 if (inside_inner_message_loop_) { 462 // Ok we timed out. That's sad. 463 inside_inner_message_loop_ = false; 464 success = false; 465 } 466 467 return success; 468 } 469 470 bool PrintViewManagerBase::OpportunisticallyCreatePrintJob(int cookie) { 471 if (print_job_.get()) 472 return true; 473 474 if (!cookie) { 475 // Out of sync. It may happens since we are completely asynchronous. Old 476 // spurious message can happen if one of the processes is overloaded. 477 return false; 478 } 479 480 // The job was initiated by a script. Time to get the corresponding worker 481 // thread. 482 scoped_refptr<PrinterQuery> queued_query = queue_->PopPrinterQuery(cookie); 483 if (!queued_query) { 484 NOTREACHED(); 485 return false; 486 } 487 488 if (!CreateNewPrintJob(queued_query)) { 489 // Don't kill anything. 490 return false; 491 } 492 493 // Settings are already loaded. Go ahead. This will set 494 // print_job_->is_job_pending() to true. 495 print_job_->StartPrinting(); 496 return true; 497 } 498 499 bool PrintViewManagerBase::PrintNowInternal(IPC::Message* message) { 500 // Don't print / print preview interstitials. 501 if (web_contents()->ShowingInterstitialPage()) { 502 delete message; 503 return false; 504 } 505 return Send(message); 506 } 507 508 void PrintViewManagerBase::ReleasePrinterQuery() { 509 if (!cookie_) 510 return; 511 512 int cookie = cookie_; 513 cookie_ = 0; 514 queue_->SetDestination(NULL); 515 516 517 printing::PrintJobManager* print_job_manager = 518 g_browser_process->print_job_manager(); 519 // May be NULL in tests. 520 if (!print_job_manager) 521 return; 522 523 scoped_refptr<printing::PrinterQuery> printer_query; 524 printer_query = queue_->PopPrinterQuery(cookie); 525 if (!printer_query) 526 return; 527 BrowserThread::PostTask( 528 BrowserThread::IO, FROM_HERE, 529 base::Bind(&PrinterQuery::StopWorker, printer_query.get())); 530 } 531 532 } // namespace printing 533