diff options
author | Szabolcs David <davidsz@inf.u-szeged.hu> | 2024-01-11 13:14:33 +0100 |
---|---|---|
committer | Peter Varga <pvarga@inf.u-szeged.hu> | 2024-01-12 19:52:50 +0000 |
commit | 62735b0857a11689bc18f8047112cae42bb05985 (patch) | |
tree | f540a9beee37980fbf0c2e19ac42dd0bb7c11354 | |
parent | 3a5ebfa97260ae7e19e4b78a7345ec39163bcc2f (diff) |
Fix printing from PDF plugin
Update the plugin finder logic everywhere to match with Chrome. This
comes with a small cleanup: collect PDF-related helper functions
scattered around WebEngine in one pdf_util_qt implementation.
Add auto test to catch this recurring issue earlier.
Task-number: QTBUG-119878
Change-Id: I03b2bd62bebf5b38afc572e0629db106d024e89d
Reviewed-by: Michael BrĂ¼ning <michael.bruning@qt.io>
(cherry picked from commit e09cf6e7a1f582d06f86ff2c166b7c2269fd4b47)
Reviewed-by: Qt Cherry-pick Bot <cherrypick_bot@qt-project.org>
(cherry picked from commit a1ffdacd23c13d97793b87f098e3ec7ab8ff1de6)
Reviewed-by: Peter Varga <pvarga@inf.u-szeged.hu>
-rw-r--r-- | src/core/CMakeLists.txt | 1 | ||||
-rw-r--r-- | src/core/extensions/pdf_iframe_navigation_throttle_qt.cpp | 8 | ||||
-rw-r--r-- | src/core/pdf_util_qt.cpp | 92 | ||||
-rw-r--r-- | src/core/pdf_util_qt.h | 34 | ||||
-rw-r--r-- | src/core/printing/pdf_web_contents_helper_client_qt.cpp | 30 | ||||
-rw-r--r-- | src/core/printing/print_view_manager_qt.cpp | 7 | ||||
-rw-r--r-- | src/core/renderer/print_web_view_helper_delegate_qt.cpp | 30 | ||||
-rw-r--r-- | src/core/web_contents_adapter.cpp | 3 | ||||
-rw-r--r-- | tests/auto/widgets/printing/tst_printing.cpp | 46 |
9 files changed, 193 insertions, 58 deletions
diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 75a41d6a6..c4e413351 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -152,6 +152,7 @@ foreach(arch ${archs}) ozone/platform_window_qt.cpp ozone/platform_window_qt.h ozone/surface_factory_qt.cpp ozone/surface_factory_qt.h permission_manager_qt.cpp permission_manager_qt.h + pdf_util_qt.cpp pdf_util_qt.h platform_notification_service_qt.cpp platform_notification_service_qt.h pointer_device_qt.cpp pref_service_adapter.cpp pref_service_adapter.h diff --git a/src/core/extensions/pdf_iframe_navigation_throttle_qt.cpp b/src/core/extensions/pdf_iframe_navigation_throttle_qt.cpp index 9b6b38a52..9a2feb816 100644 --- a/src/core/extensions/pdf_iframe_navigation_throttle_qt.cpp +++ b/src/core/extensions/pdf_iframe_navigation_throttle_qt.cpp @@ -26,12 +26,12 @@ #include "ui/base/webui/jstemplate_builder.h" #include "ui/base/webui/web_ui_util.h" +#include "pdf_util_qt.h" + #include <QtGlobal> namespace extensions { -constexpr char kPDFMimeType[] = "application/pdf"; - // Used to scope the posted navigation task to the lifetime of |web_contents|. class PdfWebContentsLifetimeHelper : public content::WebContentsUserData<PdfWebContentsLifetimeHelper> { @@ -76,7 +76,7 @@ bool IsPDFPluginEnabled(content::NavigationHandle *navigation_handle, bool *is_s process_id, routing_id, navigation_handle->GetWebContents()->GetBrowserContext(), navigation_handle->GetURL(), - kPDFMimeType, false /* allow_wildcard */, + QtWebEngineCore::kPDFMimeType, false /* allow_wildcard */, is_stale, &plugin_info, nullptr /* actual_mime_type */); } @@ -119,7 +119,7 @@ content::NavigationThrottle::ThrottleCheckResult PDFIFrameNavigationThrottleQt:: std::string mime_type; response_headers->GetMimeType(&mime_type); - if (mime_type != kPDFMimeType) + if (mime_type != QtWebEngineCore::kPDFMimeType) return content::NavigationThrottle::PROCEED; // We MUST download responses marked as attachments rather than showing diff --git a/src/core/pdf_util_qt.cpp b/src/core/pdf_util_qt.cpp new file mode 100644 index 000000000..9503f5910 --- /dev/null +++ b/src/core/pdf_util_qt.cpp @@ -0,0 +1,92 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +// Copyright 2021 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 "pdf_util_qt.h" + +#include <QtGlobal> + +#include "base/check.h" +#include "chrome/common/webui_url_constants.h" +#include "content/public/browser/render_frame_host.h" +#include "content/public/browser/render_process_host.h" +#include "content/public/browser/web_contents.h" +#include "extensions/buildflags/buildflags.h" +#include "url/gurl.h" +#include "url/origin.h" + +#if BUILDFLAG(ENABLE_EXTENSIONS) +#include "extensions/browser/guest_view/mime_handler_view/mime_handler_view_guest.h" +#include "extensions/common/constants.h" +#endif // BUILDFLAG(ENABLE_EXTENSIONS) + +namespace QtWebEngineCore { + +bool IsPdfExtensionOrigin(const url::Origin &origin) +{ +#if BUILDFLAG(ENABLE_EXTENSIONS) + return origin.scheme() == extensions::kExtensionScheme + && origin.host() == extension_misc::kPdfExtensionId; +#else + Q_UNUSED(origin); + return false; +#endif +} + +bool IsPdfInternalPluginAllowedOrigin(const url::Origin &origin) +{ + if (IsPdfExtensionOrigin(origin)) + return true; + + // Allow embedding the internal PDF plugin in chrome://print. + if (origin == url::Origin::Create(GURL(chrome::kChromeUIPrintURL))) + return true; + + // Only allow the PDF plugin in the known, trustworthy origins that are + // allowlisted above. See also https://crbug.com/520422 and + // https://crbug.com/1027173. + return false; +} + +content::RenderFrameHost *GetFullPagePlugin(content::WebContents *contents) +{ + content::RenderFrameHost *full_page_plugin = nullptr; +#if BUILDFLAG(ENABLE_EXTENSIONS) + contents->ForEachRenderFrameHostWithAction([&full_page_plugin](content::RenderFrameHost *rfh) { + auto* guest_view = extensions::MimeHandlerViewGuest::FromRenderFrameHost(rfh); + if (guest_view && guest_view->is_full_page_plugin()) { + DCHECK_EQ(guest_view->GetGuestMainFrame(), rfh); + full_page_plugin = rfh; + return content::RenderFrameHost::FrameIterationAction::kStop; + } + return content::RenderFrameHost::FrameIterationAction::kContinue; + }); +#endif // BUILDFLAG(ENABLE_EXTENSIONS) + return full_page_plugin; +} + +content::RenderFrameHost *FindPdfChildFrame(content::RenderFrameHost *rfh) +{ + if (!rfh) + return nullptr; + + if (!IsPdfExtensionOrigin(rfh->GetLastCommittedOrigin())) + return nullptr; + + content::RenderFrameHost *pdf_rfh = nullptr; + rfh->ForEachRenderFrameHost([&pdf_rfh](content::RenderFrameHost *rfh) { + if (!rfh->GetProcess()->IsPdf()) + return; + + DCHECK(IsPdfExtensionOrigin(rfh->GetParent()->GetLastCommittedOrigin())); + DCHECK(!pdf_rfh); + pdf_rfh = rfh; + }); + + return pdf_rfh; +} + +} // namespace QtWebEngineCore diff --git a/src/core/pdf_util_qt.h b/src/core/pdf_util_qt.h new file mode 100644 index 000000000..5ee211800 --- /dev/null +++ b/src/core/pdf_util_qt.h @@ -0,0 +1,34 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +// Copyright 2021 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. + +#ifndef PDF_UTIL_QT_H +#define PDF_UTIL_QT_H + +namespace content { +class RenderFrameHost; +class WebContents; +} // namespace content + +namespace url { +class Origin; +} // namespace url + +namespace QtWebEngineCore { + +// from chrome/common/pdf_util.cc: +constexpr char kPDFMimeType[] = "application/pdf"; + +bool IsPdfExtensionOrigin(const url::Origin &origin); +bool IsPdfInternalPluginAllowedOrigin(const url::Origin &origin); + +// from chrome/browser/pdf/pdf_frame_util.cc: +content::RenderFrameHost *GetFullPagePlugin(content::WebContents *contents); +content::RenderFrameHost *FindPdfChildFrame(content::RenderFrameHost *rfh); + +} // namespace QtWebEngineCore + +#endif // PDF_UTIL_QT_H diff --git a/src/core/printing/pdf_web_contents_helper_client_qt.cpp b/src/core/printing/pdf_web_contents_helper_client_qt.cpp index 4deb398c6..0ff7499c3 100644 --- a/src/core/printing/pdf_web_contents_helper_client_qt.cpp +++ b/src/core/printing/pdf_web_contents_helper_client_qt.cpp @@ -9,33 +9,7 @@ #include "extensions/browser/guest_view/mime_handler_view/mime_handler_view_guest.h" #include "extensions/common/constants.h" -namespace { -bool IsPdfExtensionOrigin(const url::Origin &origin) -{ - return origin.scheme() == extensions::kExtensionScheme && - origin.host() == extension_misc::kPdfExtensionId; -} - -// from chrome/browser/pdf/pdf_frame_util.cc: -content::RenderFrameHost *FindPdfChildFrame(content::RenderFrameHost *rfh) -{ - if (!IsPdfExtensionOrigin(rfh->GetLastCommittedOrigin())) - return nullptr; - - content::RenderFrameHost *pdf_rfh = nullptr; - rfh->ForEachRenderFrameHost( - [&pdf_rfh](content::RenderFrameHost *rfh) { - if (!rfh->GetProcess()->IsPdf()) - return; - - DCHECK(IsPdfExtensionOrigin(rfh->GetParent()->GetLastCommittedOrigin())); - DCHECK(!pdf_rfh); - pdf_rfh = rfh; - }); - - return pdf_rfh; -} -} // namespace +#include "pdf_util_qt.h" PDFWebContentsHelperClientQt::PDFWebContentsHelperClientQt() = default; PDFWebContentsHelperClientQt::~PDFWebContentsHelperClientQt() = default; @@ -43,7 +17,7 @@ PDFWebContentsHelperClientQt::~PDFWebContentsHelperClientQt() = default; content::RenderFrameHost *PDFWebContentsHelperClientQt::FindPdfFrame(content::WebContents *contents) { content::RenderFrameHost *main_frame = contents->GetPrimaryMainFrame(); - content::RenderFrameHost *pdf_frame = FindPdfChildFrame(main_frame); + content::RenderFrameHost *pdf_frame = QtWebEngineCore::FindPdfChildFrame(main_frame); return pdf_frame ? pdf_frame : main_frame; } diff --git a/src/core/printing/print_view_manager_qt.cpp b/src/core/printing/print_view_manager_qt.cpp index 892c9e406..060417e5b 100644 --- a/src/core/printing/print_view_manager_qt.cpp +++ b/src/core/printing/print_view_manager_qt.cpp @@ -8,6 +8,7 @@ #include "print_view_manager_qt.h" +#include "pdf_util_qt.h" #include "type_conversion.h" #include "web_contents_adapter_client.h" #include "web_contents_view_qt.h" @@ -238,7 +239,11 @@ bool PrintViewManagerQt::PrintToPDFInternal(const QPageLayout &pageLayout, if (web_contents()->IsCrashed()) return false; - content::RenderFrameHost* rfh = web_contents()->GetPrimaryMainFrame(); + content::RenderFrameHost *rfh = web_contents()->GetPrimaryMainFrame(); + // Use the plugin frame for printing if web_contents() is a PDF viewer guest + content::RenderFrameHost *full_page_plugin = GetFullPagePlugin(web_contents()); + if (content::RenderFrameHost *pdf_rfh = FindPdfChildFrame(full_page_plugin ? full_page_plugin : rfh)) + rfh = pdf_rfh; GetPrintRenderFrame(rfh)->InitiatePrintPreview(mojo::PendingAssociatedRemote<printing::mojom::PrintRenderer>(), false); DCHECK(!m_printPreviewRfh); diff --git a/src/core/renderer/print_web_view_helper_delegate_qt.cpp b/src/core/renderer/print_web_view_helper_delegate_qt.cpp index f77b6fbbc..f01568e65 100644 --- a/src/core/renderer/print_web_view_helper_delegate_qt.cpp +++ b/src/core/renderer/print_web_view_helper_delegate_qt.cpp @@ -15,8 +15,10 @@ #include "chrome/common/webui_url_constants.h" #include "extensions/common/constants.h" #include "third_party/blink/public/web/web_document.h" +#include "extensions/renderer/guest_view/mime_handler_view/post_message_support.h" #endif // BUILDFLAG(ENABLE_EXTENSIONS) +#include "pdf_util_qt.h" #include "print_web_view_helper_delegate_qt.h" #include "web_engine_library_info.h" @@ -24,33 +26,13 @@ namespace QtWebEngineCore { PrintWebViewHelperDelegateQt::~PrintWebViewHelperDelegateQt() {} -bool IsPdfExtensionOrigin(const url::Origin& origin) -{ -#if BUILDFLAG(ENABLE_EXTENSIONS) - return origin.scheme() == extensions::kExtensionScheme - && origin.host() == extension_misc::kPdfExtensionId; -#else - Q_UNUSED(origin); - return false; -#endif -} - blink::WebElement PrintWebViewHelperDelegateQt::GetPdfElement(blink::WebLocalFrame *frame) { #if BUILDFLAG(ENABLE_EXTENSIONS) - const url::Origin origin = frame->GetDocument().GetSecurityOrigin(); - bool inside_print_preview = origin == url::Origin::Create(GURL(chrome::kChromeUIPrintURL)); - bool inside_pdf_extension = IsPdfExtensionOrigin(origin); - if (inside_print_preview || inside_pdf_extension) { - // <object> with id="plugin" is created in - // chrome/browser/resources/pdf/pdf_viewer_base.js. - auto viewer_element = frame->GetDocument().GetElementById("viewer"); - if (!viewer_element.IsNull() && !viewer_element.ShadowRoot().IsNull()) { - auto plugin_element = viewer_element.ShadowRoot().QuerySelector("#plugin"); - if (!plugin_element.IsNull()) - return plugin_element; - } - NOTREACHED(); + if (frame->Parent() && IsPdfInternalPluginAllowedOrigin(frame->Parent()->GetSecurityOrigin())) { + auto plugin_element = frame->GetDocument().QuerySelector("embed"); + DCHECK(!plugin_element.IsNull()); + return plugin_element; } #endif // BUILDFLAG(ENABLE_EXTENSIONS) return blink::WebElement(); diff --git a/src/core/web_contents_adapter.cpp b/src/core/web_contents_adapter.cpp index fe30206ce..f44675171 100644 --- a/src/core/web_contents_adapter.cpp +++ b/src/core/web_contents_adapter.cpp @@ -15,6 +15,7 @@ #include "favicon_service_factory_qt.h" #include "find_text_helper.h" #include "media_capture_devices_dispatcher.h" +#include "pdf_util_qt.h" #include "profile_adapter.h" #include "profile_qt.h" #include "qwebengineloadinginfo.h" @@ -1974,7 +1975,7 @@ WebContentsAdapter::LifecycleState WebContentsAdapter::determineRecommendedState // Do not discard PDFs as they might contain entry that is not saved and they // don't remember their scrolling positions. See crbug.com/547286 and // crbug.com/65244. - if (m_webContents->GetContentsMimeType() == "application/pdf") + if (m_webContents->GetContentsMimeType() == kPDFMimeType) return LifecycleState::Frozen; return LifecycleState::Discarded; diff --git a/tests/auto/widgets/printing/tst_printing.cpp b/tests/auto/widgets/printing/tst_printing.cpp index 1f9b5059c..605fb57b5 100644 --- a/tests/auto/widgets/printing/tst_printing.cpp +++ b/tests/auto/widgets/printing/tst_printing.cpp @@ -3,6 +3,7 @@ #include <QtWebEngineCore/private/qtwebenginecoreglobal_p.h> #include <QtWebEngineCore/qtwebenginecore-config.h> +#include <QWebEngineSettings> #include <QWebEngineView> #include <QTemporaryDir> #include <QTest> @@ -22,6 +23,7 @@ private slots: void printRequest(); #if QT_CONFIG(webengine_system_poppler) void printToPdfPoppler(); + void printFromPdfViewer(); #endif void interruptPrinting(); }; @@ -116,6 +118,50 @@ void tst_Printing::printToPdfPoppler() QVERIFY2(pdfPage->search(ustring::from_latin1("Hello Paper World"), rect, page::search_from_top, case_sensitive ), "Could not find text"); } + +void tst_Printing::printFromPdfViewer() +{ + using namespace poppler; + + QWebEngineView view; + view.page()->settings()->setAttribute(QWebEngineSettings::PluginsEnabled, true); + view.page()->settings()->setAttribute(QWebEngineSettings::PdfViewerEnabled, true); + + // Load a basic HTML + QSignalSpy spy(&view, &QWebEngineView::loadFinished); + view.load(QUrl("qrc:///resources/basic_printing_page.html")); + QTRY_COMPARE(spy.size(), 1); + + // Create a PDF + QTemporaryDir tempDir(QDir::tempPath() + "/tst_printing-XXXXXX"); + QVERIFY(tempDir.isValid()); + QString path = tempDir.path() + "/basic_page.pdf"; + QSignalSpy savePdfSpy(view.page(), &QWebEnginePage::pdfPrintingFinished); + view.page()->printToPdf(path); + QTRY_COMPARE(savePdfSpy.size(), 1); + + // Open the new file with the PDF viewer plugin + view.load(QUrl("file://" + path)); + QTRY_COMPARE(spy.size(), 2); + + // Print from the plugin + // loadFinished signal is not reliable when loading a PDF file, because it has multiple phases. + // Workaround: Try to print it a couple of times until the result matches the expected. + CallbackSpy<QByteArray> resultSpy; + bool ok = QTest::qWaitFor([&]() -> bool { + view.printToPdf(resultSpy.ref()); + QByteArray data = resultSpy.waitForResult(); + + // Check if the result contains text from the original basic HTML + // This catches all the typical issues: empty result or printing the WebUI without PDF content. + QScopedPointer<document> pdf(document::load_from_raw_data(data.constData(), data.length())); + QScopedPointer<page> pdfPage(pdf->create_page(0)); + rectf rect; + return pdfPage->search(ustring::from_latin1("Hello Paper World"), rect, page::search_from_top, + case_sensitive); + }, 10000); + QVERIFY(ok); +} #endif void tst_Printing::interruptPrinting() |