summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMoss Heim <moss.heim@qt.io>2025-10-14 11:19:12 +0200
committerMoss Heim <moss.heim@qt.io>2025-11-25 11:56:11 +0100
commit06a7ed9e32906e7564ba612835459b02a64f56f0 (patch)
treee3ef4b96b0cf1c202aacf2f3e4b59887b414df00
parent23854a3e636cd5cca1a0f845a85b8f76d7c89fae (diff)
Interrupt printing when QWEPage is destroyed
When a page is destroyed during printing, there is no view to emit printFinished() and yet the printer thread continues in the background until the job is finished. We can improve things somewhat by requesting an interrupt and exiting early in the printer worker thread. Then at least users do not wait around forever for the printer to be idle and deleteable. Add tests both for this case and basic printing with `print(QPrinter*)` Task-number: QTBUG-140232 Pick-to: 6.8 6.10 Change-Id: If43677b7ad8021d72dd945fd36c3263234692b95 Reviewed-by: Michal Klocek <michal.klocek@qt.io>
-rw-r--r--src/core/api/qwebenginepage.cpp13
-rw-r--r--src/core/api/qwebenginepage_p.h4
-rw-r--r--src/core/printing/printer_worker.cpp6
-rw-r--r--src/webenginewidgets/api/qwebengineview.cpp9
-rw-r--r--src/webenginewidgets/api/qwebengineview_p.h2
-rw-r--r--tests/auto/widgets/printing/tst_printing.cpp54
6 files changed, 78 insertions, 10 deletions
diff --git a/src/core/api/qwebenginepage.cpp b/src/core/api/qwebenginepage.cpp
index 8b1a1d968..9dfa1dbaf 100644
--- a/src/core/api/qwebenginepage.cpp
+++ b/src/core/api/qwebenginepage.cpp
@@ -52,6 +52,7 @@
#include <QMimeData>
#include <QtCore/QPointer>
#include <QRect>
+#include <QThread>
#include <QTimer>
#include <QUrl>
#include <QVariant>
@@ -131,6 +132,10 @@ QWebEnginePagePrivate::~QWebEnginePagePrivate()
{
delete history;
delete settings;
+#if QT_CONFIG(webengine_printing_and_pdf)
+ if (printerThread)
+ printerThread->requestInterruption();
+#endif
profile->d_ptr->removeWebContentsAdapterClient(this);
}
@@ -539,10 +544,12 @@ void QWebEnginePagePrivate::didFetchDocumentInnerText(quint64 requestId, const Q
void QWebEnginePagePrivate::didPrintPage(QSharedPointer<QByteArray> result)
{
#if QT_CONFIG(webengine_printing_and_pdf)
+ Q_Q(QWebEnginePage);
Q_ASSERT(currentPrinter);
- if (view)
- view->didPrintPage(currentPrinter, result);
- else
+ if (view) {
+ printerThread = view->didPrintPage(currentPrinter, result);
+ QObject::connect(printerThread, &QThread::finished, q, [this]() { printerThread = nullptr; });
+ } else
currentPrinter = nullptr;
#else
// should not get here
diff --git a/src/core/api/qwebenginepage_p.h b/src/core/api/qwebenginepage_p.h
index ba1fbc6d5..917b8aaa0 100644
--- a/src/core/api/qwebenginepage_p.h
+++ b/src/core/api/qwebenginepage_p.h
@@ -38,6 +38,7 @@ class WebContentsAdapter;
QT_BEGIN_NAMESPACE
class QPrinter;
+class QThread;
class QWebEngineFindTextResult;
class QWebEngineHistory;
class QWebEnginePage;
@@ -72,7 +73,7 @@ public:
virtual void unhandledKeyEvent(QKeyEvent *event) = 0;
virtual bool passOnFocus(bool reverse) = 0;
virtual QObject *accessibilityParentObject() = 0;
- virtual void didPrintPage(QPrinter *&printer, QSharedPointer<QByteArray> result) = 0;
+ virtual QThread *didPrintPage(QPrinter *&printer, QSharedPointer<QByteArray> result) = 0;
virtual void didPrintPageToPdf(const QString &filePath, bool success) = 0;
virtual void printRequested() = 0;
virtual void printRequestedByFrame(QWebEngineFrame frame) = 0;
@@ -220,6 +221,7 @@ public:
QtWebEngineCore::RenderWidgetHostViewQtDelegateItem *delegateItem = nullptr;
#if QT_CONFIG(webengine_printing_and_pdf)
QPrinter *currentPrinter = nullptr;
+ QThread *printerThread = nullptr;
#endif
mutable QMap<quint64, std::function<void(const QString &)>> m_stringCallbacks;
diff --git a/src/core/printing/printer_worker.cpp b/src/core/printing/printer_worker.cpp
index 564865386..53b1b7b0f 100644
--- a/src/core/printing/printer_worker.cpp
+++ b/src/core/printing/printer_worker.cpp
@@ -8,6 +8,7 @@
#include <QPainter>
#include <QPagedPaintDevice>
+#include <QThread>
namespace QtWebEngineCore {
@@ -89,6 +90,11 @@ void PrinterWorker::print()
m_device->newPage();
for (int printedPages = 0; printedPages < pageCopies; printedPages++) {
+ // The page being printed requests interruption when it is destroyed; this lets
+ // us return early and avoid doing extra work in the background.
+ if (QThread::currentThread()->isInterruptionRequested())
+ return finish(false);
+
if (printedPages > 0)
m_device->newPage();
diff --git a/src/webenginewidgets/api/qwebengineview.cpp b/src/webenginewidgets/api/qwebengineview.cpp
index 9745654ac..8857e5336 100644
--- a/src/webenginewidgets/api/qwebengineview.cpp
+++ b/src/webenginewidgets/api/qwebengineview.cpp
@@ -849,7 +849,7 @@ QObject *QWebEngineViewPrivate::accessibilityParentObject()
return q;
}
-void QWebEngineViewPrivate::didPrintPage(QPrinter *&currentPrinter, QSharedPointer<QByteArray> result)
+QThread* QWebEngineViewPrivate::didPrintPage(QPrinter *&currentPrinter, QSharedPointer<QByteArray> result)
{
#if QT_CONFIG(webengine_printing_and_pdf)
Q_Q(QWebEngineView);
@@ -881,9 +881,11 @@ void QWebEngineViewPrivate::didPrintPage(QPrinter *&currentPrinter, QSharedPoint
printerWorker->moveToThread(printerThread);
QMetaObject::invokeMethod(printerWorker, "print");
+ return printerThread;
#else
Q_UNUSED(currentPrinter);
Q_UNUSED(result);
+ return nullptr;
#endif
}
@@ -1479,13 +1481,16 @@ void QWebEngineView::printToPdf(const std::function<void(const QByteArray&)> &re
/*!
Renders the current content of the page into a temporary PDF document, then prints it using \a printer.
+
The settings for creating and printing the PDF document will be retrieved from the \a printer
object.
When finished the signal printFinished() is emitted with the \c true for success or \c false for failure.
It is the user's responsibility to ensure the \a printer remains valid until printFinished()
- has been emitted.
+ has been emitted. If the page is destroyed before printFinished() is emitted, printing will
+ continue to run in the background for a short while. Users should ensure the printer remains
+ valid until its state is no longer active.
\note Printing runs on the browser process, which is by default not sandboxed.
diff --git a/src/webenginewidgets/api/qwebengineview_p.h b/src/webenginewidgets/api/qwebengineview_p.h
index 27ad6db71..900210c64 100644
--- a/src/webenginewidgets/api/qwebengineview_p.h
+++ b/src/webenginewidgets/api/qwebengineview_p.h
@@ -71,7 +71,7 @@ public:
QWebEngineContextMenuRequest *lastContextMenuRequest() const override;
QWebEnginePage *createPageForWindow(QWebEnginePage::WebWindowType type) override;
QObject *accessibilityParentObject() override;
- void didPrintPage(QPrinter *&printer, QSharedPointer<QByteArray> result) override;
+ QThread *didPrintPage(QPrinter *&printer, QSharedPointer<QByteArray> result) override;
void didPrintPageToPdf(const QString &filePath, bool success) override;
void printRequested() override;
void printRequestedByFrame(QWebEngineFrame frame) override;
diff --git a/tests/auto/widgets/printing/tst_printing.cpp b/tests/auto/widgets/printing/tst_printing.cpp
index 58ad8a59c..1fcdae1b0 100644
--- a/tests/auto/widgets/printing/tst_printing.cpp
+++ b/tests/auto/widgets/printing/tst_printing.cpp
@@ -2,11 +2,14 @@
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
#include <QtWebEngineCore/qtwebenginecore-config.h>
-#include <QWebEngineSettings>
-#include <QWebEngineView>
+
+#include <QPrinter>
+#include <QSignalSpy>
#include <QTemporaryDir>
#include <QTest>
-#include <QSignalSpy>
+#include <QWebEngineSettings>
+#include <QWebEngineView>
+
#include <util.h>
#ifdef QTPDF_SUPPORT
@@ -26,6 +29,8 @@ private slots:
void printHeaderAndFooter_data();
void printHeaderAndFooter();
void interruptPrinting();
+ void printOnQPrinterBasic();
+ void interruptPrintingOnQPrinter();
};
void tst_Printing::printToPdfBasic()
@@ -252,5 +257,48 @@ void tst_Printing::interruptPrinting()
view.page()->triggerAction(QWebEnginePage::Stop);
}
+void tst_Printing::printOnQPrinterBasic()
+{
+ QWebEngineView view;
+ loadSync(view.page(), QUrl("qrc:///resources/basic_printing_page.html"));
+
+ QTemporaryDir tempDir(QDir::tempPath() + "/tst_qwebengineview-XXXXXX");
+ QVERIFY(tempDir.isValid());
+
+ QPrinter printer;
+ printer.setOutputFileName(tempDir.path() + "/file.pdf");
+ QVERIFY(printer.isValid());
+ QCOMPARE(printer.outputFormat(), QPrinter::PdfFormat);
+
+ QSignalSpy printSpy(&view, &QWebEngineView::printFinished);
+ view.print(&printer);
+ QTRY_VERIFY(printSpy.size() == 1);
+ QVERIFY(printSpy.takeFirst().takeFirst().toBool());
+}
+
+void tst_Printing::interruptPrintingOnQPrinter()
+{
+ QTemporaryDir tempDir(QDir::tempPath() + "/tst_qwebengineview-XXXXXX");
+ QVERIFY(tempDir.isValid());
+
+ QPrinter printer;
+ printer.setOutputFileName(tempDir.path() + "/file.pdf");
+ printer.setCopyCount(100000);
+ QVERIFY(printer.isValid());
+ QCOMPARE(printer.outputFormat(), QPrinter::PdfFormat);
+
+ {
+ QWebEngineView view;
+ loadSync(view.page(), QUrl("qrc:///resources/basic_printing_page.html"));
+ view.print(&printer);
+ QTRY_COMPARE(printer.printerState(), QPrinter::Active);
+ QTRY_COMPARE(printer.paintingActive(), true);
+ // Destroying the view here should interrupt immediately instead of waiting for all
+ // pages to print.
+ }
+
+ QTRY_COMPARE(printer.printerState(), QPrinter::Idle);
+}
+
QTEST_MAIN(tst_Printing)
#include "tst_printing.moc"