Sunday, June 10, 2007

Events, Event Listeners and Event Propagation Model

Firefox version: 2.0.0.4 (Mac)

Adding event listeners: the good and the bad
Typical syntax for adding listeners to page elements looks something like this:

element.onclick = myEventListener;

where onclick is an attribute and myEventListener is a name of a function that takes zero or one arguments and is called when a particular event is triggered at element. This is a common and convenient syntax for JavaScript embedded in the page but it has one major and important drawback for extension writers: adding an event listener this way causes all previous listeners registered at that element for that type of an event to be de-registered. Therefore a better choice is the addEventListener method, which allows multiple listeners to be notified of the same event type:

void addEventListener(in DOMString type,
in EventListener listener,
in boolean useCapture);

Where
type is a string specifying the name of the event type (e.g., “mousedown”)
listener is name of a function taking one parameter of type Event
useCapture specifies whether events addressed to the descendants of this element should be captured by this listener first, before they reach their target (I will talk about capturing a little bit more below)

Event propagation model
The event propagation model in JavaScript is somewhat unusual and deserves some attention. Any event can go through up to three phases: capturing, at target and bubbling (see the illustration below). Let us assume that a mouseover event is triggered by a link element. That event first travels through all of the ancestors of the link element, starting with the document element itself. This is the capturing phase and only event listeners registered with useCapture = true are triggered during that phase. Subsequently, the event reaches its target and is now in the at target phase. Finally, some (but not all) events will “bubble” upwards through the DOM tree all the way up to the document again. During this bubbling phase only events registered with useCapture = false will be triggered.
illustration of how JavaScripts events propagate through the DOM

There are a number of methods for altering the propagation path of an event:

- During capturing phase, a call to event.stopPropagation() method will prevent the further propagation of the even although all other event listeners registered at the same level in the hierarchy will still receive the event;

- During at target or bubbling phase, a call to event.stopPropagation() method will prevent the further bubbling of the event, so no listeners registered higher up in the hierarchy will receive the event;

- During capturing or at target phases, one may call event.preventDefault() to prevent the event from triggering the default action in the browser (for example, to stop the browser from following a link in response to a mouse click). This only applies to events that are designated as cancelable.

Example: highlighting page elements on mouse-over
Note that the JavaScript event propagation model makes it possible to register just a single event listener at any point in the DOM hierarchy to receive all events of a particular type from all of the descendants of that element. The example below shows how to change the background color of a page element (a leaf or an intermediate node) on a mouse-over event.

var originalColors = {};

var HelloWorld = {

...

instrumentCurrentPage:
function() {
window.content.document.addEventListener("mouseover", this.handleMouseOverEvent, false);
window.content.document.addEventListener("mouseout", this.handleMouseOutEvent, false);
},

handleMouseOverEvent:
function(event) {
originalColors[event.target] = event.target.style.background;
event.target.style.background =
"#f00";
},

handleMouseOutEvent:
function(event) {
event.target.style.background = originalColors[event.target];
},
}


The originalColors variable is used to store the original background colors of elements just before the highlight is applied, so that the handleMouseOutEvent can restore the original background when the mouse is moved away.

In this case, the event handlers are triggered during bubbling phase but the same effect is achieved if useCapture were set to true.

The screen shot below shows this extension script in action.
a screen shot showing a web page element being highlighted on mouse-over


Resources
I learned the most from a W3C document on Document Object Model Events.

Tuesday, June 5, 2007

Accessing page content from an extension

Firefox version: 2.0.0.4 (Mac)

First roadblock
If JavaScript is embedded in a page, then the document or window.document variables point to the DOM model of the page. When writing an extension, window.document points to the DOM model of the Firefox GUI. How, then, does one get access to the contents of the page from an extension?

Solutions
Mira and Raphael provided two solutions:


window.content.document


or a more complicated option:


function getBrowserWindow() {
var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"].
getService(Components.interfaces.nsIWindowMediator );
return wm.getMostRecentWindow("navigator:browser").content.document;
}



I have tested both of them and they both return the pointer to the document in the currently visible window tab or window. Are the two methods really equivalent or have I missed something?

Monday, June 4, 2007

Getting started

Firefox version: 2.0.0.4 (Mac)

Why this blog?
I decided that I wanted to learn how to write Firefox extensions because some of the research I am doing will probably require that. I very quickly realized that there is very little good documentation on the topic available (or at least easily findable) on the net so I thought I would try to document my learning process for the benefit of others who will need to go down this path. Realizing that the relevant standards and implementations change very rapidly, I am going to include Firefox version number I am using in each post so that people can judge its relevance for their work.

What should you expect?
A few words about my background and what I want to accomplish. I am a reasonably experienced Java developer and I am quite familiar with HTML and CSS but I haven’t touched JavaScript since 1997, I think. I have also some experience manipulating XML from Java so I have had some exposure to the basic concepts from the Document Object Model (DOM).

My research interests include adaptive user interfaces so over the course of the next several months I think I would like to learn how to do the following:
- parse and analyze new pages as soon as the user navigates to them;
- automatically instrument new pages (especially those containing AJAX applications) in order to see how the user interacts with the page (and therefore build a usage model);
- keep persistent state;
- automatically augment third-party pages with new elements;
- exchange information with a central server.

Hello World!
The single most helpful resource for getting started was a tutorial from the Mozilla Developer Center (MDC). It explains all of the components that go into building the most basic extension (which a “Hello World!” message to the status bar) and includes pointers to all the other basic resources one needs. This tutorial and several other sites also recommend an older tutorial from mozillaZine -- the extension it describes, however, does not work with Firefox 2.0 (it seems to have been written for Firefox 1.5). I still found that tutorial helpful because unlike the one from MDC, it included JavaScript, showed how to add a custom item to the Tools menu and how to pop up custom dialog boxes. Using the structure from the first tutorial and some of the code from the second, I felt like I got a good handle on how to get started.

The code is here and it shows the following features:
• GUI: adding a custom element to the status bar and a custom menu item in the Tools menu
• Localization: the labels are defined in .dtd files in the locale directory
• JavaScript invoked by clicking on the custom menu item:
   • traverses Firefox GUI DOM model
   • logs to the JavaScript error console (available through the Tools menu) -- see more on how to write to the console
   • dynamically modifies the text in the custom status bar element

Few other pointers
- A re-introduction to JavaScript from mozillaZine -- a good refresher for those, who already know Java or C/C++/C#
- XULplanet -- a set of XUL references (XUL is the XML-based language that the Firefox user interface is written in)
- A full DOM reference from MDC
- “DOM Scripting” by Jeremy Keith -- a very nice overview of the techniques for manipulating the DOM with JavaScript; written for people who are beginner programmers so if you already have some experience, you will be able to go through it very quickly. It provides lots of examples and practical tips. I found it quite helpful and got through the first five chapters in slightly over an hour.

Tools
There do not appear to be any “ideal” tools for Firefox extension writing. I ended up installing JSEclipse and Eclipse Colorer plugins for Eclipse and I find that they are somewhat helpful but still lacking.

Acknowledgments
Besides the sources cited above, I am also learning a lot from Mira, Raphael and Jeff.