Mobile/Addons

From MozillaWiki
Jump to: navigation, search

We have decided that having addons is important to our product. We have also talked about shipping core components of the many product as addons.

We have also decided that speed is a feature, and that given a tradeoff we should favour responsiveness over flexibility.

The non-functional requirements of stability, security and user-safety are of course taken as given.

There is plenty of prior art with exactly these constraints to be looked at from IDE development (IntelliJ, Eclipse), embedded systems (OSGi), and Android Intents.

In the spirit of README driven development, I have written a few things down as if they really exist in order to give APIs to addon developers that they would want and use, rather than something that is good or easy for us browser developers.

Addons can extend addons

Addons can depend on other addons.

There are several ways dependencies could be managed, with degrees of difficulty around dependency installation and uninstallation, and startup time.

The simplest scheme is to not allow explicit dependencies. This is the case for Android app intent filters, and iOS URL schemes.

In this scheme, addons can depend on system provided addons being present and opportunistically discovering other addons to contribute to.

Required dependencies are the other extreme. This would require some recursive installation, as seen in npm or apt-get and is therefore out of scope for this document.

A middle ground might be recommended or optional dependencies, where the user could be invited to enhance their addon experience by installing more addons.

Observation: there is a relationship between how ease and sophisticated the installation flow and the size and standalone functionality of the average addon.

Extensions Points and Extensions

An extension point is like a socket. It accepts plugs whose shape is defined by the socket.

It is up to the extension point how it handles more than one, or zero extensions, though it should do so without error.

Nomenclature: an extensions /extends/ an extension point. The extension /provides/ something to the extension point, on the extension point's request.

Addons can have extension points

Each addon can have any number of extension points. Each addon can contribute any number of extensions to other extension points. Each addon can contribute any number of extensions to its own extension points.

The interface between extensions and extension points is a trade-off between flexibility, speed and security.

As an example, consider a system-provided about:home addon that managed the tile images. It might have an extension point called 'firefox.home.tileCollections'.

Extensions that contribute tile collections may be come from 500px, Whimsey Pro, Flickr, Wikicommons.

let registry = browser.addon.registry;
let tileProviders = registry.getExtensions('firefox.home.tileCollections');
selectTileProvider(tileProviders)
  .sendMessage(10) 
  // 10 is just a number, it would 
  // be extension point specific.
  .then((tiles) => {
    // number and type of arguments are extension point specific.
  });

The addon is able to manage and select the tile collection to give to clients of the extension point.

These clients may be the system provided about:home page or a replacement page from another addon.

Addons respond to well defined events

Addon code is executed in response to events from the browser.

Example events: addonDidLoad, addonWillUnload, documentWillLoad, documentDidLoad, addonDidUpdate

Such lifecycle events allow us to install/uninstall addons without restarting.

Extensions are declared in an addons.manifest

  extenions: [
    {
      extends: 'browser.addon.preferences',
      page: '/prefs.html', // add resource
      title: 'Wikipedia',
    },
    {
      extends: 'browser.content.selected',
      provider: 'lookup',
      title: 'Look up in Wikipedia',
    },
    {
      extends: 'browser.search.provider',
      search: 'http://en.wikipedia.org/search?%s',
      title: 'Wikipedia',
    },
    {
      extends: 'browser.autocomplete.provider',
      title: 'Wikipedia',
      provider: '/autcomplete',
    },
  ],

In this contrived example, four extensions are provided in the same Wikipedia add-on. 'extends' specifies the extension point. The other attributes are specific to the extension point.

Extension points are a safe and lightweight way to expose functionality to be augmented by addon developers.

Extensions are lazy-loaded

To enable fast start-up, and responsive UI, metadata can affect the UI without loading any javascript.

Lifecycle events and intra-addon communication mean we can aggressively load and unload addons as needed.

For example, addonDidLoad can be called the first time an extension is used. In this case, 'load' is meant as parsing the first Javascript of a previously un-loaded addon.

For example, if no extensions for a given addon has been sent a message for a given time, the addon can be unloaded.

Addon loading order should be irrelevant.

Implementation strategies

iOS Specific

We have a number of options to us, centred around the options we have for the bridge. In the long run, a mixed strategy will probably be necessary.

We can load WKContentScripts in and around each extension. We currently do this with browser helpers. These would allow Javascript to be injected into a loaded page.

The other is to extend Server to call into a JavascriptCore instance.

This is building a router from a GET request in Swift to a method call in Javascript.

There is also synchronizing addon preferences between Swift and javascript for the most performance critical and user-facing choices.

Which side gets the loading and using the registry of extensions is unclear, so for performance reasons should probably be done in Swift and judiciously used in chrome Javascript.

On the content side, this would be building an API to talk to the chrome context. This would necessarily be asynchronous.

An extension that wants to replace the home screen with circular tiles, but using the same images as provided by another addon may do:

addon.registry.find('firefox.home.tileCollection')
  .then((tiles) => {

    tiles.each((tile) => {
      let url = tile.url;
      let title = tile.title;
      // …
    });

  });
  .

In this example, an addon is replacing about:home with HTML and javscript (not shown).

The javascript snippet above is running in content, it calls the [guaranteed to be singleton] system extension point 'firefox.home.tileCollection'. This, in-turn, is deciding which of 'firefox.home.tileCollections' extensions should get to be displayed.

The same system `firefox.home` addon with the firefox.home.tileCollection singleton extension point would also provide preferences for a preferences UI to manage the firefox.home.tileCollections extensions.

To return to the plug and socket analogy, the firefox.home addon provides an adapter between the producers of home tiles (e.g. Whimsey, Flickr (Y!), 500px) and the consumer of home tiles.

In summary

Extension and extension points provide a safe declarative way for others to add functionality to your code.

Lazy-loading, metadata and good lifecycle management will ameleriorate the performance characteristics of a micro-kernal, but being more efficient than a monolithic system.

Installation sophistication will be required to build very complicated systems.

Even if addons aren't allowed to declared their own extension points, they are very good at documenting what is available for addons extensions to add to.