Implementation

This page describes how I implemented Text2Link in JavaScript and XUL. All Mozilla add-ons follow a standard design pattern from implementation through to packaging for installation, so you can use this page as a tutorial for writing your own Mozilla add-ons.

1. Overview

Modern web design practise separates web page development into three components: the semantic content of the page is marked up in HTML, the presentation is specified in CSS, and the behaviour is coded in JavaScript. Firefox extensions follow a similar pattern: the user interface is specified in XUL, the localisation text is recorded in a DTD, and the behaviour is coded in JavaScript. The Text2Link Firefox extension is implemented by three files:

  • text2link.xul specifies the user interface components, which are the menu options added to Firefox’s context menu;
  • text2link.dtd records the text of the user interface; and
  • text2link.js defines the JavaScript functions that extend Firefox by implementing the behaviour of the Text2Link extension.

To enable Firefox to recognise and install an extension, every Firefox extension is packaged with two other files:

  • install.rdf records information about the extension, including with which versions of Firefox the extension is compatible; and
  • chrome.manifest specifies how the user interface file (text2link.xul), localisation file (text2link.dtd) and behaviour file (text2link.js) link together.

2. User Interface

Text2Link has a simple user interface composed of menu options added to Firefox’s context menu when the current selection contains a URL or an email address. Firefox’s user interface is specified in XUL (pronounced zool), the XML language in which Firefox extensions must also be specified. The text2link.xul file begins with a DOCTYPE directive that specifies that the text2link.dtd file contains the text and access key for each context menu option:

<!DOCTYPE overlay [
   <!ENTITY % t2l_DTD SYSTEM "chrome://text2link/locale/text2link.dtd">
   %t2l_DTD;
]>

The text and access keys are placed in a separate file to ease localisation, which is described below.

The user interface of a Firefox extension is implemented as a XUL overlay, which adds user interface components to Firefox’s user interface. The t2l_Overlay overlay adds the Text2Link user interface to Firefox:

<overlay id="t2l_Overlay"
   xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">

The <script> tag specifies that the text2link.js JavaScript file implements the behaviour of the Text2Link extension:

<script type="application/x-javascript"
   src="chrome://text2link/content/text2link.js" />

The <popup> tag specifies the menu options that Text2Link adds to Firefox’s context menu when the current selection contains a URL or an email address:

<popup id="contentAreaContextMenu">

The id attribute of the <popup> tag, contentAreaContextMenu, is the DOM ID Firefox assigns to its context menu.

Each <menuitem> tag specifies one context menu option. For example, the following tag describes the Open context menu option, which opens a URL in the current window:

<menuitem id="t2l-context-open"
   insertbefore="context-undo"
   label="&t2l.context.open.label;"
   accesskey="&t2l.context.open.accesskey;"
   class="menuitem-iconic"
   oncommand="t2l_Main.open('current');" />

The id attribute of a <menuitem> tag specifies the DOM ID of the menu option. This unique ID enables Text2Link to access the properties of the menu option, such as whether the option should be displayed when the context menu is invoked. The t2l- prefix identifies the context menu item as a Text2Link object and helps to make the ID unique:

<menuitem id="t2l-context-open"

The insertbefore attribute specifies the ID of the DOM element before which the context menu item should be inserted. context-undo is the unique DOM ID Firefox assigns to the Undo item in its context menu:

insertbefore="context-undo"

The label attribute specifies the DTD entity that supplies the text of the label of the context menu item:

label="&t2l.context.open.label;"

For example, the entity reference &t2l.context.open.label; means that the entity t2l.context.open.label in the text2link.dtd file defines the text of the label, which is Open URL as shown here in an extract from text2link.dtd:

<!ENTITY t2l.context.open.label "Open URL">

The t2l. prefix identifies each entity with Text2Link and helps to make each entity name unique.

The accesskey attribute specifies the DTD entity that supplies the context-menu-option’s access key, which is the key that selects the menu option:

accesskey="&t2l.context.open.accesskey;"

The menuitem-iconic value of the class attribute specifies that Firefox should give the context menu option a left margin wide enough to display an icon that represents the extension. Although Text2Link does not have an icon, specifying an icon margin ensures that the Text2Link content menu options line up with the other options in the context menu that do have an icon:

class="menuitem-iconic"

The oncommand attribute specifies the JavaScript function to call when the menu option is selected by the mouse or by the keyboard. The functions are defined in the text2link.js file specified in the <script> tag at the beginning of the <overlay> tag:

oncommand="t2l_Main.open('current');" />

For example, when the user selects the Open URL context menu option, Firefox calls the t2l_Main.open function with the current selection as its parameter, which tells Text2Link to open the selected URL in the current window.

The <menuseparator> tag adds a horizontal line below the Text2Link context menu options to group them together and to separate them from the other options in the context menu:

<menuseparator id="t2l-context-separator" insertbefore="context-undo" />

The context-menu options specified in the text2link.xul file are added to Firefox’s context menu by Text2Link’s JavaScript behaviour.

3. Behaviour

Text2Link performs the following tasks to enable users to open selected URLs and send emails to selected addresses:

  1. retrieve the current text selection on the page;
  2. display the context menu options appropriate to the current selection;
  3. open the selected URL in the current tab, a new tab, or a new window, depending on the context menu option invoked; and
  4. open the compose window of the user’s default email application and insert the selected email address in the To: field.

This behaviour is implemented by the t2l_Main and t2l_ContextMenu JavaScript classes in the text2link.js file. The t2l_ prefix identifies each class with Text2Link and helps to make the class names unique.

Retrieving the Current Text Selection

The getSelection method of the t2l_Main class returns the current text selection on the page. getSelection first checks if a text input box on the page has a selection. The focusedElement attribute of the document.commandDispatcher object returns the HTML element with the input focus:

var focusedElement = document.commandDispatcher.focusedElement;

The selectionStart and selectionEnd attributes give the range of the element’s text selection:

var selectionStart = focusedElement.selectionStart;
var selectionEnd   = focusedElement.selectionEnd;

If there is a selection, the selectionEnd attribute will be greater than the selectionStart attribute. The selection can then be extracted as a sub-string of the element’s value:

if (selectionEnd > selectionStart)
{
   selection = focusedElement.value.substr(selectionStart, selectionEnd);
}

If none of the elements on the page have the input focus, the getSelection method checks if any of the text on the page is selected. The focusedWindow attribute of the document.commandDispatcher object returns the current tab; the getSelection method of the focusedWindow object returns the current selection, if any:

var focusedWindow = document.commandDispatcher.focusedWindow;
selection = focusedWindow.getSelection().toString();

Finally, the getSelection method of the t2l_Main class calls the trimStr method to remove any leading or trailing spaces from the selection and to collapse multiple adjacent spaces into a single space.

Displaying Context Menu Options

The displayOptions method of the t2l_ContextMenu class adds the appropriate Text2Link options to Firefox’s context menu. The showItem method of Firefox’s gContextMenu object controls whether a context menu option is displayed. For example, the following line from the displayOptions method displays the Open URL in New Tab option in Firefox’s context menu if the value of show is true:

gContextMenu.showItem("t2l-context-openTab", show);

The value of show is true whenever the current text selection contains a URL rather than an email address, which is determined by calling the isURL and isEmail methods of the t2l_Main class. The Open URL in New Tab context menu option is identified by its DOM ID, which is specified by the corresponding <menuitem> tag in the text2link.xul file:

<menuitem id="t2l-context-openTab"
   insertbefore="context-undo"
   label="&t2l.context.openTab.label;"
   accesskey="&t2l.context.openTab.accesskey;"
   class="menuitem-iconic"
   oncommand="t2l_Main.open('tab');" />

The label of the Open URL in New Tab context menu option is specified in the corresponding entity in the text2link.dtd file:

<!ENTITY t2l.context.openTab.label "Open URL in New Tab">

To enable Text2Link to add options to Firefox’s context menu, Text2Link must register itself to receive context-menu events from Firefox. The register method of the t2l_ContextMenu class adds the displayOptions method as a listener for the popupshowing event, which Firefox fires when its context menu is about to be displayed; this ensures that displayOptions can add Text2Link’s options before the context menu is displayed. The register method is called by the load method of the t2l_Main class when Firefox fires a load event for the document:

window.addEventListener("load", t2l_Main.load, false);

In contrast, the deRegister method removes the displayOptions method as a listener for the popupshowing event, ensuring that the t2l_ContextMenu class no longer displays Text2Link’s context menu options when Text2Link has been disabled, for example. The deRegister method is called by the unload method of the t2l_Main class when Firefox fires an unload event for the document:

window.addEventListener("unload", t2l_Main.unload, false);

Both the register and deRegister methods ensure that Text2Link’s context menu options and menu separator are initially hidden by calling the hideOptions method.

Opening URLs

The open method of the t2l_Main class controls Text2Link’s behaviour when the Open, Open URL in New Tab, or the Open URL in New Window context menu options are invoked. The open method is specified by the oncommand attribute in the corresponding <menuitem> tab in the text2link.xul file. For example, the following tag specifies that Firefox calls the open method of the t2l_Main class with the parameter window when the Open URL in New Window context menu options is invoked:

<menuitem id="t2l-context-openWindow"
   insertbefore="context-undo"
   label="&t2l.context.openWindow.label;"
   accesskey="&t2l.context.openWindow.accesskey;"
   class="menuitem-iconic"
   oncommand="t2l_Main.open('window');" />

The value of the parameter determines which action the open method will take. The tab parameter opens the selected URL in a new tab:

browser.addTab(url);

The window parameter opens the selected URL in a new window:

openDialog("chrome://browser/content/browser.xul",
   "_blank", "chrome,all,dialog=no", url);

The default action, specified by the current parameter, opens the selected URL in the current tab:

browser.loadURI(url);

To determine whether the current text selection contains a URL, the displayOptions method of the t2l_ContextMenu class calls the isURL method of the t2l_Main class. isURL returns true if a call to getURL successfully extracts a URL from the current text selection, which is passed as a parameter. The getURL method applies a regular expression to the current text selection. If the selection matches the URL regular expression, getURL returns the URL, otherwise it returns an empty string.

Sending Emails

The sendEmail method of the t2l_Main class controls Text2Link’s behaviour when the Send Email context menu option is invoked. The sendEmail method is specified by the oncommand attribute in the <menuitem> tab for the Send Email option in the text2link.xul file:

<menuitem id="t2l-context-sendEmail"
   insertbefore="context-undo"
   label="&t2l.context.sendEmail.label;"
   accesskey="&t2l.context.sendEmail.accesskey;"
   class="menuitem-iconic"
   oncommand="t2l_Main.sendEmail();" />

To send an email to the selected email address, Text2Link instructs Firefox to open the compose window of the user’s default email application. The To: field of the email-application’s compose window will contain the selected email address. Opening a compose window is a four-step process. The first step requests a Mozilla external protocol service object:

var protocolService =
   Components.classes["@mozilla.org/uriloader/external-protocol-service;1"].
      getService(Components.interfaces.nsIExternalProtocolService);

The second step requests a Mozilla IO service object:

var ioService = Components.classes["@mozilla.org/network/io-service;1"].
   getService(Components.interfaces.nsIIOService);

The third step creates the URI for opening an email-application’s compose window using the IO service:

var url = ioService.newURI(emailURL, null, null);

The final step opens the URI with the protocol service, which opens the email-application’s compose window:

protocolService.loadUrl(url);

To determine whether the current text selection contains an email address, the displayOptions method of the t2l_ContextMenu class calls the isEmail method of the t2l_Main class. isEmail returns true if a call to getEmail successfully extracts an email address from the current text selection, which is passed as a parameter. The getEmail method applies a regular expression to the current text selection. If the selection matches the email address regular expression, getEmail returns the email address, otherwise it returns an empty string. Before returning the email address, getEmail replaces at or AT with @ and replaces dot or DOT with ., if they occur in the email address.

Before installation, the files that contain Text2Link’s XUL user interface and its JavaScript implementation must be packaged into a Firefox extension file.

4. Packaging Text2Link

Firefox requires that all the files that implement an extension are packaged into a .xpi file. In addition to the files that implement the user interface and behaviour of the extension, the extension package must contain an install.rdf file that gives Firefox all the information it needs to install the extension. The <Description> tag of Text2Link’s install.rdf file contains three sections. The first section is mandatory and provides a unique extension ID, the name and version of the extension, and the type of extension:

<!-- required -->
<em:id>{E9AE265A-1885-4143-BDC3-2783D9124418}</em:id>
<em:name>Text2Link</em:name>
<em:version>1.0</em:version>
<em:type>2</em:type>

I generated the unique extension ID with Microsoft’s GUIDgen utility, which is shown in the following screenshot:

Microsoft's GUIDgen utility

The extension type of 2 signifies that Text2Link is an extension, rather than a theme or a locale.

The second section in the <Description> tag provides an optional description of the extension, the name of the extension’s creator and the URL of the extension’s homepage:

<!-- optional -->
<em:description>Use the context menu to open a selected textual URL
in the current tab, in a new tab, or in a new window.</em:description>

<em:creator>Jeffrey Morgan</em:creator>
<em:homepageURL>http://text2link.net/</em:homepageURL>

This information populates Text2Link’s About dialog and Text2Link’s entry and context menu in Firefox’s Add-ons dialog.

Text2Link's About dialog

Text2Link's entry in Firefox's Add-ons dialog

The final section of the <Description> tag specifies that Text2Link is compatible with versions 2.0 through 3.0 of Firefox, which is identified by its unique Mozilla application ID:

<!-- Firefox -->
<em:targetApplication>
   <Description>
      <em:id>{ec8030f7-c20a-464f-9b0e-13a3a9e97384}</em:id>
      <em:minVersion>2.0</em:minVersion>
      <em:maxVersion>3.0.*</em:maxVersion>
   </Description>
</em:targetApplication>

Each Mozilla application has a well-defined list of version numbers.

The chrome.manifest file provides the location of the files in the text2link.xpi packaged extension file. Mozilla refers to the files that implement the user interface and behaviour of a Firefox extension as the content. The content clause states that the XUL user interface and JavaScript behaviour files are located in the content/ directory of the text2link.jar file:

content text2link jar:chrome/text2link.jar!/content/ xpcnativewrappers=yes

Similarly, the locale clause states that the default localisation file (US English) is located in the locale/en-US/ directory of the text2link.jar file:

locale text2link en-US jar:chrome/text2link.jar!/locale/en-US/

Finally, the overlay clause states that the XUL user interface components specified in the text2link.xul file should be added to Firefox’s user interface components, which are specified in the browser.xul file:

overlay chrome://browser/content/browser.xul
        chrome://text2link/content/text2link.xul

I use a Windows batch file (package.bat) with Java’s JAR utility to package Text2Link’s files into a .xpi file.

cd chrome
jar -cvfM text2link.jar content locale
cd ..
jar -cvfM text2link.xpi install.rdf chrome.manifest chrome/text2link.jar

The first jar command packages the contents of the content/ and locale/ directories into the text2link.jar file. The second jar command packages the install.rdf, chrome.manifest and text2link.jar files into the text2link.xpi file, which is the file Firefox uses to install the Text2Link extension. Both commands use the following JAR parameters:

c create an archive
v produce verbose output
f write the output into a file rather than standard output
M don’t create a Java manifest file

After packaging, the text2link.xpi file has the following structure:

text2link.xpi
+- install.rdf
+- chrome.manifest
+- chrome/
   +- text2link.jar
      +- content/
         +- text2link.js
         +- text2link.xul
      +- locale/
         +- en-us/
            +- text2link.dtd

5. Resources

I found the following resources helpful when developing Text2Link: