Parts of this page show the use of the XPInstall API. The majority of this API is now deprecated and as of Gecko 1.9 no longer available. Extension, Theme, and plug-in developers must switch away from install.js based packages to the new packaging scheme with an install.rdf manifest. In particular plugin developers should see how to package a plugin as an extension.

To make your extension install in Mozilla Suite, SeaMonkey, Mozilla Firefox pre-0.9 and Mozilla Thunderbird pre-0.7, you need to have an install.js file in the root of your XPI file. If you want your extension to install in Firefox 0.9 and later and/or Thunderbird 0.7 and later only, you don't need install.js, you need an install.rdf instead.

See also this article from XulPlanet's Main Tutorial.


There is a different sample install.js template located at http://downloads.mozdev.org/xsidebar/mods/. This version has been developed as part of the effort to port Firefox extensions to SeaMonkey from the xSidebar Project.

Pros: This version consolidates all the changable information at the top of the file. And it also knows how to install packaged components, search engines and default preferences.

Cons: Code is bloated since it has to cater for several different ways an extension author can package the .JAR file contained in the .XPI file.


XPI file structure

You can use the code below (taken from Pike's extensions ). It assumes the following structure of your XPI file:

sampleext.xpi
  chrome\
    sampleext.jar
      content\
        sampleext\
      locale\  (optional)
        en-US\
          sampleext\
        ...
      skin\    (optional)
        classic\
          sampleext\
        ...
  install.js
  install.rdf (optional -- see above)

Code

// install.js
// XpiInstaller
// By Pike (Heavily inspired by code from Henrik Gemal and Stephen Clavering)

var XpiInstaller = {
	// --- Editable items begin ---
	extFullName: 'Sample Extension', // The name displayed to the user (don't include the version)
	extShortName: 'sampleext', // The leafname of the JAR file (without the .jar part)
	extVersion: '1.0',
	extAuthor: 'Insert-your-name-here',
	extLocaleNames: null, // e.g. ['en-US', 'en-GB']
	extSkinNames: null, // e.g. ['classic', 'modern']
	extPostInstallMessage: null, // Set to null for no post-install message
	// --- Editable items end ---
	
	profileInstall: true,
	silentInstall: false,
	
	install: function()
	{
		var jarName = this.extShortName + '.jar';
		var profileDir = Install.getFolder('Profile', 'chrome');
		
		// Parse HTTP arguments
		this.parseArguments();
		
		// Check if extension is already installed in profile
		if (File.exists(Install.getFolder(profileDir, jarName)))
		{
			if (!this.silentInstall)
			{
				Install.alert('Updating existing Profile install of ' + this.extFullName + ' to version ' + this.extVersion + '.');
			}
			this.profileInstall = true;
		}
		else if (!this.silentInstall)
		{
			// Ask user for install location, profile or browser dir?
			this.profileInstall = Install.confirm('Install ' + this.extFullName + ' ' + this.extVersion + ' to your Profile directory (OK) or your Browser directory (Cancel)?');
		}
		
		// Init install
		var dispName = this.extFullName + ' ' + this.extVersion;
		var regName = '/' + this.extAuthor + '/' + this.extShortName;
		Install.initInstall(dispName, regName, this.extVersion);
		
		// Find directory to install into
		var installPath;
		if (this.profileInstall) installPath = profileDir;
		else installPath = Install.getFolder('chrome');
		
		// Add JAR file
		Install.addFile(null, 'chrome/' + jarName, installPath, null);
		
		// Register chrome
		var jarPath = Install.getFolder(installPath, jarName);
		var installType = this.profileInstall ? Install.PROFILE_CHROME : Install.DELAYED_CHROME;
		
		// Register content
		Install.registerChrome(Install.CONTENT | installType, jarPath, 'content/' + this.extShortName + '/');
		
		// Register locales
		for (var locale in this.extLocaleNames)
		{
			var regPath = 'locale/' + this.extLocaleNames[locale] + '/' + this.extShortName + '/';
			Install.registerChrome(Install.LOCALE | installType, jarPath, regPath);
		}
		
		// Register skins
		for (var skin in this.extSkinNames)
		{
			var regPath = 'skin/' + this.extSkinNames[skin] + '/' + this.extShortName + '/';
			Install.registerChrome(Install.SKIN | installType, jarPath, regPath);
		}
		
		// Perform install
		var err = Install.performInstall();
		if (err == Install.SUCCESS || err == Install.REBOOT_NEEDED)
		{
			if (!this.silentInstall && this.extPostInstallMessage)
			{
				Install.alert(this.extPostInstallMessage);
			}
		}
		else
		{
			this.handleError(err);
			return;
		}
	},
	
	parseArguments: function()
	{
		// Can't use string handling in install, so use if statement instead
		var args = Install.arguments;
		if (args == 'p=0')
		{
			this.profileInstall = false;
			this.silentInstall = true;
		}
		else if (args == 'p=1')
		{
			this.profileInstall = true;
			this.silentInstall = true;
		}
	},
	
	handleError: function(err)
	{
		if (!this.silentInstall)
		{
			Install.alert('Error: Could not install ' + this.extFullName + ' ' + this.extVersion + ' (Error code: ' + err + ')');
		}
		Install.cancelInstall(err);
	}
};

XpiInstaller.install();

Document Tags and Contributors

 Contributors to this page: teoli, ethertank, Mossop, Sheppy, Philip Chee, ericjung
 Last updated by: ethertank,