At some point you might need to diplay a PDF on a Webpage.
And you do not have it as normal URL on your server but as DataUri.
It sounds very easy but in reality is not really.
I would be if Chome would not be dumb.
What we really want is a sandboxed iframe
where a PDF is displayed.
The sandbox is essential to prevent a malicious script to break out to the parent and steal sensitive data.
Warning This is just a little write-up about my experiences and not a full tutorial! Also, I did this a couple of weeks ago, so something might be missing or inaccurate!
In firefox:
- Pass the PDF DatUri via the
src
attribute: works - Pass the PDF via a BlobUrl (from
URL.createObjectURL()
of aBlob
): works (IIRC) - Using the
sandbox
attribute without any data: partially loads the viewer but does not work (it uses JS) - Using
sandbox
withallow-scripts
: works flawlessly.
In chrome:
- Pass the PDF DataUri via the
src
attribute: broken; seems to be too long - Pass the PDF via a BlobUrl: "Content Blocked"
- Pass the PDF via BlobUrl with
allow-same-origin
: Should not be a great idea because since this then counts as from your domain, we have the security problem we wanted to avoid. (Also I think it does not work regardless for som reason.)
Since the PDF viewer of Firefox is JS and open source, we could use that.
However when we host PDF.js on our server, we still have to pass the PDF file to the viewer somehow.
The BlobUrl still does not work in Chrome, so we cannot do like viewer.html?file=blob://...
.
But we can modify the viewer minimally such that it posts a messaage to the parent via Window.parent.postMessage()
when the viewer is loaded.
In the parent, we can listen to it using
const iframeWindow = document.getElementById('myframe').contentWindow;
window.addEventListener('message', message => {
if (message.source === iframeWindow) {
// Check if it is the "ready" event
// Send PDF as DataUri to the iframe: iframeWindow.postMessage()
}
});
And then in the iframe, we listen for a DataUri and pass it to the viewer via PDFViewerApplication.open()
.
It is not very clear how to use it, honestly. You can have it completely barebones but we usually want the complete package you have in firefox.
There is some kind of introduction here but without any helpful information: https://github.com/mozilla/pdf.js/wiki/Setup-pdf.js-in-a-website
So what I did is download the distribution zip. There are two folders in there: "build" and "web". You will need both, so its best to make a folder for it on your server.
Note: If you a building a Single Page Application, put it in the "public" folder or something aequivalent.
The PDF.js dist is rather big so to save some space, remove:
- web/*.pdf
- web/debugger.*
- web/*.map
- build/*.map
- (I think pdf.sandbox.js was also not needed)
The "web/viewer.html" is the one you use as "src" for your iframe. But you have to add a little code at the end that registers the message listener for messages from the parent first. And the send off the message that the viewer is ready to the parent.
I did it immediately and it seems to work but it might be better to listen for an internal ready event of PDF.js first. You could find it relatively easy, because you will find it red in the browser console because part of the event dispaatch failes becausee of the cross site prevention.
A quick ordered list of stuff to do:
- Parent: Create iframe element with
src
to the viewer on your server andsandbox="allow-scripts"
. - Parent: Add the message listener
- Magic: Browser loads the viewer url and document in the iframe
- Iframe: Registering the listener for messages from the parent
- Iframe: Posting a "ready" message to the parent
- Parent: Receives the "ready" event
- Parent: Sends the PDF as DataUri via a message to the iframe
- Iframe: The event listener is called with the PDF as DataUri
- Iframe: The PDF DataUri is passed to PDF.js via
PDFViewerApplication.open()
- ...
- Parent: When the iframe is no longer needed, remove the parent listener with
removeEventListener
.