How to hack extensions to make them compatible with SeaMonkey

Table of Contents

What you will need

Providing an install script (install.js)

To install extensions, SeaMonkey runs an install script written in JavaScript that resides in the root of the XPI package. Most extensions for Firefox and Thunderbird don't provide an install script, but a RDF file and a chrome.manifest file that are interpreted by the application.

We'll make use of a template install.js file to provide an install script for SeaMonkey. Open this file in your plain text editor and the XPI package in your archiver. XPI packages really are nothing more than renamed ZIP archives. Now, let's take a look at the install.js template.

var displayName = "foobar"; // The name displayed to the user (don't include the version) var version = "1.0"; var name = "foo"; // The leafname of the JAR file (without the .jar part)

Pretty straightforward. You can find displayName and version either in the install.rdf file, or on the page you got the extension from. As for name, it'll be the file name of the only JAR file in the XPI package.

//var packageDir = "/" var packageDir = "/" + name + "/" //var skinDir = "/" var skinDir = "/" + name + "/" //var localeDir = "/" var localeDir = "/" + name + "/"

As the comments note, this defines how the extension's directory structure looks. That is, the structure of the JAR file, which, like the XPI package, is nothing more than a renamed ZIP archive, but often with no compression. Open it with your archiver.

Note the paths for each file in the archive. You have to determine for each kind of directory how it looks.

packageDir
content/ or content/name ?
skinDir
skin/ or skin/name ?
localeDir
locale/en-US or locale/en-US/name ?

With name being what you defined name as above, and en-US being any possible locale. Comment out and comment the variables as needed. In case there is no skin directory, use the simple skinDir and give it an empty value (2).

var locales = new Array( "en-US", "fr-FR", "de-DE" ); var skins = new Array( "classic" ); // "modern" var prefs = new Array( "foo.js" );

Insert every locale that comes up in the chrome.manifest file in the locales array. You'll find the locales listed after after "locale" keywords. If if it's an older extension that was made for Firefox/Thunderbird 1.0, there will be no chrome.manifest file, and you'll have to look in the install.rdf file, where they are listed between <em:locale> containers. If there is no locale, like for an extension without a UI, empty the array (3).

The skins array depends on how the skin is stored, and you'll find that information in chrome.manifest after the "skin" keyword, or in install.rdf between <em:skin> containers. Many older extensions store them in a skin/classic directory with sometimes an additional skin/modern directory, and newer extensions store them in skin/. Add modern if the extension has such a skin directory, and give it an empty value (2) if the extension uses skin/.

As for the prefs array, refer to the .js file that is stored in the XPI package with a path of defaults/preferences/. Empty the array (3) if there is no such file.

var components = new Array( );

Some extensions ship with their own components that need to be placed in SeaMonkey's components directory. Usually they are a set of files with the extensions .js and .xpt, have nsNameService as their file name, and have components/ as their path in the XPI package. Place their file names in the array, just like you did for the others, including their extensions.

var searchPlugins = new Array( );

I have yet to encounter this, but I imagine it would consist of two files with the extensions .src and .gif, just like the search plug-ins you can find in SeaMonkey's searchplugins folder. Place their file names with the extensions in the array, just like you do for the components.

Registering the content with content/contents.rdf

SeaMonkey uses this file to register the extension's properties, from the extension's name to its home page, in chrome.rdf. Firefox/Thunderbird uses install.rdf for this purpose. More importantly, contents.rdf contains the overlay references for SeaMonkey and Firefox/Thunderbird 1.0. You might only need to modify this file for SeaMonkey compatibility, but the existing data may be outdated in favor of chrome.manifest if it's a Firefox/Thunderbird 1.5 extension, so be careful.

Just like for the install script, we'll make use of a template. Open it as well as install.rdf with your plain text editor.

<RDF:Seq about="urn:mozilla:package:root"> <RDF:li resource="urn:mozilla:package:foo"/> </RDF:Seq>

This defines which package is being described. Replace foo by the file name of the JAR file.

<RDF:Description about="urn:mozilla:package:foo" chrome:name="foo" chrome:displayName="foobar" chrome:author="bar" chrome:authorURL="http://www.bar.com/" chrome:description="foo foo foo bar" chrome:xpcNativeWrappers="no" chrome:extension="true" chrome:settingsURL="chrome://foo/content/foo_options.xul" chrome:iconURL="chrome://foo/skin/foo.png"> </RDF:Description>

This is where the properties are defined. Do the same for the first line as you did for the previous section. As for the properties, find the equivalents in install.rdf, and copy the values to their matching properties, replacing the firefly ones. Use common sense to find the matching properties, e.g. sometimes the matching value for settingsURL is stored in optionsURL. If no equivalent is found in install.rdf, remove the property, but make sure you don't accidently delete the greater-than sign. Also, don't touch chrome:xpcNativeWrappers (4) and chrome:extension.

<RDF:Seq about="urn:mozilla:overlays"> <RDF:li resource="chrome://browser/content/browser.xul"/> <RDF:li resource="chrome://navigator/content/navigator.xul"/> </RDF:Seq>

Now we're getting to the real meat, the overlays. What's being defined here is which files are being overlayed. browser.xul for Firefox, and navigator.xul for SeaMonkey (5). If you're modifying an existing contents.rdf, add the entry for navigator.xul

<RDF:Seq about="chrome://browser/content/browser.xul"> <RDF:li>chrome://foo/content/fooOverlay.xul</RDF:li> </RDF:Seq> <RDF:Seq about="chrome://navigator/content/navigator.xul"> <RDF:li>chrome://foo/content/fooOverlay.xul</RDF:li> </RDF:Seq>

Defined here are which files will overlay the files from the previous entry. If you're modifying a contents.rdf, you'll have to add the entry for navigator.xul along with its overlays. Get the overlays from chrome.manifest. The format lists the keyword "overlay", then the file that will be overlayed, and finally the file that will overlay it. The last URL is the one we want to copy to our template.

Finally, don't forget to add the file to the archive with the correct path, which will either be content/ or content/name, where name is the file name of the JAR file, depending on the directory structure.

Registering the skin(s) with skin/contents.rdf

This file is used to describe the extension's skin contents for SeaMonkey and Firefox/Thunderbird 1.0. It uses the same format as the contents.rdf file documented above. Generally, it's only used to provide a CSS file, often named overlay.css or name.css, where name is the file name of the JAR file, placed in the skin/ directory. So if there's no CSS file in the skin/ directory, a skin/contents.rdf is not needed.

Get the template and open it and chrome.manifest in your plain text editor.

<RDF:Description about="urn:mozilla:skin:classic/1.0"> <chrome:packages> <RDF:Seq about="urn:mozilla:skin:classic/1.0:packages"> <RDF:li resource="urn:mozilla:skin:classic/1.0:foo"/> </RDF:Seq> </chrome:packages> </RDF:Description>

This defines for which package the skin is being described. Obviously, replace foo by the file name of the JAR file.

If there are no style overlays specified in chrome.manifest or the extension's original skin/contents.rdf, remove or don't add the code that defines the overlays, which is discussed below.

<RDF:Seq about="urn:mozilla:stylesheets"> <RDF:li resource="chrome://navigator/content/navigator.xul"/> <RDF:li resource="chrome://browser/content/browser.xul"/> <RDF:li resource="chrome://global/content/customizeToolbar.xul"/> </RDF:Seq>

The files to be overlayed are defined here. It's just like the previous contents.rdf, with one difference, the customizeToolbar.xul entry. For the curious, this is, like the file name says, for the customisable toolbar feature of the stand-alone applications. Extensions get a button on a toolbar there for easy access. Add a navigator.xul entry if you're only modifying the existing skin/contents.rdf.

<RDF:Seq about="chrome://browser/content/browser.xul"> <RDF:li>chrome://foo/skin/foo.css</RDF:li> </RDF:Seq> <RDF:Seq about="chrome://navigator/content/navigator.xul"> <RDF:li>chrome://foo/skin/foo.css</RDF:li> </RDF:Seq> <RDF:Seq about="chrome://global/content/customizeToolbar.xul"> <RDF:li>chrome://foo/skin/foo.css</RDF:li> </RDF:Seq>

This is just like the previous contents.rdf again. The files that will overlay the previously defined files are defined here. Add a navigator.xul entry if you're only modifying this file. You can find the URL to use in chrome.manifest if it exists. The format lists the keyword "style", then the file to be overlayed, and finally the file that will overlay it. We want to copy the latter to our template. If it doesn't exist, it'll already be defined in this file, so just make sure there is a navigator.xul entry. If a skin for modern is also defined, make another contents.rdf for it.

Finally, don't forget to add the file with the correct path, which is either just skin/, skin/classic or skin/modern. Depending on the directory structure, you might have to place it in a subdirectory with the file name of the JAR file.

Registering the locale(s) with locale/contents.rdf

This file is used to describe a locale for SeaMonkey and Firefox/Thunderbird 1.0. Firefox/Thunderbird 1.5 and up uses chrome.manifest.

Get the template and open it in your plain text editor.

Note: this file doesn't need to exist if the extension doesn't have any locale.

<RDF:li resource="urn:mozilla:locale:en-US:foo"/>

Replace every occurence of en-US with the one you want to make a contents.rdf for, and replace foo in the line above with the file name of the JAR file. Do so for every locale you have defined in the install script. Then place each in their respective locale. Example: depending on the extension's directory structure, you place the contents.rdf for en-US either in locale/en-US or locale/en-US/name, where name is the file name of the JAR file.

Finishing up, and testing

Add the modified JAR file to the XPI package with a path of chrome/. Add the install script if you haven't done so already.

Now the time has come to test your modified extension! Use File > Open to open the XPI package, which will prompt an install dialog. Verify that the extension installs without errors. If you get an error ("Chrome registration failed." *grumble*), see the install.log file in SeaMonkey's application directory to see where it went wrong. For more in-depth details, look at your Error/JavaScript Console. If you don't get an error, verify that the extension is actually installed! You do this by looking at overlays.rdf and chrome.rdf in your profile folder's chrome directory (you did choose to install in the profile folder, did you?). You should see part of the information that was provided in content/contents.rdf in each.

If all is good, restart SeaMonkey, and test if the extension works. One of the most common occurences are that the extension uses <prefwindow> for its settings dialog, which will make it look weird in SeaMonkey. Installing the xSidebar extension adds support for this. Or it's possible that there's no way to reach the extension's settings in SeaMonkey at all! In that case, you'll have to paste that URL, found in content/contents.rdf, in the location bar. You could also use xSidebar or Extension Manager 2.0 to reach it, as it picks up this URL from the same file.

If you have any questions, post them on MozillaZine in the SeaMonkey Support or (preferably) the SeaMonkey General forum, and we'll assist you. Happy hacking!

Notes

(1) - Philip Chee says: Most common mistake: using a ZIP utility that creates an extra top level directory in a ZIP archive but doesn't show it to the user who then says but I created it exactly the same!

(2) - To clarify: remove what's between the quotation marks.

(3) - Note that by emptying the array, I mean that you also have to remove the quotation marks.

(4) - SeaMonkey defaults xpcNativeWrappers to "no", while Firefox/Thunderbird defaults to "yes". Neil says:

It's to do with the different chrome registries. Toolkit rebuilds the extension list every time it starts up, so it turns on wrappers for each extension unless it says not to. All xpfe does is to search the list of extensions for those that want wrappers. There's no "doesn't" search in RDF, and a list of extensions that don't want wrappers has no point, because that's the default anyway.

(toolkit is the codebase that the stand-alone applications use)

(5) - The equivalent of Firefox' browser.xul is SeaMonkey's navigator.xul.