02 February 2008

Creating a XUL extension for Mozilla/Firefox: my notebook.

(RSS readers, this file is better displayed on my blog)
Here is my notebook on how to create an extension for firefox. The following example was tested with firefox 2.0.0.11. This extension is used to insert a few default templates (such as Template:Infobox_scientist ) when editing a biography on Wikipedia. Infoboxes are used , for example by DBPedia, to create a structured version of wikipedia.

First, create a new profile for firefox, say TEST by invoking firefox with option '-P'

firefox -P

Set up your extension development environment as described here.

I'm now working in the directory ~/XUL:

Create the file ./install.rdf. It's a RDF file describing your extension:
<?xml version="1.0" encoding="UTF-8"?>
<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:em="http://www.mozilla.org/2004/em-rdf#">

<rdf:Description about="urn:mozilla:install-manifest">
<!-- my extension ID -->
<em:id>biography-helper@plindenbaum.com</em:id>
<!-- version -->
<em:version>2.0</em:version>
<!-- this is a firefox extension -->
<em:type>2</em:type>

<em:targetApplication>
<rdf:Description>
<!-- this is for firefox -->
<em:id>{ec8030f7-c20a-464f-9b0e-13a3a9e97384}</em:id>
<!-- min/max firefox version -->
<em:minVersion>2.0</em:minVersion>
<em:maxVersion>2.0.0.*</em:maxVersion>
</rdf:Description>
</em:targetApplication>

<!-- name -->
<em:name>Wikipedia Edit Helper!</em:name>
<!-- description -->
<em:description>An Extension for Editing biographies in Wikipedia</em:description>
<!-- author -->
<em:creator>Pierre Lindenbaum</em:creator>
<!-- contact -->
<em:homepageURL>http://plindenbaum.blogspot.com</em:homepageURL>
<!-- icon -->
<em:iconURL>chrome://wiki4biography/skin/darwin32.png</em:iconURL>
</rdf:Description>
</rdf:RDF>


The file ./chrome/content/menu.xul is the XUL interface which will be added to the contextual popup-menu.

<?xml version="1.0" encoding="UTF-8"?>
<overlay id="wiki4biography" xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
<script src="library.js"/>

<popup id="contentAreaContextMenu">
<menuseparator/>
<menu label="Wikipedia" id="menuWikipedia">
<menupopup>

<menuitem label="Infobox Scientist" oncommand="MY.infobox()" />

<menu label="Categories">
<menupopup>
<menuitem label="Astronomers" oncommand="MY.category('Astronomers')"/>
<menuitem label="Biologists" oncommand="MY.category('Biologists')"/>
<menuitem label="Chemists" oncommand="MY.category('Chemists')"/>
<menuitem label="Physicists" oncommand="MY.category('Physicists')"/>
</menupopup>
</menu>

<menu label="Stubs">
<menupopup>
<menuitem label="Astronomer" oncommand="MY.insertTemplate('{{astronomer-stub}}')"/>
<menuitem label="Chemist" oncommand="MY.insertTemplate('{{chemist-stub}}')"/>
<menuitem label="Biologist" oncommand="MY.insertTemplate('{{biologist-stub}}')"/>
<menuitem label="Mathematician" oncommand="MY.insertTemplate('{{mathematician-stub}}')"/>
<menuitem label="Physicist" oncommand="MY.insertTemplate('{{physicist-stub}}')"/>
</menupopup>
</menu>

</menupopup>
</menu>

</popup>
</overlay>



The script used by our menu is ./chrome/content/library.js
var MY={
/** when the xul page is loaded, register for events from the contextual popupmenu */
onload:function()
{
var element = document.getElementById("contentAreaContextMenu");
element.addEventListener("popupshowing",function(evt){MY.preparePopup(evt);},true);
},
/* prepare the contextual menu just before it is showing on screen: hide or show our menu */
preparePopup:function(evt)
{
var element = document.getElementById("menuWikipedia");
if(document.popupNode.id!="wpTextbox1")
{
element.hidden=true;
return;
}
element.hidden=false;
},
/** insert a text at the caret position in the textarea of wikipedia */
insertTemplate:function(text)
{
var area= content.document.getElementById("wpTextbox1");
if(area==null) return;
//alert(area.value.substring(0,20)+" "+area.tagName);
var selstart=area.selectionStart;
var x= area.scrollLeft;
var y= area.scrollTop;
area.value= area.value.substring(0,selstart)+
text+
area.value.substring(area.selectionEnd)
;
area.scrollLeft=x;
area.scrollTop=y;
selstart+=text.length;
area.setSelectionRange(selstart,selstart);
},
/* insert a wikipedia category */
category:function(text)
{
MY.insertTemplate("[[Category:"+text+"]]");
},
/** get current article name */
article:function()
{
var url=""+content.document.location;
var i=url.indexOf("title=",0);
if(i==-1) return "";
i+=6;
var j=url.indexOf("&action",i);
if(j==-1) return "";
return unescape(url.substr(i,j-i).replace("_"," "));
},
/* insert an infobox */
infobox:function()
{
var box="{{Infobox Scientist\n"+
"|name = "+MY.article()+"\n"+
"|box_width =\n"+
"|image = No_free_image_man_%28en%29.svg\n"+ /** sorry, most scientists in wikipedia are men */
"|image_width = 200px\n"+
"|caption = "+MY.article()+"\n"+
"|birth_date = \n"+
"|birth_place = \n"+
"|death_date = \n"+
"|death_place = \n"+
"|residence = \n"+
"|citizenship = \n"+
"|nationality = \n"+
"|ethnicity = \n"+
"|field = \n"+
"|work_institutions = \n"+
"|alma_mater = \n"+
"|doctoral_advisor = \n"+
"|doctoral_students = \n"+
"|known_for = \n"+
"|author_abbrev_bot = \n"+
"|author_abbrev_zoo = \n"+
"|influences = \n"+
"|influenced = \n"+
"|prizes = \n"+
"|footnotes = \n"+
"|signature =\n"+
"}}\n";
MY.insertTemplate(box);
}
};
/* initialize all this stuff */
window.addEventListener("load",MY.onload, false);


The icon ./chrome/skin/darwin32.png is used as an icon for the extension.

The file ./chrome.manifest says what firefox packages and overlays this extension provides.
content wiki4biography chrome/content/
overlay chrome://browser/content/browser.xul chrome://wiki4biography/content/menu.xul
skin wiki4biography classic/1.0 chrome/skin/


To test this extension a file ${HOME}/.mozilla/firefox/testmozilla/extensions/biography-helper@plindenbaum.com is created. This file contains the path to the XUL folder.
/home/pierre/tmp/XUL/

You can test the extension by invoking firefox with the profile "TEST":
firefox -no-remote -P TEST


When your extension is ready you can package it into a *.xpi archive.
zip -r wikipedia.zip chrome chrome.manifest install.rdf
mv wikipedia.zip wikipedia.xpi


That's it. You can download this extension at http://lindenb.integragen.org/xul/wikipedia.xpi and then open it with firefox which will prompt you if you want to install this extension. Then, edit an article in wikipedia and click the left button to get the new contextual menu.


Pierre

3 comments:

alf said...

Yep, that seems like a good overview. I wrote something similar recently.

Anonymous said...

Thanks for writing it. I'm thinking about writing my own extension for a while. This would be a help indeed.

Leon Victor said...

XUL is an XML grammar that provides user interface widgets like buttons, menus, toolbars, trees, etc. User actions are bound to functionality using JavaScript.