Running an external command from Firefox right now is only supported with the nsIProcess
API. You figured out this bit for yourself already.
Packaging a python script.
The SDK will only package certain files/locations. It would be easiest to just place the python script in the data/
folder, because that is one of the locations the SDK will package all files from.
nsIProcess
nsIProcess
needs an executable file, and that file actually needs to be a real file (not just something contained within the XPI, which by default is not unpacked).
So there are two cases we might want to handle:
- File is a real file - Just execute it.
- File is packaged - Need to extract/copy the data into a (temporary) real file and execute that.
The following code deals with both. I tested and it works for me on OSX (*nix, so should work on Linux or BSDs as well) and Windows, with and without em:unpack (and you should avoid em:unpack).
const self = require("sdk/self");
const {Cc, Ci, Cu} = require("chrome");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/NetUtil.jsm");
const ChromeRegistry = Cc["@mozilla.org/chrome/chrome-registry;1"].
getService(Ci.nsIChromeRegistry);
const ResProtoHandler = Services.io.getProtocolHandler("resource").
QueryInterface(Ci.nsIResProtocolHandler);
function copyToTemp(uri, callback) {
// Based on https://stackoverflow.com/a/24850643/484441
let file = Services.dirsvc.get("TmpD", Ci.nsIFile);
file.append(self.name + "_" + uri.spec.replace(/^.+\//, ""));
file.createUnique(Ci.nsIFile, 0o0700);
NetUtil.asyncFetch(uri, function(istream) {
let ostream = Cc["@mozilla.org/network/file-output-stream;1"].
createInstance(Ci.nsIFileOutputStream);
ostream.init(file, -1, -1, Ci.nsIFileOutputStream.DEFER_OPEN);
NetUtil.asyncCopy(istream, ostream, function(result) {
callback && callback(file, result);
});
});
}
function runProcessAndThen(file, callback) {
console.log("running", file.path);
let proc = Cc["@mozilla.org/process/util;1"].createInstance(Ci.nsIProcess);
try {
// Set executable bit on unix
file.permissions = file.permissions | 0o0500;
}
catch (ex) {
// Might throw?!
}
proc.init(file);
proc.runAsync([], 0, callback);
}
function runFromURIWithPotentialCopy(uri, callback) {
if (!uri.spec) {
uri = Services.io.newURI(uri, null, null);
}
if (uri.scheme === "resource") {
// Need to resolve futher. Strip one layer of indirection and recursively
// call ourselves.
uri = Services.io.newURI(ResProtoHandler.resolveURI(uri), null, null);
return runFromURIWithPotentialCopy(uri, callback);
}
if (uri.scheme === "chrome") {
// Need to resolve futher. Strip one layer of indirection and recursively
// call ourselves.
return runFromURIWithPotentialCopy(ChromeRegistry.convertChromeURL(uri), callback);
}
if (uri instanceof Ci.nsIFileURL) {
// A plain file we can execute directly.
return runProcessAndThen(uri.file, callback);
}
if (uri instanceof Ci.nsIJARURI) {
// A packaged file (in an XPI most likely).
// Need to copy the data into some plain file and run the result.
return copyToTemp(uri, function(f) {
runProcessAndThen(f, function() {
try {
// Clean up after ourselves.
f.remove(false);
}
catch (ex) {
console.error("Failed to remove tmp file again", ex);
}
callback.apply(null, arguments);
});
});
}
throw new Error("Cannot handle URI");
}
function afterRun(subject, topic, data) {
console.log(subject, topic, data);
}
function runFileFromDataDirectory(name, callback) {
try {
runFromURIWithPotentialCopy(self.data.url(name), callback);
}
catch (ex) {
console.error(ex);
}
}
runFileFromDataDirectory("test.py", afterRun);
Running a script
Running a script (as opposed to a full-blown binary) can be tricky. In case of Python e.g. the *nix OSes needs to be told that there is an interpreter and what this is, which is done by a shebang.
On Windows, Python needs to be installed with .py
file type registration, which the default installer will do, but not "portable" versions".