I started toying with XUL several months ago and I wanted to see how difficult it was to build an application using Mozilla's XULRunner framework. If you are here, you probably have discovered that most of the documentation floating out on the web hasn't kept up with the latest XULRunner releases and so, its very difficult put together a simple working "Hello World" application. So, this blog post is an attempt to help others get started developing XULRunner apps. Get ready for some fun.
This tutorial is for running a standalone XULRunner application with a C++ XPCOM component on Linux. I have read about more success in developing on Windows using a Microsoft compiler so if you're not comfortable with Linux or you're developing Firefox component you may want to find windows-focused tutorial. There are a few good ones on the web and I used those to help put together this tutorial.
First things first, you should carefully read everything below before getting started. It will give you a good understanding of XULRunner and XPCOM. If you run into problems, you will need this material for reference.
Basic understanding of XUL, XULRunner
https://developer.mozilla.org/en/The_Joy_of_XUL
https://developer.mozilla.org/en/Getting_started_with_XULRunner
Basic understanding of XPCOM
https://developer.mozilla.org/en/Creating_XPCOM_Components
http://www.ibm.com/developerworks/webservices/library/co-xpcom.html
http://www.ibm.com/developerworks/webservices/library/co-xpcom2.html
Helpful Tutorials
http://starkravingfinkle.org/blog/2006/10/mozilla-platform-xpcom-in-c/
http://blogs.acceleration.net/ryan/archive/2005/05/06/1073.aspx
http://www.iosart.com/firefox/xpcom/
http://nerdlife.net/building-a-c-xpcom-component-in-windows/
https://wiki.mozilla.org/Education/Learning/XpcomComponents
Next, you will need to prep your development environment. You will need:
- Linux
- g++ compiler
- IDE for C++
- XULRunner runtime
- XULRunner SDK. Its called Gecko.
The setup I used for this tutorial:
- Ubuntu 9.10
- g++ (gcc) 4.4.1
- Eclipse IDE 3.5.1 with the CDT plugin (for C/C++ development)
- XulRunner 1.9.1.7
- Gecko 1.9.1.7
g++ is probably included in most linux distros. You can get Eclipse here: http://www.eclipse.org/. And you will need the CDT plugin here: http://www.eclipse.org/cdt/. If you aren't familiar with Eclipse, you should write a simple hello world program in Java or C++, to get a feel for the IDE. Eclipse is fairly comprehensive and it has ton of configuration options.
Ubuntu has the XULRunner runtime installed by default. If you don't have it you will need to install it. You can check the version that you have by running XULRunner with the -v flag:
XULRunner -v
And you should get output like this:
Mozilla XULRunner 1.9.1.7 - 20100106054534
Make a note of the XULRunner version. You will need it to download the matching Gecko SDK. You can find the Gecko SDK here: https://developer.mozilla.org/en/Gecko_SDK
Unpack the SDK in a place where it will reside for a long time. You will need to compile your component against it and the component, once installed, will look for libraries from the SDK when its used.
After you've read the Getting_started_with_XULRunner tutorial, download the myapp.zip file from the tutorial and unpack the contents into an empty directory.
First, modify the application.ini file. Change the vendor, name, buildID, copyright, ID and max version. The vendor and name field will affect the install directory names and the max version should include the Gecko SDK that you downloaded. For my application, I changed the max version to 1.9.*.
You can take a look at the pref.js, chrome.manifest, main.xul files to familiarize yourself with their structure and relationship but at this point you are now ready to test the XULRunner runtime and the downloaded sample application from the tutorial.
In a terminal window, go to the root application directory (where the application.ini file is) and run the application:
xulrunner application.ini
You should now see a GUI window with the default window decoration and hello world label. This means that XULRunner is working and the XUL application structure is ok.
On first run, the XULRunner runtime will create the extensions and updates directories in the root application directory and create a .thisismyapp directory in your user's home directory. The .thisismyapp is your application's install directory and it will be named with whatever you entered in the vendor field of the application.ini file. So, in .vendor_name/app_name/ in your home directory there will be the xyzxyz.default directory. This directory contains the compreg.dat and xpti.dat where your XPCOM components will be registered. There won't be a need to edit these files since they are maintained by the XULRunner runtime, but you should be familiar with their structure because this will be the first place you will check to see if XULRunner recognizes your component.
So, now lets create a XPCOM component.
First, create a components directory in the root application directory. This is where you will place your type library file and your compiled component.
To get started with the code, you will first need to create the .idl file. This file will be used to generate the interface header and type library files.
Here is the IMyComponent.idl file:
#include "nsISupports.idl"
[scriptable, uuid(Your_Interface_GUID)]
interface IMyComponent : nsISupports
{
long Add(in long a, in long b);
};
Use uuidgen or http://mozilla.pettay.fi/cgi-bin/mozuuid.pl to generate the GUID and replace Your_Interface_GUID with the generated GUID. If you are curious about Mozilla and GUID's, here's some info: https://developer.mozilla.org/En/Generating_GUIDs
Now, generate the .xpt and interface header by running xpidl twice. The xpidl command is in the bin directory of the Gecko SDK.
/PATH/TO/GECKO/xulrunner-sdk/sdk/bin/xpidl -m header -I /PATH/TO/GECKO/xulrunner-sdk/sdk/idl IMyComponent.idl
/PATH/TO/GECKO/xulrunner-sdk/sdk/bin/xpidl -m typelib -I /PATH/TO/GECKO/xulrunner-sdk/sdk/idl IMyComponent.idl
You should now have two files: the .xpt file and the interface header file. Now, lets start developing in Eclipse.
Start Eclipse and create a new shared library project. It needs to be shared library because in the end you will need to have a .so library file for the XULRunner runtime. Then, modify these Eclipse project properties:
Add to includes:
- /PATH/TO/GECKO/xulrunner-sdk/sdk/includes
- /PATH/TO/GECKO/xulrunner-sdk/include
Screenshot of where to modify the project properties for the includes(click to enlarge):
Add to library paths:
- /PATH/TO/GECKO/xulrunner-sdk/lib
- /PATH/TO/GECKO/xulrunner-sdk/sdk/lib/
Add to the G++ Linker Properties:
- xpcomglue
- xpcomglue_s
- xpcom
Screenshot of where to modify the project properties for the library and linker properties(click to enlarge):
Add the LD_RUN_PATH environment variable and set it to this:
- /PATH/TO/GECKO/xulrunner-sdk/bin
Here's some info on LD_RUN_PATH if you're not familiar: http://www.eyrie.org/~eagle/notes/rpath.html
Screenshot of where to modify the project properties for LD_Run_Path (click to enlarge):
Create 2 header files and 2 cpp source files in Eclipse.
The header files should be:
- IMyComponent.h
- MyComponent.h
and the cpp files should be:
- MyComponent.cpp
- MyComponentModule.cpp
Copy and paste the contents of IMyComponent.h that you generated with xpidl into the identically named header file you just created in Eclipse. Inside this header file are the code stubs for MyComponent.h and MyComponent.cpp. Copy everything between /* Header file */ and /* Implementation file */ and paste it into MyComponent.h. Then add the necessary #defines replace all occurrences of '_MYCLASS_' with 'MyComponent'. When finished, your MyComponent.h file should look like this:
#ifndef MYCOMPONENT_H_
#define MYCOMPONENT_H_
#include "IMyComponent.h"
#define MYCOMPONENT_CONTRACTID "@mydomain.com/myappname/mycomponent;1"
#define MYCOMPONENT_CLASSNAME "My First Component"
/* 0ba456c3-faf7-44c0-b9bf-fd529f0af123 */
#define MYCOMPONENT_CID { 0x0ba456c3, 0xfaf7, 0x44c0, \
{ 0xb9, 0xbf, 0xfd, 0x52, 0x9f, 0x0a, 0xf1, 0x23 } }
class MyComponent : public IMyComponent
{
public:
NS_DECL_ISUPPORTS
NS_DECL_IMYCOMPONENT
MyComponent();
private:
~MyComponent();
protected:
/* additional members */
};
#endif /* MYCOMPONENT_H_ */
You should replace the listed CID with a unique GUID. It's not the same GUID that you created for the interface. Also note that the CONTRACTID is the identifier that you will use in the javascript that calls the component.
Now, and also from the IMyComponent.h file, copy everything between /* Implementation file */ and /* End of implementation class template. */ and paste it into the MyComponent.cpp file. Again, add the necessary #includes, #defines and replace all occurrences of '_MYCLASS_' with 'MyComponent'. When finished your MyComponent.cpp file should look like this:
#include "MyComponent.h"
#include "IMyComponent.h"
#include "xpcom-config.h"
#define XPCOM_GLUE
NS_IMPL_ISUPPORTS1(MyComponent, IMyComponent)
MyComponent::MyComponent()
{
/* member initializers and constructor code */
}
MyComponent::~MyComponent()
{
/* destructor code */
}
/* long Add (in long a, in long b); */
NS_IMETHODIMP MyComponent::Add(PRInt32 a, PRInt32 b, PRInt32 *_retval NS_OUTPARAM)
{
//return NS_ERROR_NOT_IMPLEMENTED;
*_retval = a + b;
return NS_OK;
}
I also modified the Add function to actually do the addition and commented out the NS_ERROR_NOT_IMPLEMENTED return type from the stub.
Lastly, we need to create the factory. Just copy/paste the code below into your MyComponentModule.cpp file.
#include "nsIGenericFactory.h"
#include "MyComponent.h"
#include "IMyComponent.h"
#include "xpcom-config.h"
#define XPCOM_GLUE
NS_GENERIC_FACTORY_CONSTRUCTOR(MyComponent)
static nsModuleComponentInfo components[] =
{
{
MYCOMPONENT_CLASSNAME,
MYCOMPONENT_CID,
MYCOMPONENT_CONTRACTID,
MyComponentConstructor,
}
};
NS_IMPL_NSGETMODULE("MyComponentsModule", components)
Now you are ready to build the project. At this point you should have four files: IMyComponent.h, MyComponent.h, MyComponent.cpp and MyComponentModule.cpp. If you have macro errors at this point, check your #includes and the include paths in your Eclipse project settings.
Build the project and Eclipse should generate an libmycomponent.so file. Export this file into the components directory inside the root application directory. Then copy the .xpt file into the components directory as well.
At this point you will need to register the component with your application. There are references to regxpcom or the creation of a .autoreg file in the install directory in the Mozilla documentation, but if your .so was compiled successfully and placed in the components directory XULRunner should automatically register the component.
Before running the application, check the component's library dependencies with LDD:
LDD -r libmycomponent.so.
All of the referenced libraries should be found and there shouldn't be any undefined symbols. If there are either of these, then XULRunner will not register your component. If you notice a problem, check your Eclipse project settings. After that, check your system for the required libraries. If it appears that you still have dependency problems check these links for information on necessary libraries and linker flags:
https://developer.mozilla.org/en/Troubleshooting_XPCOM_components_registration
https://developer.mozilla.org/en/GRE
https://developer.mozilla.org/en/XPCOM_Glue
For reference, the console compiler output should look like this:
g++ -I/PATH/TO/GECKO/xulrunner-sdk/sdk/include -I/PATH/TO/GECKO//xulrunner-sdk/include -O0 -g3 -Wall -c -fmessage-length=0 -osrc/MyComponentModule.o ../src/MyComponentModule.cpp
g++ -L/PATH/TO/GECKO/xulrunner-sdk/lib -L/PATH/TO/GECKO/xulrunner-sdk/sdk/lib/ -shared -olibmycomponent.so src/MyComponentModule.o src/MyComponent.o -lxpcomglue_s -lxpcom -lxpcomglue
Build complete for project mycomponent
If the LDD output is good, then run the application. The XULRunner runtime should automatically detect and register your component. To make sure, check the xpti.dat file in the application install directory to see if XULRunner picked up the component interface. There should be two entries that look like this:
0,IMyComponent.xpt,0,144,12669345446000
510,IMyComponent,{Your_Interface_GUID},0,-1,1
If the two entries are there, it means that XULRunner recognizes your .xpt file. Now, check the compreg.dat file. There should be three entries:
rel:libMyComponent.so,12676332322000
{Your_Component_GUID},,application/x-mozilla-native,,rel:libMyComponent.so
@Your_Component_CONTRACTID;1,{Your_Component_GUID}
If there are three entries similar to the above, the XULRunner runtime recognized and registered the component.
Now let's modify the XUL and add some javascript to call the component. First, lets add a button to the GUI.
Open up main.xul and add the following between the <window> tags:
<button onclick="RunMyComponent();">Run</button>
If you run the application, you should now a see a button.
Next, add the javascript event handler. Create a xpcom.js file in the same directory as main.xul. Copy and paste the following into the xpcom.js file:
function RunMyComponent() {
try {
// normally Firefox extensions implicitly have XPCOM privileges, but since this is a file we have to request it.
netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
const cid = "@mydomain.com/myappname/mycomponent;1";
obj = Components.classes[cid].createInstance();
// bind the instance we just created to our interface
obj = obj.QueryInterface(Components.interfaces.IMyComponent);
} catch (err) {
alert(err);
return;
}
var res = obj.Add(3, 4);
alert('Performing 3+4. Returned ' + res + '.');
}
You may have to edit the cid if you used different contract id.
Finally, add the script src to the main.xul file. Add the following between the <window> tags:
<script src="xpcom.js"/>
Save the main.xul file and run the application. Click the button. A javascript alert dialog should appear with the results of the add function.
Well that's it. I hope this sheds some light on xpcom development. Feel free to ask questions in the comments.