summaryrefslogtreecommitdiffstats
path: root/chrome/browser/printing/print_view_manager.cc
diff options
context:
space:
mode:
Diffstat (limited to 'chrome/browser/printing/print_view_manager.cc')
-rw-r--r--chrome/browser/printing/print_view_manager.cc578
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