Callback patterns in IDL
XPCOM components use IDL to create interfaces. These interfaces are used to manipulate the component in C++ and JavaScript. A common pattern used with interfaces to create a bi-directional communication between two groups of code is the observer (or listener) pattern. Basically, the component defines an observer (or listener) interface which is implemented by some external code and this implementation is passed to the component. The component can then call methods on the observer interface to signal the external code when predefined events occur. Here is a very simple example of the observer pattern:
[scriptable, uuid(...)] interface StringParserObserver { void onWord(string word); }; [scriptable, uuid(...)] interface StringParser { void parse(string data); void addObserver(StringParserObserver observer); };
In this example, the StringParser
will call the StringParserObserver.onWord
method whenever it finishes parsing a word found in the raw string data. Here is an example of how to use the callback system:
var wordHandler = { onWord : function(word) { alert(word); } }; var stringParser = /* get a reference to the parser somehow */ stringParser.addObserver(wordHandler); stringParser.parse("pay no attention to the man behind the curtain");
You can find examples of this pattern all over the Mozilla codebase. In fact, there is even a specific interface for this form of push callbacks, nsIObserverService.
JavaScript functions as callbacks
Another common use of the pattern is found in addEventListener
/ removeEventListener
. A nice feature of addEventListener
is that you can pass a JavaScript function in place of the callback listener interface. Remember (or discover) that addEventListener
is a method of the nsIDOMEventTarget
interface and is defined as such:
void addEventListener(in DOMString type, in nsIDOMEventListener listener, in boolean useCapture);
However, it is extremely common to see developers pass a normal JavaScript function for the listener instead of an nsIDOMEventListener
implementation:
function doLoad(event) { // do something here } window.addEventListener("load", doLoad, false);
Revealing the magic
How is this possible? Is nsIDOMEventListener
magical? Well, it actually is a little magical. But you can use the same magic in your own IDL callbacks. The nsIDOMEventListener
interface is "marked" with the function
attribute. See it here. The function
attribute tells the XPConnect machinery to treat the JavaScript function as if it was an implementation of the callback interface. Note, that since the JavaScript function is a single method, this magic only works for callback interfaces with a single method, like nsIDOMEventListener
. The JavaScript function is passed the same arguments as defined by the callback method.
So we could convert the example above to accept JavaScript functions in place of the StringParserObserver
by making the following changes:
[scriptable, function, uuid(...)] interface StringParserObserver : nsISupports { void onWord(string word); }; [scriptable, uuid(...)] interface StringParser { void parse(string data); void addObserver(StringParserObserver observer); };
Note the only change was adding function
to the interface attributes of the callback interface. Now we can create a callback JavaScript function to handle the onWord
event:
function handleWord(word) { alert(word); } var stringParser = /* get a reference to the parser somehow */ stringParser.addObserver(handleWord); stringParser.parse("pay no attention to the man behind the curtain");
Yes, you can still use the normal interface-based callback implementation too. Using JavaScript functions as callback handlers for components can be a nice convenience to developers and there is virtually zero work to expose the feature.