From e356aa993a01887f532168187ccab04a069b5904 Mon Sep 17 00:00:00 2001 From: delchev Date: Tue, 4 Aug 2026 18:53:36 +0300 Subject: [PATCH] fix(harmonia): the printed PDF's blob URL lives as long as its owner page; popup-blocked prints download instead (#6510) The generated print flows opened the rendered PDF at an object URL and revoked it on a 60-second timer - which does not close the tab, it kills the URL under the still-open viewer, so reloading or printing the tab after the timer fails with a dead blob ("the file was removed"). And window.open runs after async work, outside the click's gesture stack, so a popup blocker made the whole print fail silently. The object URL now lives until the SPA page unloads (its natural owner lifetime - a print-sized PDF held until then costs nothing), and a blocked popup falls back to downloading the PDF via a temporary anchor, which browsers always allow. The report/export flows already use the safe immediate-download pattern and are untouched. Co-Authored-By: Claude Fable 5 --- .../document/document-page.js.template | 17 +++++++++++++++-- .../ui/perspective/manage/form-page.js.template | 17 +++++++++++++++-- 2 files changed, 30 insertions(+), 4 deletions(-) diff --git a/components/template/template-application-ui-harmonia-java/src/main/resources/META-INF/dirigible/template-application-ui-harmonia-java/ui/perspective/document/document-page.js.template b/components/template/template-application-ui-harmonia-java/src/main/resources/META-INF/dirigible/template-application-ui-harmonia-java/ui/perspective/document/document-page.js.template index cd38bc28f6..685df2e88b 100644 --- a/components/template/template-application-ui-harmonia-java/src/main/resources/META-INF/dirigible/template-application-ui-harmonia-java/ui/perspective/document/document-page.js.template +++ b/components/template/template-application-ui-harmonia-java/src/main/resources/META-INF/dirigible/template-application-ui-harmonia-java/ui/perspective/document/document-page.js.template @@ -266,8 +266,21 @@ document.addEventListener('alpine:init', () => { } const blob = await response.blob(); const url = URL.createObjectURL(blob); - window.open(url, '_blank'); - setTimeout(() => URL.revokeObjectURL(url), 60000); + // The object URL must outlive the tab that renders it: a timed revoke kills the PDF + // viewer's reload/print the moment it fires (observed live - the blob tab worked once, + // then "the file was removed"). The URL is released when THIS page unloads, which is the + // blob's natural owner lifetime; a print-sized PDF held until then costs nothing. + const opened = window.open(url, '_blank'); + if (!opened) { + // Popup blocked - the open runs after async work, outside the click's gesture stack. + // Fall back to a download, which the browser always allows. + const link = document.createElement('a'); + link.href = url; + link.download = '${name}-' + this.id + '.pdf'; + document.body.appendChild(link); + link.click(); + link.remove(); + } } catch (e) { console.error('Print failed', e); this.printError = 'Print failed'; diff --git a/components/template/template-application-ui-harmonia-java/src/main/resources/META-INF/dirigible/template-application-ui-harmonia-java/ui/perspective/manage/form-page.js.template b/components/template/template-application-ui-harmonia-java/src/main/resources/META-INF/dirigible/template-application-ui-harmonia-java/ui/perspective/manage/form-page.js.template index eb2e2368ed..5a4fc74ca2 100644 --- a/components/template/template-application-ui-harmonia-java/src/main/resources/META-INF/dirigible/template-application-ui-harmonia-java/ui/perspective/manage/form-page.js.template +++ b/components/template/template-application-ui-harmonia-java/src/main/resources/META-INF/dirigible/template-application-ui-harmonia-java/ui/perspective/manage/form-page.js.template @@ -599,8 +599,21 @@ document.addEventListener('alpine:init', () => { } const blob = await response.blob(); const url = URL.createObjectURL(blob); - window.open(url, '_blank'); - setTimeout(() => URL.revokeObjectURL(url), 60000); + // The object URL must outlive the tab that renders it: a timed revoke kills the PDF + // viewer's reload/print the moment it fires (observed live - the blob tab worked once, + // then "the file was removed"). The URL is released when THIS page unloads, which is the + // blob's natural owner lifetime; a print-sized PDF held until then costs nothing. + const opened = window.open(url, '_blank'); + if (!opened) { + // Popup blocked - the open runs after async work, outside the click's gesture stack. + // Fall back to a download, which the browser always allows. + const link = document.createElement('a'); + link.href = url; + link.download = '${name}-' + this.id + '.pdf'; + document.body.appendChild(link); + link.click(); + link.remove(); + } } catch (e) { console.error('Print failed', e); this.printError = 'Print failed';