/* Any copyright is dedicated to the Public Domain. * http://creativecommons.org/publicdomain/zero/1.0/ */ "use strict"; const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components; /* Shorthand constructors to construct an nsI(Local)File and zip reader: */ const LocalFile = new Components.Constructor("@mozilla.org/file/local;1", Ci.nsIFile, "initWithPath"); const ZipReader = new Components.Constructor("@mozilla.org/libjar/zip-reader;1", "nsIZipReader", "open"); Cu.import("resource://gre/modules/Services.jsm"); Cu.import("resource://gre/modules/osfile.jsm"); /** * Returns a promise that is resolved with a list of files that have one of the * extensions passed, represented by their nsIURI objects, which exist inside * the directory passed. * * @param dir the directory which to scan for files (nsIFile) * @param extensions the extensions of files we're interested in (Array). */ function generateURIsFromDirTree(dir, extensions) { if (!Array.isArray(extensions)) { extensions = [extensions]; } let dirQueue = [dir.path]; return (async function() { let rv = []; while (dirQueue.length) { let nextDir = dirQueue.shift(); let {subdirs, files} = await iterateOverPath(nextDir, extensions); dirQueue.push(...subdirs); rv.push(...files); } return rv; })(); } /** * Uses OS.File.DirectoryIterator to asynchronously iterate over a directory. * It returns a promise that is resolved with an object with two properties: * - files: an array of nsIURIs corresponding to files that match the extensions passed * - subdirs: an array of paths for subdirectories we need to recurse into * (handled by generateURIsFromDirTree above) * * @param path the path to check (string) * @param extensions the file extensions we're interested in. */ function iterateOverPath(path, extensions) { let iterator = new OS.File.DirectoryIterator(path); let parentDir = new LocalFile(path); let subdirs = []; let files = []; let pathEntryIterator = (entry) => { if (entry.isDir) { subdirs.push(entry.path); } else if (extensions.some((extension) => entry.name.endsWith(extension))) { let file = parentDir.clone(); file.append(entry.name); // the build system might leave dead symlinks hanging around, which are // returned as part of the directory iterator, but don't actually exist: if (file.exists()) { let uriSpec = getURLForFile(file); files.push(Services.io.newURI(uriSpec)); } } else if (entry.name.endsWith(".ja") || entry.name.endsWith(".jar") || entry.name.endsWith(".zip") || entry.name.endsWith(".xpi")) { let file = parentDir.clone(); file.append(entry.name); for (let extension of extensions) { let jarEntryIterator = generateEntriesFromJarFile(file, extension); files.push(...jarEntryIterator); } } }; return new Promise((resolve, reject) => { (async function() { try { // Iterate through the directory await iterator.forEach(pathEntryIterator); resolve({files, subdirs}); } catch (ex) { reject(ex); } finally { iterator.close(); } })(); }); } /* Helper function to generate a URI spec (NB: not an nsIURI yet!) * given an nsIFile object */ function getURLForFile(file) { let fileHandler = Services.io.getProtocolHandler("file"); fileHandler = fileHandler.QueryInterface(Ci.nsIFileProtocolHandler); return fileHandler.getURLSpecFromActualFile(file); } /** * A generator that generates nsIURIs for particular files found in jar files * like omni.ja. * * @param jarFile an nsIFile object for the jar file that needs checking. * @param extension the extension we're interested in. */ function* generateEntriesFromJarFile(jarFile, extension) { let zr = new ZipReader(jarFile); let entryEnumerator = zr.findEntries("*" + extension + "$"); const kURIStart = getURLForFile(jarFile); while (entryEnumerator.hasMore()) { let entry = entryEnumerator.getNext(); // Ignore the JS cache which is stored in omni.ja if (entry.startsWith("jsloader") || entry.startsWith("jssubloader")) { continue; } let entryURISpec = "jar:" + kURIStart + "!/" + entry; yield Services.io.newURI(entryURISpec); } zr.close(); } function fetchFile(uri) { return new Promise((resolve, reject) => { let xhr = new XMLHttpRequest(); xhr.responseType = "text"; xhr.open("GET", uri, true); xhr.onreadystatechange = function() { if (this.readyState != this.DONE) { return; } try { resolve(this.responseText); } catch (ex) { ok(false, `Script error reading ${uri}: ${ex}`); resolve(""); } }; xhr.onerror = error => { ok(false, `XHR error reading ${uri}: ${error}`); resolve(""); }; xhr.send(null); }); }