diff options
Diffstat (limited to 'chrome/browser/printing/print_view_manager.cc')
-rw-r--r-- | chrome/browser/printing/print_view_manager.cc | 578 |
1 files changed, 578 insertions, 0 deletions
diff --git a/chrome/browser/printing/print_view_manager.cc b/chrome/browser/printing/print_view_manager.cc new file mode 100644 index 0000000..9c299b1 --- /dev/null +++ b/chrome/browser/printing/print_view_manager.cc @@ -0,0 +1,578 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "chrome/browser/printing/print_view_manager.h" + +#include "chrome/browser/browser_process.h" +#include "chrome/browser/navigation_entry.h" +#include "chrome/browser/printing/print_job.h" +#include "chrome/browser/printing/print_job_manager.h" +#include "chrome/browser/printing/printed_document.h" +#include "chrome/browser/printing/printer_query.h" +#include "chrome/browser/render_view_host.h" +#include "chrome/browser/web_contents.h" +#include "chrome/common/gfx/emf.h" +#include "chrome/common/l10n_util.h" + +#include "generated_resources.h" + +namespace printing { + +PrintViewManager::PrintViewManager(WebContents& owner) + : owner_(owner), + waiting_to_print_(false), + inside_inner_message_loop_(false), + waiting_to_show_print_dialog_(false) { + memset(&print_params_, 0, sizeof(print_params_)); +} + +void PrintViewManager::Destroy() { + DisconnectFromCurrentPrintJob(); +} + +void PrintViewManager::Stop() { + // Cancel the current job, wait for the worker to finish. + TerminatePrintJob(true); +} + +void PrintViewManager::ShowPrintDialog() { + if (!CreateNewPrintJob(NULL)) + return; + + // Retrieve default settings. PrintJob will send back a + // NOTIFY_PRINT_JOB_EVENT with either INIT_DONE, INIT_CANCELED or FAILED. + // On failure, simply back off. Otherwise, request the number of pages to + // the renderer. Wait for its response (DidGetPrintedPagesCount), which will + // give the value used to initialize the Print... dialog. PrintJob will send + // back (again) a NOTIFY_PRINT_JOB_EVENT with either INIT_DONE, INIT_CANCELED + // or FAILED. The result is to call PrintNowInternal or to back off. + waiting_to_show_print_dialog_ = true; + print_job_->GetSettings(PrintJob::DEFAULTS, NULL); +} + +bool PrintViewManager::PrintNow() { + if (!CreateNewPrintJob(NULL)) + return false; + + // Retrieve default settings. PrintJob will send back a NOTIFY_PRINT_JOB_EVENT + // with either DEFAULT_INIT_DONE or FAILED. On failure, simply back off. + // Otherwise, call PrintNowInternal() again to start the print job. + waiting_to_print_ = true; + print_job_->GetSettings(PrintJob::DEFAULTS, NULL); + return true; +} + +bool PrintViewManager::OnRendererGone(RenderViewHost* render_view_host) { + if (!print_job_.get()) + return true; + + if (render_view_host != owner_.render_view_host()) + return false; + + scoped_refptr<PrintedDocument> document(print_job_->document()); + if (document) { + // If IsComplete() returns false, the document isn't completely renderered. + // Since our renderer is gone, there's nothing to do, cancel it. Otherwise, + // the print job may finish without problem. + TerminatePrintJob(!document->IsComplete()); + } + return true; +} + +void PrintViewManager::DidGetPrintedPagesCount(int cookie, int number_pages) { + DCHECK_GT(cookie, 0); + if (!OpportunisticallyCreatePrintJob(cookie)) + return; + + PrintedDocument* document = print_job_->document(); + if (!document || cookie != document->cookie()) { + // Out of sync. It may happens since we are completely asynchronous. Old + // spurious message can happen if one of the processes is overloaded. + return; + } + + // Time to inform our print job. Make sure it is for the right document. + if (!document->page_count()) { + document->set_page_count(number_pages); + if (waiting_to_show_print_dialog_) { + // Ask for user settings. There's a timing issue since we may not have + // received the INIT_DONE notification yet. If so, the dialog will be + // shown in Observe() since the page count arrived before the settings. + print_job_->GetSettings(PrintJob::ASK_USER, + ::GetParent(owner_.GetContainerHWND())); + waiting_to_show_print_dialog_ = false; + } + } +} + +void PrintViewManager::DidPrintPage( + const ViewHostMsg_DidPrintPage_Params& params) { + DCHECK(!inside_inner_message_loop_); + if (!OpportunisticallyCreatePrintJob(params.document_cookie)) + return; + + PrintedDocument* document = print_job_->document(); + if (!document || params.document_cookie != document->cookie()) { + // Out of sync. It may happens since we are completely asynchronous. Old + // spurious message can happen if one of the processes is overloaded. + return; + } + + // http://msdn2.microsoft.com/en-us/library/ms535522.aspx + // Windows 2000/XP: When a page in a spooled file exceeds approximately 350 + // MB, it can fail to print and not send an error message. + if (params.data_size && params.data_size >= 350*1024*1024) { + NOTREACHED() << "size:" << params.data_size; + TerminatePrintJob(true); + owner_.Stop(); + return; + } + + SharedMemory shared_buf(params.emf_data_handle, true); + if (!shared_buf.Map(params.data_size)) { + NOTREACHED() << "couldn't map"; + owner_.Stop(); + return; + } + + gfx::Emf* emf = new gfx::Emf; + if (!emf->CreateFromData(shared_buf.memory(), params.data_size)) { + NOTREACHED() << "Invalid EMF header"; + delete emf; + owner_.Stop(); + return; + } + + // Update the rendered document. It will send notifications to the listener. + document->SetPage(params.page_number, emf, params.actual_shrink); + ShouldQuitFromInnerMessageLoop(); +} + +void PrintViewManager::RenderOnePrintedPage(PrintedDocument* document, + int page_number) { + // Currently a no-op. Rationale: printing is now completely synchronous and is + // handled by PrintAllPages. The reason is that PrintPreview is not used + // anymore and to make sure to not corrupt the screen, the whole generation is + // done synchronously. To make this work completely asynchronously, a + // duplicate copy of RenderView must be made to have an "innert" web page. + // Once this object is created, we'll have all the leasure to do whatever we + // want. +} + +std::wstring PrintViewManager::RenderSourceName() { + std::wstring name(owner_.GetTitle()); + if (name.empty()) + name = l10n_util::GetString(IDS_DEFAULT_PRINT_DOCUMENT_TITLE); + return name; +} + +GURL PrintViewManager::RenderSourceUrl() { + NavigationEntry* entry = owner_.controller()->GetActiveEntry(); + if (entry) + return entry->GetDisplayURL(); + else + return GURL(); +} + +void PrintViewManager::Observe(NotificationType type, + const NotificationSource& source, + const NotificationDetails& details) { + switch (type) { + case NOTIFY_PRINT_JOB_EVENT: { + OnNotifyPrintJobEvent(*Details<JobEventDetails>(details).ptr()); + break; + } + default: { + NOTREACHED(); + break; + } + } +} + +void PrintViewManager::OnNotifyPrintJobEvent( + const JobEventDetails& event_details) { + switch (event_details.type()) { + case JobEventDetails::FAILED: { + // TODO(maruel): bug 1123882 Show some kind of notification. + TerminatePrintJob(true); + break; + } + case JobEventDetails::USER_INIT_DONE: + case JobEventDetails::DEFAULT_INIT_DONE: + case JobEventDetails::USER_INIT_CANCELED: { + OnNotifyPrintJobInitEvent(event_details); + break; + } + case JobEventDetails::ALL_PAGES_REQUESTED: { + ShouldQuitFromInnerMessageLoop(); + break; + } + case JobEventDetails::NEW_DOC: + case JobEventDetails::NEW_PAGE: + case JobEventDetails::PAGE_DONE: { + // Don't care about the actual printing process. + break; + } + case JobEventDetails::DOC_DONE: { + waiting_to_print_ = false; + break; + } + case JobEventDetails::JOB_DONE: { + // Printing is done, we don't need it anymore. + // print_job_->is_job_pending() may still be true, depending on the order + // of object registration. + ReleasePrintJob(); + break; + } + default: { + NOTREACHED(); + break; + } + } +} + +void PrintViewManager::OnNotifyPrintJobInitEvent( + const JobEventDetails& event_details) { + ViewMsg_Print_Params old_print_params(print_params_); + + // Backup the print settings relevant to the renderer. + DCHECK_EQ(print_job_->document(), event_details.document()); + event_details.document()->settings().RenderParams(&print_params_); + print_params_.document_cookie = event_details.document()->cookie(); + DCHECK_GT(print_params_.document_cookie, 0); + + // If settings changed + DCHECK(owner_.render_view_host()); + // Equals() doesn't compare the cookie value. + if (owner_.render_view_host() && + owner_.render_view_host()->IsRenderViewLive() && + (!old_print_params.Equals(print_params_) || + !event_details.document()->page_count())) { + // This will generate a DidGetPrintedPagesCount() callback. + if (!owner_.render_view_host()->GetPrintedPagesCount(print_params_)) { + NOTREACHED(); + if (inside_inner_message_loop_) { + MessageLoop::current()->Quit(); + return; + } + } + } + + // Continue even if owner_.render_view_host() is dead because we may already + // have buffered all the necessary pages. + switch (event_details.type()) { + case JobEventDetails::USER_INIT_DONE: { + // The user clicked the "Print" button in the Print... dialog. + // Time to print. + DCHECK_EQ(waiting_to_print_, false); + DCHECK_EQ(waiting_to_show_print_dialog_, false); + waiting_to_print_ = true; + PrintNowInternal(); + break; + } + case JobEventDetails::USER_INIT_CANCELED: { + DCHECK(!waiting_to_show_print_dialog_); + // The print dialog box has been dismissed (Cancel button or the X). + TerminatePrintJob(false); + break; + } + case JobEventDetails::DEFAULT_INIT_DONE: { + // Init(false) returned. + if (waiting_to_print_) { + // PrintNow() is pending. + DCHECK_EQ(waiting_to_show_print_dialog_, false); + PrintNowInternal(); + } else if (waiting_to_show_print_dialog_ && + event_details.document()->page_count()) { + // Time to ask the user for the print settings. + print_job_->GetSettings(PrintJob::ASK_USER, + ::GetParent(owner_.GetContainerHWND())); + waiting_to_show_print_dialog_ = false; + } else { + // event_details.document()->page_count() is false. This simply means + // that the renderer was slower to calculate the number of pages than + // the print_job_ to initialize the default settings. If so, the dialog + // will be shown in DidGetPrintedPagesCount() since the settings arrived + // before the page count. + DCHECK_EQ(waiting_to_show_print_dialog_, true); + } + break; + } + default: { + NOTREACHED(); + break; + } + } +} + +bool PrintViewManager::RenderAllMissingPagesNow() { + if (waiting_to_show_print_dialog_) { + // TODO(maruel): http://b/1186708 This happens in one of these case: + // - javascript:window.print();window.close(); which closes the window very + // fast. + // - The worker thread is hung, like the network printer failed, the network + // print server failed or the network cable is disconnected. + // In the first case we want to wait, but not on the second case. + + if (!RunInnerMessageLoop()) { + // This function is always called from DisconnectFromCurrentPrintJob() + // so we know that the job will be stopped/canceled in any case. + return false; + } + } + + if (!print_job_.get() || !print_job_->is_job_pending()) { + DCHECK_EQ(waiting_to_print_, false); + return false; + } + + // We can't print if there is no renderer. + if (!owner_.render_view_host() || + !owner_.render_view_host()->IsRenderViewLive()) { + waiting_to_print_ = false; + return false; + } + + // Is the document already complete? + if (print_job_->document() && print_job_->document()->IsComplete()) { + waiting_to_print_ = false; + return true; + } + + // WebContents is either dying or a second consecutive request to print + // happened before the first had time to finish. We need to render all the + // pages in an hurry if a print_job_ is still pending. No need to wait for it + // to actually spool the pages, only to have the renderer generate them. Run + // a message loop until we get our signal that the print job is satisfied. + // PrintJob will send a ALL_PAGES_REQUESTED after having received all the + // pages it needs. MessageLoop::current()->Quit() will be called as soon as + // print_job_->document()->IsComplete() is true on either ALL_PAGES_REQUESTED + // or in DidPrintPage(). The check is done in + // ShouldQuitFromInnerMessageLoop(). + // BLOCKS until all the pages are received. (Need to enable recursive task) + if (!RunInnerMessageLoop()) { + // This function is always called from DisconnectFromCurrentPrintJob() so we + // know that the job will be stopped/canceled in any case. + return false; + } + return true; +} + +void PrintViewManager::ShouldQuitFromInnerMessageLoop() { + // Look at the reason. + DCHECK(print_job_->document()); + if (print_job_->document() && + print_job_->document()->IsComplete() && + inside_inner_message_loop_) { + // We are in a message loop created by RenderAllMissingPagesNow. Quit from + // it. + MessageLoop::current()->Quit(); + inside_inner_message_loop_ = false; + waiting_to_print_ = false; + } +} + +bool PrintViewManager::CreateNewPrintJob(PrintJobWorkerOwner* job) { + DCHECK(!inside_inner_message_loop_); + if (waiting_to_print_ || waiting_to_show_print_dialog_) { + // We can't help; we are waiting for a print job initialization. The user is + // button bashing. The only thing we could do is to batch up the requests. + return false; + } + + // Disconnect the current print_job_. + DisconnectFromCurrentPrintJob(); + + // We can't print if there is no renderer. + if (!owner_.render_view_host() || + !owner_.render_view_host()->IsRenderViewLive()) { + return false; + } + + // Ask the renderer to generate the print preview, create the print preview + // view and switch to it, initialize the printer and show the print dialog. + DCHECK(!print_job_.get()); + if (job) { + print_job_ = new PrintJob(); + print_job_->Initialize(job, this); + } else { + print_job_ = new PrintJob(this); + } + NotificationService::current()-> + AddObserver(this, + NOTIFY_PRINT_JOB_EVENT, + Source<PrintJob>(print_job_.get())); + return true; +} + +void PrintViewManager::DisconnectFromCurrentPrintJob() { + // Make sure all the necessary rendered page are done. Don't bother with the + // return value. + bool result = RenderAllMissingPagesNow(); + + // Verify that assertion. + if (print_job_.get() && + print_job_->document() && + !print_job_->document()->IsComplete()) { + DCHECK(!result); + // That failed. + TerminatePrintJob(true); + } else { + // DO NOT wait for the job to finish. + ReleasePrintJob(); + } +} + +void PrintViewManager::TerminatePrintJob(bool cancel) { + if (!print_job_.get()) + return; + + if (cancel) { + // We don't need the EMF data anymore because the printing is canceled. + print_job_->Cancel(); + waiting_to_print_ = false; + waiting_to_show_print_dialog_ = false; + inside_inner_message_loop_ = false; + } else { + DCHECK(!inside_inner_message_loop_); + DCHECK(!waiting_to_show_print_dialog_); + DCHECK(!print_job_->document() || print_job_->document()->IsComplete() || + !waiting_to_print_); + + // WebContents is either dying or navigating elsewhere. We need to render + // all the pages in an hurry if a print job is still pending. This does the + // trick since it runs a blocking message loop: + print_job_->Stop(); + } + ReleasePrintJob(); +} + +void PrintViewManager::ReleasePrintJob() { + DCHECK_EQ(waiting_to_print_, false); + if (!print_job_.get()) + return; + NotificationService::current()->RemoveObserver( + this, + NOTIFY_PRINT_JOB_EVENT, + Source<PrintJob>(print_job_.get())); + + print_job_->DisconnectSource(); + // Don't close the worker thread. + print_job_ = NULL; + memset(&print_params_, 0, sizeof(print_params_)); +} + +void PrintViewManager::PrintNowInternal() { + DCHECK(waiting_to_print_); + + // Settings are already loaded. Go ahead. This will set + // print_job_->is_job_pending() to true. + print_job_->StartPrinting(); + + if (!print_job_->document() || + !print_job_->document()->IsComplete()) { + ViewMsg_PrintPages_Params params; + params.params = print_params_; + params.pages = PageRange::GetPages(print_job_->settings().ranges); + owner_.render_view_host()->PrintPages(params); + } +} + +bool PrintViewManager::RunInnerMessageLoop() { + // This value may actually be too low: + // + // - If we're looping because of printer settings initializaton, the premise + // here is that some poor users have their print server away on a VPN over + // dialup. In this situation, the simple fact of opening the printer can be + // dead slow. On the other side, we don't want to die infinitely for a real + // network error. Give the printer 60 seconds to comply. + // + // - If we're looping because of renderer page generation, the renderer could + // be cpu bound, the page overly complex/large or the system just + // memory-bound. + static const int kPrinterSettingsTimeout = 60000; + MessageLoop::QuitTask timeout_task; + Timer* timeout = MessageLoop::current()->timer_manager()->StartTimer( + kPrinterSettingsTimeout, + &timeout_task, + false); + inside_inner_message_loop_ = true; + // Need to enable recursive task. + bool old_state = MessageLoop::current()->NestableTasksAllowed(); + MessageLoop::current()->SetNestableTasksAllowed(true); + MessageLoop::current()->Run(); + // Restore task state. + MessageLoop::current()->SetNestableTasksAllowed(old_state); + + bool success = true; + if (inside_inner_message_loop_) { + // Ok we timed out. That's sad. + inside_inner_message_loop_ = false; + success = false; + } + + if (timeout) { + MessageLoop::current()->timer_manager()->StopTimer(timeout); + delete timeout; + timeout = NULL; + } + return success; +} + +bool PrintViewManager::OpportunisticallyCreatePrintJob(int cookie) { + if (print_job_.get()) + return true; + + if (!cookie) { + // Out of sync. It may happens since we are completely asynchronous. Old + // spurious message can happen if one of the processes is overloaded. + return false; + } + + // The job was initiated by a script. Time to get the corresponding worker + // thread. + scoped_refptr<PrinterQuery> queued_query; + g_browser_process->print_job_manager()->PopPrinterQuery(cookie, + &queued_query); + DCHECK(queued_query.get()); + if (!queued_query.get()) + return false; + + if (!CreateNewPrintJob(queued_query.get())) { + // Don't kill anything. + return false; + } + + // Settings are already loaded. Go ahead. This will set + // print_job_->is_job_pending() to true. + print_job_->StartPrinting(); + return true; +} + +} // namespace printing |