diff options
| author | Moss Heim <moss.heim@qt.io> | 2025-10-14 11:19:12 +0200 |
|---|---|---|
| committer | Moss Heim <moss.heim@qt.io> | 2025-11-25 11:56:11 +0100 |
| commit | 06a7ed9e32906e7564ba612835459b02a64f56f0 (patch) | |
| tree | e3ef4b96b0cf1c202aacf2f3e4b59887b414df00 | |
| parent | 23854a3e636cd5cca1a0f845a85b8f76d7c89fae (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.cpp | 13 | ||||
| -rw-r--r-- | src/core/api/qwebenginepage_p.h | 4 | ||||
| -rw-r--r-- | src/core/printing/printer_worker.cpp | 6 | ||||
| -rw-r--r-- | src/webenginewidgets/api/qwebengineview.cpp | 9 | ||||
| -rw-r--r-- | src/webenginewidgets/api/qwebengineview_p.h | 2 | ||||
| -rw-r--r-- | tests/auto/widgets/printing/tst_printing.cpp | 54 |
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 *¤tPrinter, QSharedPointer<QByteArray> result) +QThread* QWebEngineViewPrivate::didPrintPage(QPrinter *¤tPrinter, QSharedPointer<QByteArray> result) { #if QT_CONFIG(webengine_printing_and_pdf) Q_Q(QWebEngineView); @@ -881,9 +881,11 @@ void QWebEngineViewPrivate::didPrintPage(QPrinter *¤tPrinter, 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" |
