user1543201 user1543201 - 1 month ago 5
Javascript Question

Using chrome.downloads to download a file from a URL that was found via chrome.webRequest?

My ultimate goal is to download a file from a website. Unfortunately, the page which contains the URL fetches the download URL via JavaScript when you click on a button. Thus, I can't just pull the URL from the HTML.

Right now, I'm using a chrome app and

chrome.webRequest.onCompleted.addListener
to fetch the URL when the button is clicked, and then passing that URL to
chrome.downloads.download
to download the file.

The problem is that this creates an infinite loop because running
chrome.download
with the URL just triggers
chrome.webRequest
again. Can someone please assist me in solving this chicken or the egg problem? Here is the code:

manifest.json


{
"name": "Chrome test",
"version": "0.1",
"description": "A download test",
"manifest_version": 2,
"permissions": [
"webRequest", "*://*.theSite.com/",
"downloads"
],
"background": {
"scripts": ["backgroundjs.js"],
"persistent": true
}
}


and here is the background javascript:

backgroundjs.js


chrome.webRequest.onCompleted.addListener(function(details){
var fileurl = details.url;
chrome.downloads.download({
url: fileurl
})

},
{urls: ["*://*.theSite.com/KnownGeneralPathOfFile/*"]});

Answer

There are a variety of ways to accomplish what you are wanting to do. A couple of different methods that immediately spring to mind are:

  1. Keep a list of URLs for which you have already initiated a download and just not initiate another download to any URL which matches a URL in your list of URLs.
  2. Differentiate what you do based on the properties of the details object that is passed to your webRequest listener. For downloads, it appears that the details.tabId===-1 and details.type==='other'. Thus, you could just not start a download when those two conditions are met.

Keeping a list of URLs to exclude from downloading (again):
Given that there is only one webRequest initiated for each URL you are downloading, you can choose to either leave the URL in the list once you find that you have matched it once, or remove it after that first match. If you remove it after the first match, then only the webRequest that is generated by the current download will be prevented from downloading again. If you choose to leave the URL in the list, then the same URL will not be downloaded twice. Personally, I expect that your users would normally prefer to only download each URL once per session. But, that choice is up to you, or could be a preference which you allow your users to select.

The following is an extension which implements keeping a list of URLs to exclude from downloading. As it is here, each URL is only kept until found the first time. This will prevent the webRequest from the download causing another download to be initiated. If you want to only download each URL once, then comment out or remove the splice line.

background.js:

var excludedWebRequestDownloadUrls=[];
chrome.webRequest.onCompleted.addListener(function(details){
    let fileUrl = details.url;
    let foundIndex = excludedWebRequestDownloadUrls.indexOf(fileUrl);
    if(foundIndex>-1){
        //Remove the found item (only one webRequest is executed for the download).
        //  May want to not remove the URL from the list of excluded URLs, as you probably
        //  only want to download the same URL once, not multiple times.
        excludedWebRequestDownloadUrls.splice(foundIndex,1);
        //Don't try to download the found URL again.
        return;
    }
    //Remember the URL which we are going to download.
    excludedWebRequestDownloadUrls.push(fileUrl);
    chrome.downloads.download({
        url: fileUrl
    });
},{urls: ["*://*.stackoverflow.com/questions/*"]});

manifest.json:

{
  "name": "Chrome test",
  "version": "0.1",
  "description": "A download test",
  "manifest_version": 2,
  "permissions": [
    "webRequest", "*://*.stackoverflow.com/",
    "downloads"
  ],
  "background": {
    "scripts": ["background.js"],
    "persistent": true
  }
}

The above code was tested in both Chrome and Firefox.