1

i am developing chrome extension and i want to set the download location for the downloadable files. So i am using chrome.downloads.download API saveAs:true.It is working fine in windows OS but in Mac OS saveAs popup is flashing on the screen and then extension popup and saveAs dialogue is closing before i see them.

Any idea?

My updated code:

manifest.json

{
  "name": "Download Selected Links",
  "description": "Select links on a page and download them.",
  "version": "0.1",
  "minimum_chrome_version": "16.0.884",
  "permissions": ["downloads", "<all_urls>"],
  "background": {
    "scripts": ["background.js"]
  },
  "browser_action": {"default_popup": "popup.html"},
  "manifest_version": 2
}

popup.js

var allLinks = [];
var visibleLinks = [];
var filename = [];
var count = 0;

// Display all visible links.
function showLinks() {
  var linksTable = document.getElementById('links');
  while (linksTable.children.length > 1) {
    linksTable.removeChild(linksTable.children[linksTable.children.length - 1])
  }
  for (var i = 0; i < visibleLinks.length; ++i) {
    var row = document.createElement('tr');
    var col0 = document.createElement('td');
    var col1 = document.createElement('td');
    var checkbox = document.createElement('input');
    checkbox.checked = true;
    checkbox.type = 'checkbox';
    checkbox.id = 'check' + i;
    col0.appendChild(checkbox);
    col1.innerText = visibleLinks[i];
    col1.style.whiteSpace = 'nowrap';
    col1.onclick = function() {
      checkbox.checked = !checkbox.checked;
    }
    row.appendChild(col0);
    row.appendChild(col1);
    linksTable.appendChild(row);
  }
}

function toggleAll() {
  var checked = document.getElementById('toggle_all').checked;
  for (var i = 0; i < visibleLinks.length; ++i) {
    document.getElementById('check' + i).checked = checked;
  }
}

function  downloadLinks() {
var urlArray = new Array();
  for (var i = 0; i < visibleLinks.length; ++i) {
    if (document.getElementById('check' + i).checked) {
      urlArray.push(visibleLinks[i]);
    }
  }
  var zip = new JSZip();
  downloadFile(urlArray[count], onDownloadComplete, urlArray, zip);
}

// Re-filter allLinks into visibleLinks and reshow visibleLinks.
function filterLinks() {
  var filterValue = document.getElementById('filter').value;
  if (document.getElementById('regex').checked) {
    visibleLinks = allLinks.filter(function(link) {
      return link.match(filterValue);
    });
  } else {
    var terms = filterValue.split(' ');
    visibleLinks = allLinks.filter(function(link) {
      for (var termI = 0; termI < terms.length; ++termI) {
        var term = terms[termI];
        if (term.length != 0) {
          var expected = (term[0] != '-');
          if (!expected) {
            term = term.substr(1);
            if (term.length == 0) {
              continue;
            }
          }
          var found = (-1 !== link.indexOf(term));
          if (found != expected) {
            return false;
          }
        }
      }
      return true;
    });
  }
  showLinks();
}

chrome.runtime.onMessage.addListener(function(links) {
  for (var index in links) {
    allLinks.push(links[index]);
  }
  allLinks.sort();
  visibleLinks = allLinks;
  showLinks();
});

window.onload = function() {
  document.getElementById('filter').onkeyup = filterLinks;
  document.getElementById('regex').onchange = filterLinks;
  document.getElementById('toggle_all').onchange = toggleAll;
  document.getElementById('downloadButtonId').onclick = downloadLinks;

  chrome.windows.getCurrent(function (currentWindow) {
    chrome.tabs.query({active: true, windowId: currentWindow.id},
                      function(activeTabs) {
      chrome.tabs.executeScript(
        activeTabs[0].id, {file: 'source.js', allFrames: true});
    });
  });
};

source.js

var links = [].slice.apply(document.getElementsByTagName('a'));
links = links.map(function(element) {
  var href = element.href;
  var hashIndex = href.indexOf('#');
  if (hashIndex >= 0) {
    href = href.substr(0, hashIndex);
  }
  return href;
});

links.sort();

// Remove duplicates and invalid URLs.
var kBadPrefix = 'javascript';
for (var i = 0; i < links.length;) {
  if (((i > 0) && (links[i] == links[i - 1])) ||
      (links[i] == '') ||
      (kBadPrefix == links[i].toLowerCase().substr(0, kBadPrefix.length))) {
    links.splice(i, 1);
  } else {
    ++i;
  }
}

chrome.runtime.sendMessage(links);

background.js

function downloadFile(url, onSuccess, arrayOfUrl, zip) {
  var xhr = new XMLHttpRequest();
  xhr.open('GET', url, true);
  xhr.responseType = "blob";
  xhr.onreadystatechange = function () {
    if (xhr.readyState == 4) {
      if (onSuccess) {
        onDownloadComplete(xhr.response, arrayOfUrl, zip);
      }
    }
  }
  xhr.send(null);
}

function onDownloadComplete(blobData, urls, zip){
  if (count < urls.length) {
    blobToBase64(blobData, function(binaryData){
      // add downloaded file to zip:
      var fileName = urls[count].substring(urls[count].lastIndexOf('/')+1);
      // zip.file(fileName, binaryData, {base64: true});
      zip.file(fileName+".docx", binaryData, {base64: true}); //file"+count+".docx"
      if (count < urls.length -1){
        count++;
        downloadFile(urls[count], onDownloadComplete, urls, zip);
      } else {
        chrome.runtime.getBackgroundPage(function () {
          zipAndSaveFiles(zip);
        });
      }
    });
  }
}

function blobToBase64(blob, callback) {
  var reader = new FileReader();
  reader.onload = function() {
    var dataUrl = reader.result;
    var base64 = dataUrl.split(',')[1];
    callback(base64);
  };
  reader.readAsDataURL(blob);
}

function zipAndSaveFiles(zip) {
  chrome.windows.getLastFocused(function(window) {
    var content = zip.generate(zip);
    var zipName = 'download.zip';
    var dataURL = 'data:application/zip;base64,' + content;
    chrome.downloads.download({
      url:      dataURL,
      filename: zipName,
      saveAs:   true
    });
  });
}
gkalpak
  • 47,844
  • 8
  • 105
  • 118
Arvind Anandala
  • 539
  • 2
  • 7
  • 15
  • It would be helpful to include a little more "context": Where is the above code executed from (popup, background-page) ? What are the relevant parts of code in background-page and popup. (Ideally, an **[SSCCE](http://sscce.org)** would be nice !) – gkalpak Jan 09 '14 at 08:39
  • @ExpertSystem i asked you this in my previous question and you told me to ask fresh question regarding this check the link http://stackoverflow.com/questions/20988041/how-to-set-the-file-download-location-for-chrome-extension-using-javascript – Arvind Anandala Jan 09 '14 at 11:16
  • I know :) You did well to ask the question, but in order for MAC people to be able to more effectively help you, you should include some more info (see my 1st comment). – gkalpak Jan 09 '14 at 11:23
  • @ExpertSystem i updated my question with complete code.I think it give you idea to solve this. Thanks – Arvind Anandala Jan 09 '14 at 13:51
  • @ExpertSystem One thing i observed here is after inspecting popup when i click on download button the Save dialogue is not closing – Arvind Anandala Jan 09 '14 at 15:28
  • Yes, that is expected behaviour since as long as the popup is inspected it does not close no matter what (even if it loses focus). I'll take a look at the code later, but as I said I will be only guessing (due to my lack of MAC). – gkalpak Jan 09 '14 at 15:36
  • Ok Thank you for your great patience. Help me to find the answer – Arvind Anandala Jan 09 '14 at 16:13
  • 1
    I have two remarks: 1. You are using the deprecated `chrome.extension.sendRequest/onRequest`. Use `chrome.runtime.sendMessage/onMessage` instead. 2. As I already advised in your previous question, you should try delegating the zipping and downloading to the background page. (Take a look at my answer for sample code.) – gkalpak Jan 09 '14 at 18:42
  • I tried to move zipping and downloading logic to background.js. But i am unable to handle chrome.runtime.sendMessage/onMessage methods as you told.I know i am asking too much detailed answer because i am new to javascript and this is my first project i hope you understand.See my updated code. – Arvind Anandala Jan 10 '14 at 09:28
  • Can you tell me how to handle chrome.runtime.sendMessage/onMessage methods logic with code please. Thanks – Arvind Anandala Jan 10 '14 at 09:36
  • What was wrong with this code: http://stackoverflow.com/questions/20988041/how-to-set-the-file-download-location-for-chrome-extension-using-javascript#20993777 – gkalpak Jan 10 '14 at 09:44
  • you can see chrome.runtime.onMessage.addListener in my popup.js and chrome.extension.sendRequest(links) in source.js where i already handling those methods in my code.And you told me to handle above two methods in backgroun.js and popup.js.Here i am facing problem.i know your giving me right answer with good logic. – Arvind Anandala Jan 10 '14 at 09:52
  • 1
    Let me take a look and get back to you. BTW, I noticed you are using `xhr.send("null");` instead of the intended `xhr.send(null);` (although it doesn't really make any difference for a `GET` request. – gkalpak Jan 10 '14 at 10:15
  • i changed it to xhr.send(null); no luck. Thanks – Arvind Anandala Jan 10 '14 at 10:28

1 Answers1

3

This is a known bug on MAC for over 3 years now. As a work-around, you can delegate the dialog-opening-zipping-and-downloading action to your background page.

In order to achieve this, you need to make the following modification to your code (organized by file):

popup.html (or whatever you call it)

Remove JSZip (you only need it in the background-page now).

manifest.json

// Replace:
"background": {
  "scripts": ["background.js"]
},
// with:
"background": {
  "scripts": ["jszip.js(or whatever the name)", "background.js"]
},

background.js

// Replace:
if (onSuccess) {
    onDownloadComplete(xhr.response, arrayOfUrl, zip);
}
// with:
if (onSuccess) {
    onSuccess(xhr.response, arrayOfUrl, zip);
}

// Replace:
chrome.runtime.getBackgroundPage(function () {
    zipAndSaveFiles(zip);
});
// with:
zipAndSaveFiles(zip);

// Add:
var count;
chrome.runtime.onMessage.addListener(function (msg) {
  if ((msg.action === 'download') && (msg.urls !== undefined)) {
    // You should check that `msg.urls` is a non-empty array...
    count = 0;
    var zip = new JSZip();
    downloadFile(msg.urls[count], onDownloadComplete, msg.urls, zip);
  }
}

popup.js

// Replace:
function downloadLinks() {
    ...
}
// with:
function downloadLinks() {
  var urlArray = new Array();
  for (var i = 0; i < visibleLinks.length; ++i) {
    if (document.getElementById('check' + i).checked) {
      urlArray.push(visibleLinks[i]);
    }
  }
  //var zip = new JSZip();
  //downloadFile(urlArray[count], onDownloadComplete, urlArray, zip);
  chrome.runtime.sendMessage({
    action: 'download',
    urls:   urlArray
  });
}

(As I already mentioned, I am only guessing here, since I am not able to reproduce the issue.)

gkalpak
  • 47,844
  • 8
  • 105
  • 118
  • its working like charm. Thank you very much for your great and complete detailed Answer...... – Arvind Anandala Jan 10 '14 at 14:24
  • Always glad to help ! Feel free to also upvote my answers if you really liked them :) – gkalpak Jan 10 '14 at 14:29
  • Now i am using in my popup.html.When click on 'Choose File'button Here also same thing is happening, the file browser opens for just a moment then both the browser action and the file browser are automatically closing. Can you check this please. Thanks... – Arvind Anandala Jan 13 '14 at 17:22
  • The above issue is in MAC OS – Arvind Anandala Jan 13 '14 at 17:29
  • I don't really understand. Besides it heavily depends on the code you use. I suggest you post a new question where you explain how exactly you use `input type="file"` and provide an **[SSCCE](http://sscce.org)** to reproduce the problem. It might be a bug on MAC that should be reported, but someone on MAC has to take a look. – gkalpak Jan 13 '14 at 18:55
  • 1
    It turns out this is a **[known bug](https://code.google.com/p/chromium/issues/detail?id=61632)** on OSX for over 3 years now. Yet posting the code might help someone come up with a wokaround until this bug is fixed (if it ever will). – gkalpak Jan 13 '14 at 19:03
  • I am using the code as you suggested above and now i am going to add a button(input type="file") when i click the button browser window comes to select the file. Here both popup and browser window's are closing before i see them. Any way i am going to ask it as a new question.Thanks – Arvind Anandala Jan 13 '14 at 19:06
  • Is it still bug in MAC Chrome or any alternative solution there to fix this – Arvind Anandala Jan 13 '14 at 19:08
  • It is a bug in Chrome on MAC and there doesn't seem to be a solution right now. The way you describe the problem, it might be possible to move the dialog-opening part to the background-page as well (but I can't be sure). – gkalpak Jan 13 '14 at 19:14
  • I am searching for this in internet since from morning. But not able to get single hint. Anyway thanks for your quick responce – Arvind Anandala Jan 13 '14 at 19:19
  • Any idea how to move dialog-opening part to the background-page. I am creating the fileUploader button using the following code in popup.html – Arvind Anandala Jan 13 '14 at 19:32
  • You could define the element in your background, attach a `change` listener to it and call it's `click()` method (all these from within the background page). But I need to see some code in order to give a definitive answer. Why are you using an `input[type="file"]` instead of the `downloads` API ? – gkalpak Jan 13 '14 at 19:52
  • That saveAs:ture issue is over. But I am adding new feature to my Extension. So i am adding the in popup.html, When click on 'choose Files' button it should open the file browser window and let user to select the files from computer and upload it to server .But file browser window opens for just a moment and closes before i see that window. – Arvind Anandala Jan 15 '14 at 06:04
  • I see. The basic idea is as described in my previous comment. For something more concrete I need to see the relevant parts of the code. – gkalpak Jan 15 '14 at 06:46
  • Background.js and Popup.js now looks like this, but still the problem of dialog persistence is there: Background.js: chrome.runtime.onMessage.addListener(function (msg) { if(msg.action === 'browse') { var myForm=document.createElement("FORM"); var myFile=document.createElement("INPUT"); myFile.type="file"; myFile.id="selectFile"; myForm.appendChild(myFile); document.body.appendChild(myForm); }}); Popup.js: chrome.runtime.sendMessage({ action: 'browse' }); – Arvind Anandala Jan 15 '14 at 08:20
  • Or shall i post a new question?? – Arvind Anandala Jan 15 '14 at 08:20
  • I would suggest posting a new answer (ideally with a descriptive general title) so other users will benefit from the answers (since noone will ever read through all those comments of ours). – gkalpak Jan 15 '14 at 08:44
  • here's the link: http://stackoverflow.com/questions/21132971/upload-file-as-a-form-data-through-chrome-extension – Arvind Anandala Jan 15 '14 at 09:34