Extensions & Experiments

This document describes address bar extensions and experiments: what they are, how to run them, how to write them, and the processes involved in each.

The primary purpose right now for writing address bar extensions is to run address bar experiments. But extensions are useful outside of experiments, and not all experiments use extensions.

Like all Firefox extensions, address bar extensions use the WebExtensions framework.

WebExtensions

WebExtensions is the name of Firefox’s extension architecture. The “web” part of the name hints at the fact that Firefox extensions are built using Web technologies: JavaScript, HTML, CSS, and to a certain extent the DOM.

Individual extensions themselves often are referred to as WebExtensions. For clarity and conciseness, this document will refer to WebExtensions as extensions.

Why are we interested in extensions? Mainly because they’re a powerful way to run experiments in Firefox. See Experiments for more on that. In addition, we’d also like to build up a robust set of APIs useful to extension authors, although right now the API can only be used by Mozilla extensions.

WebExtensions are introduced and discussed in detail on MDN. You’ll need a lot of that knowledge in order to build address bar extensions.

Developing Address Bar Extensions

Overview

The address bar WebExtensions API currently lives in two API namespaces, browser.urlbar and browser.experiments.urlbar. The reason for this is historical and is discussed in the Developing Address Bar Extension APIs section. As a consumer of the API, there are only two important things you need to know:

  • There’s no meaningful difference between the APIs of the two namespaces. Their kinds of functions, events, and properties are similar. You should think of the address bar API as one single API that happens to be split into two namespaces.

  • However, there is a big difference between the two when it comes to setting up your extension to use them. This is discussed next.

The browser.urlbar API namespace is built into Firefox. It’s a privileged API, which means that only Mozilla-signed and temporarily installed extensions can use it. The only thing your Mozilla extension needs to do in order to use it is to request the urlbar permission in its manifest.json, as illustrated here.

In contrast, the browser.experiments.urlbar API namespace is bundled inside your extension. APIs that are bundled inside extensions are called experimental APIs, and the extensions in which they’re bundled are called WebExtension experiments. As with privileged APIs, experimental APIs are available only to Mozilla-signed and temporarily installed extensions. (“WebExtension experiments” is a term of art and shouldn’t be confused with the general notion of experiments that happen to use extensions.) For more on experimental APIs and WebExtension experiments, see the WebExtensions API implementation documentation.

Since browser.experiments.urlbar is bundled inside your extension, you’ll need to include it in your extension’s repo by doing the following:

  1. The implementation consists of two files, api.js and schema.json. In your extension repo, create a experiments/urlbar subdirectory and copy the files there. See this repo for an example.

  2. Add the following experiment_apis key to your manifest.json (see here for an example in context):

    "experiment_apis": {
      "experiments_urlbar": {
        "schema": "experiments/urlbar/schema.json",
        "parent": {
          "scopes": ["addon_parent"],
          "paths": [["experiments", "urlbar"]],
          "script": "experiments/urlbar/api.js"
        }
      }
    }
    

As mentioned, only Mozilla-signed and temporarily installed extensions can use these two API namespaces. For information on running the extensions you develop that use these namespaces, see Running Address Bar Extensions.

browser.urlbar

Currently the only documentation for browser.urlbar is its schema. Fortunately WebExtension schemas are JSON and aren’t too hard to read. If you need help understanding it, see the WebExtensions API implementation documentation.

For examples on using the API, see the Cookbook section.

browser.experiments.urlbar

As with browser.urlbar, currently the only documentation for browser.experiments.urlbar is its schema. For examples on using the API, see the Cookbook section.

Workflow

The web-ext command-line tool makes the extension-development workflow very simple. Simply start it with the run command, passing it the location of the Firefox binary you want to use. web-ext will launch your Firefox and remain running until you stop it, watching for changes you make to your extension’s files. When it sees a change, it automatically reloads your extension — in Firefox, in the background — without your having to do anything. It’s really nice.

The web-ext documentation lists all its options, but here are some worth calling out for the run command:

--browser-console

Automatically open the browser console when Firefox starts. Very useful for watching your extension’s console logging. (Make sure “Show Content Messages” is checked in the console.)

-p

This option lets you specify a path to a profile directory.

--keep-profile-changes

Normally web-ext doesn’t save any changes you make to the profile. Use this option along with -p to reuse the same profile again and again.

--verbose

web-ext suppresses Firefox messages in the terminal unless you pass this option. If you’ve added some dump calls in Firefox because you’re working on a new browser.urlbar API, for example, you won’t see them without this.

web-ext also has a build command that packages your extension’s files into a zip file. The following build options are useful:

--overwrite-dest

Without this option, web-ext won’t overwrite a zip file it previously created.

web-ext can load its configuration from your extension’s package.json. That’s the recommended way to configure it. Here’s an example.

Finally, web-ext can also sign extensions, but if you’re developing your extension for an experiment, you’ll use a different process for signing. See The Experiment Development Process.

Automated Tests

It’s possible to write browser chrome mochitests for your extension the same way we write tests for Firefox. One of the example extensions linked throughout this document includes a test, for instance.

See the readme in the example-addon-experiment repo for a workflow.

Further Reading

WebExtensions on MDN

The place to learn about developing WebExtensions in general.

Getting started with web-ext

MDN’s tutorial on using web-ext.

web-ext command reference

MDN’s documentation on web-ext’s commands and their options.

Developing Address Bar Extension APIs

Built-In APIs vs. Experimental APIs

Originally we developed the address bar extension API in the browser.urlbar namespace, which is built into Firefox as discussed above. By “built into Firefox,” we mean that the API is developed in mozilla-central and shipped inside Firefox just like any other Firefox feature. At the time, that seemed like the right thing to do because we wanted to build an API that ultimately could be used by all extension authors, not only Mozilla.

However, there were a number of disadvantages to this development model. The biggest was that it tightly coupled our experiments to specific versions of Firefox. For example, if we were working on an experiment that targeted Firefox 72, then any APIs used by that experiment needed to land and ship in 72. If we weren’t able to finish an API by the time 72 shipped, then the experiment would have to be postponed until 73. Our experiment development timeframes were always very short because we always wanted to ship our experiments ASAP. Often we targeted the Firefox version that was then in Nightly; sometimes we even targeted the version in Beta. Either way, it meant that we were always uplifting patch after patch to Beta. This tight coupling between Firefox versions and experiments erased what should have been a big advantage of implementing experiments as extensions in the first place: the ability to ship experiments outside the usual cyclical release process.

Another notable disadvantage of this model was just the cognitive weight of the idea that we were developing APIs not only for ourselves and our experiments but potentially for all extensions. This meant that not only did we have to design APIs to meet our immediate needs, we also had to imagine use cases that could potentially arise and then design for them as well.

For these reasons, we stopped developing browser.urlbar and created the browser.experiments.urlbar experimental API. As discussed earlier, experimental APIs are APIs that are bundled inside extensions. Experimental APIs can do anything that built-in APIs can do with the added flexibility of not being tied to specific versions of Firefox.

Adding New APIs

All new address bar APIs should be added to browser.experiments.urlbar. Although this API does not ship in Firefox, it’s currently developed in mozilla-central, in browser/components/urlbar/tests/ext/ – note the “tests” subdirectory. Developing it in mozilla-central lets us take advantage of our usual build and testing infrastructure. This way we have API tests running against each mozilla-central checkin, against all versions of Firefox that are tested on Mozilla’s infrastructure, and we’re alerted to any breaking changes we accidentally make. When we start a new extension repo, we copy schema.json and api.js to it as described earlier (or clone an example repo with up-to-date copies of these files).

Generally changes to the API should be reviewed by someone on the address bar team and someone on the WebExtensions team. Shane (mixedpuppy) is a good contact.

Anatomy of an API

Roughly speaking, a WebExtensions API implementation comprises three different pieces:

Schema

The schema declares the functions, properties, events, and types that the API makes available to extensions. Schemas are written in JSON.

The browser.experiments.urlbar schema is schema.json, and the browser.urlbar schema is urlbar.json.

For reference, the schemas of built-in APIs are in browser/components/extensions/schemas and toolkit/components/extensions/schemas.

Internals

Every API hooks into some internal part of Firefox. For the address bar API, that’s the Urlbar implementation in browser/components/urlbar.

Glue

Finally, there’s some glue code that implements everything declared in the schema. Essentially, this code mediates between the previous two pieces. It translates the function calls, property accesses, and event listener registrations made by extensions using the public-facing API into terms that the Firefox internals understand, and vice versa.

For browser.experiments.urlbar, this is api.js, and for browser.urlbar, it’s ext-urlbar.js.

For reference, the implementations of built-in APIs are in browser/components/extensions and toolkit/components/extensions, in the parent and child subdirecties. As you might guess, code in parent runs in the main process, and code in child runs in the extensions process. Address bar APIs deal with browser chrome and their implementations therefore run in the parent process.

Keep in mind that extensions run in a different process from the main process. That has implications for your APIs. They’ll generally need to be async, for example.

Further Reading

WebExtensions API implementation documentation

Detailed info on implementing a WebExtensions API.

Running Address Bar Extensions

As discussed above, browser.experiments.urlbar and browser.urlbar are privileged APIs. There are two different points to consider when it comes to running an extension that uses privileged APIs: loading the extension in the first place, and granting it access to privileged APIs. There’s a certain bar for loading any extension regardless of its API usage that depends on its signed state and the Firefox build you want to run it in. There’s yet a higher bar for granting it access to privileged APIs. This section discusses how to load extensions so that they can access privileged APIs.

Since we’re interested in extensions primarily for running experiments, there are three particular signed states relevant to us:

Unsigned

There are two ways to run unsigned extensions that use privileged APIs.

They can be loaded temporarily using a Firefox Nightly build or Developer Edition but not Beta or Release [source], and the extensions.experiments.enabled preference must be set to true [source]. You can load extensions temporarily by visiting about:debugging#/runtime/this-firefox and clicking “Load Temporary Add-on.” web-ext also loads extensions temporarily.

They can be also be loaded normally (not temporarily) in a custom build where the build-time setting AppConstants.MOZ_REQUIRE_SIGNING [source, source] and xpinstall.signatures.required pref are both false. As in the previous paragraph, such builds include Nightly and Developer Edition but not Beta or Release [source]. In addition, your custom build must modify the Extension.isPrivileged getter to return true. This getter determines whether an extension can access privileged APIs.

Extensions remain unsigned as you develop them. See the Workflow section for more.

Signed for testing (Signed for QA)

Signed-for-testing extensions that use privileged APIs can be run using the same techniques for running unsigned extensions.

They can also be loaded normally (not temporarily) if you use a Firefox build where the build-time setting AppConstants.MOZ_REQUIRE_SIGNING is false and you set the xpinstall.signatures.dev-root pref to true [source]. xpinstall.signatures.dev-root does not exist by default and must be created.

You encounter extensions that are signed for testing when you are writing extensions for experiments. See the Experiments section for details.

“Signed for QA” is another way of referring to this signed state.

Signed for release

Signed-for-release extensions that use privileged APIs can be run in any Firefox build with no special requirements.

You encounter extensions that are signed for release when you are writing extensions for experiments. See the Experiments section for details.

Important

To see console logs from extensions in the browser console, select the “Show Content Messages” option in the console’s settings. This is necessary because extensions run outside the main process.

Experiments

Experiments let us try out ideas in Firefox outside the usual release cycle and on particular populations of users.

For example, say we have a hunch that the top sites shown on the new-tab page aren’t very discoverable, so we want to make them more visible. We have one idea that might work — show them every time the user begins an interaction with the address bar — but we aren’t sure how good an idea it is. So we test it. We write an extension that does just that, make sure it collects telemetry that will help us answer our question, ship it outside the usual release cycle to a small percentage of Beta users, collect and analyze the telemetry, and determine whether the experiment was successful. If it was, then we might want to ship the feature to all Firefox users.

Experiments sometimes are also called studies (not to be confused with user studies, which are face-to-face interviews with users conducted by user researchers).

There are two types of experiments:

Pref-flip experiments

Pref-flip experiments are simple. If we have a fully baked feature in the browser that’s preffed off, a pref-flip experiment just flips the pref on, enabling the feature for users running the experiment. No code is required. We tell the experiments team the name of the pref we want to flip, and they handle it.

One important caveat to pref-flip studies is that they’re currently capable of flipping only a single pref. There’s an extension called Multipreffer that can flip multiple prefs, though.

Add-on experiments

Add-on experiments are much more complex but much more powerful. (Here add-on is a synonym for extension.) They’re the type of experiments that this document has been discussing all along.

An add-on experiment is shipped as an extension that we write and that implements the experimental feature we want to test. To reiterate, the extension is a WebExtension and uses WebExtensions APIs. If the current WebExtensions APIs do not meet the needs of your experiment, then you must create either experimental or built-in APIs so that your extension can use them. If necessary, you can make any new built-in APIs privileged so that they are available only to Mozilla extensions.

An add-on experiment can collect additional telemetry that’s not collected in the product by using the privileged browser.telemetry WebExtensions API, and of course the product will continue to collect all the telemetry it usually does. The telemetry pings from users running the experiment will be correlated with the experiment with no extra work on our part.

A single experiment can deliver different UXes to different groups of users running the experiment. Each group or UX within an experiment is called a branch. Experiments often have two branches, control and treatment. The control branch actually makes no UX changes. It may capture additional telemetry, though. Think of it as the control in a science experiment. It’s there so we can compare it to data from the treatment branch, which does make UX changes. Some experiments may require multiple treatment branches, in which case the different branches will have different names. Add-on experiments can implement all branches in the same extension or each branch in its own extension.

Experiments are delivered to users by a system called Normandy. Normandy comprises a client side that lives in Firefox and a server side. In Normandy, experiments are defined server-side in files called recipes. Recipes include information about the experiment like the Firefox release channel and version that the experiment targets, the number of users to be included in the experiment, the branches in the experiment, the percentage of users on each branch, and so on.

Experiments are tracked by Mozilla project management using a system called Experimenter.

Finally, there was an older version of the experiments program called Shield. Experiments under this system were called Shield studies and could be be shipped as extensions too.

Further Reading

Pref-Flip and Add-On Experiments

A comprehensive document on experiments from the Experimenter team. See the child pages in the sidebar, too.

Client Implementation Guidelines for Experiments

Relevant documentation from the telemetry team.

#ask-experimenter Slack channel

A friendly place to get answers to your experiment questions.

The Experiment Development Process

This section describes an experiment’s life cycle.

  1. Experiments usually originate with product management and UX. They’re responsible for identifying a problem, deciding how an experiment should approach it, the questions we want to answer, the data we need to answer those questions, the user population that should be enrolled in the experiment, the definition of success, and so on.

  2. UX makes a spec that describes what the extension looks like and how it behaves.

  3. There’s a kickoff meeting among the team to introduce the experiment and UX spec. It’s an opportunity for engineering to ask questions of management, UX, and data science. It’s really important for engineering to get a precise and accurate understanding of how the extension is supposed to behave — right down to the UI changes — so that no one makes erroneous assumptions during development.

  4. At some point around this time, the team (usually management) creates a few artifacts to track the work and facilitate communication with outside teams involved in shipping experiments. They include:

  5. Engineering breaks down the work and files bugs. There’s another engineering meeting to discuss the breakdown, or it’s discussed asynchronously.

  6. Engineering sets up a GitHub repo for the extension. See Implementing Experiments for an example repo you can clone to get started. Disable GitHub Issues on the repo so that QA will file bugs in Bugzilla instead of GitHub. There’s nothing wrong with GitHub Issues, but our team’s project management tracks all work through Bugzilla. If it’s not there, it’s not captured.

  7. Engineering or management fills out the Add-on section of the Experimenter page as much as possible at this point. “Active Experiment Name” isn’t necessary, and “Signed Release URL” won’t be available until the end of the process.

  8. Engineering implements the extension and any new WebExtensions APIs it requires.

  9. When the extension is done, engineering or management clicks the “Ready for Sign-Off” button on the Experimenter page. That changes the page’s status from “Draft” to “Ready for Sign-Off,” which allows QA and other teams to sign off on their portions of the experiment.

  10. Engineering requests the extension be signed “for testing” (or “for QA”). Michael (mythmon) from the Experiments team and Rehan (rdalal) from Services Engineering are good contacts. Build the extension zip file using web-ext as discussed in Workflow. Attach it to a bug (a metabug for implementing the extension, for example), needinfo Michael or Rehan, and ask him to sign it. He’ll attach the signed version to the bug. If neither Michael nor Rehan is available, try asking in the #ask-experimenter Slack channel.

  11. Engineering sends QA the link to the signed extension and works with them to resolve bugs they find.

  12. When QA signs off, engineering asks Michael to sign the extension “for release” using the same needinfo process described earlier.

  13. Paste the URL of the signed extension in the “Signed Release URL” textbox of the Add-on section of the Experimenter page.

  14. Other teams sign off as they’re ready.

  15. The experiment ships! 🎉

Implementing Experiments

This section discusses how to implement add-on experiments. Pref-flip experiments are much simpler and don’t need a lot of explanation. You should be familiar with the concepts discussed in the Developing Address Bar Extensions and Running Address Bar Extensions sections before reading this one.

The most salient thing about add-on experiments is that they’re implemented simply as privileged extensions. Other than being privileged and possibly containing bundled experimental APIs, they’re similar to all other extensions.

The top-sites experiment extension is an example of a real, shipped experiment.

Setup

example-addon-experiment is a repo you can clone to get started. It’s geared toward urlbar extensions and includes the stub of a browser chrome mochitest.

browser.normandyAddonStudy

As discussed in Experiments, an experiment typically has more than one branch so that it can test different UXes. The experiment’s extension(s) needs to know the branch the user is enrolled in so that it can behave appropriately for the branch: show the user the proper UX, collect the proper telemetry, and so on.

This is the purpose of the browser.normandyAddonStudy WebExtensions API. Like browser.urlbar, it’s a privileged API available only to Mozilla extensions.

Its schema is normandyAddonStudy.json.

It’s a very simple API. The primary function is getStudy, which returns the study the user is currently enrolled in or null if there isn’t one. (Recall that study is a synonym for experiment.) One of the first things an experiment extension typically does is to call this function.

The Normandy client in Firefox will keep an experiment extension installed only while the experiment is active. Therefore, getStudy should always return a non-null study object. Nevertheless, the study object has an active boolean property that’s trivial to sanity check. (The example extension does.)

The more important property is branch, the name of the branch that the user is enrolled in. Your extension should use it to determine the appropriate UX.

Finally, there’s an onUnenroll event that’s fired when the user is unenrolled in the study. It’s not quite clear in what cases an extension would need to listen for this event given that Normandy automatically uninstalls extensions on unenrollment. Maybe if they create some persistent state that’s not automatically undone on uninstall by the WebExtensions framework?

If your extension itself needs to unenroll the user for some reason, call endStudy.

Telemetry

Experiments can capture telemetry in two places: in the product itself and through the privileged browser.telemetry WebExtensions API. The API schema is telemetry.json.

The telemetry pings from users running experiments are automatically correlated with those experiments, no extra work required. That’s true regardless of whether the telemetry is captured in the product or though browser.telemetry.

The address bar has some in-product, preffed off telemetry that we want to enable for all our experiments — at least that’s the thinking as of August 2019. It’s called engagement event telemetry, and it records user engagements with and abandonments of the address bar [source]. We added a BrowserSetting on browser.urlbar just to let us flip the pref and enable this telemetry in our experiment extensions. Call it like this:

await browser.urlbar.engagementTelemetry.set({ value: true });

Engineering Best Practices

Clear up questions with your UX person early and often. There’s often a gap between what they have in their mind and what you have in yours. Nothing wrong with that, it’s just the nature of development. But misunderstandings can cause big problems when they’re discovered late. This is especially true of UX behaviors, as opposed to visuals or styling. It’s no fun to realize at the end of a release cycle that you’ve designed the wrong WebExtensions API because some UX detail was overlooked.


Related to the previous point, make builds of your extension for your UX person so they can test it.


Taking the previous point even further, if your experiment will require a substantial new API(s), you might think about prototyping the experiment entirely in a custom Firefox build before designing the API at all. Give it to your UX person. Let them disect it and tell you all the problems with it. Fill in all the gaps in your understanding, and then design the API. We’ve never actually done this, though.


It’s a good idea to work on the extension as you’re designing and developing the APIs it’ll use. You might even go as far as writing the first draft of the extension before even starting to implement the APIs. That lets you spot problems that may not be obvious were you to design the API in isolation.


Your extension’s ID should end in @shield.mozilla.org. QA will flag it if it doesn’t.


Set "hidden": true in your extension’s manifest.json. That hides it on about:addons. (It can still be seen on about:studies.) QA will spot this if you don’t.


There are drawbacks of hiding features behind prefs and enabling them in experiment extensions. Consider not doing that if feasible, or at least weigh these drawbacks against your expected benefits.

  • Prefs stay flipped on in private windows, but experiments often have special requirements around private-browsing mode (PBM). Usually, they shouldn’t be active in PBM at all, unless of course the point of the experiment is to test PBM. Extensions also must request PBM access (“incognito” in WebExtensions terms), and the user can disable access at any time. The result is that part of your experiment could remain enabled — the part behind the pref — while other parts are disabled.

  • Prefs stay flipped on in safe mode, even though your extension (like all extensions) will be disabled. This might be a bug in the WebExtensions framework, though.