0

I'm converting an XUL-based Firefox add-on to an SDK based version for simplicity. The XPCOM modules I use in the XUL-based version seem to work but ci.nsIFile is behaving differently.

I can't figure out how to navigate to smartProxy.py which currently sits on the highest level of the directory.

In the XUL version smartProxy.py sits on chrome/bin/smartproxy.py. I use the commands below to execute the program and it works without error.

getExeFile: function() { var file = cc["@mozilla.org/file/directory_service;1"].getService(ci.nsIProperties).get("ProfD", ci.nsIFile); file.append("smartProxy.py"); return file; }, 

This below is where it's executed which should give a full picture of how the add-on works.

start: function() { if (this.process && this.process.isRunning) return; this.process = cc["@mozilla.org/process/util;1"].createInstance(ci.nsIProcess); this.process.init(this.getExeFile()); this.process.runAsync([], 0, this.processObserver); this.setProxy(); this.executeObservers(); }, 

How do I go about finding smartProxy.py so that it can be executed?

8
  • What exactly is not working? Does the reutnred file not have the proper path to smartProxy.py? Is that what you mean by "cant figure out how to navigate to smartyProxy.py that sits at highest level in direcotry"? Try: Cu.import("resource://gre/modules/FileUtils.jsm"); var py = FileUtils.getFile('ProfD', ['extensions', 'smartProxy.py']); Commented Jul 23, 2014 at 3:04
  • What's not working: It's navigating to a folder that isn't my extension.It doesn't even seem to be associated with firefox. I tried your solution but it wasn't working as I intended so I checked py.path and navigated to it. I found my extension by it was packaged as a .xpi. The actual folders and resources weren't there. I could unpack it but that's not a solution that will work for production. Commented Jul 23, 2014 at 5:03
  • 1
    Oh whoa, thats weird. Is this file packed with your extension? If this is true, your extension is an xpi, its not a folder. If you want to navigate to the extension that is your folder I'm not sure how to do it with sdk, but its definitely not a folder ,its an xpi. Unless did you set unpack true in install.rdf? Commented Jul 23, 2014 at 5:29
  • 1
    I figured it out! Thanks so much. In the SDK they use package.json which is used to generate install.rdf. They allow you to set unpack to be true but they didn't put it in the documentation because of "performance issues." bugzilla.mozilla.org/show_bug.cgi?id=885673 Commented Jul 23, 2014 at 6:13
  • 1
    var file = FileUtils.getFile('ProfD', ['extensions', 'jid1-xS8bcmiRpHSixA@jetpack', 'resources', 'my-addon', smartProxy.py]); Now I just need to figure out how to stop the SDK from deleting smartProxy.py when it compiles it and test to see what influences the name of jid1-xS8bcmiRpHSixA@jetpack. If it's something that changes often like with the version number I may need to write a script that handles continuity automatically. Commented Jul 23, 2014 at 8:24

2 Answers 2

5

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:

  1. File is a real file - Just execute it.
  2. 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".

Sign up to request clarification or add additional context in comments.

4 Comments

In depth! I had a thought. Is it possible to wrap a resource:// in new FileUtils.File? So like in this guys case var py = new FileUtils.File(self.data.url('module/script.py')) then use this in process.run?
How to put this nicely: No! :D
lolol :( ok thx lol ur nice guy :P what about feeding the process thing a fileOutputStream, rather than making a copy of the file? so like after the ostream.init(file, -1, -1, Ci.nsIFileOutputStream.DEFER_OPEN); we can skip the asyncCopy and just feed the stream somehow?
This is great! Thanks. I ended up getting it working but this is a better method.
2

Hey man I just looked up some stuff on sdk. Don't do the unpacking in the install.rdf because of those "performance issues" that are cited. Instead just put the thing in your data folder. Than access it like this:

https://developer.mozilla.org/en-US/Add-ons/SDK/High-Level_APIs/self

var self = require("sdk/self"); var py = self.data.url("my-panel-content.html") 

4 Comments

That's a good hack for preventing the compiler from removing smartProxy from the package but how would I run it without using nsIProcess which I think requires nsIFile? Even if it's an executable or something which doesn't require the command line to run, I don't see any any way to execute it through self. It works for HTML because it has the panel API to display it
It works perfectly to prevent the SDK from deleting it.
I say use NetUtils and copy the file to just the ProfD folder and on uninstall of your addon make sure to delete that file. Or use netutils to read it with file input stream and then send it to process with file output stream, this way is actually kind of interesiting i would learn a lot from it. Post up a topic we can have some experts on the forum answer that, im very interested to see that answer.
While this answer is not wrong per-se, it does not actually attempt to answer the actual question at hand, namely how to run a file contained within the XPI, with the sub-question of why the SDK does not package the .py file in the XPI.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.