diff options
Diffstat (limited to 'src/core/print_view_manager_base_qt.cpp')
-rw-r--r-- | src/core/print_view_manager_base_qt.cpp | 520 |
1 files changed, 520 insertions, 0 deletions
diff --git a/src/core/print_view_manager_base_qt.cpp b/src/core/print_view_manager_base_qt.cpp new file mode 100644 index 000000000..ea6345f94 --- /dev/null +++ b/src/core/print_view_manager_base_qt.cpp @@ -0,0 +1,520 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtWebEngine module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "print_view_manager_qt.h" + +#include "type_conversion.h" +#include "web_engine_context.h" + +#include "base/single_thread_task_runner.h" +#include "base/thread_task_runner_handle.h" +#include "base/timer/timer.h" +#include "base/values.h" +#include "chrome/browser/chrome_notification_types.h" +#include "chrome/browser/printing/print_job.h" +#include "chrome/browser/printing/print_job_manager.h" +#include "chrome/browser/printing/printer_query.h" +#include "components/printing/common/print_messages.h" +#include "content/public/browser/browser_thread.h" +#include "content/public/browser/render_view_host.h" +#include "content/public/browser/notification_service.h" +#include "content/public/browser/notification_types.h" +#include "printing/pdf_metafile_skia.h" +#include "printing/print_job_constants.h" +#include "printing/printed_document.h" + +namespace QtWebEngineCore { + +PrintViewManagerBaseQt::~PrintViewManagerBaseQt() +{ +} + +// PrintedPagesSource implementation. +base::string16 PrintViewManagerBaseQt::RenderSourceName() +{ + return toString16(QLatin1String("")); +} + +PrintViewManagerBaseQt::PrintViewManagerBaseQt(content::WebContents *contents) + : printing::PrintManager(contents) + , m_isInsideInnerMessageLoop(false) + , m_isExpectingFirstPage(false) + , m_didPrintingSucceed(false) + , m_printerQueriesQueue(WebEngineContext::current()->getPrintJobManager()->queue()) +{ + +} + +void PrintViewManagerBaseQt::OnNotifyPrintJobEvent( + const printing::JobEventDetails& event_details) { + switch (event_details.type()) { + case printing::JobEventDetails::FAILED: { + TerminatePrintJob(true); + + content::NotificationService::current()->Notify( + chrome::NOTIFICATION_PRINT_JOB_RELEASED, + content::Source<content::WebContents>(web_contents()), + content::NotificationService::NoDetails()); + break; + } + case printing::JobEventDetails::USER_INIT_DONE: + case printing::JobEventDetails::DEFAULT_INIT_DONE: + case printing::JobEventDetails::USER_INIT_CANCELED: { + NOTREACHED(); + break; + } + case printing::JobEventDetails::ALL_PAGES_REQUESTED: { + break; + } + case printing::JobEventDetails::NEW_DOC: + case printing::JobEventDetails::NEW_PAGE: + case printing::JobEventDetails::PAGE_DONE: + case printing::JobEventDetails::DOC_DONE: { + // Don't care about the actual printing process. + break; + } + case printing::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. + m_didPrintingSucceed = true; + ReleasePrintJob(); + + content::NotificationService::current()->Notify( + chrome::NOTIFICATION_PRINT_JOB_RELEASED, + content::Source<content::WebContents>(web_contents()), + content::NotificationService::NoDetails()); + break; + } + default: { + NOTREACHED(); + break; + } + } +} + + +// content::WebContentsObserver implementation. +// Cancels the print job. +void PrintViewManagerBaseQt::NavigationStopped() +{ +} + +// content::WebContentsObserver implementation. +void PrintViewManagerBaseQt::DidStartLoading() +{ +} + +// content::NotificationObserver implementation. +void PrintViewManagerBaseQt::Observe( + int type, + const content::NotificationSource& source, + const content::NotificationDetails& details) { + switch (type) { + case chrome::NOTIFICATION_PRINT_JOB_EVENT: + OnNotifyPrintJobEvent(*content::Details<printing::JobEventDetails>(details).ptr()); + break; + default: + NOTREACHED(); + break; + + } +} + // Terminates or cancels the print job if one was pending. +void PrintViewManagerBaseQt::RenderProcessGone(base::TerminationStatus status) +{ + PrintManager::RenderProcessGone(status); + ReleasePrinterQuery(); + + if (!m_printJob.get()) + return; + + scoped_refptr<printing::PrintedDocument> document(m_printJob->document()); + if (document.get()) { + // If IsComplete() returns false, the document isn't completely rendered. + // Since our renderer is gone, there's nothing to do, cancel it. Otherwise, + // the print job may finish without problem. + TerminatePrintJob(!document->IsComplete()); + } +} + +void PrintViewManagerBaseQt::ReleasePrinterQuery() { + if (!cookie_) + return; + + int cookie = cookie_; + cookie_ = 0; + + printing::PrintJobManager* printJobManager = + WebEngineContext::current()->getPrintJobManager(); + // May be NULL in tests. + if (!printJobManager) + return; + + scoped_refptr<printing::PrinterQuery> printerQuery; + printerQuery = m_printerQueriesQueue->PopPrinterQuery(cookie); + if (!printerQuery.get()) + return; + content::BrowserThread::PostTask( + content::BrowserThread::IO, FROM_HERE, + base::Bind(&printing::PrinterQuery::StopWorker, printerQuery.get())); +} + + +// content::WebContentsObserver implementation. +bool PrintViewManagerBaseQt::OnMessageReceived(const IPC::Message& message) +{ + bool handled = true; + IPC_BEGIN_MESSAGE_MAP(PrintViewManagerBaseQt, message) + IPC_MESSAGE_HANDLER(PrintHostMsg_DidPrintPage, OnDidPrintPage) + IPC_MESSAGE_HANDLER(PrintHostMsg_ShowInvalidPrinterSettingsError, + OnShowInvalidPrinterSettingsError); + IPC_MESSAGE_UNHANDLED(handled = false) + IPC_END_MESSAGE_MAP() + return handled || PrintManager::OnMessageReceived(message); +} + +void PrintViewManagerBaseQt::StopWorker(int documentCookie) { + if (documentCookie <= 0) + return; + scoped_refptr<printing::PrinterQuery> printer_query = + m_printerQueriesQueue->PopPrinterQuery(documentCookie); + if (printer_query.get()) { + content::BrowserThread::PostTask(content::BrowserThread::IO, FROM_HERE, + base::Bind(&printing::PrinterQuery::StopWorker, + printer_query)); + } +} + + +// IPC handlers +void PrintViewManagerBaseQt::OnDidPrintPage( + const PrintHostMsg_DidPrintPage_Params& params) { + if (!OpportunisticallyCreatePrintJob(params.document_cookie)) + return; + + printing::PrintedDocument* document = m_printJob->document(); + if (!document || params.document_cookie != document->cookie()) { + // Out of sync. It may happen since we are completely asynchronous. Old + // spurious messages can be received if one of the processes is overloaded. + return; + } + +#if defined(OS_MACOSX) + const bool metafile_must_be_valid = true; +#else + const bool metafile_must_be_valid = m_isExpectingFirstPage; + m_isExpectingFirstPage = false; +#endif + + // Only used when |metafile_must_be_valid| is true. + scoped_ptr<base::SharedMemory> shared_buf; + if (metafile_must_be_valid) { + if (!base::SharedMemory::IsHandleValid(params.metafile_data_handle)) { + NOTREACHED() << "invalid memory handle"; + web_contents()->Stop(); + return; + } + shared_buf.reset(new base::SharedMemory(params.metafile_data_handle, true)); + if (!shared_buf->Map(params.data_size)) { + NOTREACHED() << "couldn't map"; + web_contents()->Stop(); + return; + } + } else { + if (base::SharedMemory::IsHandleValid(params.metafile_data_handle)) { + NOTREACHED() << "unexpected valid memory handle"; + web_contents()->Stop(); + base::SharedMemory::CloseHandle(params.metafile_data_handle); + return; + } + } + + scoped_ptr<printing::PdfMetafileSkia> metafile(new printing::PdfMetafileSkia); + if (metafile_must_be_valid) { + if (!metafile->InitFromData(shared_buf->memory(), params.data_size)) { + NOTREACHED() << "Invalid metafile header"; + web_contents()->Stop(); + return; + } + } + +#if defined(OS_WIN) && !defined(TOOLKIT_QT) + if (metafile_must_be_valid) { + scoped_refptr<base::RefCountedBytes> bytes = new base::RefCountedBytes( + reinterpret_cast<const unsigned char*>(shared_buf->memory()), + params.data_size); + + document->DebugDumpData(bytes.get(), FILE_PATH_LITERAL(".pdf")); + m_printJob->StartPdfToEmfConversion( + bytes, params.page_size, params.content_area); + } +#else + // Update the rendered document. It will send notifications to the listener. + document->SetPage(params.page_number, + metafile.Pass(), +#if defined(OS_WIN) + 1.0f, // shrink factor, needed on windows. +#endif // defined(OS_WIN) + params.page_size, + params.content_area); + + ShouldQuitFromInnerMessageLoop(); +#endif // defined (OS_WIN) && !defined(TOOLKIT_QT) +} + +void PrintViewManagerBaseQt::OnShowInvalidPrinterSettingsError() +{ +} + +bool PrintViewManagerBaseQt::CreateNewPrintJob(printing::PrintJobWorkerOwner* job) { + DCHECK(!m_isInsideInnerMessageLoop); + + // Disconnect the current print_job_. + DisconnectFromCurrentPrintJob(); + + // We can't print if there is no renderer. + if (!web_contents()->GetRenderViewHost() || + !web_contents()->GetRenderViewHost()->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(!m_printJob.get()); + DCHECK(job); + if (!job) + return false; + + m_printJob = new printing::PrintJob(); + m_printJob->Initialize(job, this, number_pages_); + m_registrar.Add(this, chrome::NOTIFICATION_PRINT_JOB_EVENT, + content::Source<printing::PrintJob>(m_printJob.get())); + m_didPrintingSucceed = false; + return true; +} + +void PrintViewManagerBaseQt::DisconnectFromCurrentPrintJob() { + // Make sure all the necessary rendered page are done. Don't bother with the + // return value. + bool result = RenderAllMissingPagesNow(); + + // Verify that assertion. + if (m_printJob.get() && + m_printJob->document() && + !m_printJob->document()->IsComplete()) { + DCHECK(!result); + // That failed. + TerminatePrintJob(true); + } else { + // DO NOT wait for the job to finish. + ReleasePrintJob(); + } +#if !defined(OS_MACOSX) + m_isExpectingFirstPage = true; +#endif +} + +void PrintViewManagerBaseQt::PrintingDone(bool success) { + if (!m_printJob.get()) + return; + Send(new PrintMsg_PrintingDone(routing_id(), success)); +} + +void PrintViewManagerBaseQt::TerminatePrintJob(bool cancel) { + if (!m_printJob.get()) + return; + + if (cancel) { + // We don't need the metafile data anymore because the printing is canceled. + m_printJob->Cancel(); + m_isInsideInnerMessageLoop = false; + } else { + DCHECK(!m_isInsideInnerMessageLoop); + DCHECK(!m_printJob->document() || m_printJob->document()->IsComplete()); + + // 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: + m_printJob->Stop(); + } + ReleasePrintJob(); +} + +bool PrintViewManagerBaseQt::OpportunisticallyCreatePrintJob(int cookie) +{ + if (m_printJob.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<printing::PrinterQuery> queued_query = m_printerQueriesQueue->PopPrinterQuery(cookie); + if (!queued_query.get()) { + NOTREACHED(); + 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. + m_printJob->StartPrinting(); + return true; +} + +void PrintViewManagerBaseQt::ReleasePrintJob() { + if (!m_printJob.get()) + return; + + PrintingDone(m_didPrintingSucceed); + + m_registrar.Remove(this, chrome::NOTIFICATION_PRINT_JOB_EVENT, + content::Source<printing::PrintJob>(m_printJob.get())); + m_printJob->DisconnectSource(); + // Don't close the worker thread. + m_printJob = NULL; +} + +// Requests the RenderView to render all the missing pages for the print job. +// No-op if no print job is pending. Returns true if at least one page has +// been requested to the renderer. +bool PrintViewManagerBaseQt::RenderAllMissingPagesNow() +{ + if (!m_printJob.get() || !m_printJob->is_job_pending()) + return false; + + // We can't print if there is no renderer. + if (!web_contents() || + !web_contents()->GetRenderViewHost() || + !web_contents()->GetRenderViewHost()->IsRenderViewLive()) { + return false; + } + + // Is the document already complete? + if (m_printJob->document() && m_printJob->document()->IsComplete()) { + m_didPrintingSucceed = true; + 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; +} + +bool PrintViewManagerBaseQt::RunInnerMessageLoop() { + // This value may actually be too low: + // + // - If we're looping because of printer settings initialization, the premise + // here is that some poor users have their print server away on a VPN over a + // slow connection. 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; + base::OneShotTimer quit_timer; + quit_timer.Start(FROM_HERE, + base::TimeDelta::FromMilliseconds(kPrinterSettingsTimeout), + base::MessageLoop::current(), &base::MessageLoop::Quit); + + m_isInsideInnerMessageLoop = true; + + // Need to enable recursive task. + { + base::MessageLoop::ScopedNestableTaskAllower allow( + base::MessageLoop::current()); + base::MessageLoop::current()->Run(); + } + + bool success = true; + if (m_isInsideInnerMessageLoop) { + // Ok we timed out. That's sad. + m_isInsideInnerMessageLoop = false; + success = false; + } + + return success; +} + +// Quits the current message loop if these conditions hold true: a document is +// loaded and is complete and waiting_for_pages_to_be_rendered_ is true. This +// function is called in DidPrintPage() or on ALL_PAGES_REQUESTED +// notification. The inner message loop is created was created by +// RenderAllMissingPagesNow(). +void PrintViewManagerBaseQt::ShouldQuitFromInnerMessageLoop() +{ + // Look at the reason. + DCHECK(m_printJob->document()); + if (m_printJob->document() && + m_printJob->document()->IsComplete() && + m_isInsideInnerMessageLoop) { + // We are in a message loop created by RenderAllMissingPagesNow. Quit from + // it. + base::MessageLoop::current()->Quit(); + m_isInsideInnerMessageLoop = false; + } +} + +} // namespace QtWebEngineCore |