2

A snippet of client-side Blob saving code has suddenly stopped working in Google Chrome. The same code continues to work in Firefox. The code is almost identical to that provided in this answer, among others.

var downloadLink = document.createElement("a");
var url = URL.createObjectURL(new Blob(["\ufeff", rows]));
downloadLink.href = url;
downloadLink.download = filename;

document.body.appendChild(downloadLink);
downloadLink.click();
document.body.removeChild(downloadLink);

Setting a breakpoint on the .click() and then single-stepping causes the file to download as expected, but running the code normally does not. I did find that wrapping the .click() in a setTimeout does allow it to succeeed, but only if the timeout is sufficiently long

var downloadLink = document.createElement("a");
var url = URL.createObjectURL(new Blob(["\ufeff", rows]));
downloadLink.href = url;
downloadLink.download = filename;

document.body.appendChild(downloadLink);
setTimeout(function() {
    downloadLink.click();
    document.body.removeChild(downloadLink);
}, duration);

where

Duration  |  Download
----------+----------
0 ms      |  Fail
1 ms      |  Fail
10 ms     |  Fail
100 ms    |  Fail
1000 ms   |  Succeed

I don't know for sure that the problem started at Version 57, but I am looking for any information or advice on how to improve the Blob download without resorting to a setTimeout hack.

** Edit **

I should be clear that the Blob download is being triggered by a button click initiated by the user and is not a piece of code that runs unconditionally upon page load.

Community
  • 1
  • 1
Lucas
  • 8,035
  • 2
  • 32
  • 45
  • Swapping out the code above with a call to a copy of FileSaver 1.1.2 I had lying around allows the download to execute as expected. – Lucas Mar 13 '17 at 16:45

3 Answers3

4

Since replacing the provided download code with a call to the FileSaver,js saveAs function worked, I decided to look at the FileSave code and see how it differed from my implementation.

The core difference is that File Saver creates an anchor node, but does not add it to the DOM. After removing some extraneous code, I distilled the issue down to the appendChild call

Working Code

var downloadLink = document.createElement("a");
var url = URL.createObjectURL(blob);
downloadLink.href = url;
downloadLink.download = filename;

// document.body.appendChild(downloadLink);
downloadLink.click();

Removing the commented line causes the code to fail again.

Lucas
  • 8,035
  • 2
  • 32
  • 45
1

I am using 57 and the following works for me. Can you confirm it works for you as well to pinpoint whether its something to do with this code or something else in your page.

<html>
<head>
<script>
    function download(filename, text) {
      var element = document.createElement('a');          
      var url = URL.createObjectURL(new Blob(['\ufeff', text]));

      element.id = 'downloadLink';
      element.href = url;
      element.download = filename;

      document.body.appendChild(element);

      var downloadLink = document.getElementById('downloadLink');
      downloadLink.click();

      document.body.removeChild(downloadLink);
    }
</script>
</head>
<body>
    <button onclick="download('test.txt', 'Hello this is a test')">Click Me</button>
</body>
</html>
Andrew Wynham
  • 2,310
  • 21
  • 25
  • That works. So clearly something more "interesting" is going on. – Lucas Mar 13 '17 at 16:17
  • To be honest, I don't know how the `setTimeout` would even allow it to work as the link would be removed from the document before the click event was fired on the link and other answers seem to indicate its needs to be appended to the document for it to work. – Andrew Wynham Mar 13 '17 at 16:20
  • That's because I had a copy/paste error. The removeChild is inside the callback function. Code updated. – Lucas Mar 13 '17 at 16:22
  • Just in case something weird is happening with scope, try adding an `id` attribute like I did above and then retrieve it by `id` before calling click on it. Take all of this out of your `setTimeout` as well. See my edits. – Andrew Wynham Mar 13 '17 at 16:28
  • No luck. If I take out the `removeChild` call and look up the anchor in the Console and manually execute `click()`, e.g. `document.getElementById('downloadLink').click()` everything works as expected as well. – Lucas Mar 13 '17 at 16:35
  • If you create a plunkr I'll take a deeper look for ya. – Andrew Wynham Mar 13 '17 at 16:37
0

If its a timing issue then the document is probably not finished loading yet (hence setting the timeout long enough allows for it to be). Instead, try attaching to an event where you know the document is fully loaded.

var downloadLink = document.createElement("a");
var url = URL.createObjectURL(new Blob(["\ufeff", rows]));
downloadLink.href = url;
downloadLink.download = filename;

document.body.appendChild(downloadLink);
document.onload = function() {
    downloadLink.click();
    document.body.removeChild(downloadLink);
};

Note The reason the code works in the example you mentioned is because its a user fired event (submission of a form).

Andrew Wynham
  • 2,310
  • 21
  • 25