Reputation: 15508
I'm currently writing a little program that generates an html file and opens it with the default browser to start multiple downloads.
I don't want to open a tab/window for every download, so creating hidden iframes for the downloads seemed like a good solution.
I'm using onload
on the iframes to find out if the download prompts for each download have shown up yet. This approach seems to be very unreliable in the Internet Explorer though.
So I'm wondering if there is there a way to fix this or maybe a better approach?
(Without libraries please.)
Here is my html/js code:
<!DOCTYPE html>
<!-- saved from url=(0016)http://localhost -->
<html><head>
<meta content="text/html;charset=utf-8" http-equiv="Content-Type">
<meta content="utf-8" http-equiv="encoding">
<title>Downloads</title>
<script>
"use strict";
var downloadsInfo = {
"http://download-installer.cdn.mozilla.net/pub/firefox/releases/26.0/win32/en-US/Firefox%20Setup%2026.0.exe":"Status: Connecting",
"http://download.piriform.com/ccsetup410.exe":"Status: Connecting"
};
var i = 0;
var iv = setInterval(function() {
i = ++i % 4;
var j = 0;
var finished = true;
for (var key in downloadsInfo) {
var value = downloadsInfo[key];
if (value != "Status: Download Started!") {
value = value+Array(i+1).join(".");
finished = false;
}
document.getElementsByTagName("div")[j].innerHTML = key+"<br/>"+value;
j = j+1;
}
if (finished) {
alert('Done! You can close this window/tab now.');
clearInterval(iv);
}
}, 800);
</script>
</head><body>
<h3>Please wait for your downloads to start and do not reload this site.</h3>
<div></div> <br/><br/>
<div></div> <br/><br/>
<iframe src="http://download-installer.cdn.mozilla.net/pub/firefox/releases/26.0/win32/en-US/Firefox%20Setup%2026.0.exe" onload="downloadsInfo['http://download-installer.cdn.mozilla.net/pub/firefox/releases/26.0/win32/en-US/Firefox%20Setup%2026.0.exe'] = 'Status: Download Started!';" style="display:none"></iframe>
<iframe src="http://download.piriform.com/ccsetup410.exe" onload="downloadsInfo['http://download.piriform.com/ccsetup410.exe'] = 'Status: Download Started!';" style="display:none"></iframe>
</body></html>
Upvotes: 19
Views: 1478
Reputation: 4175
Indeed IE is reported to not always behave nicely with the onload event handler of iframes. There is an active bug tracker record opened.
The problem is discussed in a number of places around the web, and what seems to be the most reliable solution is to have an indirect download with nested iframes: the iframe loads a HTML file with an iframe that loads the file to download. The reason for that is that IE does not seem to like iframes that point to something else than HTML. So if you have the possibility to do that in your program:
For each file to download, generate a HTML file with a body that looks like this:
<iframe src="http://filetodownload.exe" style="display:none"></iframe>
Store this file in a temporary folder, e.g. C:\tmp\filetodownload.html
In your "master" generated HTML file, replace the iframe source with this intermediate file:
<iframe src="C:\tmp\filetodownload.html"
onload="downloadsInfo['http://filetodownload.exe']='Status: Download Started!';"
style="display:none"></iframe>
That may do the trick. But following IE's tradition, this could or could not work depending on the case...
If it does not work, some solutions that have proved useful include:
onload="return theonloadfunction()"
(even if the function does not return anything)Instead of using the onload attribute, attach the event handler in javascript, like so:
iframe = document.getElementById("theiframeid")
iframe.attachEvent("onload", theonloadfunction);`
Note that attachEvent
is for IE only. If you want to support other browsers you will have to detect it and use addEventListener
for the non-IE cases.
Finally, you may try combinations of two or more of these solutions :)
Upvotes: 1
Reputation: 3726
<html>
<head>
<meta content = 'text/html;charset=utf-8' http-equiv = 'Content-Type'>
<meta content = 'utf-8' http-equiv = 'encoding'>
<script>
/*
First, I removed the setInterval(). Since you rely on the onload property we can aswell just check it on each onload.
Second, I changed your downloadsInfo to an object array.
Also be aware while testing, that some browsers cache your cancel/block choice and do not reask again for the same url.
Additionally firefox does not fire on frame downloads.
Furthermore the alert in your test might not show for overlapping or setting reasons.
*/
var downloadsInfo = [
{url: "http://download-installer.cdn.mozilla.net/pub/firefox/releases/26.0/win32/en-US/Firefox%20Setup%2026.0.exe", Status: "Connecting"},
{url: "http://download.piriform.com/ccsetup410.exe", Status: "Connecting"}
];
//IE has a problem in sometimes merely firing the onload propery once, which we bypass by dynamically creating them
//It is also less limited.
function iframeConnect(){
for(var i=0, j=downloadsInfo.length; i<j; i++){
var tF = document.createElement('iframe');
tF.arrayIndex = i; //For convenience
tF.style.display = 'none';
//Normal load event, working in ie8-11, chrome, safari
tF.onload = function(){
iframeExecuted(this.arrayIndex);
};
//Workaround for firefox, opera and some ie9
tF.addEventListener('DOMSubtreeModified', function(){
iframeExecuted(this.arrayIndex);
}, false);
document.body.appendChild(tF);
tF.src = downloadsInfo[i].url;
}
}
function iframeExecuted(i){
downloadsInfo[i].Status = 'Executed';
var tStatus = iframeFinished();
var tE = document.querySelector('h3');
if (tStatus.Done) tE.innerHTML = 'Finished'
else tE.innerHTML = 'Processed ' + tStatus.Processed + ' of ' + tStatus.Started;
}
function iframeFinished(){
for(var i=0, j=downloadsInfo.length; i<j; i++){
if (downloadsInfo[i].Status != 'Executed') break;
}
//Note that the Processed value is not accurate, yet it solves is testing purpose.
return {Done: (i == j), Processed: i, Started: j}
}
</script>
</head>
<body onload = 'iframeConnect()'>
<h3>Please wait for your downloads to start and do not reload this site.</h3>
</body>
</html>
Upvotes: -3
Reputation: 26995
Quite simply you can't know whether a native browser download started. Every browser has different ways this is handled, the user may set up his browser to prompt the location or he might just let it auto download to the Downloads
folder (the default in most browsers nowadays). If he's prompting for a location he might cancel by mistake, yet your setup would still claim the download started. So, no, there is no way whatsoever to reliably inform the user that they can close a tab once all downloads are started/finished... provided that you use the native browser download mechanism.
The way to achieve this effect would be possibly by first downloading the file using Javascript (requiring you to have access to those files, hotlinking to third party files is of course not an option then). To see this in action try downloading a file from mega.nz. I was planning on writing up how to do this by hand, but there is already a nice (quite outdated) answer outlining this.
If the intention is only to ensure that the download has started you could implement a trigger on the back end to note when the file has been accessed. In it's simplest form this would look like:
download.html
requests file.php?location=[...]&randomHash=1234
file.php
is actually loaded it will set a flag in memory or the database that randomHash
id 1234
has started.file.php
redirects the page with a 302
header to the actual file location.download.html
checks periodically using Ajax whether flag randomHash=1234
has been raised. If so it knows the download has started.Upvotes: 4