An overview of Containers for add-on developers

Containers enable users to log in to multiple accounts on the same site simultaneously, and give users the ability to segregate site data for improved privacy and security. At Firefox, we have been working on Containers for quite some time.

We started with platform work in the browser itself and added a basic user interface, then we moved on to a Test Pilot experiment, in which we expanded the feature in an extension. Now we are graduating from Test Pilot and moving our extension, Firefox Multi-Account Containers to addons.mozilla.org.

In addition, we have updated the Firefox platform so that Containers can be managed by extensions.  This means that developers have access to the necessary APIs to create new Container extensions. You can build new extensions on top of Container APIs to meet your needs and use cases! This already happening, with a number of new extensions popping up on addons.mozilla.org.

This post introduces the contextualIdentities API, and walks through an example Container add-on with developers in mind.

What are Containers?

Containers work by giving users the ability to place barriers on the flow of data across sites by isolating cookies, indexedDB, localStorage, and caches within discrete browsing contexts. For instance, the browser storage associated with a user’s Personal Container is separated from the user’s Work Container. In this way, users can take on different identities depending on the context they are in – we refer to this as contextual identity.

The Cookie Store is a key WebExtension API concept that represents storage isolation in the browser. In other browsers, the Cookie Store is used to differentiate private windows from regular windows. In Firefox, the Cookie Store will now also differentiate containers from each other.

Containers are unique to Firefox. These new APIs empower all developers to create new Privacy, Security and Tab management experiences that aren’t available in other browsers today.

Setting up your manifest

To use the contextualIdentities API, add the “contextualIdentities” and “cookies” permissions to your manifest.json so your extension can create Container Tabs associated with a Cookie Store.

"permissions": [
  "cookies",
  "contextualIdentities"
]

Managing Container Tabs

The cookies permission provides access to the cookieStoreId property needed for container tab management. The contexualIdentities API methods return the cookieStoreId that can be used for methods like tab.create.

const containers = await browser.contextualIdentities.query({});
browser.tabs.create({
  cookieStoreId: container[0].cookieStoreId,
  url: "https://example.com"
});

This code creates a new tab that requests https://example.com using the cookieStoreId associated with the user’s first container.

We can also use the cookieStoreId to find all open Container Tabs associated with it:

browser.tabs.query({cookieStoreId});

Monitoring for changes

Firefox and Container add-ons have the ability to create, update and remove containers. Extensions can now monitor these changes, alerting them to update their layout or management interfaces when the list of containers has changed.

To monitor for changes an extension can use the onCreated, onUpdated and onRemoved listeners that are passed the modified container object.

const rebuildEvent = () => {
  this.rebuildMenu();
};
browser.contextualIdentities.onRemoved.addListener((container) => {
  this.removeContainer(container.cookieStoreId);
  rebuildEvent();
});
browser.contextualIdentities.onUpdated.addListener(rebuildEvent);
browser.contextualIdentities.onCreated.addListener(rebuildEvent);
this.rebuildMenu();

Ensure that you use the onRemoved listener to monitor for containers being removed by the user or by other extensions. This is especially important if your extension programmatically creates Container Tabs. For example, an extension may be configured to open a Shopping tab at 5pm every Friday to order beer from the local pub. But, if the user had previously deleted the Shopping tab, then their extension would likely be broken if it did not monitor for the remove event. A sad outcome.

Keeping a consistent UI with colors & icons

We noticed that some Container add-ons uploaded to addons.mozilla.org were using different icons and looked very different than Firefox. In order to make life a little easier for extension developers, and more harmonious for users, we are now exposing the color code and an icon URL you can use to keep the colors you choose consistent with our palette. We think it’s a win-win.

On Firefox and across our Container extensions, we’ve optimized our color palette. Firefox designers have worked hard to choose colors that are visible within dark and light themes for accessibility reasons. When developers use CSS color keywords like “pink” and “blue” the result looks very different from the native colors Firefox provides.

Our updated APIs provide a public icon URL and Hex color code for the container’s color. Please use the “colorCode” property within your extension, since in the future we may update the color codes to match changes in Firefox.

Consistency across Firefox and Container extensions is important to prevent bugs and provide a pleasing user experience. So, as with the color palette, we also provide URLs for our icons in the iconUrl property. We encourage you to work with our assets to provide an easy-to-navigate user interface as we build together with Containers. And we thank you in advance.

In an extension, you can query for the current active containers like this:

const containers = await browser.contextualIdentities.query({});
containers.forEach((container) => {
  console.log(container);
  /* Object {
       name: "Personal",
       icon: "fingerprint", 
       iconUrl: "resource://usercontext-content/fingerprint.svg",
       color: "blue",
       colorCode: "#37adff",
       cookieStoreId: "firefox-container-1" 
     } */
});

Building an example Container add-on

To demonstrate how the Web Extension APIs for Containers can be used,  I’ll now walk you through a Container extension I made. This add-on gives the user the option to automatically redirect HTTP traffic to HTTPS based on a per container preference.

A user may decide to turn on HTTPS for the Banking Container, as seen above. Then when the user is in a Banking tab and visits http://example-bank.com, they will see that their tab actually ends up visiting the HTTPS page instead: https://example-bank.com

In the extension I wrote, I have the following functions:

createIcon(container) {
  const icon = document.createElement("div");
  icon.classList.add("icon");
  const iconUrl = container.iconUrl || "img/blank-tab.svg";
  icon.style.mask = `url(${iconUrl}) top left / contain`;
  icon.style.background = container.colorCode || "#000";
  return icon;
}

async createRow(container) {
  const li = document.createElement("li");
  li.appendChild(this.createIcon(container));
  ...
}

async rebuildMenu() {
  const containers = await browser.contextualIdentities.query({});
  ...
  containers.unshift({
    cookieStoreId: "firefox-default",
    name: "Default"
  });
  const rowPromises = [];
  containers.forEach((container) => {
    rowPromises.push(this.createRow(container));
  });
  ...
}

In my rebuildMenu function I query for all the containers the user has. Then I add an item for default Firefox tabs. When the code calls createIcon with a container object, the iconUrl and colorCode properties can be used to get the associated icon. I use the icon as an SVG mask in CSS for the div, which results in the background color being used for the icon color, as it does in native Firefox menus.

Making Container APIs reliable

Containers is a platform feature that has been disabled by default in Firefox Beta and general release. Until now, extension developers have had to inform the user to enable Containers in about:preferences in order to use the Container APIs. This changes with the release of Firefox Quantum (now in Developer Edition). In Firefox Quantum, if you are a developer creating a Containers extension, your extension enables Containers. So now, when the user installs your extension, they don’t have that additional step. If they try to disable Containers, they will need to first disable your extension.

This provides an assurance to extension developers that the Containers APIs will work when the extension is installed. In the past, users could disable Containers at any point and break all Container-dependent extensions. Now they must disable the extension itself first, in order to disable Container Tabs.

We also made changes to the existing “query”, “get”, “update” and “remove” methods to be more “promise friendly”. Rather than resolving the promise with null or false values, we now reject promises when there are errors. In a situation where a container can’t be found or there is some internal error, we reject the promise of the API, so wrapping API calls in try...catch blocks allows your code to handle these errors:

async getContainer(cookieStoreId) {
  let container;
  try {
    container = await browser.contextualIdentities.get(cookieStoreId);
  } catch (e) {
    /* Containers may be disabled, the API might have failed
       or the container has been deleted. */
    this.warnUser(e);
  }
  return container;
}

Adding containers to existing extensions

Extensions often implement options for their users that aren’t ideal for all browsing activities. Specific extensions may provide privacy, security, or other user interface benefits and enhancements. Maybe the extension is a simple timer to track how often you look at cat gifs in social media during your work day. You might not need it after you’re done with your Work Container. Most extensions require the user to initiate an interaction, and it’s probably not necessary or beneficial for your extension to be “always on.”

Using Containers instead as an indicator of “context” simplifies the user experience of your extension. Extensions that add new features when a specific container is open, are more likely to be actively used because they hook into existing containers. For instance, HTTPS Everywhere’s “always https” option breaks many websites, but it’s always relevant and in context if it’s implemented by default when you’re in the Banking Container.

Whilst extensions can already change their behaviour based on URL, we feel that the security and privacy benefits of containers create new incentive for users to configure settings.

Ideas for new Container extensions

We’re excited about the possibilities of Container extensions for providing context-based enhancements to browsing. When a user wants to be in a Work tab, an extension might be configured to block not safe for work pages. When a user doesn’t want to be reminded of work while at home, an extension may be configured to auto-delete a user’s Work history, but remember the Personal history.

For instance, extensions could:

  • Autoload social pages into a Social tab
  • Remove cookies on tab close when in a Work tab
  • Block key logging scripts when in a Shopping tab
  • Create unique containers for pinned tabs
  • Load multiple versions of a website for QA testing, whilst still providing history and development tools embedded in the browser (instead of headless browser testing).

For example, we have already seen a number of Container extensions created:

  • Containers on the go – gives users a temporary container that lasts for the lifetime of a tab.  The temporary container simulates a private tab as containers are isolated from each other. As soon as the tab is thrown away, the container is deleted, which removes the cookies and other storage associated with it.
  • Cookie AutoDelete – has been modified to be progressively enhanced when containers are enabled, giving users the ability to change cookie deletion settings per container.
  • Conex – a containers implementation of the panorama extension
  • And many more

The Container WebExtension APIs allow developers to rewrite containers themselves. Developers can fork our extension and build improvements on top of it. If you’re looking for ideas, we have a large list of open enhancement requests that extension developers could solve in their own extension using the provided APIs.

As you can see from all these changes and updates, we truly have embraced the use of containers for tab management.

Where next for Container extensions

We have a few more enhancements to make to our APIs.  Here’s what’s in the queue:

Publishing your extension

Terminology

When creating a container extension, we would recommend using “Container Tabs” as a term to explain the add-on instead of using the API name contextualIdentities.

Privacy best practices

If you’ve built an extension that uses Containers, but does so in a way that compromises user privacy, please disclose this. Let users know that your extension doesn’t meet the isolation criteria designed for Containers. For example, moving tabs between containers introduces the risk of exposing the user to additional trackers.

Fixing issues with existing extensions

For your browser extensions to work in Firefox, please remember to check for the cookieStoreId when creating and querying tabs. Some of the reported extension breakage that we’ve seen is due to extensions that copy tab urls and reopen them later without considering the cookieStoreId the tab pertains to. Here’s an example of the issue as reported on Github

I want to thank the countless users, testers, coders and staff that have worked on Containers.

Hit us up with feedback: containers@mozilla.com or on Discourse.

About Jonathan Kingston

Front End Security for Firefox, working on HTTPS adoption, containers and content security.

More articles by Jonathan Kingston…


12 comments

  1. greg

    Until containers automatically create new tabs (Ctrl+T) in the SAME container that the new tab was spawned from, they are a novelty idea that fails in practical application and have little feasibility for everyday use.

    October 3rd, 2017 at 12:03

    1. Jonathan Kingston

      Given that “Conex” already makes this choice, you can download this and get that experience. This article is mostly about celebrating that add-ons can give users the tweaks that they want.

      The reason we don’t make this the default is it is would then be far too easy to de-anonymise yourself.

      Also mentioned in the article we are exploring how extensions like Conex will be able to set the default tab which will make the user experience of this better.

      October 3rd, 2017 at 12:17

      1. Sam Adams

        Could you maybe give an option that the user can select on what the Cntrl + T shortcut will do?

        The default behavior can stay as is, so that it doesn’t break backwards compatibility or the expectations of new users but for those users that want to change, it gives them the flexibility to choose whether the shortcut will open in the same container or the default container. I would rather not have to install an extension to do this, as I already have a quite a few. And the more extensions that are installed, the more overhead you are adding.

        October 4th, 2017 at 11:55

        1. Jonathan Kingston

          We will discuss if/when we will adopt this once the platform change has happened for making this possible. As mentioned in the “Where next for Container extensions” section “Ability for an extension to specify the default tab cookieStoreId” would solve this along with more exciting privacy options like Private containers by default which I actually think solves many problems like migrating data.

          The overhead of WebExtensions is far less than previous add-ons and given that we are still experimenting in this direction I personally wouldn’t worry about installing a really slim extension that just did this.

          That said, we would rather not make this the default for containers users either as it lends itself to bad practice much the same that the default tab can.

          October 4th, 2017 at 14:18

  2. WalterK

    Thank you Firefox team for innovating with Containers!

    While Containers are usable already, it looks like work on getting the last 20% done has slowed down.

    There is a growing backlog of issues tracked in bug 1191418. Major privacy-related ones for me are the missing ability to manage browsing history and cookies by container. Both history and cookies views should be enhanced to allow sorting and filtering by container.

    Also, new Photon design makes it harder to see which container a tab belongs to when multiple tabs are open. The color line is too thin and it needs bottom margins in order not to blend into the tab bottom border.

    October 3rd, 2017 at 18:18

    1. Jonathan Kingston

      The backlog will likely continue to grow given the number of areas that Containers touches in Gecko.
      As we have spread our workload into multiple areas not categorised by that bug like GitHub you likely aren’t seeing the complete picture at all. For example we have ~400 closed bugs on GitHub.
      We are prioritising work based upon factors including: project focus, user feedback and complexity. There has been some progress happening with history changes as of 57, we are tracking changes as they develop.
      Working on these Container APIs will allow developers to rapidly prototype this completely new feature to provide feedback into this radically unique feature that Firefox now has.

      Thanks for reminding me to raise the bug RE the highlight from Photon this has been bugging me since I implemented it: https://bugzilla.mozilla.org/show_bug.cgi?id=1405542

      October 3rd, 2017 at 20:09

  3. Graham Perrin

    Great. This will be a very useful point of reference.

    Might you add more `#content-`… anchors for headings and subheadings?

    I foresee frequent reference to _Privacy best practices_ …

    Many thanks

    October 3rd, 2017 at 21:39

    1. Jonathan Kingston

      Thanks for the reminder, given the wall of text I created this makes total sense.
      Apologies for the length of this article.

      October 4th, 2017 at 02:47

  4. glandium

    I was looking at the APIs the other day to implement an addon to migrate sites to containers, and it seems possible, except that there doesn’t seem to be any awareness of containers/cookie store id for local storage.

    (The usecase I’m after is something along the lines: “I want $site1 and $site2 to always use $container, but since I’ve been using them since well before containers existed, everything related to them (cookies, local storage, etc.) is in the default container, and opening them in $container would mean logging in there, and possibly other things, while I just want to “continue” the session from the default container (and kill the cookies/local storage in the default container)))

    October 3rd, 2017 at 22:02

    1. Jonathan Kingston

      We don’t have the complete API picture here to make this possible. Specifically the main problem to making this possible is IndexedDb and discovering the database names of the pages.
      Ignoring IndexedDb, this would be possible with content scripts which would allow an extension to migrate the data from one container into the background script, then back out into another Container Tab content script.

      However, I would advise against using this given that it causes the data migration privacy issue. If you are to do this, please warn users of the risks, even if an initial migration.

      The overarching Tl;Dr:
      The web is messy, merging storages will allow trackers to cross the container boundary. Managing all the edge cases will likely create broken websites.

      October 4th, 2017 at 03:00

      1. glandium

        Not all uses of containers are for privacy reasons, though. One of my own use cases is to isolate mozilla sites that use auth0 but on which I need to authenticate with *different* accounts, which is a PITA when you don’t use containers (and it was even worse with what we were using before auth0). It would have been more convenient if I had been able to migrate the corresponding cookies automatically and not have to care about it.

        October 4th, 2017 at 03:48

        1. Jonathan Kingston

          Right, which is why we ask if add-ons that don’t care about our privacy and security advantages to provide some form of warning. It’s not a requirement either but it would be good practice to do so, like warning users about breaking cross origin domain security perhaps.

          Trying to migrate storages from a default tab to a container is pretty much like trying to scrape the edges from toast to get back to the original bread.
          Once the user has visited multiple sites, an extension can’t guarantee which storage items are needed not to break the site. So the problem of migrating data in your example is pretty much an all or nothing situation, you can’t safely just take Google cookies and assume the oAuth will work, likewise you can’t delete Google Analytic cookies and fix the privacy issues without risking site breakage.

          However we aren’t stopping you and much like Ad Block Plus or HTTPS Everywhere they are somewhat an imperfect solution which works well, my worry however with any extension like this is that it will spiral into maintaining large lists of edge cases like those extensions have. This is largely why Firefox can’t ship solutions like this natively.

          October 4th, 2017 at 14:12

Comments are closed for this article.