From ad869aa6f835999877cae0ef52db124810728f9f Mon Sep 17 00:00:00 2001 From: Dave Date: Sun, 4 Oct 2026 13:15:08 +0200 Subject: [PATCH] fix(pdf-print): open the PDF in a new tab instead of printing the iframe MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `iframe.contentWindow.print()` does not work for a PDF in Chrome. Chrome backs every PDF — src URL or blob URL — with its internal `pdf_embedder` extension rather than a real DOM document, so the frame's `contentDocument` is an empty shell: The PDF *paints* — you can see it in the preview — but `print()` runs against that empty body, so the operator gets a print dialog full of blank pages and no error to explain why. Read straight out of a live page to confirm. A new tab gets Chrome's real PDF viewer, whose own print button works. Same document, no blank pages. Affects the delivery-note preview, the quote PDF viewer, and the second quote viewer in the same file. Popup-blocked is now reported explicitly rather than appearing to do nothing. --- .../delivery-notes/preview-dialog.tsx | 109 +++++++++++++----- src/components/quote-pdf-viewer.tsx | 23 ++-- 2 files changed, 91 insertions(+), 41 deletions(-) diff --git a/src/components/delivery-notes/preview-dialog.tsx b/src/components/delivery-notes/preview-dialog.tsx index 6015053..e2ffc3e 100644 --- a/src/components/delivery-notes/preview-dialog.tsx +++ b/src/components/delivery-notes/preview-dialog.tsx @@ -11,8 +11,9 @@ * The dialog is intentionally chrome-minimal so the PDF * takes most of the viewport. The footer has: * - Close - * - Print (calls iframe.contentWindow.print() so the - * operator prints just the PDF, not the whole page) + * - Print (opens the PDF in a new tab — see the note on + * `handlePrint` for why the iframe's own print() gives blank + * pages) * - Download (uses the `download` attribute so the * browser saves the file rather than opening it in a * viewer) @@ -67,28 +68,77 @@ export function PdfPreviewDialog({ subtitle, downloadFilename, }: PdfPreviewDialogProps) { - // Ref on the iframe so Print can call the iframe's own - // print() — this prints just the PDF, not the whole page - // (the parent page has the modal chrome which would also - // print otherwise). + // Ref on the iframe so we can tell whether the PDF has finished + // loading before offering a print action. const iframeRef = useRef(null); + const [blocked, setBlocked] = useState(false); + + // Clear the pop-up-blocked notice on close rather than in an effect: + // syncing state from an effect triggers a second render pass, and + // the flag is only ever set by a click inside this component. + const handleOpenChange = (next: boolean) => { + if (!next) setBlocked(false); + onOpenChange(next); + }; const handlePrint = () => { - // Calling the iframe's contentWindow.print() prints the - // PDF document inside the iframe. Works in Chrome, Edge, - // Firefox, Safari. If the iframe didn't load (network - // blip, broken PDF), the call is a no-op — the operator - // can still use the Download button. - try { - iframeRef.current?.contentWindow?.print(); - } catch { - // Ignore — some browsers throw on cross-origin or - // blocked-iframe cases. The Download button remains. + // Deliberately opens the PDF in a new tab rather than calling + // `iframe.contentWindow.print()`. + // + // That older approach does NOT work for a PDF in Chrome. Chrome + // renders PDFs with its internal `pdf_embedder` extension, not as + // a real DOM document, so the iframe's `contentDocument` is an + // empty shell: + // + // + // + // + // The PDF *paints* (you can see it), but `print()` runs against + // that empty body — so the operator gets a print dialog full of + // blank pages and no error to explain why. Verified in Chrome by + // reading the iframe's document straight out of the page. + // + // A new tab hands the operator Chrome's real PDF viewer, whose own + // print button works. Same document, no blank pages. + const url = pdfUrl; + const win = window.open(url, "_blank", "noopener,noreferrer"); + if (!win) { + // Popup blocked. Say so rather than appearing to do nothing. + setBlocked(true); } }; + if (blocked) { + return ( + + + + Pop-up blocked + + Your browser blocked the new tab needed to print {title}. Allow + pop-ups for this site, or use Download to save the PDF and open it + directly. + + + + + + + + + + + ); + } + return ( - + onOpenChange(false)} + onClick={() => handleOpenChange(false)} aria-label="Close preview" data-testid="pdf-preview-close" > @@ -137,14 +187,15 @@ export function PdfPreviewDialog({ - {/* Print — calls the iframe's own print() so the - operator prints just the PDF, not the page - chrome behind the modal. */} + {/* Print — opens the PDF in a new tab so the operator gets + Chrome's real PDF viewer, whose print button works. See + the note on `handlePrint` for why the iframe's own + `print()` produces blank pages. */}