Error printing before pdf preview is complete
Clicking print in the Chrome or Firefox preview before the pdf file is fully loaded I get the following error.
"An error occurred while trying to print. Please check the printer and try again."
If I wait for the preview to fully load it works fine. Some clients are not so patient. 🤷🏻♂️
I have the same issue. I am using version 1.6.0 of print.js. While trying to solve this issue, I created an async wrapper so I can await the call to make sure nothing goes out of scope:
const printJSAsync = cfg => new Promise((resolve, reject) =>
printJS({
...cfg,
onPrintDialogClose: resolve,
onError: reject,
})
)
Then, in my function, I call like this:
try {
showPageLoad('PDF generating...') //our internal modal
await printJSAsync({
printable: getPDFUrl(), //constructs url, returns PDF file bytes
type: 'pdf',
showModal: false, //I have my own modal
})
} catch (err) {
console.log(err)
} finally {
hidePageLoad()
}
As @matias87 said, if I click "Print" before the PDF fully loads, I receive the Chrome "Print Failed" modal with the message he described. This behavior does not happen if I print from a PDF I have opened in Chrome.
How can I prevent this?
I'm not sure how active this project is, so I've taken it upon myself to implement a solution of sorts. It's hacky, but it works.
Root Cause
The error occurs because the cleanUp method--which destroys the iFrame containing the content to print--is triggered by the window.onfocus event (for Chrome & Firefox, anyway). The intent, I assume, was for the iFrame to be destroyed once the user has completed printing and focus has returned to the main window.
This approach assumes the following order of events:
- User initiates print
- PDF data is fetched
- Print dialog appears ("Print" button disabled)
- PDF preview starts rendering
- PDF preview completes rendering (after this, Chrome is no longer dependent on the
iFramefor data) - Print dialog "Print" button is enabled
- Use clicks "Print" (or "Cancel") button
- PDF preview closes and focus returns to the main window
-
focusevent fires, triggering theiFrametounload(in the handler located atfunctions.js:83)
In reality, steps 5 & 6 (rendering complete & "Print" button enabled) are sometimes reversed; i.e., the user can sometimes print before the preview has loaded. The error occurs when the following scenario occurs:
- (same) User initiates print
- (same) PDF data is fetched
- (same) Print dialog appears ("Print" button disabled)
- (same) PDF preview starts rendering
- Print dialog "Print" button is enabled
- Use clicks "Print" (or "Cancel") button
- PDF preview closes and focus returns to the main window
-
focusevent fires, triggering theiFrametounload -
Rendering (and therefore printing) fails due to source
iFramebeing destroyed; error message shown to user
This is a tough problem to solve because there is no event I'm aware of triggers when there is no longer a dependency on the iFrame. Really, I think this is a bug in Chrome, or at least a really inconvenient implementation.
Solution (sort of)
Chrome does try to disable the "Print" button until rendering is complete; it just re-enables it a little too early. How much earlier? Never more than a couple seconds for me (literally < 2), even when I artificially slow things down. But I'm on a fast computer, and there's no way to know the perfect time.
The penalty for unloading too early is that the print attempt can fail entirely. But what is the penalty for unloading too late? Not much, it turns out.
- Because the
iFrameis invisible, the user is completely unaware if it is preserved a few extra seconds - It won't break future prints, since each new print checks for used frames and removes them (
init.js:95-97) - There are performance implications (memory usage), but they are limited & situational
- I'm new to the library, and there may be edge cases I haven't considered
The reality is that most of these print errors happen due to users clicking immediately on the "Print" button, so even a short delay of just a few seconds before unloading the iFrame could make a meaningful difference. But with so little penalty for waiting a little longer (and making success a little more likely), I settled on an (admittedly arbitrary) delay of 10 seconds. Thus, the existing lines of code (functions.js:90-94):
// Remove iframe from the DOM
const iframe = document.getElementById(params.frameId)
if (iframe) {
iframe.remove()
}
Become:
// Remove iframe from the DOM
setTimeout(() => {
const iframe = document.getElementById(params.frameId)
if (iframe) {
iframe.remove()
}
}, 10_000) // 10 second delay
In my testing, I was unable to reproduce the issue at all after making the change.
Implementation
Hackiness notwithstanding, some people might want to implement this fix. I didn't consider it a reasonable PR--at least not without prior approval of this approach--so in the meanwhile I've created a fork that contains a single commit with this fix and no other modifications. Users that want this fix but don't want to make the modification themselves can use that repo.
If the maintainer of this repo wants to incorporate this change, they can do so themselves or just let me know they want a PR. Given the arbitrary/hacky nature of the change, they might want to implement a new option that allows the user to specify a delay, and runs the unload synchronously if none is specified. This would ensure that behavior does not change while providing a fix to those that need it.
Debug Branch
My fork has a new branch ("debug_build") where I added lots of logging and tests to track the iFrame lifetime. If someone wants to do their own search for a more elegant solution, it might prove as a useful starting point. It also has a bunch of environment changes, but you can safely cherry-pick the commit with the code changes and ignore the one with the environment changes.
TL;DR
If you're having this same issue with the library, clone my fork and build yourself a fixed copy. You might wish to tweak the delay value from its current 10 seconds to something shorter (e.g. 3 or 5) to minimize performance impact, or something longer (e.g. 30 or 60) if you want to guarantee that the operation never fails (at the expense of a little extra memory usage).