Print.js
Print.js copied to clipboard
Unable to print when missing images are present
<div id="print">
<img id="bad-image" src="/badimage.jpg" style="width: 200px; height: 200px;" />
<p>Printing won't work when there is a missing image present</p>
<a href="javascript:;" onclick="printJS('print', 'html');">Try Printing!</a>
<br />
<br />
<br />
<a href="javascript:;" onclick="document.getElementById('bad-image').remove();">Remove Bad Image</a>
</div>
If you click "Try Printing!", nothing happens. Click "Remove Bad Image" and then click "Try Printing!" again. Now it can print.
This was tested on Firefox, Chrome, and IE. Same behavior in all cases.
I realize you might be thinking, "Well, why not just fix the broken image?"
In my case, the URLs for images are being generated remotely by a web service and I cannot know ahead of time that the images will be bad.
Perhaps this is something that cannot be fixed. But I did not see that anyone else had mentioned this issue. And this was quite a head scratcher to debug because there were no errors in the JavaScript console.
Hey @desrochers, This issue was fixed here: https://github.com/crabbly/Print.js/commit/c16e2385f65322b3e81c8e2b0bb9ce292d41b7a4
But this logic was changed again here: https://github.com/crabbly/Print.js/commit/3b751a698f27a106088ef496e8a1c89b94f7e7af#diff-8a73c673fe1fdbff40be7a06d8715373
Maybe this change broke the fix.
The issue comes down to waiting for the load event to trigger, which will never happen when the image src is invalid.
We should change the logic to something like this:
const promises = images.map(image => {
if (image.src && image.src !== window.location.href) {
return loadIframeImage(image)
}
})
Is it getting stuck here?
function loadIframeImage (image) {
return new Promise(resolve => {
const pollImage = () => {
!image || typeof image.naturalWidth === 'undefined' || image.naturalWidth === 0 || !image.complete
? setTimeout(pollImage, 500)
: resolve()
}
pollImage()
})
}
image.naturalWidth === 0 is returning true for an image that is broken. Not sure if this is browser dependent or not but it's what's causing the issue for me in Firefox.
So I believe the broken getting image is stuck in an infinite loop.
I modified loadIframeImage() look like this:
function loadIframeImage (image) {
return new Promise(resolve => {
const pollImage = () => {
(!image || !image.complete) && (typeof image.naturalWidth === 'undefined' || image.naturalWidth === 0)
? setTimeout(pollImage, 500)
: resolve()
}
pollImage()
})
}
I am now able to print the page from my example. I added a few random images that work to my sample:
<!DOCTYPE html>
<html>
<head>
<script type="text/javascript" src="print.js"></script>
</head>
<body>
<div id="print">
<img id="bad-image" src="/badimage.jpg" style="width: 200px; height: 200px;" />
<img src="https://3.img-dpreview.com/files/p/E~TS590x0~articles/8692662059/8283897908.jpeg" />
<img src="https://2.img-dpreview.com/files/p/TS120x120~products/canon_eos6dmkii/5e720868021f42adabdb0992a8bd924d.png" />
<img src="https://4.img-dpreview.com/files/p/TC290x290S290x290~sample_galleries/1004647162/4827700653.jpg" />
<p>Printing won't work when there is a missing image present</p>
<a href="javascript:;" onclick="printJS('print', 'html');">Try Printing!</a>
<br />
<br />
<br />
<a href="javascript:;" onclick="document.getElementById('bad-image').remove();">Remove Bad Image</a>
</div>
</body>
</html>
And now it is working. Confirmed in Firefox and Chrome.
I'm somewhat new to submitting changes to a public repository. Assuming this code looks good to you, is there a specific branch that you would prefer I make my request to?
Thank you.
Hey @desrochers, thank you for contributing to the library.
Yes, the code will get stuck inside loadIframeImage if the image src is invalid. That's why we try to validate the image src first, before calling this method.
About this code:
!image || typeof image.naturalWidth === 'undefined' || image.naturalWidth === 0 || !image.complete
Being changed to this:
(!image || !image.complete) && (typeof image.naturalWidth === 'undefined' || image.naturalWidth === 0)
Could you please explain what's the logic behind this change?
About creating a pull request, you can target the master branch. Thank you.
ps.: I changed that reduce logic to a map. Please run a pull to get this change.
Yes, the code will get stuck inside loadIframeImage if the image src is invalid. That's why we try to validate the image src first, before calling this method.
I originally included my logic prior to loadIframeImage. My concern was that while an image might be broken, it could still contain valuable information that affects the flow of the page. For instance, if I use the browser print function, it still includes a placeholder for broken images. If the bad image is filtered out ahead of time, does that mean the img element itself is stripped entirely from the printed page? That was what I believed would happen.
(At one point, I was setting the src of bad images to a base64 encoded 1x1 transparent gif. But that felt hacky.)
I also was not sure how much I wanted to lean on the completed property before calling loadIframeImage, given there was a slight possibility of a race condition. Say someone hits "Print" while an image is still loading.
So hopefully that provides some context for the following:
(!image || !image.complete) && (typeof image.naturalWidth === 'undefined' || image.naturalWidth === 0)
We were running into an issue where the following was true for an img with a bad src:
image = [Object] image.completed = true image.naturalWidth = 0
A bad image with a 404 would have a flag of completed and a naturalWidth of 0.
Thus the logic
!image || typeof image.naturalWidth === 'undefined' || image.naturalWidth === 0 || !image.complete
would always resolve to
false || false || true || false
Because naturalWidth === 0 for a bad image, we always get stuck in the polling stage.
My code
(!image || !image.complete) && (typeof image.naturalWidth === 'undefined' || image.naturalWidth === 0)
Only the first half is different. We moved the testing of !image || !image.completed into its own sub-condition.
I'm not sure if !image will ever be true.
But when an image has not yet loaded, it would look like:
(!image || !image.complete) && (typeof image.naturalWidth === 'undefined' || image.naturalWidth === 0)
becomes
(false || true) && (true || true)
Once it has successfully loaded:
(false || false) && (false || false)
If it has failed to load due to a 404 or some other error:
(false || false) && (false || true)
/// Also possible that it could resolve to:
(false || false) && (true || false)
I think checking naturalWidth might be redundant. Does naturalWidth become available at the same time as completed is set to true? I’m not sure if completed = true is set, then the browser renders the image, and only then the naturalWidth property is set. So maybe it makes sense to check that property even after completed is true.
I will try to do a pull request soon. Currently not at my workstation but wanted to knock out a response to clarify, and if necessary, change the code before making the request.
I have the same issue with image.naturalWidth = 0 even with page on my local machine with a valid image. The updated condition works fine. Can this update be merged ?
Also according to Mozilla: If the intrinsic height is not available—either because the image does not specify an intrinsic height or because the image data is not available in order to obtain this information, naturalHeight returns 0. https://developer.mozilla.org/en-US/docs/Web/API/HTMLImageElement/naturalHeight
Any updates on this?
Meanwhile i used a simple regex to extract image urls from my html string, then fetched all of them. I then replaced any missing images with a small base64 data string of a "broken image" placeholder, that would get printed instead. It's not bulletproof but good enough for now.
However, if there is any other reason for which printing would fail, it would be great if printJS would throw an error or something instead of hanging. This way we could inform the user better if anything goes wrong.
Thank you
Me too,this seems to be a problem that needs to be optimized.Thank you.

I hope this help someone, on images you can add an onerror attribute, this executes js code inside of it, so you con use 'this' to modify the image in case of broken url. What i did is to change the url for another url for printjs to work. I set the width and height to 0, and because of the image exists printjs works, and if you need to hide the parent container you can do it like i did.
<img src="brokenurl.com" onerror="this.width=0;this.height=0;this.src='https://us.123rf.com/450wm/mheim301165/mheim3011651612/mheim301165161200099/69661500-limpie-el-fondo-blanco-puro-vac%C3%ADo-con-espacio-de-copia-en-blanco-para-su-uso-como-una-plantilla-de-d.jpg';document.getElementById('tableImg').style.display ='none';">
@Hellogark very interesting find if onerror actually works within PrintJS and can prevent it from hanging by changing the src attribute, indeed it would be an easier solution if you control the source being printed. There are however a few things to consider:
- you need to manually paste this code into every image single tag. Depending on the number of images you're dealing with, maintenance quickly becomes tedious
- when you deal with dynamic content you still need to regex and inject the
onerrorattribute in all image tags - you probably should use a
base64-encoded url for the newsrcvalue, because that123rf.comdomain may easily get rejected by CSP rules or fail to load under unstable network conditions (downloading any image may fail for a large number of reasons actually)
But, if you only have a handful of images and offline mode is ignorable, then sure, it's a lot easier and you can skip a regex. Nice find :)
@steodor about the maintenance, whe generate dinamically those images, so i only need to change them in the backend. I followed the third point, it will be better, thanks for the advice