8
Using JavaScript Code Modules in Firefox 4 Add-Ons Using JavaScript Code Modules in Firefox 4 Add-Ons Finnbarr P. Murphy ([email protected]) The concept of a JavaScript code module in the Gecko layout engine was first introduced in Gecko 1.9. This post discusses how such code modules can be used to simplify preference and add-on management in Firefox 4 which uses Gecko 2.0 and JavaScript 1.8.5. It uses a simple Firefox add-on called HTML5toggle as an example of how to modify existing code to use Javascript code modules. A JavaScript code module is simply some JavaScript code located in a registered (well-known) location. JavaScript code modules are primarily used to share code between different privileged scopes. They can also be used to create global JavaScript singletons that previously required using JavaScript XPCOM objects. Here is a list of the current JavaScript code modules in Firefox 4.0: Name Purpose AddonManager.jsm Provides routines to install, manage, and uninstall add-ons. AddonRepository.jsm Allows searching of the add-ons repository. ctypes.jsm Interface that allows JavaScript code to call native libraries without requiring the development of an XPCOM component. DownloadLastDir.jsm Provides the path to the directory into which the last download occurred. Geometry.jsm Provides routines for performing basic geometric operations on points and rectangles. ISO8601DateUtils.jsm Provides routines to convert between JavaScript Date objects and ISO 8601 date strings. NetUtil.jsm Provides networking utility routines, including the ability to easily copy data from an input stream to an output stream asynchronously. openLocationLastURL.jsm Provides access to the last URL opened using the "Open Location" option in the File menu. PerfMeasurement.jsm Provides access to low-level hardware and OS performance measurement tools. PluralForm.jsm Provides an easy way to get the correct plural forms for the current locale, as well as ways to localize to a specific plural rule. PopupNotifications.jsm Provides an easy way to present non-modal notifications to users. Services.jsm Provides getters for conveniently obtaining access to commonly-used services. XPCOMUtils.jsm Contains utilities for JavaScript components loaded by the JavaScript component loader. The Components.utils.import method is used to import a JavaScript code module into a specific JavaScript scope. A module must be imported before any functionality in the module is used. Modules are cached when loaded and subsequent imports do not reload a new version of the module, but instead use the previously cached version. Thus a given module is shared when imported multiple times. For personnal use only 02-25-2011 Copyright 2004-2011 Finnbarr P. Murphy. All rights reserved. 1/8

Using Javascript Code Modules in Firefox Add-ons

Embed Size (px)

DESCRIPTION

Discusses how to use JavaScript code modules in Firefox 4 add-ons

Citation preview

Page 1: Using Javascript Code Modules in Firefox Add-ons

Using JavaScript Code Modules in Firefox 4 Add-Ons

Using JavaScript Code Modules inFirefox 4 Add-Ons

Finnbarr P. Murphy([email protected])

The concept of a JavaScript code module in the Gecko layout engine was first introduced in Gecko1.9. This post discusses how such code modules can be used to simplify preference and add-onmanagement in Firefox 4 which uses Gecko 2.0 and JavaScript 1.8.5. It uses a simple Firefoxadd-on called HTML5toggle as an example of how to modify existing code to use Javascript codemodules.

A JavaScript code module is simply some JavaScript code located in a registered (well-known)location. JavaScript code modules are primarily used to share code between different privilegedscopes. They can also be used to create global JavaScript singletons that previously required usingJavaScript XPCOM objects.

Here is a list of the current JavaScript code modules in Firefox 4.0:

Name PurposeAddonManager.jsm Provides routines to install, manage, and uninstall add-ons.AddonRepository.jsm Allows searching of the add-ons repository.

ctypes.jsm Interface that allows JavaScript code to call native libraries withoutrequiring the development of an XPCOM component.

DownloadLastDir.jsm Provides the path to the directory into which the last downloadoccurred.

Geometry.jsm Provides routines for performing basic geometric operations on pointsand rectangles.

ISO8601DateUtils.jsm Provides routines to convert between JavaScript Date objects and ISO8601 date strings.

NetUtil.jsm Provides networking utility routines, including the ability to easilycopy data from an input stream to an output stream asynchronously.

openLocationLastURL.jsm Provides access to the last URL opened using the "Open Location"option in the File menu.

PerfMeasurement.jsm Provides access to low-level hardware and OS performancemeasurement tools.

PluralForm.jsm Provides an easy way to get the correct plural forms for the currentlocale, as well as ways to localize to a specific plural rule.

PopupNotifications.jsm Provides an easy way to present non-modal notifications to users.

Services.jsm Provides getters for conveniently obtaining access to commonly-usedservices.

XPCOMUtils.jsm Contains utilities for JavaScript components loaded by the JavaScriptcomponent loader.

The Components.utils.import method is used to import a JavaScript code module into a specificJavaScript scope. A module must be imported before any functionality in the module is used.Modules are cached when loaded and subsequent imports do not reload a new version of themodule, but instead use the previously cached version. Thus a given module is shared whenimported multiple times.

For p

erson

nal u

se on

ly

02-25-2011 Copyright 2004-2011 Finnbarr P. Murphy. All rights reserved. 1/8

Page 2: Using Javascript Code Modules in Firefox Add-ons

Using JavaScript Code Modules in Firefox 4 Add-Ons

One of the most commonly used modules is the Services.jsm module which offers a wideassortment of lazy getters that simplify the process of obtaining references to commonly usedservices. A lazy getter is a getter function that checks the item which it is to retrieve (get) andinitializes it if it is not set.

Here is a list of the available services in Services.jsm:

Service Accessor Service Interface Service Nameappinfo nsIXULAppInfo nsIXULRuntime Application information serviceconsole nsIConsoleService Error console servicedirsvc nsIDirectoryService nsIProperties Directory servicedroppedLinkHandler nsIDroppedLinkHandler Dropped link handler serviceio nsIIOService nsIIOService2 I/O Servicelocale nsILocaleService Locale serviceobs nsIObserverService Observer serviceperms nsIPermissionManager Permission manager service

prefs nsIPrefBranch nsIPrefBranch2 nsIPrefService Preferences service

prompt nsIPromptService Prompt servicescriptloader mozIJSSubScriptLoader JavaScript subscript loader servicesearch nsIBrowserSearchService Browser search servicestorage mozIStorageService Storage API servicestrings nsIStringBundleService String bundle servicetm nsIThreadManager Thread Manager servicevc nsIVersionComparator Version comparator servicewm nsIWindowMediator Window mediator serviceww nsIWindowWatcher Window watcher service

For Firefox 4 the add-on manager was extensively reworked. It is now based on a JavaScript codemodule called AddonManager.jsm. See the source code for AddonManagerInternal in…/mozapps/extensions/AddonManager.jsm for full details. Visually it is implemented as a tabinstead of a separate window and uses icons that are 64×64 pixels instead of 32×32 pixels.Add-on metadata is now stored in an SQLite database instead of RDF-based storage.

Here is a list of the available methods in AddonManager.jsm:

METHOD PURPOSEgetInstallForURL Asynchronously gets an AddonInstall for a URLgetInstallForFile Asynchronously gets an AddonInstall for an nsIFilegetAddonByID Asynchronously gets an add-on with a specific IDgetAddonsByIDs Asynchronously gets an array of add-ons

getAddonsWithOperationsByTypes Asynchronously gets add-ons that have operations waiting foran application restart to complete

getAddonsByTypes Asynchronously gets add-ons of specific typesgetAllAddons Asynchronously gets all installed add-ons

getInstallsByTypes Asynchronously gets all current AddonInstalls optionallylimiting to a list of types

getAllInstalls Asynchronously gets all current AddonInstalls

isInstallEnabled Check whether installation is enabled for a particularmimetype

For p

erson

nal u

se on

ly

02-25-2011 Copyright 2004-2011 Finnbarr P. Murphy. All rights reserved. 2/8

Page 3: Using Javascript Code Modules in Firefox Add-ons

Using JavaScript Code Modules in Firefox 4 Add-Ons

METHOD PURPOSE

isInstallAllowed Checks whether a particular source is allowed to installadd-ons of a given mimetype

installAddonsFromWebpage Starts installation of an array of AddonInstalls notifying theregistered web install listener of blocked or started installs

addInstallListener Adds a new InstallListener if the listener is not alreadyregistered

removeInstallListener Removes an InstallListener if the listener is registered

addAddonListener Adds a new AddonListener if the listener is not alreadyregistered

removeAddonListener Removes an AddonListener if the listener is registered

Note that all of the above methods are asynchronous which mean that a callback function isrequired. The callback may well only be called after the method returns.

In earlier versions of Firefox, you have to observe em-action-requested to determine whether anadd-on should be uninstalled or not. For example, the following code could be used in Firefox 3 todetect when to cleanup up the preferences for HTML5toggle during add-on removal.

var uninstall = NULL, observe: function(subject, topic, data) { if (topic == "em-action-requested") { subject.QueryInterface(Components.interfaces.nsIUpdateItem); if (subject.id == MY_EXTENSION_UUID) { if (data == "item-uninstalled") { this.uninstall = true; } else if (data == "item-cancel-action") { this.uninstall = false; } } } if (topic == "quit-application-granted") { if (this.uninstall) { var prefService = Components.classes["@mozilla.org/preferences-service;1"] .getService(Components.interfaces.nsIPrefService); var prefBranch = prefService.getBranch('extensions.html5toggle.'); prefBranch.deleteBranch(""); } this.unregister(); } }, register: function() { var observerService = Components.classes["@mozilla.org/observer-service;1"]. getService(Components.interfaces.nsIObserverService); observerService.addObserver(this, "em-action-requested", false); observerService.addObserver(this, "quit-application-requested", false); observerService.addObserver(this, "quit-application-granted", false); }, unregister : function() { var observerService = Components.classes["@mozilla.org/observer-service;1"]. getService(Components.interfaces.nsIObserverService); observerService.removeObserver(this, "em-action-requested"); observerService.removeObserver(this, "quit-application-granted"); observerService.removeObserver(this, "quit-application-requested"); }

For p

erson

nal u

se on

ly

02-25-2011 Copyright 2004-2011 Finnbarr P. Murphy. All rights reserved. 3/8

Page 4: Using Javascript Code Modules in Firefox Add-ons

Using JavaScript Code Modules in Firefox 4 Add-Ons

This code no longer works in Firefox 4. Instead, an add-on needs to handle two events,onUninstalling and onOperationCancelled, as shown in below.

Here is the source code for overlay.js prior to being upgraded to using JavaScript code modules. Itis from HTML5toggle v1.02.

const MY_EXTENSION_UUID = "{16f32596-71ca-4053-a4f3-cfc8b157f541}";var html5toggle = { beingUninstalled: null, onLoad: function() { var prefs = Components.classes["@mozilla.org/preferences-service;1"] .getService(Components.interfaces.nsIPrefService).getBranch("html5."); var value = prefs.getBoolPref("enable"); // save the current value of html5.enable preference in our own branch var myprefs = Components.classes["@mozilla.org/preferences-service;1"] .getService(Components.interfaces.nsIPrefService); var mybranch = myprefs.getBranch('extensions.html5toggle.'); mybranch.setBoolPref('enable', value); myprefs.savePrefFile(null); this.register(); this.setTBbutton(value); this.setMIchecked(value); }, setMIchecked: function (value) { var menuitem = document.getElementById('html5toggle-menuitem'); if (value) { menuitem.setAttribute("checked", "false"); } else { menuitem.setAttribute("checked", "true"); } }, setTBbutton: function (value) { var tbButton = document.getElementById('html5toggle-toolbar-button'); var stringsBundle = document.getElementById('html5toggle-string-bundle'); if (tbButton) { // button might still be on Toolbar Palette if (value) { tbButton.tooltipText = stringsBundle.getString('onString'); tbButton.setAttribute("image","chrome://html5toggle/skin/images/HTML5on16N.png"); } else { tbButton.tooltipText = stringsBundle.getString('offString'); tbButton.setAttribute("image","chrome://html5toggle/skin/images/HTML5off16N.png"); } } }, onToolbarButton: function(e) { var prefs = Components.classes["@mozilla.org/preferences-service;1"] .getService(Components.interfaces.nsIPrefService).getBranch("html5."); var value = prefs.getBoolPref("enable"); value = !value; prefs.setBoolPref("enable", value); this.setTBbutton(value); this.setMIchecked(value); }, onMenuItem: function(e) { var prefs = Components.classes["@mozilla.org/preferences-service;1"] .getService(Components.interfaces.nsIPrefService).getBranch("html5."); var value = prefs.getBoolPref("enable"); var menuitem = document.getElementById('html5toggle-menuitem'); value = menuitem.getAttribute("checked") == "true" ? false : true; prefs.setBoolPref("enable", value); this.setTBbutton(value); }, observe: function(subject, topic, data)

For p

erson

nal u

se on

ly

02-25-2011 Copyright 2004-2011 Finnbarr P. Murphy. All rights reserved. 4/8

Page 5: Using Javascript Code Modules in Firefox Add-ons

Using JavaScript Code Modules in Firefox 4 Add-Ons

{ if (topic == "quit-application-granted") { dump("\nhtml5toggle:quit-application-granted ...."); if (this.beingUninstalled) { // restore initial value of html5.enable preference var myprefs = Components.classes["@mozilla.org/preferences-service;1"] .getService(Components.interfaces.nsIPrefService); var mybranch = myprefs.getBranch('extensions.html5toggle.'); var value = mybranch.getBoolPref("enable"); mybranch.deleteBranch(""); var prefs = Components.classes["@mozilla.org/preferences-service;1"] .getService(Components.interfaces.nsIPrefService); var branch = prefs.getBranch('html5.'); branch.setBoolPref('enable', value); prefs.savePrefFile(null); } this.unregister(); } }, onUninstalling: function(addon) { if (addon.id == MY_EXTENSION_UUID) { this.beingUninstalled = true; } }, onOperationCancelled: function(addon) { if (addon.id == MY_EXTENSION_UUID) { this.beingUninstalled = (addon.pendingOperations & AddonManager.PENDING_UNINSTALL) != 0; } }, register: function() { dump("\nhtml5toggle:register ...."); var observerService = Components.classes["@mozilla.org/observer-service;1"]. getService(Components.interfaces.nsIObserverService); observerService.addObserver(this, "quit-application-granted", false); Components.utils.import("resource://gre/modules/AddonManager.jsm"); AddonManager.addAddonListener(this); }, unregister: function() { dump("\nhtml5toggle:unregister ...."); var observerService = Components.classes["@mozilla.org/observer-service;1"]. getService(Components.interfaces.nsIObserverService); observerService.removeObserver(this, "quit-application-granted"); AddonManager.removeAddonListener(this); }};window.addEventListener("load", function () { html5toggle.onLoad(); }, false);

Here is the same source file after being modified to use JavaScript code modules. It is fromHTML5toggle v1.03.

const MY_EXTENSION_UUID = "{16f32596-71ca-4053-a4f3-cfc8b157f541}";const PREF_BRANCH_HTML5 = Services.prefs.getBranch("html5.");const PREF_BRANCH_HTML5TOGGLE = Services.prefs.getBranch("extensions.html5toggle.");const OBSERVER = Services.obs;const ADDONMANAGER = AddonManager;var html5toggle = { beingUninstalled: null, onLoad: function() { let value = PREF_BRANCH_HTML5.getBoolPref("enable"); // save the current value of html5.enable preference in our own branch PREF_BRANCH_HTML5TOGGLE.setBoolPref('enable', value); Services.prefs.savePrefFile(null);

For p

erson

nal u

se on

ly

02-25-2011 Copyright 2004-2011 Finnbarr P. Murphy. All rights reserved. 5/8

Page 6: Using Javascript Code Modules in Firefox Add-ons

Using JavaScript Code Modules in Firefox 4 Add-Ons

this.register(); this.setTBbutton(value); this.setMIchecked(value); }, setMIchecked: function (value) { let menuitem = document.getElementById('html5toggle-menuitem'); if (value) { menuitem.setAttribute("checked", "false"); } else { menuitem.setAttribute("checked", "true"); } }, setTBbutton: function (value) { let tbButton = document.getElementById('html5toggle-toolbar-button'); let stringsBundle = document.getElementById('html5toggle-string-bundle'); if (tbButton) { // button might still be on Toolbar Palette if (value) { tbButton.tooltipText = stringsBundle.getString('onString'); tbButton.setAttribute("image","chrome://html5toggle/skin/images/HTML5on16N.png"); } else { tbButton.tooltipText = stringsBundle.getString('offString'); tbButton.setAttribute("image","chrome://html5toggle/skin/images/HTML5off16N.png"); } } }, onToolbarButton: function(e) { let value = PREF_BRANCH_HTML5.getBoolPref("enable"); PREF_BRANCH_HTML5.setBoolPref("enable", !value); this.setTBbutton(value); this.setMIchecked(value); }, onMenuItem: function(e) { let menuitem = document.getElementById('html5toggle-menuitem'); let value = menuitem.getAttribute("checked") == "true" ? false : true; PREF_BRANCH_HTML5.setBoolPref("enable", value); this.setTBbutton(value); }, observe: function(subject, topic, data) { if (topic == "quit-application-granted") { dump("\nhtml5toggle:quit-application-granted ...."); if (this.beingUninstalled) { // restore initial value of html5.enable preference let value = PREF_BRANCH_HTML5TOGGLE.getBoolPref("enable"); PREF_BRANCH_HTML5TOGGLE.deleteBranch(""); PREF_BRANCH_HTML5.setBoolPref('enable', value); Service.prefs.savePrefFile(null); } this.unregister(); } }, onUninstalling: function(addon) { if (addon.id == MY_EXTENSION_UUID) { this.beingUninstalled = true; } }, onOperationCancelled: function(addon) { if (addon.id == MY_EXTENSION_UUID) { this.beingUninstalled = (addon.pendingOperations & AddonManager.PENDING_UNINSTALL) != 0; } }, register: function() { dump("\nhtml5toggle:register ...."); OBSERVER.addObserver(this, "quit-application-granted", false);

For p

erson

nal u

se on

ly

02-25-2011 Copyright 2004-2011 Finnbarr P. Murphy. All rights reserved. 6/8

Page 7: Using Javascript Code Modules in Firefox Add-ons

Using JavaScript Code Modules in Firefox 4 Add-Ons

ADDONMANAGER.addAddonListener(this); }, unregister: function() { dump("\nhtml5toggle:unregister ...."); OBSERVER.removeObserver(this, "quit-application-granted"); ADDONMANAGER.removeAddonListener(this); }};Components.utils.import("resource://gre/modules/Services.jsm");Components.utils.import("resource://gre/modules/AddonManager.jsm");window.addEventListener("load", function () { html5toggle.onLoad(); }, false);

As you can see if you study the above source code, using JavaScript code modules reduces thenumber of lines of code and makes the source code more readable. I assume you are familiar withJavaScript in particular and Mozilla add-ons in general if you are reading this post so I am notgoing to try and explain the above code in any detail.

You should also have noticed that I updated the source code to use the JavaScript let keywordinstead of var where possible. This keyword was introduced in JavaScript 1.7 which is a Mozillaonly extension. Variables declared by let have local scope, i.e. their scope (visibility) is the block inwhich they are defined as well as in any sub-blocks in which they are not redefined. Contrast thatwith variables declared by var which have as their scope the entire enclosing function.

Consider the following short JavaScript example:

var i = 0;for ( let i = i; i < 2 ; i++ ) print ( "> " + i );print ( i );

If you used the var keyword in the for statement, the variable would be visible within the wholefunction containing the loop. To reduce the visibility of the variable to just the scope of the forloop, we use the let keyword. Using the 0> 1> 20

There is an enormous amount of useful functionality in the current set of JavaScript code modules.You need to read the appropriate documentation for each code module to gain an understandingof what is available in each module. Unfortunately the documentation is written in the usual tersestyle adopted by the Mozilla developers and use case examples are scarce.

For example, if you examine the Services.jsm documentation page, you will notice a promptservice accessor. This provides access to a number of useful methods including alert which showsan alert dialog with an OK button. It works the same way as window.alert but accepts a title forthe dialog.

Components.utils.import("resource://gre/modules/Services.jsm");....Services.prompt.alert(null, "My blog", "Hello there, dear reader of my blog");

The current set of JavaScript code modules does not include string localization. I would like to seesomebody develop a good JavaScript code module for string localization as that would simplify the

For p

erson

nal u

se on

ly

02-25-2011 Copyright 2004-2011 Finnbarr P. Murphy. All rights reserved. 7/8

Page 8: Using Javascript Code Modules in Firefox Add-ons

Using JavaScript Code Modules in Firefox 4 Add-Ons

coding of restartless add-ons.

Well, that is all that your should need to know to get you started on updating your Firefox add-onsto take advantage of JavaScript code modules. One thing to watch out for with JavaScript codemodules is that you do not break backwards compatibility if your add-on is expected to work withboth Firefox 3 and Firefox 4.Good luck!

P.S. The full source code for HTML5toggle versions 1.02 and 1.03 is available on my website.

For p

erson

nal u

se on

ly

02-25-2011 Copyright 2004-2011 Finnbarr P. Murphy. All rights reserved. 8/8