Add-ons using the techniques described in this document are considered a legacy technology in Firefox. Don't use these techniques to develop new add-ons. Use WebExtensions instead. If you maintain an add-on which uses the techniques described here, consider migrating it to use WebExtensions.
From Firefox 53 onwards, no new legacy add-ons will be accepted on addons.mozilla.org (AMO).
From Firefox 57 onwards, WebExtensions will be the only supported extension type, and Firefox will not load other types.
Even before Firefox 57, changes coming up in the Firefox platform will break many legacy extensions. These changes include multiprocess Firefox (e10s), sandboxing, and multiple content processes. Legacy extensions that are affected by these changes should migrate to WebExtensions if they can. See the "Compatibility Milestones" document for more.
A wiki page containing resources, migration paths, office hours, and more, is available to help developers transition to the new technologies.
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:
- What does nsILocalFile.initWithPath do? What arguments does it take?
- 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:
- Download gonzui-win32-1.2.2.zip and expand it into a suitable directory.
- 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).
- Open a command prompt and navigate to the directory containing gonzui.
- The next command will import the source code:
gonzui-import.exe mozilla
- Once the import process is complete, type the following command to launch the gonzui server:
gonzui-server.exe
- 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