19

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>
Forivin
  • 14,780
  • 27
  • 106
  • 199
  • whats your purpose from unreliable about IE ? – vahid kargar Jun 02 '15 at 16:45
  • Why `this seems to be very unreliable in the Internet Explorer`? Do you have any error in IE? if it is so you can show them? – Cliff Burton Jun 04 '15 at 10:39
  • In my IE 11 it simply isn't always working. The download always starts, but the onload is sometimes not called. – Forivin Jun 04 '15 at 15:28
  • use a downloader script so you don't have to relay on iframe hacks. – dandavis Jun 09 '15 at 07:52
  • Could you elaborate on that? (I hope you realize that the download needs to be done through a browser.) – Forivin Jun 09 '15 at 11:07
  • @Forivin: Did you come up with any solution? I am kind of interested in this :) – Lain Jun 12 '15 at 07:59
  • @Forivin i tried to help if you want to be rude i will block you good day sir – Barkermn01 Sep 29 '15 at 14:05
  • @MartinBarker I spent about 10 minutes reading your long comment on your answer because you didn't use any punctuation, proper spelling and half of that comment didn't even make any sense grammatically. Then I asked you if you were seriously expecting me to understand what you wrote. And now again no punctuation? And you call me rude?... Seriously? – Forivin Sep 29 '15 at 16:02
  • @Forivin Is requirement to notify user when each , all download dialog windows have been displayed ? – guest271314 Sep 30 '15 at 02:49
  • @Forivin are all those files servers that allow cross-origin requests? Because the solution I have would need that. – Buzinas Oct 02 '15 at 13:30

3 Answers3

4

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.
Community
  • 1
  • 1
David Mulder
  • 26,123
  • 9
  • 51
  • 114
  • I don't need to know if the download actually started, I just want to know if something related to downloading the file happened, like a "save as"/download prompt, auto download, or whatever. – Forivin Jun 02 '15 at 18:59
  • This won't work. There is no Server. As I said, the HTML file will be generated and opened locally. – Forivin Jun 03 '15 at 05:02
  • @Forivin If you have an executable running on the client side I would simply advice you to use it to do the downloading instead of trying to hand it of to the browser. – David Mulder Jun 09 '15 at 10:17
  • ANd to whoever downvoted this, I would love hearing what the reasoning behind that was. – David Mulder Jun 09 '15 at 11:24
  • I don't want to handle the downloading in my own application. My application doesn't even have a UI and I want the user to have progress bars for the downloads because some of them are really big. I already implemented support to send the links to download managers like JDownloader. But some people only have a browser. – Forivin Jun 09 '15 at 15:38
  • @Forivin uGet is an open source (LGPL) download manager, maybe you can consider bundling it? That seems to be a pretty easy solution in that case and as long as you link to the download manager sources I think you should be fine license wise (do check that though). – David Mulder Jun 09 '15 at 16:19
  • No, I'm not looking for alternatives. The downloads need to happen in the browser. – Forivin Jun 10 '15 at 04:19
1

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 :)

Djizeus
  • 4,161
  • 1
  • 24
  • 42
-3
<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>
Lain
  • 3,657
  • 1
  • 20
  • 27