Search code examples
javascriptfirefox-addonfirefox-addon-sdkthunderbird-addonfirefox-addon-overlay

How to iterate over files in a directory within an extractionless add-on's .xpi file


My add-on, Color Source, needs to iterate files in a directory within my add-on (e.g., the content subdirectory) when the add-on is not unpacked (i.e., is kept zipped as an XPI).

We are using code like this to get the Addon object:

Cu.import("resource://gre/modules/AddonManager.jsm", null)
        .AddonManager
        .getAddonByID(
            "[email protected]",
            function(addon) {
                var uri = addon.getResourceURI();
            }
        );

In order to pass a path to new OS.File.DirectoryIterator, we have tried:

  1. OS.Path.join(uri.path, 'content'); which apparently works in *nix but not Windows
  2. Getting and then cloning uri.QueryInterface(Ci.nsIFileURL).file, and then calling append('content') on the clone and this works, but only when the add-on has the <em:unpack> directive set to true in install.rdf.

How do we get a path that works even when the add-on is zipped up?

(Note: I have added a Firefox add-on tag to this post because the issue should be similar there)


Solution

  • The .xpi file is a zip file and should be accessed using nsIZipReader.

    The following should create a nsIUTF8StringEnumerator over which you can iterate. The contents of the zip file which match a pattern are obtained using findEntries()

    Components.utils.import("resource://gre/modules/FileUtils.jsm");
    var zipReader = Components.classes["@mozilla.org/libjar/zip-reader;1"]
                .createInstance(Components.interfaces.nsIZipReader);
    var addonFile = new FileUtils.File(uri.path);
    zipReader.open(addonFile);
    var contentEnumerator = zipReader.findEntries("content/*");
    

    This should provide you with all the files and directories which are contained within the content directory. However, you are probably only interested in the files which are directly within the content directory:

    for(contentFile of contentEnumerator) {
        if(/content\/.*\//.test(contentFile) {
            continue;
        }
        //If you get here contentFile should contain only direct descendants of
        //  the content directory. 
    }