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