From a959476dc28de683c2123263fc8662d69e76b4ec Mon Sep 17 00:00:00 2001 From: "trop[bot]" <37223003+trop[bot]@users.noreply.github.com> Date: Thu, 15 Aug 2024 10:30:22 -0400 Subject: [PATCH] fix: blank page when printing pdf (#43327) Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com> Co-authored-by: Shelley Vohr --- .../browser/api/electron_api_web_contents.cc | 18 +++---- shell/browser/printing/printing_utils.cc | 51 +++++++++++++++++++ shell/browser/printing/printing_utils.h | 8 +++ spec/api-web-contents-spec.ts | 32 +++++++++--- 4 files changed, 92 insertions(+), 17 deletions(-) diff --git a/shell/browser/api/electron_api_web_contents.cc b/shell/browser/api/electron_api_web_contents.cc index 341c85b13aac..ed4d02105bcd 100644 --- a/shell/browser/api/electron_api_web_contents.cc +++ b/shell/browser/api/electron_api_web_contents.cc @@ -2906,11 +2906,7 @@ void WebContents::OnGetDeviceNameToUse( if (!print_view_manager) return; - auto* focused_frame = web_contents()->GetFocusedFrame(); - auto* rfh = focused_frame && focused_frame->HasSelection() - ? focused_frame - : web_contents()->GetPrimaryMainFrame(); - + content::RenderFrameHost* rfh = GetRenderFrameHostToUse(web_contents()); print_view_manager->PrintNow(rfh, std::move(print_settings), std::move(print_callback)); } @@ -3107,12 +3103,13 @@ v8::Local WebContents::PrintToPDF(const base::Value& settings) { auto generate_document_outline = settings.GetDict().FindBool("generateDocumentOutline"); + content::RenderFrameHost* rfh = GetRenderFrameHostToUse(web_contents()); absl::variant print_pages_params = print_to_pdf::GetPrintPagesParams( - web_contents()->GetPrimaryMainFrame()->GetLastCommittedURL(), - landscape, display_header_footer, print_background, scale, - paper_width, paper_height, margin_top, margin_bottom, margin_left, - margin_right, std::make_optional(header_template), + rfh->GetLastCommittedURL(), landscape, display_header_footer, + print_background, scale, paper_width, paper_height, margin_top, + margin_bottom, margin_left, margin_right, + std::make_optional(header_template), std::make_optional(footer_template), prefer_css_page_size, generate_tagged_pdf, generate_document_outline); @@ -3132,8 +3129,7 @@ v8::Local WebContents::PrintToPDF(const base::Value& settings) { absl::get(print_pages_params)); params->params->document_cookie = unique_id.value_or(0); - manager->PrintToPdf(web_contents()->GetPrimaryMainFrame(), page_ranges, - std::move(params), + manager->PrintToPdf(rfh, page_ranges, std::move(params), base::BindOnce(&WebContents::OnPDFCreated, GetWeakPtr(), std::move(promise))); diff --git a/shell/browser/printing/printing_utils.cc b/shell/browser/printing/printing_utils.cc index 5b0f934cd793..14dc82a3245a 100644 --- a/shell/browser/printing/printing_utils.cc +++ b/shell/browser/printing/printing_utils.cc @@ -10,12 +10,20 @@ #include "base/task/task_traits.h" #include "base/task/thread_pool.h" #include "chrome/browser/browser_process.h" +#include "components/pdf/browser/pdf_frame_util.h" #include "content/public/browser/browser_thread.h" +#include "content/public/browser/render_frame_host.h" +#include "content/public/browser/web_contents.h" #include "electron/buildflags/buildflags.h" +#include "pdf/pdf_features.h" #include "printing/backend/print_backend.h" // nogncheck #include "printing/units.h" #include "shell/common/thread_restrictions.h" +#if BUILDFLAG(ENABLE_ELECTRON_EXTENSIONS) +#include "extensions/browser/guest_view/mime_handler_view/mime_handler_view_guest.h" +#endif + #if BUILDFLAG(IS_MAC) #include #endif @@ -68,6 +76,49 @@ bool IsDeviceNameValid(const std::u16string& device_name) { #endif } +// Duplicated from chrome/browser/printing/print_view_manager_common.cc +content::RenderFrameHost* GetFullPagePlugin(content::WebContents* contents) { + content::RenderFrameHost* full_page_plugin = nullptr; +#if BUILDFLAG(ENABLE_ELECTRON_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_ELECTRON_EXTENSIONS) + return full_page_plugin; +} + +// Pick the right RenderFrameHost based on the WebContents. +// Modified from chrome/browser/printing/print_view_manager_common.cc +content::RenderFrameHost* GetRenderFrameHostToUse( + content::WebContents* contents) { +#if BUILDFLAG(ENABLE_PDF_VIEWER) + // Pick the plugin frame host if `contents` is a PDF viewer guest. If using + // OOPIF PDF viewer, pick the PDF extension frame host. + content::RenderFrameHost* full_page_pdf_embedder_host = + chrome_pdf::features::IsOopifPdfEnabled() + ? pdf_frame_util::FindFullPagePdfExtensionHost(contents) + : GetFullPagePlugin(contents); + content::RenderFrameHost* pdf_rfh = pdf_frame_util::FindPdfChildFrame( + full_page_pdf_embedder_host ? full_page_pdf_embedder_host + : contents->GetPrimaryMainFrame()); + if (pdf_rfh) { + return pdf_rfh; + } +#endif // BUILDFLAG(ENABLE_PDF) + auto* focused_frame = contents->GetFocusedFrame(); + return (focused_frame && focused_frame->HasSelection()) + ? focused_frame + : contents->GetPrimaryMainFrame(); +} + std::pair GetDeviceNameToUse( const std::u16string& device_name) { #if BUILDFLAG(IS_WIN) diff --git a/shell/browser/printing/printing_utils.h b/shell/browser/printing/printing_utils.h index f18174f0876d..e795aeecec7c 100644 --- a/shell/browser/printing/printing_utils.h +++ b/shell/browser/printing/printing_utils.h @@ -14,6 +14,11 @@ namespace gfx { class Size; } +namespace content { +class RenderFrameHost; +class WebContents; +} // namespace content + namespace electron { // This function returns the per-platform default printer's DPI. @@ -24,6 +29,9 @@ gfx::Size GetDefaultPrinterDPI(const std::u16string& device_name); // sanity checking of device_name validity and so will crash on invalid names. bool IsDeviceNameValid(const std::u16string& device_name); +content::RenderFrameHost* GetRenderFrameHostToUse( + content::WebContents* contents); + // This function returns a validated device name. // If the user passed one to webContents.print(), we check that it's valid and // return it or fail if the network doesn't recognize it. If the user didn't diff --git a/spec/api-web-contents-spec.ts b/spec/api-web-contents-spec.ts index a54990fcb8ab..188763e0ca49 100644 --- a/spec/api-web-contents-spec.ts +++ b/spec/api-web-contents-spec.ts @@ -2126,6 +2126,10 @@ describe('webContents module', () => { ifdescribe(features.isPrintingEnabled())('printToPDF()', () => { let w: BrowserWindow; + const containsText = (items: any[], text: RegExp) => { + return items.some(({ str }: { str: string }) => str.match(text)); + }; + beforeEach(() => { w = new BrowserWindow({ show: false, @@ -2248,7 +2252,7 @@ describe('webContents module', () => { }); it('with custom header and footer', async () => { - await w.loadFile(path.join(__dirname, 'fixtures', 'api', 'print-to-pdf-small.html')); + await w.loadFile(path.join(fixturesPath, 'api', 'print-to-pdf-small.html')); const data = await w.webContents.printToPDF({ displayHeaderFooter: true, @@ -2261,11 +2265,8 @@ describe('webContents module', () => { const { items } = await page.getTextContent(); - // Check that generated PDF contains a header. - const containsText = (text: RegExp) => items.some(({ str }: { str: string }) => str.match(text)); - - expect(containsText(/I'm a PDF header/)).to.be.true(); - expect(containsText(/I'm a PDF footer/)).to.be.true(); + expect(containsText(items, /I'm a PDF header/)).to.be.true(); + expect(containsText(items, /I'm a PDF footer/)).to.be.true(); }); it('in landscape mode', async () => { @@ -2317,6 +2318,25 @@ describe('webContents module', () => { Suspects: false }); }); + + it('from an existing pdf document', async () => { + const pdfPath = path.join(fixturesPath, 'cat.pdf'); + await w.loadFile(pdfPath); + + // TODO(codebytere): the PDF plugin is not always ready immediately + // after the document is loaded, so we need to wait for it to be ready. + // We should find a better way to do this. + await setTimeout(3000); + + const data = await w.webContents.printToPDF({}); + const doc = await pdfjs.getDocument(data).promise; + expect(doc.numPages).to.equal(2); + + const page = await doc.getPage(1); + + const { items } = await page.getTextContent(); + expect(containsText(items, /Cat: The Ideal Pet/)).to.be.true(); + }); }); describe('PictureInPicture video', () => {