blob: 34c86e5060c8d814590466b093426d4f396f029e [file] [log] [blame]
/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://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 https://www.qt.io/terms-conditions. For further
** information use the contact form at https://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.LGPL3 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-3.0.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 (at your option) the GNU General
** Public license version 3 or any later version approved by the KDE Free
** Qt Foundation. The licenses are as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-2.0.html and
** https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
// This is based on chrome/browser/printing/print_view_manager_base.cc:
// 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.Chromium file.
#include "print_view_manager_qt.h"
#include "type_conversion.h"
#include "web_engine_context.h"
#include "base/memory/ref_counted_memory.h"
#include "base/message_loop/message_loop.h"
#include "base/message_loop/message_loop_current.h"
#include "base/run_loop.h"
#include "base/single_thread_task_runner.h"
#include "base/task/post_task.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_task_traits.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/render_frame_host.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/metafile_skia.h"
#include "printing/print_job_constants.h"
#include "printing/printed_document.h"
namespace QtWebEngineCore {
PrintViewManagerBaseQt::PrintViewManagerBaseQt(content::WebContents *contents)
: printing::PrintManager(contents)
, m_isInsideInnerMessageLoop(false)
, m_didPrintingSucceed(false)
, m_printerQueriesQueue(WebEngineContext::current()->getPrintJobManager()->queue())
, m_printingRFH(nullptr)
{
// FIXME: Check if this needs to be executed async:
// TODO: Add isEnabled to profile
PrintViewManagerBaseQt::UpdatePrintingEnabled();
}
PrintViewManagerBaseQt::~PrintViewManagerBaseQt()
{
ReleasePrinterQuery();
DisconnectFromCurrentPrintJob();
}
void PrintViewManagerBaseQt::SetPrintingRFH(content::RenderFrameHost *rfh)
{
DCHECK(!m_printingRFH);
m_printingRFH = rfh;
}
void PrintViewManagerBaseQt::UpdatePrintingEnabled()
{
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
bool enabled = false;
#if QT_CONFIG(webengine_printing_and_pdf)
enabled = true;
#endif
web_contents()->ForEachFrame(
base::Bind(&PrintViewManagerBaseQt::SendPrintingEnabled,
base::Unretained(this), enabled));
}
void PrintViewManagerBaseQt::NavigationStopped()
{
// Cancel the current job, wait for the worker to finish.
TerminatePrintJob(true);
}
base::string16 PrintViewManagerBaseQt::RenderSourceName()
{
return toString16(QLatin1String(""));
}
void PrintViewManagerBaseQt::PrintDocument(printing::PrintedDocument *document,
const scoped_refptr<base::RefCountedMemory> &print_data,
const gfx::Size &page_size,
const gfx::Rect &content_area,
const gfx::Point &offsets)
{
std::unique_ptr<printing::MetafileSkia> metafile =
std::make_unique<printing::MetafileSkia>();
CHECK(metafile->InitFromData(print_data->front(), print_data->size()));
// Update the rendered document. It will send notifications to the listener.
document->SetDocument(std::move(metafile), page_size, content_area);
ShouldQuitFromInnerMessageLoop();
}
printing::PrintedDocument *PrintViewManagerBaseQt::GetDocument(int cookie)
{
if (!OpportunisticallyCreatePrintJob(cookie))
return nullptr;
printing::PrintedDocument* document = m_printJob->document();
if (!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 nullptr;
}
return document;
}
// IPC handlers
void PrintViewManagerBaseQt::OnDidPrintDocument(content::RenderFrameHost* /*render_frame_host*/,
const PrintHostMsg_DidPrintDocument_Params &params,
std::unique_ptr<DelayedFrameDispatchHelper> helper)
{
printing::PrintedDocument *document = GetDocument(params.document_cookie);
if (!document)
return;
const PrintHostMsg_DidPrintContent_Params &content = params.content;
if (!content.metafile_data_region.IsValid()) {
NOTREACHED() << "invalid memory handle";
web_contents()->Stop();
return;
}
auto data = base::RefCountedSharedMemoryMapping::CreateFromWholeRegion(content.metafile_data_region);
if (!data) {
NOTREACHED() << "couldn't map";
web_contents()->Stop();
return;
}
PrintDocument(document, data, params.page_size, params.content_area,
params.physical_offsets);
if (helper)
helper->SendCompleted();
}
void PrintViewManagerBaseQt::OnGetDefaultPrintSettings(content::RenderFrameHost *render_frame_host,
IPC::Message *reply_msg)
{
NOTREACHED() << "should be handled by printing::PrintingMessageFilter";
}
void PrintViewManagerBaseQt::OnScriptedPrint(content::RenderFrameHost *render_frame_host,
const PrintHostMsg_ScriptedPrint_Params &params,
IPC::Message *reply_msg)
{
NOTREACHED() << "should be handled by printing::PrintingMessageFilter";
}
void PrintViewManagerBaseQt::OnShowInvalidPrinterSettingsError()
{
}
void PrintViewManagerBaseQt::DidStartLoading()
{
UpdatePrintingEnabled();
}
// Note: In PrintViewManagerQt we always initiate printing with PrintMsg_InitiatePrintPreview
// so m_printingRFH is never set and used at the moment.
void PrintViewManagerBaseQt::RenderFrameDeleted(content::RenderFrameHost *render_frame_host)
{
// Terminates or cancels the print job if one was pending.
if (render_frame_host != m_printingRFH)
return;
m_printingRFH = nullptr;
PrintManager::PrintingRenderFrameDeleted();
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());
}
}
bool PrintViewManagerBaseQt::OnMessageReceived(const IPC::Message& message,
content::RenderFrameHost* render_frame_host)
{
bool handled = true;
IPC_BEGIN_MESSAGE_MAP(PrintViewManagerBaseQt, message)
IPC_MESSAGE_HANDLER(PrintHostMsg_ShowInvalidPrinterSettingsError,
OnShowInvalidPrinterSettingsError);
IPC_MESSAGE_UNHANDLED(handled = false)
IPC_END_MESSAGE_MAP()
return handled || PrintManager::OnMessageReceived(message, render_frame_host);
}
void PrintViewManagerBaseQt::Observe(int type,
const content::NotificationSource& /*source*/,
const content::NotificationDetails& details)
{
DCHECK_EQ(chrome::NOTIFICATION_PRINT_JOB_EVENT, type);
OnNotifyPrintJobEvent(*content::Details<printing::JobEventDetails>(details).ptr());
}
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: {
ShouldQuitFromInnerMessageLoop();
break;
}
case printing::JobEventDetails::NEW_DOC:
#if defined(OS_WIN)
case printing::JobEventDetails::PAGE_DONE:
#endif
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;
}
}
// 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;
// Is the document already complete?
if (m_printJob->document() && m_printJob->document()->IsComplete()) {
m_didPrintingSucceed = true;
return true;
}
// We can't print if there is no renderer.
if (!web_contents() ||
!web_contents()->GetRenderViewHost() ||
!web_contents()->GetRenderViewHost()->IsRenderViewLive()) {
return false;
}
// 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;
}
// 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::RunLoop::QuitCurrentWhenIdleDeprecated();
m_isInsideInnerMessageLoop = false;
}
}
bool PrintViewManagerBaseQt::CreateNewPrintJob(std::unique_ptr<printing::PrinterQuery> query)
{
DCHECK(!m_isInsideInnerMessageLoop);
DCHECK(query);
// Disconnect the current |m_printJob|.
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());
m_printJob = base::MakeRefCounted<printing::PrintJob>();
m_printJob->Initialize(std::move(query), RenderSourceName(), 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();
}
}
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();
}
void PrintViewManagerBaseQt::ReleasePrintJob()
{
content::RenderFrameHost* rfh = m_printingRFH;
m_printingRFH = nullptr;
if (!m_printJob.get())
return;
if (rfh)
GetPrintRenderFrame(rfh)->PrintingDone(m_didPrintingSucceed);
m_registrar.Remove(this, chrome::NOTIFICATION_PRINT_JOB_EVENT,
content::Source<printing::PrintJob>(m_printJob.get()));
// Don't close the worker thread.
m_printJob = nullptr;
}
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;
base::RunLoop run_loop;
quit_timer.Start(FROM_HERE,
base::TimeDelta::FromMilliseconds(kPrinterSettingsTimeout),
run_loop.QuitWhenIdleClosure());
m_isInsideInnerMessageLoop = true;
// Need to enable recursive task.
{
base::MessageLoopCurrent::ScopedNestableTaskAllower allow;
run_loop.Run();
}
bool success = true;
if (m_isInsideInnerMessageLoop) {
// Ok we timed out. That's sad.
m_isInsideInnerMessageLoop = false;
success = false;
}
return success;
}
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.
std::unique_ptr<printing::PrinterQuery> queued_query = m_printerQueriesQueue->PopPrinterQuery(cookie);
if (!queued_query) {
NOTREACHED();
return false;
}
if (!CreateNewPrintJob(std::move(queued_query))) {
// 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::ReleasePrinterQuery()
{
if (!cookie_)
return;
int cookie = cookie_;
cookie_ = 0;
printing::PrintJobManager* printJobManager = WebEngineContext::current()->getPrintJobManager();
// May be NULL in tests.
if (!printJobManager)
return;
std::unique_ptr<printing::PrinterQuery> printerQuery;
printerQuery = m_printerQueriesQueue->PopPrinterQuery(cookie);
if (!printerQuery)
return;
base::PostTask(FROM_HERE, {content::BrowserThread::IO},
base::BindOnce(&printing::PrinterQuery::StopWorker, std::move(printerQuery)));
}
// Originally from print_preview_message_handler.cc:
void PrintViewManagerBaseQt::StopWorker(int documentCookie)
{
if (documentCookie <= 0)
return;
std::unique_ptr<printing::PrinterQuery> printer_query =
m_printerQueriesQueue->PopPrinterQuery(documentCookie);
if (printer_query.get()) {
base::PostTask(FROM_HERE, {content::BrowserThread::IO},
base::BindOnce(&printing::PrinterQuery::StopWorker, std::move(printer_query)));
}
}
void PrintViewManagerBaseQt::SendPrintingEnabled(bool enabled, content::RenderFrameHost* rfh)
{
GetPrintRenderFrame(rfh)->SetPrintingEnabled(enabled);
}
} // namespace QtWebEngineCore