| /**************************************************************************** |
| ** |
| ** 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 ¶ms, |
| 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 ¶ms, |
| 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 |