Chapter 6: Firefox extensions and XUL applications

Draft
This page is not complete.

This document was authored by Taro (btm) Matsuzawa and was originally published in Japanese for the Firefox Developers Conference Summer 2007. Matsuzawa-san is a co-author of Firefox 3 Hacks (O'Reilly Japan, 2008.)

This chapter discusses tools to assist in developing extensions.

Tools for extension developers

FIXME: Are we sure we'll talking about Venkman since it's not well maintained

FIXME: We maybe should talk about Firebug and Chromebug

FIXME: and what about Console 2 and Docked-JS Console?

Venkman, the JavaScript debugger

Venkman is a full-fledged JavaScript debugger that runs inside Firefox. It works like typical debuggers, and is useful not only for developing extensions, but also for general web development.

Also, because it works with the JavaScript in extensions and Firefox itself, the debugger can be a good way to learn about design methods.

Installing

This is bundled with the Mozilla Suite and the current version of SeaMonkey, but it needs to be installed as an extension in Firefox. See https://addons.mozilla.org/firefox/216/.

Install the extension in Firefox directly from the above URL and relaunch Firefox.

Starting up

Select “JavaScript Debugger” from the Tools menu.

Targeting JavaScript for debugging.

When Venkman first launches, it can only debug the file currently open in your web browser. This is because a lot of JavaScript files can slow down the browser down if they’re opened for debugging, but you’ll need to lift this restriction in order to develop extensions. Uncheck the “Exclude Browser Files” menu item under the Debug menu (Figure 1).

FIXME: Figure 1: Uncheck “Exclude Browser Files.”

Set up QuickNote

For the purposes of this explanation, we’re going to use the file-storage mechanism in QuickNote1 for our debugger. Install QuickNote from the following URL and relaunch Firefox. https://addons.mozilla.org/firefox/46/.

After relaunching Firefox, activate QuickNote. Then select Tools:QuickNote:Float to display it in its own window.

Using Venkman

Read in source code

Activate Venkman, and then select Open Windows:quicknote.xul:Files on the top-left of the screen. Double-click on quicknote.js to open it in source-code view (Figure 2).

FIXME: Figure 2: Selecting source code

Inserting breakpoints

Now you’ll want to set some breakpoints. Try inserting one within the QuickNote_saveNote function, around line 345 (Figure 3).

FIXME: Figure 3: Inserting a breakpoint

Start debugger

Type in some text into the QuickNote screen, and then select Save Current Tab from the menu. This should halt the program at the spot where you inserted the breakpoint with Venkman, and start the debugger.

The Local Variables screen should always display variables and objects during step-by-step execution. With functions that include a lot of variables, this will be hard to read, so you can point out specific variables that you want to watch (Figure 4).

Watched variables appear under the Watches tab, and are updated every time they are evaluated.

You can then continue with stepwise execution and watch the changes in the program and variables.

Once you’ve finished your inspection, you can continue and let the halted program resume execution.

The author has done almost no work developing extensions, but has used this to investigate how extensions were implemented. In the QuickNote example, we first set one breakpoint and then stepped through it.

Use Step Over to avoid a function, and Step Into to always call it; once you are done investigating a function, use Step Out, and then you can Step Into the next function.

Set breakpoints in locations of interest where you’re not sure what’s going on, and the next time you run it, use Continue to shift to that location and start investigating again.

FIXME: Figure 4: Adding a watch target

MozUnit

MozUnit is a tool for JavaScript that assists with unit testing. It provides an UI similar to that of Eclipse’s JUnit test runner. It should be very easy for Java programmers to get up to speed on it quickly.

Installing

MozUnit is a feature of Mozlab. Install Mozlab from the following URL and relaunch Firefox.

http://hyperstruct.net/projects/mozunit

After relaunching, set up the editor you’ll use to write tests. Open the about:config screen and edit the extensions.mozlab.mozunit.editor property. Parameters are as shown in Table 1. If you want to use Terapad2, for example, insert the following:

C:\app\tpad090\Terapad.exe /jl=%l %f

Table 1: Parameters used in MozUnit

Parameter

Description

%f

Filename

%l

Line number

%c

Column number

The author uses Meadow, which is opened using gnuserv.

C:\app\Meadow\bin\gnuclientw.exe +%l:%c %f

Note that, at least in these tests, you will not be able to edit program files in your editor. And it’s probably for the best that you don’t use any filenames that have whitespace in them.

Usage

An RPN calculator

We’re going to implement a simple RPN3 calculator in JavaScript to see how this can be used.

On the program side, we’ll create a class called RpnCalc. First we’ll sketch out a simple interface for it (Listing 1). Save this as a file called calc.js.

Listing 1: calc.js (stage 1)

function RpnCalc() {
}
RpnCalc.prototype = {
  init: function() {
  },
  push: function(val) {
  },
  plus: function() {
  },
  pop: function() {
  }
}

Implement the addition operation

Create test case

Begin by creating the test case. First, select the menu item Tools:Mozlab:Open MozUnit Runner to bring up the MozUnit window. Select the menu item File:New, and save test_calc.js in the same directory as the file that is the test target. Next, click the Edit button to set up the “2 1+” test case (Listing 2). Note that in this file, the method names serve as the test names.

In JUnit and similar packages, methods containing tests that start with testhoge are recognized as tests. MozUnit uses the same kind of notation, but you can also make the string the method name, making the test self-documenting. Pretty clever.

Listing 2: Content for test_calc.js (first-round test case)

var TestCase = mozlab.mozunit.TestCase;
var assert = mozlab.mozunit.assertions;
var tc = new TestCase('Rpn Calc Testcase');
var module = new ModuleManager();
var rpncalc = module.require('package', 'calc');
tc.tests = {
  '2 1 +': function() {
    var calc = new rpncalc.RpnCalc();
    calc.init();
    calc.push(2);
    calc.push(1);
    calc.plus();
    assert.equals(calc.pop(), 3);
  }
}

Listing 3: Additional content for calc.js

function RpnCalc() {
  this.stack = new Array();
}
RpnCalc.prototype = {
  init: function() {
    this.stack = new Array();
  },
  push: function(val) {
    this.stack.push(Number(val));
  },
  _letfunc: function(func) {
    a = this.pop();
    b = this.pop();
    this.push(func(a, b));
  },
  plus: function() {
    return this._letfunc(this._plus);
  },
  _plus: function(a, b) {
    return a + b;
  },
  pop: function() {
    return this.stack.pop();
  }
}
Check for errors

Now you’re ready for your first test. When you run it, you should see a red bar, showing that it failed.

Implement in calc.js

We need to flesh out calc.js. Update it with the contents of Listing 3.

Run test

Run the test again. You should see a green bar showing that the test succeeded.

Implement the subtraction operation

Now let’s add subtraction.

Create test case

First, add the test case for the subtraction method. Open test_calc.js and update it with the method in Listing 4.

Implement in calc.js

Next we need to add the executable code. This will work much as it did for addition. Update calc.js with the method in Listing 5.

Run test

If we run the test at this point, we’ll get a red bar. What’s the matter? The screen will display “Expected -1, got 1”. Somehow, using pop() got the order of the arguments mixed up. So let’s update the _letfunc function as shown in Listing 6.

If we run the test again, we should get a green bar, showing that it ran correctly.

Benefits of testing

Implementing the tests described here confirms that the methods work correctly, and builds confidence in your implementations.

Multiplication and division are implemented the same way, but what will you do if an invalid string is passed? What if you attempt to run with nothing in the stack—what result will you return then? Try to keep in mind real-world circumstances while you’re working on this.

Listing 4: test_calc.js (adding sample test case)

'2 1 -': function() {
  var calc = new rpncalc.RpnCalc();
  calc.init();
  calc.push(2);
  calc.push(1);
  calc.minus();
  assert.equals(calc.pop(), 1);
},

Listing 5: calc.js (implementing subtraction operation)

minus: function() {
  this._letfunc(this._minus);
},
_minus: function(a, b) {
  return a - b;
},

Listing 6: calc.js (correcting mistake in implementation of subtraction)

_letfunc: function(func) {
// correct pop order
b = this.pop();
a = this.pop();
this.push(func(a, b));
},

Understanding source code

In the open-source community, you can learn a lot about how software works by studying its source code; with a massive project like Firefox, people typically use special source-code browsing tools to make sense of it.

Mozilla Cross-Reference

Mozilla Cross-Reference is a full-text searchable source-code listing hosted at mozilla.org. We’re going to use MXR (http://mxr.mozilla.org), a Linux source-code browser.

Getting started

Visit this URL: http://mxr.mozilla.org . In the left-hand sidebar, you should see a link to a search starting point and a search form (Figure 5).

FIXME: Figure 5: mxr.mozilla.org

Usage

What am I looking for?

If you just start poking through the source code at random, you may not find much of anything. So let’s figure out where to start looking.

Let’s say we’ve found the contents of Listing 7 in the source. We can guess from the name that this has something to do with handling files, but we’ve still got the following questions:

  1. What does nsILocalFile.initWithPath do? What arguments does it take?
  2. How is nsILocalFile.initWithPath actually implemented?

Let’s examine these questions one at a time.

What does this do? What arguments does it take?

First we’ll search for the component’s definition. Type interface nsILocalFile (with a trailing space) in the search field and go.

Nearly all components inherit from nsISupports (FIXME: why 'Nearly' ?), so adding a trailing space when you search serves as a delimiter on the inheritance position, and makes it easier to specify what you’re looking for (see Figures 6, 7).4

This results in the file nsILocalFile.idl. Let’s display it. Every method includes explanatory comments.

In initWithPath, the nsILocalFile object gets initialized, and any information already in it gets reset. Also be aware of a potential problem when using paths not representing entities.5

This requires a full path as an argument. Relative paths will throw errors.

This should help you understand the initWithPath specification.

FIXME: Figure 6: Searching without a trailing space

FIXME: Figure 7: Searching with a trailing space

FIXME: Listing 7: Source-code fragment

var file = Components.classes['@mozilla.org/file/local;1']
           .createInstance(Components.interfaces.nsILocalFile);
file.initWithPath(hoge);
How is this actually implemented?

If we want to understand how something was implemented, how do we search for that? Drop the I from nsILocalFile, double the colons, and capitalize the first letter of the function name, resulting in nsLocalFile::InitWithPath. That's the C++ name of the function implementing initWithPath. Use that as your search string.

That should produce nsLocalFileWin.cpp and other OS-specific files. Let’s look inside nsLocalFileWin.cpp—we’ll see, for example, that it’s checking for the presence of a “:” in the drive name.

We can also see how network paths are handled differently on WinCE. Just looking at InitWithPath, we can see how the path-validation process works.

Conclusion

In this chapter, we looked at some tools beyond the introductory level. If you had any “aha!” moments while reading this, then I hope you’ll take the next step and actually put them to work. I hope you’ll find that this chapter helps make you a more effective extension developer.

gonzui

gonzui is a full-text source-code search engine from Satoru Takabayashi, the well-known developer of the Namazu search engine software. FIXME: Not sure we should talk about this tool

Installation

Windows users can take advantage of a self-contained version created by Soutaro Matsumoto called “gonzui for win32”

http://soutaro.com/gonzui-win32/

Run it as follows:

  1. Download gonzui-win32-1.2.2.zip and expand it into a suitable directory.
  2. Download the Firefox source code and place it in the same directory as gonzui (simply because it’s easier to specify a file that’s in the same directory).
  3. Open a command prompt and navigate to the directory containing gonzui.
  4. The next command will import the source code: gonzui-import.exe mozilla
  5. Once the import process is complete, type the following command to launch the gonzui server: gonzui-server.exe
  6. Now you can access gonzui from your web browser by typing the following into your location bar : http://localhost:46984

This lets you browse all packages, click on links to traverse them, and take traversed-link locations as search starting points (Figure A).

FIXME: Figure A: gonzui

3 - Reverse-Polish Notation. A way to represent arithmetic formulas. For example, (1+2)*(3-4) would be represented in RPN as 1 2 + 3 4 - * It uses no parentheses. It is also easy to program.
4 - gonzui does not handle search strings with spaces very well; instead, go to the Advanced Search page, search on a few terms, and then use your browser’s Find function to search for the “:” character.
5 - Mac OS X and other OSs seemingly do not internationalize their directory names, but this hasn’t been confirmed.