Forivin
Forivin

Reputation: 15508

Start multiple downloads and get feedback when download prompt window showed

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

Answers (3)

Djizeus
Djizeus

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:

  1. 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>
    
  2. Store this file in a temporary folder, e.g. C:\tmp\filetodownload.html

  3. 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:

  • Put the onload handler in a function, and write in the definition of the iframe: 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

Lain
Lain

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

David Mulder
David Mulder

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:

  1. Page download.html requests file.php?location=[...]&randomHash=1234
  2. Once file.php is actually loaded it will set a flag in memory or the database that randomHash id 1234 has started.
  3. file.php redirects the page with a 302 header to the actual file location.
  4. download.html checks periodically using Ajax whether flag randomHash=1234 has been raised. If so it knows the download has started.

Upvotes: 4

Related Questions