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.

Web IDL

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.
  [Pref="dom.manifest.onappinstalled"]
  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
                  EventNameType_None,
                  eBasicEventClass) 

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:


NS_EVENT_MESSAGE(eAppInstalled)

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 {
  [Pref="dom.manifest.oninstall"]
  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);

Testing

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));
}

add_task(function*(){
  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";
SimpleTest.waitForExplicitFinish();
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);
    document.body.appendChild(iframe);
  });
}

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);
enableOnInstallPref()
  .then(createIframe)
  .then(firstTest)
  .then(secondTest)
  .then(finish)
  .catch(err => {
    dump(`${err}: ${err.stack}`);
    finish();
  });

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.

Transitioning to TLS

tl;dr: Migrating old content and infrastructure to TLS is still f’ing hard. If you are using CloudFlare, see “How do I fix the infinite redirect loop error after enabling Flexible SSL with WordPress?“.

After the recent push by many in the standards community to transition our sites to TLS, I decided to move my blog over to TLS. The push is driven the the need to generally try to make the Web more secure + we are trying to move the Web forward by requiring TLS for certain APIs (e.g., Service Workers).

This is not the first time I’ve moved this blog to use TLS.

Early attempt

The first time I did this was a few years ago when I became interested in encryption. During that time, AFAIK, in order to have a SSL certificate you needed to have a static IP address. Furthermore, there was no intermediary services, like StartSSL or CloudFlare, to provide sites with essentially free certificates. Doing the transition to SSL with my host (DreamHost) was fairly straight forward, but quite expensive for me at the time (I was a student or a junior engineer at the time). I don’t recall the exact numbers, but it was effectively something like $25/year for a static IP and then probably something like $50/year for the cert. This on top of the 10/year for the domain + $150 something/year I pay for hosting. Eventually, the cost became a burden to me: only a small handful of people ever visit this site (I don’t know how many as I don’t use analytics our of respect for my visitors) – but I just didn’t feel it was worth the cost so I abandoned it.

This was pre-Snowden, so I was fairly naive to the dragnet activities of the likes of the those assholes at the NSA. I also didn’t understand that, even if your site’s content is harmless, it serves as a data-point in a visitors history. By using TLS, one obscures the navigational trail: they may know you looked at marcosc.com, but not what you were reading. It can help a little.

CloudFlare

More recently, in light of all the bad shit that’s happening in the world, and prompted by Anne van Kesteren, I decided to re-encrypt this site. However, this time around I had a very negative experience trying to transition my personal blog to TLS.

After complaining to Anne about the costs, he pointed me to CloudFlare with the claims about it being free. The setup experience with CloudFlare was pretty straight forward: put a few things into a form and then go and tell DreamHost that the DNS values had changed. No big deal… but then things started to get fun.

Firstly, although my site started working straight away over TLS, I had to wait 4 days for the certificate to actually say it was for “marcosc.com”- during which time browsers accessing my site over HTTPS would see the red warning screen (effectively blocking anyone visiting the site over HTTPS). Thankfully, I had left plain old HTTP access enabled.

Once my certificate was actually granted and working, I decided to disable HTTP altogether. I did this by telling WordPress that this site’s URL was now “https://marcosc.com” – which immediately triggered a cascade of shit. Firstly, the site went into an infinite redirect loop and the whole site became inaccessible. I tried to revert this change in WordPress’ database, but it didn’t seem to help. I then tried to revert various things – but also didn’t work so I just left it be as I had other work to do.

I was offline for about 1 week, then thankfully WordPress issued an update. This seemed to fix things somewhat: at least the site started working again over HTTP. However, switching to HTTPS is still quite broken – the admin section is still completely broken. It won’t load styles because WordPress is too stupid to realize it is running over HTTPS instead of HTTP.

Anyway, and I’m still struggling with various things. Migrating old content and infrastructure to TLS is still hard.