6

I'm trying to download multiple files in a Chrome extension. The following code creates a dummy link to a file, then triggers the .click() event which downloads the file. The problem is that only the first .click() event triggers a download. Subsequent .click() events are ignored.

Here the manifest.json:

{
  "name": "Simple File Downloader",
  "version": "0.1",
  "permissions": ["contextMenus", "http://*/"],
  "background": {
    "persistent": false,
    "scripts": ["sample.js"]
  },
  "content_security_policy": "script-src 'self'; object-src 'self'",
  "manifest_version": 2
}

Here the sample.js:

function onClickHandler(info, tab) {
    var a = document.createElement('a');
    a.href = 'http://or.cdn.sstatic.net/chat/so.mp3';
    a.download = 'so.mp3';
    document.body.appendChild(a);
    a.click(); // this click triggers the download
    // this timeout violates content security policy
    // setTimeout(a, 300); 
    a.click(); // this click doesn't do anything
    document.body.removeChild(a);

    a = document.createElement('a');
    a.href = 'http://or.cdn.sstatic.net/chat/so.mp3';
    a.download = 'so.mp3'; 
    document.body.appendChild(a);
    a.click(); // this click doesn't do anything either
    document.body.removeChild(a);
};

chrome.contextMenus.onClicked.addListener(onClickHandler);
chrome.runtime.onInstalled.addListener(function() {
  chrome.contextMenus.create({"title": "Download File", "id":"download_file"});
});

I've tried:

Surprised why it's so hard to simply save multiple files. Appreciate any help.

Community
  • 1
  • 1
toby88
  • 105
  • 2
  • 5
  • 1
    Wait a couple of months, and the [`chrome.downloads`](https://developer.chrome.com/dev/extensions/downloads.html) API is widely available. About the CSP error: `a` is a link, whose `toString` property returns the target of the link. So, if you use `setTimeout(a, 300);`, it attempts to evaluate the target of the link. String-as-code evaluation is forbidden by default, so you get the error. If you use `setTimeout(function() {a.click();}, 300);`, the file is still not getting downloaded, though. – Rob W Dec 25 '12 at 20:36
  • Did you find a temp solution to this problem? – coneybeare Mar 11 '13 at 04:13
  • I think it's no possible for security problem. If it would be possible, then virtually I could open infinite popup/download with a single click. – Luca Rainone Mar 11 '13 at 16:06
  • Since activity around this question has picked up again, here's what I ended up doing: I've followed Rob W's suggestion and am using the chrome.downloads.download(DownloadOptions options, function callback) method. It works perfectly. My Chrome extension is only for internal use, so everyone who uses it does so with Chrome Canary (https://www.google.com/intl/en/chrome/browser/canary.html). – toby88 Mar 13 '13 at 21:27
  • I am not working on an extension (just pasting snippets into Console), but I got it working using `el.dispatchEvent(clickEvent)` (see answer by zertosh), combined with `setTimeout(function() { download_next() }, 500)`. 500 ms is reasonable; when using 100 ms or less, some files didn't download. – Tomasz Gandor May 08 '18 at 09:37

2 Answers2

5

The trick is not to use the element.click method but rather to create multiple MouseEvent. For this to work, you'd need to create one MouseEvent for each time you need a click.

function clicker(el, clickCount) {
  var mousedownEvent;
  while(clickCount--) {
    mousedownEvent = document.createEvent("MouseEvent");
    mousedownEvent.initMouseEvent("click", true, true, window, 0, null, null, null, null, false , false, false, false, 0, null);
    el.dispatchEvent(mousedownEvent);
  }
}

clicker(a, 3);
// your anchor 'a' gets clicked on 3 times.

When using this method in Chrome though, you get a warning from the browser asking "This site is attempting to download multiple files. Do you want to allow this? [Deny] [Allow]". So, if you do this inside an extension's background page, the background page receives the warning, the user can't see it, so the user has no way to click "Allow".

A (gross/nasty) workaround is to create a tab that "clicks" the anchor. Something like this:

function _anchorDownloader(url, filename) {
  var timeout = 500;
  return 'javascript:\'<!doctype html><html>'+
    '<head></head>' +
    '<script>' +
      'function initDownload() {'+
        'var el = document.getElementById("anchor");'+
        'el.click();' +
        'setTimeout(function() { window.close(); }, ' + timeout + ');' +
      '}'+
    '</script>' +
    '<body onload="initDownload()">' +
      '<a id="anchor" href="' + url + '" download="'+ filename + '"></a>'+
    '</body>' +
    '</html>\'';
};

function downloadResource(info, tab) {
  // ...
  chrome.tabs.create( { 'url' : _anchorDownloader( url, filename ), 'active' : false  } );
  // ...
}

chrome.contextMenus.create({"title": "Save Image…", "contexts":["image"], "onclick": downloadResource });

For this to work, the extension has to have "tabs" as a permission in the manifest.json. You can tweak the timeout to close the tab, however, if you close it too fast then no download will happen.

zertosh
  • 5,333
  • 1
  • 17
  • 9
  • I had high hopes for this, but it appears Chrome is still blocking more than one dispatch event. I am not trying to 'click' on the same link 3 times, but rather 3 different ones, not even on the same page necessarily. Only the first gets "clicked" here – coneybeare Mar 13 '13 at 16:32
  • I think I understand what you mean. Try MouseEvent/dispatchEvent on the element without appending/removing it to/from the document. So just create it, assign the properties, and dispatch the event, without ever appending/removing. I just tried this on Chrome 25.0.1364.172 and it worked for me. – zertosh Mar 13 '13 at 16:54
  • Is there a way to create an element and not add it? createElement() seems to do both. – coneybeare Mar 13 '13 at 17:01
  • createElement creates an element but doesn't add it to the DOM. just omit document.body.appendChild(a) and document.body.removeChild(a). – zertosh Mar 13 '13 at 17:05
  • Ok, so I am already not adding it. The original question is not mine, but I did setup the bounty. My code is pretty trivial, but still not working on 25.0.1364.172 https://gist.github.com/coneybeare/5130575 – coneybeare Mar 13 '13 at 17:11
  • Here is the problem: When you try to download an Anchor more than once, Chrome gives you this warning http://imgur.com/b6VwBGi and since you're running the download code inside of a background page, you can't see it to "allow" the warning. I can't think of an elegant work around for this that doesn't involve injecting a script into each page. – zertosh Mar 13 '13 at 17:37
  • let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/26116/discussion-between-zertosh-and-coneybeare) – zertosh Mar 13 '13 at 19:33
0

Instead of using the .live() methode which is no longer recommended try .on()

$(document).on("click", "a", function( event ){
    // do whatever
});

here is the documentation

Loourr
  • 4,995
  • 10
  • 44
  • 68