I have a web application. It runs in Google Chrome and is not required to work in any other browser.
I have PDF data which has been generated on the server and sent back to the client in an AJAX request. I create a blob from the PDF data. I use window.URL.createObjectURL to create a URL from the blob, which I then load into a window (my preview_window) which has previously been created to show the PDF.
To load the URL, I set preview_window.location.href.
I would like to call revokeObjectURL to avoid wasting more and more resources as new PDFs are generated and previewed in the window. The problem is that calling it immediately after setting preview_window.location.href is too soon, and stops the PDF from being displayed. So I would like to call revokeObjectURL only once the URL has been loaded. I have tried setting preview_window.onload to a callback for this purpose, but it never gets called.
I would like to know:
If I cannot trigger revokeObjectURL when the window finishes loading the URL, I may revoke each URL immediately before generating a new one. But I would rather revoke the URL as soon as it is done loading, if possible.
I have prepared a html file which demonstrates the situation pretty well:
<html>
<head>
<title>Show PDF Demo</title>
<script>
var build_blob = function(mime_type, data) {
var buf = new ArrayBuffer(data.length);
var ia = new Uint8Array(buf);
for (var i = 0; i < data.length; i++) ia[i] = data.charCodeAt(i);
var blob = new Blob([ buf ], { type: mime_type });
return blob;
};
window.onload = function(e) {
document.getElementById('preview_button').onclick = function(e) {
// open the window in the onclick handler so we don't trigger popup blocking
var preview_window = window.open(null, 'preview_window');
// use setTimeout to simulate an asynchronous AJAX request
setTimeout(function(e) {
var pdf_data = atob(
"JVBERi0xLjQKMSAwIG9iago8PCAvVHlwZSAvQ2F0YWxvZwovT3V0bGluZXMgMiAwIFIKL1BhZ2Vz" +
"IDMgMCBSCj4+CmVuZG9iagoyIDAgb2JqCjw8IC9UeXBlIC9PdXRsaW5lcwovQ291bnQgMAo+Pgpl" +
"bmRvYmoKMyAwIG9iago8PCAvVHlwZSAvUGFnZXMKL0tpZHMgWzQgMCBSXQovQ291bnQgMQo+Pgpl" +
"bmRvYmoKNCAwIG9iago8PCAvVHlwZSAvUGFnZQovUGFyZW50IDMgMCBSCi9NZWRpYUJveCBbMCAw" +
"IDUwMCAyMDBdCi9Db250ZW50cyA1IDAgUgovUmVzb3VyY2VzIDw8IC9Qcm9jU2V0IDYgMCBSCi9G" +
"b250IDw8IC9GMSA3IDAgUiA+Pgo+Pgo+PgplbmRvYmoKNSAwIG9iago8PCAvTGVuZ3RoIDczID4+" +
"CnN0cmVhbQpCVAovRjEgMjQgVGYKMTAwIDEwMCBUZAooU01BTEwgVEVTVCBQREYgRklMRSkgVGoK" +
"RVQKZW5kc3RyZWFtCmVuZG9iago2IDAgb2JqClsvUERGIC9UZXh0XQplbmRvYmoKNyAwIG9iago8" +
"PCAvVHlwZSAvRm9udAovU3VidHlwZSAvVHlwZTEKL05hbWUgL0YxCi9CYXNlRm9udCAvSGVsdmV0" +
"aWNhCi9FbmNvZGluZyAvTWFjUm9tYW5FbmNvZGluZwo+PgplbmRvYmoKeHJlZgowIDgKMDAwMDAw" +
"MDAwMCA2NTUzNSBmCjAwMDAwMDAwMDkgMDAwMDAgbgowMDAwMDAwMDc0IDAwMDAwIG4KMDAwMDAw" +
"MDEyMCAwMDAwMCBuCjAwMDAwMDAxNzkgMDAwMDAgbgowMDAwMDAwMzY0IDAwMDAwIG4KMDAwMDAw" +
"MDQ2NiAwMDAwMCBuCjAwMDAwMDA0OTYgMDAwMDAgbgp0cmFpbGVyCjw8IC9TaXplIDgKL1Jvb3Qg" +
"MSAwIFIKPj4Kc3RhcnR4cmVmCjYyNQolJUVPRg=="
);
/*
Warning: for my Chrome (Version 44.0.2403.155 m), the in-built PDF viewer doesn't seem
to work with a blob when this html page is loaded from the local filesystem. I have only
got this to work when fetching this page via HTTP.
*/
var pdf_blob = build_blob('application/pdf', pdf_data);
var pdf_url = window.URL.createObjectURL(pdf_blob);
preview_window.onload = function(e) {
console.log("preview_window.onload called"); // never happens
window.URL.revokeObjectURL(pdf_url);
};
preview_window.location.href = pdf_url;
console.log("preview_window.location.href set");
}, 500);
};
};
</script>
</head>
<body>
<button id="preview_button">Show Preview</button>
</body>
</html>
Although my demo code above avoids it, I do have jQuery loaded for my application, so if that makes things easier I'm open to using it.
I did find this question in a search, but in that situation the main window ("window") is pointed to a new URL, and the OP never got a response when asking in comments whether it makes a difference if the window came from window.open.
As you found out, you can't set open()
ed windows' onload event from the opener.
You will have to inject some script in the second page that will call its window.opener
functions.
But since you are opening a pdf file, the browser will re-parse entirely your page and your injected code will vanish.
The solution, as you found out yourself in the comments, is to inject the blob's url in an iframe, and wait for this iframe's load event.
Here is how :
index.html
<script>
// The callback that our pop-up will call when loaded
function imDone(url){
window.URL.revokeObjectURL(url);
}
var build_blob = function(mime_type, data) {
var buf = new ArrayBuffer(data.length);
var ia = new Uint8Array(buf);
for (var i = 0; i < data.length; i++) ia[i] = data.charCodeAt(i);
var blob = new Blob([ buf ], { type: mime_type });
return blob;
};
var preview_window=null;
window.onload = function(e) {
document.getElementById('preview_button').onclick = function(e) {
if(preview_window===null || preview_window.closed){
// open the window in the onclick handler so we don't trigger popup blocking
preview_window = window.open('html2.html', 'preview_window');
}
// avoid reopening the window since it may cache our last blob
else preview_window.focus();
// use setTimeout to simulate an asynchronous AJAX request
setTimeout(function(e) {
var pdf_data = /* Your pdf data */
var pdf_blob = build_blob('application/pdf', pdf_data);
var pdf_url = window.URL.createObjectURL(pdf_blob);
// Simple loop if our target document is not ready yet
var loopLoad = function(url){
var doc = preview_window.document;
if(doc){
var iframe = doc.querySelector('iframe');
if(iframe)iframe.src = url;
else setTimeout(function(){loopLoad(url);},200);
}
else setTimeout(function(){loopLoad(url);},200)
};
loopLoad(pdf_url);
}, 0);
};
};
</script>
and the html2.html
<html>
<head>
<title>Iframe PDF Demo</title>
<style>
body, html, iframe{margin:0; border:0}
</style>
</head>
<body>
<iframe width="100%" height="100%"></iframe>
<script>
document.querySelector('iframe').onload = function(){
//first check that our src is set
if(this.src.indexOf('blob')===0)
// then call index.html's callback
window.opener.imDone(this.src);
}
</script>
</body>
</html>