Thursday, October 11, 2007

Java Firefox Extension



I know, you won't find this as the first search result on Google, but anyway...

Because I promised some (many) posts ago and because recently I found the time to create a decent example (actually, I HAD to find the time), here's the long waited demo of a Java Firefox extension, i. e. Firefox extension that uses Java code. I first got this idea from Simile's Piggybank extension, and then I got their example from http://simile.mit.edu/wiki/Java_Firefox_Extension. I found that to be quite complicated so I created a new one, simpler (and optimized in some points) and, I think, easier to understand.
I assume you are familiar with the Firefox extension development. If you're not, have a look at http://developer.mozilla.org/en/docs/Extensions.
What we already have in Firefox:


  • LiveConnect

  • that allows us to connect to Java code from javascript code. We can write stuff like var x = new java.lang.String("test") and x will hold a reference to a Java object. We can then call all x's functions from javascript and get the results back. We can not send javascript objects other than numbers (integer, double), strings and arrays to Java. I mean, we can, but we'll get a JSObject object which is kind of empty and useless (no functions, no members).


  • XPCOM

  • that allows us to build a COM-like component for Firefox, platform independent (because it is written in javascript).


We will create a XPCOM component that will use LiveConnect to get to the Java code.

Why can't we do it only with LiveConnect? Because XPCOM allows a single instantiation of the component through all the browser instances (shared). Now you decide for yourself why you might need that (I personally needed it because I had some native libraries loaded by the Java code and I had to be sure that happenned only once since I would get an error otherwise).
And it's pretty cool to learn how to do both the Java access and the XPCOM.
Now get the code from this temporary deposit (change extension in xpi and drag it to a Firefox window to install) and follow me:
Any XPCOM component must implement an interface, at least nsISupports, which is the most general so that it can be managed by the Firefox component manager. The trick is that we don't need any function from the interface, we will get the javascript object that implements the interface and call its functions, so we should implement an interface with no functions at all -- we can create our own (like simile's example does) and go through all the mess of compiling the idl file in a xpt interface definition file or we can simply implement nsISupports.
Once that is done (see the GreeterComponent.js file in /components) we can simply call:


Components.classes["@lucaa.students.info.uaic.ro/je-greeter;1"]
.getService(Components.interfaces.nsISupports);

to get an instance to our component ("@lucaa.students.info.uaic.ro/je-greeter;1" is the component's contract id, specified in the implementation).

But what's the trick to access the Java code? One might say that we have LiveConnect's java object and that is enough. Well, not quite, since our own classes ar not loaded and we can not access them. We need to load them dynamically through our own class loader (this is done by the component upon initialization):

var greeterURL = new this._java.io.File(greeterJarLocation.path).toURL();
//create the classLoader
var classLoader = new this._java.net.URLClassLoader([greeterURL]);
//get an instance of the GreetingGenerator through reflection
var greetingGeneratorClass = this._java.lang.Class.forName(
"ro.uaic.info.students.lucaa.je.GreetingGenerator",
true, classLoader);
this._greeter = greetingGeneratorClass.newInstance();

Then, the call to the java functions of the GreetingGenerator can be simply done through:

this._greeter.generateGreeting()

Remember that the java LiveConnect object is not provided by default to the components environment and we must pass it from the calling code, upon initializing the wrappedJSObject of the component:

//get a component
var greetComponent = Components.classes["@lucaa.students.info.uaic.ro/je-greeter;1"]
.getService(Components.interfaces.nsISupports);
//initialize the wrappedJSObject
greetComponent.wrappedJSObject.initialize(java, true);

In the example, the GreetingGenerator class in Java holds a counter incremented by each call to the generateGreeting function. Go to the Tools menu, the "Greet!" menu item and get your greeting. Now open a new browser instance and do the same. The counter goes on, as a sign that it's the exact same object called.

Starting from this, you can do all sorts of stuff: you've just created yourself an unique entrypoint in the Java code, making sure that if you have configurations to set up, servers to start, etc, etc, those will happen exactly once.

Happy coding!