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