Listing a user directory with javascript in a Chrome extension

I have been struggling with a way to get my Google Chrome extension to be able to list a user’s directory in order to automatically load and display files from that directory. It turns out, there is no easy way to list the files in an arbitrary directory in a user’s filesystem using Javascript, even in a Chrome extension. The File API in HTML 5 allows web apps to have their own filesystem, but it is an isolated filesystem so that’s no good. You can actually read the contens of a file using XMLRequest (for example this code from Read local file with XmlHttpRequest - JavaScript - Snipplr Social Snippet Repository does just that):

/* Read a file  using xmlhttprequest

If the HTML file with your javascript app has been saved to disk,
this is an easy way to read in a data file.  Writing out is
more complicated and requires either an ActiveX object (IE)
or XPCOM (Mozilla).

fname - relative path to the file
callback - function to call with file text
*/
function readFileHttp(fname, callback) {
    xmlhttp = getXmlHttp();
    xmlhttp.onreadystatechange = function() {
        if (xmlhttp.readyState==4) {
                callback(xmlhttp.responseText);
        }
    }
    xmlhttp.open("GET", fname, true);
    xmlhttp.send(null);
}

/*  
Return a cross-browser xmlhttp request object
*/
function getXmlHttp() {
    if (window.XMLHttpRequest) {
        xmlhttp=new XMLHttpRequest();
    } else if (window.ActiveXObject) {
        xmlhttp=new ActiveXObject("Microsoft.XMLHTTP");
    }
    if (xmlhttp == null) {
        alert("Your browser does not support XMLHTTP.");
    }
    return xmlhttp;
}

However, it does not allow you to list the directory itself, which is what I was looking for.

My solution is a bit complicated, and not elegant at all, but it works. The only catch is that it requires the Chrome extension to have access to file URLs, which is something that users must explicitly activat in the extensions page.

The solution is:

  1. Open a new Tab
  2. Load that tab with a file URL: file://[user directory to list]. That creates a page that looks like
  3. Inject a piece of javascript into the tab, that
    • Parses the DOM of the tab’s document, looking for the files listed
    • Sends a message with a file list!
  4. Remove the tab

Code

// Directorylister.js:


function DirectoryList ( path ) {
    this.path = path;
    this.fileList = [];
}

DirectoryList.prototype.getFileList = function( fileListener ) {
    var $this = this;
    chrome.extension.onMessage.addListener(
        function(request, sender, sendResponse) {
            chrome.extension.onMessage.removeListener(arguments.callee);
            chrome.tabs.remove($this.tabid);

            $this.fileList = request;

            fileListener(request);
        });

    chrome.tabs.onUpdated.addListener(function onTabUpdated(tabId, changeInfo, _tab) {
        if ( _tab.id.toString() === $this.tabid.toString() ) {
            chrome.tabs.executeScript(_tab.id, {file: "directorylister-tab.js"});
            chrome.tabs.onUpdated.removeListener(arguments.callee);
        }
    });

    chrome.tabs.create({}, function(_tab) {
        $this.tabid = _tab.id;
        console.log("Creating listing tab (" + $this.tabid + ") for: " + $this.path);
        chrome.tabs.update(_tab.id, {url:"file://"+$this.path, selected:false});
    });

};


// directorylister-tab.js

console.log("Directory Lister tab script loaded");

var links = document.getElementsByClassName("icon file");

var files = [];
for (var i = 0; i < links.length; i++) {
    filename = links[i].href.substring(links[i].href.lastIndexOf("/")+1, links[i].href.length);
    if ( !(filename.indexOf(".") == 0) ) { // skip system files

        files.push(links[i].href);
    }
}

chrome.extension.sendMessage(files);
comments powered by Disqus