Putting down event infrastructure in Gecko

Implementing simple DOM Events in Gecko is fairly straight forward. A number of C/C++ macros have been written over the years to take away some of the pain.

In this post, I’ll explain how “window.onappinstalled” is implemented.


In this case, I’m interested in implementing the “appinstalled” event handler on the Window object. To do that, we start by defining the WebIDL in Window.webidl:

partial interface Window {
  // Pref'ed - see how to pref things in Gecko.
  attribute EventHandler onappinstalled;

Window only events

The events in Gecko are defined in the EventNameList.h header file. This allows you to define a variety of different event types.

For our case, we are interested in a “window only” event, so we can use the “WINDOW_ONLY_EVENT” macro.

As stated in EventNameList.h, each event entry in this file consists of 4 pieces of information:

The name of the event
This needs to match a GkAtom in nsGkAtomList.h
The event message
This is also generated with a macro, but needs to be defined via EventMessageList.h
The event type
See the EventNameType enum in nsContentUtils.h)
The event struct type for this event.

so, basically, for “window.onappinstall”, we define:

// Install events as per W3C Manifest spec
WINDOW_ONLY_EVENT(appinstalled, // nsGkAtomList.h
                  eAppInstalled, // EventMessageList.h

Event Message (EventMessageList.h)

As stated in the file, an event message must be defined as follows:

  1. Starting with “e” prefix and use camelcase.
  2. Basically, use same name as the DOM name which is fired at dispatching the event.
  3. If the event message name becomes too generic, e.g., “eInvalid”, that may conflict with another enum’s item name, append something after the “e”
    prefix, e.g., “eFormInvalid”.

So, in our case, we just want “eAppInstalled” as the message:


That should do it.

Putting APIs behind a pref in Gecko

Putting WebIDL APIs behind a flag in Gecko is surprisingly easy. Gecko supports an extended WebIDL attribute called “Pref”. You can use it on either interfaces or individual attributes, like so:

[NoInterfaceObject, Exposed=(Window)]
interface AppInstallEventHandlersMixin {
  attribute EventHandler oninstall;

You can see other examples in this dxr search for Pref= throughout Gecko’s tree.

Where it gets a little more tricky is testing, which I cover below.

Setting the preference

Normally, if you want something behind a preference, you want to have it disabled. The place to declare the preferences is in the modules/libpref/init/all.js file.

For example:

// Whether window.oninstall from "W3C Web Manifest" is enabled
pref("dom.manifest.oninstall", false);


Once you’ve added Pref= extended attribute, and disabled your feature, you will naturally want to test it using Mochitests.

Browser test

Creating a browser test for something like the above is fairly straight forward. Just Cu.import Services and set the pref type. In my case, I just had a boolean, so we can create a simple promise wrapper for SpecialPowers.pushPrefEnv():

// Enable window.oninstall, so we can fire events at it.
function enableOnInstallPref() {
  const prefs = new Map([
    ["dom.manifest.oninstall", true]
  const ops = {
    "set": [...prefs.entries()],
  return new Promise(resolve => SpecialPowers.pushPrefEnv(ops, resolve));

  yield enableOnInstallPref();
  // do the rest of the test.

You could synchronously set the preference here using Services.prefs. API, but Boris Zbarsky warns: “Yes, but what unsets it so you don’t pollute the environment for later tests? pushPrefEnv handles this for you.”

Content tests

Content tests are a little more tricky, because you need to asynchronously set the preference, and then create an iframe. As Boris explains:

WebIDL conditional annotations for an interface are evaluated once per global: when the interface prototype object is created. The right way to write tests that toggle such prefs is to set the pref (via pushPrefEnv, please, not setBoolPref!) then once the pref is set create a new global to test in: either window.open or creating an iframe.

So, let’s now let’s pretend you have a file called “test_window_oninstall_event.html”. We can create a enableOnInstallPref() function that returns a Promise. Once the promise resolves, we create the iframe and again resolve with Window object. We can then run:

"use strict";
function enableOnInstallPref() {
  const prefs = new Map([
    ["dom.manifest.oninstall", true]
  const ops = {
    "set": [...prefs.entries()],
  return new Promise(resolve => SpecialPowers.pushPrefEnv(ops, resolve));

function createIframe() {
  return new Promise((resolve) => {
    const iframe = document.createElement("iframe");
    iframe.src = "about:blank";
    iframe.onload = () => resolve(iframe.contentWindow);

function firstTest(ifrWindow){
  return new Promise(()=>{
    const hasOnInstallProp = ifrWindow.hasOwnProperty("oninstall");
    ok(hasOnInstallProp, "window has own oninstall property");

    // no point in continuing
    if (!hasOnInstallProp) {
      const err = new Error("No 'oninstall' IDL attribute. Aborting early.");
      return reject(err);
    // do other tests

function secondTest(ifrWindow){/* other test */}

// We can now do everything in order
const finish = SimpleTest.finish.bind(SimpleTest);
  .catch(err => {
    dump(`${err}: ${err.stack}`);

If you would like to see the full code that this example is based on, please take a look at the attachments in Gecko Bug 1280777 – put window.oninstall behind a pref.

Gecko: gBrowser and tabs

As anyone who hacks on Gecko knows, the project’s documentation is, um, lacking. This is sometimes Ok if all you do is work on Gecko; But for us that dive in and out of Gecko every few weeks … let’s just say, it can drive one a little insane. It also makes it really hard to learn what the f$%# is going on and wastes tremendous amounts of (my) time just getting something up an running (I’m talking days).

Anyway, I’m writing these posts mostly for myself, so I can avoid having to go through the pain of having to (re)learn Gecko’s JS APIs every few weeks.

If you want to follow along, make sure you clone Mozilla central and then:

./mach build
./mach run

After you build you also need go to “about:config” in the browser and set the preferences <code>devtools.debugger.remote-enabled</code>, <code>devtools.debugger.chrome-enabled</code>, and <code>devtools.chrome.enabled</code>. This will allow the -jsdebugger argument (the browser toolbox) to work. Thanks to Andreas Tolfsen who pointed this out in the comments!

Now, shut down the running instance of Nightly, because we need to launch a new one. Type into your terminal:

./mach run -P "coding" -jsdebugger -purgecaches

The -P is for profile (I’m using one called “coding”). The -jsdebugger option opens the “Browser toolbox” automatically. And -purgecaches dumps the JSM caches, avoiding a whole bunch of headaches.


At the top of the Gecko hierarchy, there is a window (similar to HTML), but this window represents the actual application window. This means that the interface is different to HTML’s Window object. You can test this by, for instance:

//Maximize the window, and the restore it to its previous location

This window actually points to “chrome://browser/content/browser.xul”, which is basically what the browser is composed of (XUL elements – which implement the common DOM Interfaces).


Inside window there is gBrowser: this object represents the browser window. You can confirm this by, for instance, calling gBrowser.addTab("http://google.com"). This will add a tab. So:

const tab = gBrowser.addTab("http://google.com");

Tabs are opened in the background and are not “selected” (i.e., the focused one, independently of window focus).

//bring the new tab to the foreground
gBrowser.selectedTab = tab;

The main purpose for creating a tab will generally be to load content and possibly do something with that content. Inside each tab, there is a Browser, which is not directly accessible through the Tab‘s interface. Also, a Tab object does not provide direct access to the Window object of the tab. Instead you need to use a method from gBrowser to get at it:

//Get the "browser"
const browser = tab.linkedBrowser;
//Get the actual window object.
//NOTE: YOU CAN'T USE win yet! see below!
const win = browser.contentWindow

Note that this puts your in a race condition (the Document object won’t be ready), so you need to wait for the actual page to “show”. this is done through the undocumented pageshow event.

const pageShow = function() {
browser.removeEventListener('pageshow', pageShow);
//do something interesting
win.document.body.innerHTML = '<h1>Yay!</h1>';
browser.addEventListener('pageshow', pageShow);

Of course, once you have the Window object, then you are back in Web Development land and all is good. Once you are finished with a tab, you can remove it:

//clean up!

Note: don’t be fooled by the .remove() method on the Tab object. That corresponds to the DOM interface’s .remove() method, which removes the node from the XBL tree, but doesn’t actually destroy the Tab. So, calling this can cause memory leaks. For example, when you run this against mochitests, you might see something like:

TEST-UNEXPECTED-FAIL | dom/manifest/test/browser_ManifestObtainer_obtain.jsleaked 1 docShell(s) until shutdown