From 069472a7feaa8abc54a28bb7ae8c585e0fbdc06c Mon Sep 17 00:00:00 2001
From: Daniel
Date: Thu, 23 Apr 2026 16:25:12 -0600
Subject: [PATCH 1/2] feat: support documentTitle for PDF printing via
document.title guard
For PDF iframes, Chrome uses document.title (not the iframe's )
for "Save as PDF" filenames. Since print() on a PDF iframe is
non-blocking, Chrome reads document.title asynchronously after return.
This adds a MutationObserver-based guard that temporarily holds
document.title to the desired value, preventing framework re-renders
(React , Vue router, etc.) from resetting it during that window.
Adds documentTitleHoldMs param (default 3000ms) to let users tune the
hold duration.
Closes #487
Closes #66
Co-Authored-By: Claude Opus 4.6 (1M context)
---
src/index.d.ts | 1 +
src/js/init.js | 1 +
src/js/print.js | 45 ++++++++++++++++++++++++++++++++++++++++-----
3 files changed, 42 insertions(+), 5 deletions(-)
diff --git a/src/index.d.ts b/src/index.d.ts
index 5c5d325..c6b87e0 100644
--- a/src/index.d.ts
+++ b/src/index.d.ts
@@ -9,6 +9,7 @@ declare namespace printJS {
fallbackPrintable?: string;
type?: PrintTypes;
documentTitle?: string;
+ documentTitleHoldMs?: number;
header?: any;
headerStyle?: string;
footer?: any;
diff --git a/src/js/init.js b/src/js/init.js
index 8d2515e..b0b7978 100644
--- a/src/js/init.js
+++ b/src/js/init.js
@@ -35,6 +35,7 @@ export default {
frameRemoveDelay: null,
printableElement: null,
documentTitle: 'Document',
+ documentTitleHoldMs: 3000,
targetStyle: ['clear', 'display', 'width', 'min-width', 'height', 'min-height', 'max-height'],
targetStyles: ['border', 'box', 'break', 'text-decoration'],
ignoreElements: [],
diff --git a/src/js/print.js b/src/js/print.js
index 40f9629..cdf6572 100644
--- a/src/js/print.js
+++ b/src/js/print.js
@@ -1,6 +1,29 @@
import Browser from './browser'
import { cleanUp } from './functions'
+function guardDocumentTitle (title, holdMs, onDone) {
+ if (!title) { onDone(); return }
+
+ var originalTitle = document.title
+ document.title = title
+
+ var titleEl = document.querySelector('title')
+ var observer = null
+
+ if (titleEl && typeof MutationObserver !== 'undefined') {
+ observer = new MutationObserver(function () {
+ if (document.title !== title) document.title = title
+ })
+ observer.observe(titleEl, { characterData: true, childList: true, subtree: true })
+ }
+
+ setTimeout(function () {
+ if (observer) observer.disconnect()
+ document.title = originalTitle
+ onDone()
+ }, holdMs)
+}
+
const Print = {
send: (params, printFrame) => {
// Append iframe element to document body
@@ -51,6 +74,16 @@ const Print = {
}
function performPrint (iframeElement, params) {
+ var isPdfWithTitle = params.type === 'pdf' &&
+ params.documentTitle &&
+ params.documentTitle !== 'Document'
+
+ if (isPdfWithTitle) {
+ guardDocumentTitle(params.documentTitle, params.documentTitleHoldMs || 3000, function () {
+ cleanUp(params)
+ })
+ }
+
try {
iframeElement.focus()
@@ -59,15 +92,15 @@ function performPrint (iframeElement, params) {
try {
iframeElement.contentWindow.document.execCommand('print', false, null)
} catch (e) {
- setTimeout(function(){
+ setTimeout(function () {
iframeElement.contentWindow.print()
- },1000)
+ }, 1000)
}
} else {
// Other browsers
- setTimeout(function(){
+ setTimeout(function () {
iframeElement.contentWindow.print()
- },1000)
+ }, 1000)
}
} catch (error) {
params.onError(error)
@@ -78,7 +111,9 @@ function performPrint (iframeElement, params) {
iframeElement.style.left = '-1px'
}
- cleanUp(params)
+ if (!isPdfWithTitle) {
+ cleanUp(params)
+ }
}
}
From 397cb64c9669acb12d8809f07b2daca7e5ae57ab Mon Sep 17 00:00:00 2001
From: Daniel
Date: Thu, 23 Apr 2026 16:35:16 -0600
Subject: [PATCH 2/2] test: add manual test cases for PDF documentTitle guard
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Two buttons added:
- "Print PDF with documentTitle" — basic test, verify Chrome's print
dialog shows "My Custom Report" as the filename
- "Print PDF with documentTitle (simulated framework reset)" — uses a
setInterval to aggressively reset document.title, proving the
MutationObserver guard holds the title stable
Co-Authored-By: Claude Opus 4.6 (1M context)
---
test/manual/index.html | 32 +++++++++++++++++++++++++++++++-
1 file changed, 31 insertions(+), 1 deletion(-)
diff --git a/test/manual/index.html b/test/manual/index.html
index 56f154e..5efb031 100644
--- a/test/manual/index.html
+++ b/test/manual/index.html
@@ -14,6 +14,30 @@
})
}
+ function printPdfWithDocumentTitle() {
+ printJS({
+ printable: '/test/manual/test.pdf',
+ type: 'pdf',
+ documentTitle: 'My Custom Report'
+ })
+ }
+
+ function printPdfWithDocumentTitleAndSimulatedReset() {
+ // Simulate a framework (React, Vue, etc.) aggressively resetting document.title
+ var interval = setInterval(function () {
+ document.title = 'RESET BY FRAMEWORK'
+ }, 50)
+
+ // Stop the simulation after 5s so it doesn't run forever
+ setTimeout(function () { clearInterval(interval) }, 5000)
+
+ printJS({
+ printable: '/test/manual/test.pdf',
+ type: 'pdf',
+ documentTitle: 'Guarded Title Test'
+ })
+ }
+
function printPdfWithModal() {
printJS({
printable: '/test/manual/test.pdf',
@@ -288,7 +312,13 @@ Print.js Test Page
-
+
+
+
Form Elements