Construction d'une extension cross-browser

L'introduction de l'API d'extension de navigateur a créé un paysage plus uniforme pour le développement d'extensions des navigateurs. Cependant, parmi les navigateurs qui utilisent les API d'extensions (les principales étant Chrome, Firefox, Opera et Edge), vous verrez des différences à la fois dans l'implémentation de l'API et dans la portée de la couverture. Et puis, Safari utilise ses propres extensions Safari Extensions JS.

Maximiser la portée de votre extension de navigateur signifie la développer pour au moins deux navigateurs différents, voire plus. Cet article examine six des principaux défis rencontrés lors de la création d'une extension multi-navigateurs, et dans chaque cas, suggère comment relever ce défi.

Cet article ne traite pas de la création d'extensions de navigateur pour Safari. Il est possible de partager certaines ressources avec une extension Safari, comme des images et du contenu HTML. Cependant, le codage JavaScript doit être entrepris comme un projet de développement séparé, à moins que vous ne souhaitiez créer votre propre polyfill.

Obstacles lors du codage d'extension multiplateforme

Il y a six domaines que vous devez aborder lorsque vous vous attaquez à une extension multiplateforme :

  • Espace de nommage de l'API
  • Gestion asynchrone des événements API
  • Couverture des fonctions API
  • Clés du Manifest
  • Package d'Extension
  • Publication

Espace de nommage de l'API

Deux espaces de noms API sont utilisés parmi les quatre principaux navigateurs :

  • browser.*, le standard proposé pour l'API d'extensions, utilisé par Firefox et Edge.
  • chrome.* utilisé par Chrome et Opera.

Firefox prend également en charge l'espace de noms chrome.* pour les API compatibles avec Chrome, principalement pour faciliter le portage. Cependant, il est préférable d'utiliser l'espace de nommage browser.*. En plus d'être la norme proposée, browser.* utilise des promesses — un mécanisme moderne et pratique pour gérer les événements asynchrones.

Ce n'est que dans les extensions les plus triviales que l'espace de nommage sera probablement le seul problème multiplateforme qui devra être traité. Il est donc rarement, voire jamais, utile d'essayer d'aborder cette question seul. La meilleure approche consiste à traiter ce problème avec une gestion asynchrone des événements.

Gestion asynchrone des événements API

Il existe deux approches pour gérer les événements asynchrones utilisées par les quatre principaux navigateurs :

  • Promises, le standard proposé pour l'API d'extensions, utilisé par Firefox.
  • callbacks, utilisés par Chrome, Edge, et Opera.

Firefox prend également en charge les rappels pour les API qui prennent en charge l'espace de noms chrome.*. Cependant, il est recommandé d'utiliser des promesses (et l'espace de noms browser.* du navigateur). Des promesses ont été adoptées dans le cadre de la norme proposée. Il simplifie grandement la gestion asynchrone des événements, en particulier lorsque vous devez enchaîner des événements.

Si vous n'êtes pas familier avec les différences entre ces deux méthodes, jetez un coup d'oeil à Apprendre à connaître le Javascript asynchrone : Rappels, promesses et synchronisation/attente ou la page des promesses d'utilisation de MDN.

Alors, comment tirer profit des promesses facilement, alors que Firefox est le seul navigateur qui les supporte ? La solution est de coder pour Firefox en utilisant des promesses et d'utiliser le navigateur WebExtension API Polyfill. Cette polyfill prend en compte l'espace de nommage de l'API et la gestion asynchrone des événements dans Firefox, Chrome et Opera. Au moment de la rédaction du présent document (novembre 2018), le soutien pour Edge était en cours d'élaboration.

Vous installez le navigateur WebExtension API Polyfill dans votre environnement de développement à l'aide de npm ou vous le téléchargez directement depuis les versions de GitHub. Vous référencerez alors browser-polyfill.js dans :

  • manifest.json, pour mettre à disposition des scripts de fond et de contenu.
  • Documents HTML, tels que les popups browserAction ou les pages à onglet.
  • L'appel executeScript dans les scripts de contenu dynamiquement injectés chargés par tabs.executeScript, où il n'a pas été chargé en utilisant une déclaration content_scripts dans manifest.json.

Ainsi, par exemple, ce code manifest.json rend le polyfill disponible pour vos scripts d'arrière-plan :

{
 // ...
 "background": {
   "scripts": [
     "browser-polyfill.js",
     "background.js"
   ]
 }
}

Votre but est de vous assurer que le polyfill s'exécute dans votre extension avant tout autre script qui attend le browser.* API namespace s'exécute.

Pour plus de détails et d'informations sur l'utilisation du polyfill avec un module bundler, voir le readme du projet sur GitHub.

Il existe d'autres options de polyfill mais, au moment d'écrire ces lignes, aucune ne fournit la couverture de l'API Polyfill du navigateur WebExtension. Ainsi, lorsque vous n'avez pas choisi Firefox comme premier choix, vos options sont d'accepter les limitations des polyfills alternatifs, de porter sur Firefox et d'ajouter la prise en charge du cross-browser, ou de développer votre propre polyfill.

Couverture des fonctions API

Les différences dans les fonctions API offertes dans chacun des quatre principaux navigateurs se répartissent en trois grandes catégories :

  • Manque de soutien pour l'ensemble d'une fonction. Par exemple, au moment d'écrire ces lignes, Edge ne prenait pas en charge la fonction de vie privée.
  • Variations in the support for features within a function. For example, at the time of writing, Firefox doesn’t support the notification function method onButtonClicked while Firefox is the only browser that supports onShown.
  • Proprietary functions, supporting browser specific features. For example, at the time of writing, containers was a Firefox-specific feature supported by the contextualIdentities function.

You can find details about the support for the extension APIs among the four main browsers and Firefox for Android on the Mozilla Developer Network Browser support for JavaScript APIs page. Browser compatibility information is also included with each function and its methods, types, and events in the Mozilla Developer Network JavaScript APIs reference pages.

A simple approach to addressing these differences is to limit the functions used in your extension to functions that offer the same functionality across your range of targeted browsers. In practice, for most extensions, this approach is likely to be too restrictive.

The approach you should take where there are differences among the APIs, is to offer either alternative implementations or fallback functionality. Remember that you may also need to do this to allow for differences in API support between versions of the same browser.

The use of runtime checks on the availability of a function’s features is the recommended approach to implementing alternative or fallback functionality. The benefit of performing a runtime check is that if the function becomes available you don’t need to update and redistribute the extension to take advantage of it.

The following code enables you to perform a runtime check:

if (typeof <function> === "function") {
   // safe to use the function
}

Manifest keys

The differences in the manifest.json file keys supported by the four main browsers fall broadly into three categories:

  • Extension information attributes. For example, at the time of writing, Firefox and Opera include the developer key for details about the developer, as well as the author, of the extension.
  • Extension features. For example, at the time of writing, Edge did not support the commands key that enables shortcut keys to be defined for an extension.
  • Key optionality. For example, at the time of writing, the author key is mandatory in Edge but optional in the other main browsers.

Browser compatibility information is included with each key in the Mozilla Developer Network manifest.json key reference pages.

As manifest.json files tend to change little—except for release numbers, which may be different between the various browsers—creating and editing a static version for each browser is usually the simplest approach.

Extension packaging

Packaging an extension for distribution through the browser extension stores is relatively straightforward. Firefox, Chrome, and Opera all use a simple zip format that requires the manifest.json file to be at the root of the zip package. However, submitting to the Microsoft store requires additional packaging of the extension file.

For details on packaging, refer to the guidance on the respective extension’s developer portals.

Publishing

Each of the four major browsers maintains browser extension stores. As a consequence, you need to approach adding and updating your extension in each separately. In some cases you can upload your extension using a utility. Each of the stores also performs a review of your extension to check for security vulnerabilities. The table below summarizes the approach and features of each store:

Registration fee

Upload utility

Pre-publication review process

Account two factor authentication

Firefox

No

web-ext

Automatic, a few seconds1

No

Chrome

Yes

Yes

Automatic, less than an hour

Yes

Opera

No

No

Manual, no SLA provided

No

Edge

Yes

No

Manual, up to 72 hours2

Yes

1) A manual review of the extension takes place after publication, which may result in the extension being suspended where issues that need fixing are found.

2) At the time of writing, Microsoft was only allowing publication of preapproved extensions.

Other considerations

Extension naming

Microsoft requires that extensions have unique names and provides a process for claiming one or more names for your extension through the Windows Dev Center. It may therefore be prudent to reserve an extension name with Microsoft, even if you’re not intending to support Edge immediately. None of the other stores apply name restrictions.

Version numbering

The Firefox and Chrome stores require that each uploaded version has a separate version number. This means that you cannot revert to an earlier version number, if you come across issues in a release.

Share content

Even when you include developing extensions for Safari, there are a number of assets you can potentially share across all of your implementations. These include:

  • Images
  • HTML
  • CSS

Conclusion

When approaching a cross-platform extension development, addressing the fundamental differences between extension API implementations can be addressed by targeting Firefox and using the WebExtension browser API Polyfill. Following this approach you benefit from using API features that are closely aligned with the proposed extensions API standard and offer you the simplicity of promises for asynchronous event handling.

The bulk of your cross-platform work is likely to focus on handling variations among the API features supported by the main browsers. Creating your manifest.json files should be relatively straightforward and something you can do manually. You will then need to account for the variations in extension packaging and the processes for submitting to each of the extension stores.

Following the advice in this article, you should be able to create an extension that works well on all of the four main browsers, enabling you to deliver your extension features to more people.