Part of future success I see for Se-IDE is the ability for individuals, or more likely companies, to create plugins for it. Witness Firebug as the model of this. To this end I’ve been trying to champion a bit of an internal restructuring of Se-IDE to enable the great pluginification which would be large enough a change to warrant a version bump to 2.0.

While certainly the internals of Se-IDE need a bit of work (in general), it ends up that Firefox plugins are done through ‘overlays’ which means anything can extend anything else if you know where to look for things.

So after some gentle nudging from Jérémy Hérault and the Getting started with extension development page I went looking and have started an Se-IDE plugin called ‘Preflight Checks’ which I’ll be writing up about as I figure it out.

What is Preflight Checks? Well, I’m not quite sure, but at the very least it will be able to do the following things (it is afterall, a demo / learning exercise):

  • Have user-controlled preferences
  • Add custom methods that can be accessed from Se-IDE
  • Add custom locators
  • Add custom formatter
  • Add buttons to the main interface
  • Do ‘magic’ behind the scenes

This post covers the first item, ‘have user-controlled preferences’ along with how to build and develop it.

First, what you need is an ‘install.rdf’ file and is located in the root dir of your workspace. This blob of xml tells firefox all about your plugin. If you forget it, or you bundle the .xpin incorrectly (more on this later) then it won’t even know that it is installed. It is pretty obvious what each bit does, but I’ve annotated it anyways.

<?xml version="1.0" encoding="UTF-8"??><rdf xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:em="http://www.mozilla.org/2004/em-rdf#"><description about="urn:mozilla:install-manifest"><id>preflight@adam.goucher</id><name>preflight</name><version>1.0</version><creator>Adam Goucher</creator><description>A sample plugin for a Selenium-IDE API</description><type>2</type><optionsurl>chrome://preflight/content/view/options.xul</optionsurl><targetapplication><description><id>{ec8030f7-c20a-464f-9b0e-13a3a9e97384}</id><minversion>1.5</minversion><maxversion>3.5.*</maxversion></description></targetapplication><requires><description><id>{a6fd85ed-e919-4a43-a5af-8da18bda539f}</id><minversion>1.0b2</minversion><maxversion>1.*</maxversion></description></requires></description></rdf>

The other bit of boilerplate you need is the ‘chrome.manifest’ which is also in the root. It is just a flat file which manages resources within your plugin and lets firefox know which to overlay what.

content preflight  chrome/content/

locale  preflight  en-US chrome/locale/en-US/

overlay chrome://selenium-ide/content/selenium-ide-common.xul chrome://preflight/content/view/optionsOverlay.xul

From here you can start to see what we are going to do; we are going to overlay our content/view/optionsOverlay.xul file on top of selenium-ide-common.xul. This is what optionsOverlay.xul looks like. (Oh, and if you haven’t figured it out yet, there is a lot of xml involved in creating a firefox plugin. Sorry.)

<?xml version="1.0"??><?xml-stylesheet href="chrome://global/skin/" type="text/css"??><overlay id="selenium_overlay" xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"><menupopup id="options-popup"><menuitem accesskey="P" label="Preflight Options..." oncommand="window.open('chrome://preflight/content/view/options.xul','Preflight Options','chrome=yes,,centerscreen=yes');"></menuitem></menupopup></overlay>

What this says is to find the block of xml <menupopup id=”options-popup”> in selenium-ide-common.xul and add a menuitem.

And when this menuitem is accessed, then open a window described in content/view/options.xul. options.xul holds the guts of what this first part does.

<?xml version="1.0"??><?xml-stylesheet href="chrome://global/skin/" type="text/css"??><prefwindow height="200" id="preflight-prefs" title="Preflight Settings" width="520" xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"><prefpane id="preflight-panel" label="Preflight Settings"><preferences><preference id="pref_perform" name="extensions.selenium-ide.preflight.performpreflight" type="bool"></preference></preferences><tabbox><tabs><tab label="General"></tab></tabs><tabpanels flex="1"><tabpanel><vbox flex="1"><hbox align="center"><label control="name" value="&performpreflight;"></label>
              <checkbox id="perform" preference="pref_perform"></checkbox></hbox><spacer height="100"></spacer></vbox></tabpanel></tabpanels></tabbox></prefpane></prefwindow>

This creates a new prefwindow, and inside that prefpane there are a couple things of interest. First is that we are going to be modifying a single preference called extensions.selenium-ide.preflight.performpreflight and it is a bool (true/false). In the actual shown-to-the-user part a checkbox is created that will reflect the state of the preference.

There is a lot of magic going on behind the scenes because it is a ‘prefpane’. I suspect later installments will need to do that magic by hand.

But what if the user never opens your option panel? How does the plugin get its options from? From a specially named file whose sole mission is to set defaults. The file is a JS file ind defaults/preferences whose name is the same as the em:name you specified in the install.rdf file. Here is what preflight.js has:

pref("extensions.selenium-ide.preflight.performpreflight",true);

Pretty simple. If there was more than one preference being modified than there would be more than one line.

Notice the name of the preference. If we all just use extentions.selenium-ide.<plugin name>.<option name> then I think things would be nicely organized.

One last thing before we worry about bundling things up. See the &performpreflight; in options.xul? That is part of the localization framework that comes with firefox plugins. I’ll figure this all out later, but for now, you need a file called locale/en-US/options.dtd with the following line in it.

Thats it. Your workspace should now look something like this. Just ignore the skin dir for now. That will come later.

Now to build it.

Firefox plugins get distributed as .xpi files which are really just zip archives of a bunch files and dirs. Here are two build files; one for windows and one for unix.

Windows:

@echo off

setlocal

set APP_NAME="preflight"
set CHROME_PROVIDERS="content"

set ROOT_DIR=%CD%
set TMP_DIR="build"

rem remove any left-over files from previous build
del /Q %APP_NAME%.xpi
del /S /Q %TMP_DIR%

mkdir %TMP_DIR%\chrome\content

robocopy content %TMP_DIR%\chrome\content /E
robocopy locale %TMP_DIR%\chrome\locale /E
robocopy skin %TMP_DIR%\chrome\skin /E
robocopy defaults %TMP_DIR%\defaults /E
copy install.rdf %TMP_DIR%
copy chrome.manifest %TMP_DIR%

rem generate the XPI file
cd %TMP_DIR%
echo "Generating %APP_NAME%.xpi..."

"c:\program files\7-zip\7z.exe" a -r -y -tzip ../%APP_NAME%.zip *

cd %ROOT_DIR%
rename %APP_NAME%.zip %APP_NAME%.xpi

endlocal

Unix

#!/bin/bash

APP_NAME=preflight          # short-name, jar and xpi files name. Must be lowercase with no spaces
CHROME_PROVIDERS="content"  # which chrome providers we have (space-separated list)
ROOT_DIRS="defaults"         # ...and these directories       (space separated list)

ROOT_DIR=`pwd`
TMP_DIR=build

# remove any left-over files from previous build
rm -f $APP_NAME.jar $APP_NAME.xpi
rm -rf $TMP_DIR

mkdir -p $TMP_DIR/chrome/content

cp -v -R content $TMP_DIR/chrome
cp -v -R locale $TMP_DIR/chrome
cp -v -R skin $TMP_DIR/chrome
cp -v -R defaults $TMP_DIR
cp -v install.rdf $TMP_DIR
cp -v chrome.manifest $TMP_DIR

# generate the XPI file
cd $TMP_DIR
echo "Generating $APP_NAME.xpi..."
zip -r ../$APP_NAME.xpi *

cd "$ROOT_DIR"

And by typing ‘build’ you should end up with a .xpi file which Firefox will merrily install that lets you change the value of a preference.

A quick not on debugging extensions. It is a pain in the butt to have to constantly uninstall then reinstall a plugin. See Firefox extention proxy file on how to load things from the local filesystem.