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