Deep Integration of GWT/JavaScript and Eclipse using XULRunner and JavaXPCOM

March 19th, 2008 Bryan Campbell Posted in Eclipse, GWT, Java | No Comments »

With Eclipse 3.3, it’s easier than ever to use Mozilla as the underlying rendering engine for SWT browser widgets using XULRunner. This also allows for rich communication between the Eclipse Platform and its embedded Mozilla browser widgets via the JavaXPCOM layer. An array of related technologies are used to make this communication possible including XPIDL, XPConnect, and the XPCOM system. This post will show you how a sample GWT web application can seamlessly expose extra features when integrated within the Eclipse rich client platform.

This tutorial is an extension of a talk from EclipseCon 2008.

Getting Started

The easiest way to get started is to download the windows demo package here. This zip file includes the slides from a 10 minute EclipseCon presentation, an eclipse workspace set up with a GWT web application and a demo rcp plugin project, and all the other tools that you need to build xpcom components. Unzip the demo package to C:\xpcom_demo (if you choose a different location, you will need to change the baseURL variable in BrowserView.java). You will also need Eclipse 3.3 or higher somewhere on your machine. Next you’ll need to add the xpcom.jar eclipse plugin, into the plugins directory of your Eclipse install. Go ahead and start up Eclipse and point it to the workspace named “workspace” which was included in the zip file. You should see two projects in the package explorer. If not, you’ll have to import them into you workspace manually.To run the demo app, right click on the Demo Project in eclipse and choose Run..->Eclipse Application. This will launch an Eclipse RCP instance. Switch to the “XULRunner” perspective and you should see the GWT kitchen sink example on the left, and a view on the right (screenshot). Click on the XPCOM tab and you will see some examples of actions that will interact with the Eclipse runtime such as message passing to an eclipse view, and switching perspectives.

Manual Setup (Optional)

If you downloaded the windows demo package, you will already have all the required files. You can use this section if you would like to start a new project from scratch, or if you are running on a non-windows machine.

XULRunnerYou will need a copy of xulrunner runtime which you can pick up at the xulrunner release site. At the time of this writing the latest version is 1.8.1.3. There will be a specific build depending on your platform (win32, linux-i686, and mac). Unzip xulrunner to where ever you would like on your hard drive. I have it set up at C:\xulrunner\1.8.1.3 to allow for multiple versions of xulrunner at the same time.

Gecko-sdk – The gecko sdk contains a package of all the xpcom components that are part of the rendering engine as well as a set of tools and libraries to build your own. The sdk is built with xulrunner so the version numbers should correspond. Unzip this to wherever you like – I have this located in C:\dev\gecko-sdk.

Mozilla Build PrerequisitesTo compile idl files into binary xpt files, your system will need to have all of the required libraries for building mozilla. See these pages for your specific platform (linux, mac). For windows, a static pre-build zip file called wintools.zip is all that’s needed. Unzip these files into any place you like, mine is at C:\dev\gecko-wintools.

Setting up XULRunner

The first thing that we need to do is to tell the Eclipse browser to use xulrunner instead of the system default. On Windows this will be Internet Explorer (screenshot). By using xulrunner, we will instead be using the Gecko Rendering Engine which gives virtually complete access to all of it’s components via the Cross Platform Component Object Model (XPCOM), and will in turn let us write our own custom components. It is through this component that will allow JavaScript and Java to communicate.We will need to do two things to get Eclipse to use xulrunner for the browser widget. The first is done when constructing a browser widget by passing Mozilla as the SWT style parameter. In the demo project, this is done in BrowserView.java. Now that this flag is set and Eclipse knows that it should use xulrunner, we need to tell it where xulrunner is located. The way I prefer to do this is to set a Java system property, prior to creating the browser object. The code in BrowserView looks like this:

System.setProperty("org.eclipse.swt.browser.XULRunnerPath", "C:\\xulrunner\\1.8.1.3");

This property must be set before you create the new browser widget in your eclipse plugin code.It should now be possible to create a browser widget using xulrunner. If you’d like to check, go ahead and change the setUrl() in BrowserView.java to a website which will identify which browser you are using such as www.whatsmybrowser.com. You should see this (screenshot).

Defining the component

Next we will define the component, which is the interface describing which Java methods will be exposed through XPCOM. XPCOM components are written in a language called XPIDL (Cross Platform Interface Description Language). Lets take a look at the MessagingComponent.idl:

#include "nsISupports.idl"
[scriptable, uuid(696f6fd8-7e4d-11dc-8314-0800200c9a66)]
interface MessagingComponent : nsISupports {
void addToList(in string message);
};

The full description of the format of the IDL language is out of scope for this document, but more information can be found at the XPIDL Project page. You should notice a few things about this interface. One is that it includes and extends nsISupports. nsISupports is the base class for all interfaces and any new interface must extend it directly or extend a subclass of it. Next you should notice that the interface has a UUID (Universal Unique Identifier) which is a 128 bit globally unique identifier. This will identify the component in XPCOM and will be used later when the interface is implemented. There are many tools that can generate UUIDs, but the one I most frequently use is a web based tool found here. Last you should notice the body of the idl file; specifically the method addToList(in string message). This is the method that will be implemented in Java, and called from JavaScript.The IDL file can be thought of as source for the xpcom component. For XPCOM to actually understand the component, we’ll need to compile it into binary type library (.xpt). To do this, we will use a tool from the gecko-sdk called xpidl . I am using a Windows machine for this example so i’ll be using xpidl with the pre-built libraries found in wintools.zip. Navigate to the gecko-dev directory which contains xpidl.exe, glib-1.2.dll, gmodule-1.2.dll, gthread-1.2.dll, libIDL-0.6.dll, nsrootidl.idl, nsISupports.idl, and MessagingComponent.idl. I have created this directory specifically for compiling idl files with the xpidl tool. The files residing inside came from a lot of different places. The four .dlls came from the wintools.zip package, the two idl files came from ..\gecko-sdk\idl, the xpidl tools it self came from ..\gecko-sdk\bin and the MessagingComponent.idl is the file that we created to define the component. To generate the typelib for the component navigate to the gecko-sdk directory in a command prompt and run the following command:

gecko-dev> xpidl -m typelib -e MessagingComponent.xpt MessagingComponent.idl

Once this command is run, you should see a new file in your gecko-dev directory called MessagingComponent.xpt. To install the component, drop the xpt file into the components directory located in ..\xulrunner\1.8.1.3\components.

JavaScript & XPCOM

Now that the messaging component is in place, we need to be able to access it from JavaScript. To facilitate this communication is a layer called XPConnect. XPConnect gives JavaScript an API for accessing and manipulating XPCOM objects. The actual JavaScript calls in the WebApp project are encapsulated in a utility class called XPComUtils.java. Here is the striped down version of the call made to addToList(msg):

netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");var messagingComponent = Components.classes['@com.lombardi/MessagingComponent;1'].createInstance(Components.interfaces.MessagingComponent);component.addToList(msg);

When we create the MessagingComponent through the Components Object, XPConnect is going to create a proxy object at runtime which we will be able to invoke the methods that are found in the MessagingComponent (in this case addToList).One thing you might have noticed is the first line “netscape.security....“. This is a necessary step since it could be very dangerous if any old web application could get access to XPCOM without the users permission. When the the web application requests UniversalXPConnect Privileges, it will pop up an annoying little Internet Security Message (screenshot). In most cases, we’ll want to hide this from the user and pre-authorize the web app to have the right access. This can be accomplished by setting two prefs in ..\xulrunner\1.8.1.3\defaults\pref\xulrunner.js file.

pref("capability.principal.codebase.messagingcomponent.granted", "UniversalXPConnect");
pref("capability.principal.codebase.messagingcomponent.id", "file://");

The first line is identifying which privileges we’d like to grant. The second preference identifies the web application based on it’s domain. In most cases since the web app will be mapped to some domain, instead of “file://” it would be something like “http://www.lombardi.com/”. Now, any web application originating from the Lombardi domain would be pre-authorized with XPCOM privileges and thus the user wouldn’t have to deal with that ugly Internet Security popup.

Java & XPCOM

At this point the XPCOM component is in place, and Javascript can make calls through XPConnect. Now we need to move over to the Java side of the component. There are 3 major pieces of functionality that needs to be implemented in the Java plugin code: Implementing the messaging component in java, Implementing a factory to handle the creation of new java messaging component objects, and last will be registering the component implementation with XPCOM during runtime.Before we start this you’ll need to make sure you have a couple of extra packages available in your class path. First, to be able to compile the src, MozillaInterfaces.jar will need to be added to the classpath. This will give you access to all of the xpcom interface (e.g. nsISupports, nsIFactory,…). Next we’ll need to add the plugin dependency, xpcom.jar to the plugin project. xpcom.jar actually just wraps MozillaInterface.jar, and includes a Manifest that Eclipse will like.

Component Implementation

We’ll start with writing a java interface which is basically a java version of the MessagesComponent IDL. This can actually be generated by the xpidl tool by using the -m java switch. This interface is going to contain all the methods from MessagingComponent.idl, and will also contain a UUID which is the same as the MessagingComponent IDL.Next we need to implement IMessagingComponent – take a look at MessagingComponent.java in the demo.xpcom package. You’ll notice that the MessagingComponent has two IDs. The CID, is another UUID which will identify this implementation – this is NOT the same as the IID found in the interface. You will need to genereate another UUID the CID. You’ll also notice a more human readable ID called the Contract_ID. This is another unique identifier for the component implementation and is explicitly used from the JavaScript side. Since we are finally implementing the business logic, you can see what code is actually getting run inside addToList(msg).Component Factory ImplementationSince the XPCOM component is unaware of it’s implementation details, it doesn’t know how to create an instance of itself. So we’ll need to implement an nsIFactory that will take care of creating the component. Take a look at the implementation of the factory in MessagingComponentFactory.java. The createInstance method is going to be called by XPCOM when it needs to create a new component instance.

Component Registration

At this point, we have specified the Java business logic in MessagingComponent.java, and we have specified also specified how to create a MessagingComponent in MessagingComponentFactory.java. Now we need to bind the factory with the implementation. This is accomplished by talking to the XPCOM ComponentRegistrar. The registerComponent() method taken from BrowserView.java:

Mozilla mozilla = Mozilla.getInstance();nsIComponentRegistrar registrar = mozilla.getComponentRegistrar();registrar.registerFactory(MessagingComponent.CID, "MessagingComponent", MessagingComponent.CONTRACT_ID, MessagingComponentFactory.getInstance());

Through the Mozilla singleton, we can get access to the xpcom component registrar. With the registrar, we pass along the CID, Name, and Contract_ID of the component implementation, and then we also give it an instance of the factory. As I said before, you can think of this as binding the implementation to the factory that will be in charge of object creation. Once the registration process is complete, the web application can us XPConnect to agnostically create and use the component implementations that have been defined.

Resources

Leave a Reply