Skip to main content
 

Addonception: Addons extending addons.

8 min read

Copenhagen airport

I'd like to talk about addons again. This time about extendable addons. This is a natural consequence of go-faster.

I'm going to assume the shape of the manifest is largely like Chrome Extensions and WebExtensions.

Here, I'm proposing two new top-level properties of the manifest.json: extension-points and extenders.

Addons can contribute extenders to extension-points. Addons can consume contributed extenders via extension-points.

Extenders are fragments of the manifest.

The normalized forms of this would be:

{
  "name": "contributing-addon",
  …
  "extenders": {
    "${extension-point-id}": ARRAY_OF_EXTENDER_OBJECTS
  }
  …
}
{
  "name": "host-addon",
  …
  "extension-points": {
    "${extension-point-id}": CONFIG_OBJECT
  }
  …
}

The config object isn't strictly necessary, but may become useful to extension point tooling.

For the sake of Product type readers, the first bit is a clear cut use-case. For the second part, I use a README-driven-development style.

Here's a use case for us to talk about and around.

What

The example here: an addon to parse the content document for micro-formats.

Sidebar for the lay-reader: microformats are specially crafted generic HTML for structured data like addresses or contact details. There is a loose set of standards.

The bulk of the work is done in a microformatting core addon. This can detect and linkify the text, but what to do when the user interacts with it?

Why

We wish to be able to customize the browser based upon territory, or for partnership reasons.

Where the user is taken when she interacts with the microformat is customizable – by default mozilla and its internationalization and partnership choices.

The micro-formats team is part mozilla and part contributor based; they are likely proficient in any language but they already have good implementations of a detector in javascript, and are unlikely to want to start over. They are motivated to see their existing implementations running on iOS and will be able make an addon.

The micro-formats team would like to have a different release cadence to the Firefox iOS team.

The Firefox for iOS team have not the bandwidth to take on a microformat detector in Swift project especially for an addon so ripe for re-use from desktop.

Tapes

User choice

The choices of map, or contact resolver may be ultimately up to the user when they interact with a microformat, but the choices that the user selects from can be provided from several (once competing) places:

  • the very first run defaults. This is provided by the microformats team.
  • the i18n contributors. The most likely useful address finders in one country may be different to the next.
  • the partnership engineering team.

The browser only needs to glue the pieces together.

Concepts. Words.

In the eclipse world, the unit of distibution Firefox calls an addon is a 'plugin'.

In OSGi, it is a 'bundle'.

The other eclipse words would be 'extension' and 'extension point'. How the extensions were joined to the extension points was the job of the registry.

OSGi bundles have 'service provider' and 'service'. There was no explict Service Registry, but it described itself in terms of a 'whiteboad' pattern.

Analogues with Android intents are noted, and differs mainly in granularity and lifecycle control.

I prefer the Eclipse nomenclature simply because its more descriptive.

However, I am aware that other browsers already use 'extension'. Thus, I will now consistently use extender and extension point (or EP).

Back to business

For the rest of this post I'll assume a set of addons:

  • microformat-core
  • microformat-i18n-xx
  • microformat-commercial-xx

How these addons get there we can talk about later on.

The microformat-core addon advertises an extension point for microformat linkifiers. Linkifiers are templates for a given detected object that the core addon can use to send the user to a new tab.

Declaratively, we could define an address resolver like so:

{
  "name": "Open Street Map",
  "template": "http://www.openstreetmap.org/search?query={line1}%2C{line2}%2C{zipcode}%2C{country}"
}

Linkifiers are extenders provided by the i18n-xx and commercial-xx addons, as well as any other addon that the user could install. Importantly microformat-core addon can provide one, as a fallback when all other addons have been uninstalled.

The linkifier extension point is not part of the manifest spec, but for addon review purposes we'd like to be able to declare it in the manifest. But the syntax to declare extension-points and extenders should be in the spec, so more developers can add to the Firefox ecosystem without stepping on each others toes.

For similar review purposes, and convenience for small addon creators, the extenders should be declared in or near the manifest.json file.

{
  "name": "Microformatter"
  …
  "extension-points": {
    "mf-linkifier-address": {
      "selector": "pref"
    },
  },
  "permissions": [
    "extenders"
  ]

Somewhere, in a manifest not far away.

  "extenders": {
    "mf-linkifier-address": {
      "name": "Open Street Map",
      "template": "http://www.openstreetmap.org/search?query={line1}%2C{line2}%2C{zipcode}%2C{country}"
    },
  }
  // no explicit permission is needed to declare extenders in the manifest.
}

The extension point collects all the extenders up from different addons and presents them as a list.

In the microformats-core addon, the extension point is consumed in an event based manner:

function findAddress(detectedObject) {
  var ep = chrome.extenders.get('mf-linkifier-address');
  var linkifiers = ep.all();
  var linkifier = linkifiers[0]; // naïve

  var url = makeURL(linkifier.template, detectedObject);
  return url;
}
// 

It is possible to limit the access to extension points via the manifest.

Thus, we have parts of other addon manifests squirted into an addon that invites it, and the many-to-many relationship is mediated by an addon 'registry'.

Further work

Dinosaurs

Bootstrapping addons

The underlying technology is not particularly difficult: a dictionary of extension-point ids mapping onto a list of extenders, with some events and ordering independence thrown in.

The same objects can be used to implement other parts of the addon manifest interfaces (e.g. content-scripts, background-pages, pageActions).

It starts to become magical when extension points are notified when extenders appear and disappear.

var ep = chrome.extenders.get('firefox.addon-manifests');
ep.onAdd(function (addonManifest) {
  var allExtenders = addonManifest.extenders || {};
  …
  for (extensionPointID in allExtenders) {
      // …
      for (var extender in allExtenders[extensionPointID]) {
        var extender = … // iterating through the manifest
        chrome.extenders.add(extensionPointID, extender);
        // …
      }
  }
});

ep.onRemove(function (manifest) {
  …
  chrome.extenders.remove(extensionPointID, extender);
  …
});

Thus, we have restart-less install.

Developer tools to ease extender

The mf-linkifier-address extension point above declares a "selector".

  var linkifier = ep.choose(); // select based upon a preference page choice.

Swift consumers of extension points

  let ep = registry.get("browserAction")
  let buttonMetadata = ep.choose(); // select based upon a preference page choice.

The integration between javascript and Swift is just nice enough to get things done, but pretty ugly syntax wise.

However, the registry can be kept in Swift or Javascript, and be shared relatively painlessly between Swift consumers of extension points.

Going further, Swift knows enough about the extension point and the contributed extenders that it can generate a preference page for the extension point.

Extenders with js functions

JSContexts can share functions quite comfortably executing functions in the context they were declared in. JSContexts seem to behave almost exactly like a module-pattern module would.

Thus, programatic access to add functions as or part of an extender would be really interesting.

The extender registry provides a simple API to add and remove extenders – simple enough for a manifest parser to do it in an automated way. Providing access through JS may not be advisable for addon-review purposes, so some manifest driven access

Concerns with JS adding extenders

  • Content-scripts access to the registry is needed (WebExtensions needs getManifest()) anyway, but sharing functions across this boundary may be unneccessary or difficult.
  • Contributing addons may expose user-data to other addons.
  • A good fences rule will be needed to ensure one addon doesn't crash another.

Security

  • Addons can only consume the extension-points they declare in their manifest.
  • Addons contribute only the data they want to – extenders do not have access to the extension-points.
  • Extension points starting with 'firefox.' are not accessible from any addon.
  • A policy where multiple addons consume the same extension-point.

Ghetto Gecko Back to our example

So we have an microformats-core addon and a microformats-i18n, microformats-commercial addons.

Now we also have API for home tiles. This would also be a likely candidate for the same pattern.

However, the units of extension are extenders, which are can be distributed as a group in single addon.

  • feature-a
  • feature-b
  • feature-c
  • i18n-xx
  • commercial-xx

The feature addons are shipped with the app.

The locale specific configuration for feature addons can either be distributed from a remote server – and appropriately signed, or distributed with the app and chosen at runtime.

Additional conifg for a, b and c can of course come from other addons the user installs.

This, plus an addon update mechanism gives us great scope to ship code faster, prototype new additions to the spec.

Summary

I've described an extender registry: a fairly simple data structure which can be the enabling technology for addon management and implementation, but also enables a secure and stable extension mechanism for third party addons.

 

Parts needed for Addon infrastructure for Firefox for iOS

10 min read

I've been thinking a lot about addons recently.

This post is exploratory and technical in nature.

Context: I work on the Firefox for iOS team; we have an inkling that Firefox for iOS addons may be a Good Thing™, but have not got anything concrete on our current road map. This particular post is just finding the things that will be hard or impossible and/or what to ask the Webkit developers next time they come to speak to us.

Disclaimer: We may want to adopt the proto-spec that is WebExtensions, supporting some or all of it; we may want to contribute to the spec by adding our own innovations. We may want to ignore the spec altogether. It is too early even for these decisions.

Arches My assumptions on this matter for this post are:

  • We do want to support extensions (otherwise, it's a short conversation, and not go-faster).
  • We are convinced that Apple won't reject the app, or we know what to do if they do.
  • We'd like to be able to follow along with the rest of the Firefox addons ecosystem, for porting reasons.
  • Tradeoffs mean that we can or will never support 100% all platforms Firefox has a presence.

Instead, here I'm going to focus on sketching out the major assumptions that any addons need and the pile of technology that iOS provides.

WebExtensions non-functional requirements start with:

  • Porting add-ons to and from other browsers should be easier.
  • Reviewing add-ons for addons.mozilla.org should be easier.
  • WebExtensions must be compatible with multiprocess Firefox (Electrolysis).
  • Changes to Firefox's internal code should be less likely to break add-ons.
  • WebExtensions should be easier to use than the existing Firefox XPCOM/XUL APIs.

Additionally:

  • Install and uninstall should be done without a restart. The concept of 'restart' doesn't really exist on mobile.
  • Given that it's our stated aim that we want to ship major components as addons, and we may want to extend those major components with other addons, we should allow limited and controlled communication between addons.
  • Addon authors should have a reasonable expectation of secure separation between addons.
  • Speed is always a feature.

Some interesting addons worth thinking about:

  • WhimseyPro
  • µblock Origin
  • PDF.js (it doesn't need to be done, but /could it/).

From an architectural view, my assumptions are:

  • an addon has a manifest.json which declares the components of the addon.
    • html pages loaded in a new tab or popup
    • browser chrome implemented in swift with event listeners in javascript.
    • addon reviewers should have a strong expectation that the addons capabilities is limited to what it declares in manifest.json.
  • an addon can have javascript and css that runs in the content – in the WKWebView
    • it is conditionally run based on the page's location.
    • the js can be run before or after the page is loaded
    • the js can fetch resources from domains specified
  • an addon can have javascript that runs in the background
    • the background can interact with the browser chrome,
  • javascript from the two different contexts can communicate, but by message passing.
  • Wiring javascript to chrome events and access to the addon api is done through the chrome object.
    • the chrome object has api objects added to it, via permissions declared in each addon's manifest.
    • non-persistent background scripts are said to be event scripts.
  • The actual api that developers will use is not being discussed here: just the foundations on which that API will be built.

The pile of bits provided by iOS8 consist of:

  • JSVirtualMachine, which can host multiple JSContext objects, which must all run in the same thread, which doesn't have to be the main thread.
    • Each context has its own global object, but not much else. JSON is provided, but XHR isn't.
    • Objects can be shared between JSContexts in the same JSVirtualMachine, including functions, functions called by functions in the same shared object behave are run in the context they are declared in. Side-effects stay with whichever context the function is declared in. I have not experimented with this twiddling. From a javascript pov, it feels almost exactly like the seperation provided by CommonJS modules.
    • There is a rich API for sharing Swift mutable objects with Javascript, and vice versa. This is upto and including passing function literals back and forth.
  • webView.configuration.userContentController WKUserContentController, WKUserScript, WKScriptMessage and its associated WKScriptMessageHandler.
    • User scripts can be executed each time the content is loaded, before the content or after the document is loaded: there is no sharing of state across reloads.
    • evaluateJavascript: completionHandler: provides a bridge from swift to js.
    • WKScriptMessage and WKScriptMessageHandler provide a bridge from js to swift.
    • WKUserContentController can add WKUserScripts one at a time, but can only remove all scripts at once.
    • Out-the-box, I haven't found a way to allow user-scripts to XHR a domain other than allowed by the server from where the content came. There are workarounds, but none of them are pretty, or cheap. Research and help needed.
  • GCDWebServer. This would be very useful if content scripts could load resources via HTTP, but since we can't mutate the single origin policy of webcontent, it is limited to only loading addon pages into content. If we can, then we could
    • implement chrome.runtime.connect with WebSockets into the GCDWebServer
    • load addon CSS asynchronously by writing script and style tags

Wrong ATM

Our job now is to decide how to make the first list of things out of the second list of things.

Having done a lot of this research I'm pretty convinced that we should run all background and chrome event scripts in a single virtual machine, preferably off the main thread. This would mean all addons run on the same thread, but the extra thread seperation would come at the cost of implementation speed and flexibility.

Since the manifest is meant to be the central auditable glue that ties the addon together, it should almost certainly be parsed in Swift, and the Source of Truth About Addons to be kept there. However, it should be accessible from Javascript, both for WebExtensions spec and to allow us to implement the chrome object in terms of javascript as well as swift. Collectively, the JS and Swift access to the source of truth, I am referring to as the registry.

Given how easy it is to move objects between JSContexts, and the extra seperation it provides it should be obvious to have a JSContext object per addon (shared between all background and event scripts).

However, given that manifest.json access is expected by the addons, and we'll be constructing some of the chrome object in Javascript, we should also have a tying-the-js-knot JSContext that manages the JS side of manifest semantics, and the JS-implemented bits of the chrome object.

I haven't looked at this yet, but we may be able to extend WKUserContentController to allow finer grain control over which WKUserScripts are loaded: I do not know if these can be changed after the new location is known (after redirects) and before the scripts are loaded. This would be the ideal situation.

If not, we have to load all content scripts for all addons for all pages, later becoming low hanging fruit for optimization. The worst case will be passing javascript over the bridge to an AddonContentHelper.js user script; this would be horrible, especially to simulate the AtDocumentStart.

CSS contributed by addons could go either route.

There needs to be some addon runtime made of two WKUserScripts loaded at run AtDocumentStart and AtDocumentEnd.

This would, minimally, construct the chrome object and metadata for each addon before running it. Maximally, it needs to be able to receive javascript and CSS or coordinate which of the addon scripts are going to run, and CSS applied.

We have enough bridges between Swift and the various Javascript contexts that chrome.runtime.sendMessage can be done with a little bookkeeping. I'm not sure about chrome.runtime.connect.

Eye test

Javascript Build tools

I can't decide whether to go balls out bleeding edge and futuristic or so simple its painful but won't scale. This would be primarily for the more capable background and event javascript runtimes.

The JS side of addon management can be cleanly locked away in a different context to the addons, so can structured how we want.

A require function would be a perequisite for nice code and developer productivity, and once we have a require step running, there is essentially no limit to the tooling JS and npm can bring to the table.

For the lay reader: require is the CommonJS way of doing imports. Namespacing and encapsulation in the webdev javascript before ES6 modules has always been shit show.

In fantasy dream land, I'd like to be writing this code in ES6, with Flow Types.

Of course, we are not the first to be aggressively using JSContext: react-native packager may be of use here, which does the ES6->plain old JS->Flowtypes->Packaging, ready for execution in JSContext.

Getting require to work in JSContext may take some work, but would yield benefits almost immediately: by not having 1000 line javascript files.

There will be a bunch of 'standard' APIs we will need to shim, including local storage and XHR.

UX Considerations

Addons bring a lot of choice for the user, and there isn't the same real estate that desktop has.

Addons can contribute to the UIView UI, and can be filtered agressively (url, mimetype?) though it is possible (likely?) that there isn't a clear winner (e.g. pageAction). Additionally, browserActions can come from zero or more addons, so we should expect multiples of these choosable by the user – either by preferences or habits, or pickers.

I have been thinking in terms of a OneOfSome pattern of OneOfSome<T?> views, with UserPreferencedOneOfSome, URLFilteredOneOfSome, FrecencyOneOfSome; perhaps with a submenu to select from when there's more than one, or an ordered list of choices (toggleable or not) maintained in a preference page.

We are already seeing this play out when extending about:home tiles.

Additionally, in the same way Android doesn't force each app to invent its own preference pages, we may want to be able to offer a generic preferences template which localized content can be squirted in from the addon registry.

Further more, addons are exciting! We get to concrete the cowpaths and invent different places to put other people's content, all while handing off most of the work to other people!

Summary

I've explored some of the iOS APIs which we can implement a WebExtensions-like API, and sketched out a possible route to get there. I've identified some places which need further research, and problems that need to be solved.

I am optmistic that this can be done.

Flowers

Links