Sur le papier, une extension de navigateur moderne devrait presque automatiquement être un produit multiplateforme. Chrome, Firefox, Edge et Safari prennent en charge WebExtensions, utilisent un fichier manifest.json similaire et proposent des mécanismes comparables : content scripts, stockage, communication entre contextes, bouton dans la barre d'outils et pages de réglages.
C'est vrai, mais seulement jusqu'à un certain point.
Après avoir publié et porté neuf extensions POLPROG, nous savons désormais que du code partagé ne signifie pas un produit identique. La majeure partie de la logique métier peut être réutilisée, mais le comportement du navigateur, le cycle de vie du processus en arrière-plan, l'interface, les permissions, le packaging et la validation par la boutique restent des problèmes distincts.
Le portefeuille comprend :
- AutoScroll,
- ClickClean,
- DevScope,
- LeakLens,
- LoopIt,
- Loudly,
- OneMoreJump,
- TabZoo,
- TubePilot.
Il ne s'agit pas de neuf copies du même type d'extension. L'ensemble contient des outils pour la vidéo, l'amplification audio, la confidentialité, le nettoyage de données, le développement, la gestion des onglets ainsi qu'un jeu léger. Les problèmes n'étaient donc pas limités à une seule API. Ils concernaient les service workers, les pop-ups, les content scripts, l'audio, le stockage, les icônes, les permissions et le processus de publication.
POLPROG maintient publiquement neuf produits d'extension, d'abord publiés principalement pour Chrome et Firefox, puis également portés vers Edge et Safari. [1][2]
Cet article n'est pas un benchmark de laboratoire. Nous n'avons pas chronométré chaque minute consacrée à chaque boutique depuis le premier commit. Nous n'affirmerons donc pas que Safari coûte exactement 2,7 fois plus cher que Chrome, ni que 94 % du code est partagé. Ce serait une précision artificielle.
Nous pouvons en revanche montrer quelque chose de plus utile : quelles hypothèses se sont révélées fausses, où les véritables pannes sont apparues et comment nous concevrions aujourd'hui l'ensemble du processus depuis le départ.
Dernière vérification des sources : 17 juillet 2026.
Quatre navigateurs, quatre rôles différents
| Plateforme | Rôle dans notre processus | Principal avantage | Piège le plus fréquent |
|---|---|---|---|
| Chrome | Base principale de compatibilité Manifest V3 | Documentation la plus large et plus grand marché d'extensions | Service worker suspendu et exigences strictes sur les permissions |
| Edge | Portage basé sur Chromium | Faible différence de code par rapport à Chrome | Partner Center, métadonnées et certification séparés |
| Firefox | Implémentation indépendante de WebExtensions | Révèle bien les hypothèses dépendantes de Chromium | Différences d'API et de manifest, signature et exigences sur les sources |
| Safari | Produit séparé dans l'écosystème Apple | Accès aux utilisateurs macOS, iOS et iPadOS | Xcode, application conteneur, signature et comportement d'exécution différent |
MDN souligne que l'objectif de WebExtensions est de permettre aux extensions de fonctionner dans les principaux navigateurs après un minimum de modifications, tout en documentant des différences importantes entre Chrome, Firefox et Safari concernant la disponibilité des API, les manifests et le traitement des opérations asynchrones. [7][8]
Cela correspond bien à notre expérience. Les « modifications minimales » sont réalistes pour une extension simple. Plus une extension interagit avec la page, les médias, le cycle de vie des onglets ou le processus d'arrière-plan, moins le mot « minimal » est pertinent.
Leçon 1 : une base de code, oui ; un seul manifest, pas forcément
Le meilleur point de départ est une logique partagée avec des configurations de build séparées.
Dans la pratique, la structure la plus pratique partage :
- la logique des fonctionnalités,
- les modèles de données,
- les modules de stockage,
- la validation,
- la majorité de l'interface du pop-up et des réglages,
- les traductions,
- les tests unitaires.
Restent séparés :
- les variantes du manifest,
- les identifiants des extensions,
- les versions minimales des navigateurs,
- la configuration du background,
- une partie des permissions,
- les icônes et les ressources de boutique,
- le packaging,
- la signature,
- les scripts de publication.
Il ne s'agit pas de maintenir quatre dossiers copiés manuellement. Cela crée rapidement des divergences. Un meilleur modèle consiste à utiliser des sources communes accompagnées d'un générateur ou d'une couche de configuration produisant un artefact distinct pour chaque plateforme.
Firefox exige par exemple ses propres réglages dans browser_specific_settings. Avec Manifest V3, un identifiant d'extension est nécessaire pour la signature et, depuis novembre 2025, les nouvelles extensions envoyées à AMO doivent également déclarer les paramètres liés à la collecte de données. [9]
Safari exige pour sa part un projet Xcode et une application contenant l'extension. Apple fournit un convertisseur WebExtension, mais le résultat doit toujours être considéré comme un projet natif destiné à être signé et distribué dans l'App Store. [3][4]
Un manifest commun constituerait donc une fausse économie. Il est plus simple de maintenir quelques différences petites et explicites qu'un seul fichier rempli d'exceptions et de conditions.
Leçon 2 : Manifest V3 n'a pas unifié le comportement des navigateurs
Manifest V3 a amélioré une partie de la compatibilité et structuré le modèle d'extension, mais il n'a pas créé un runtime identique.
Le changement le plus visible dans Chrome est le remplacement des pages d'arrière-plan persistantes par un service worker d'extension. Google le décrit comme un gestionnaire central d'événements lancé lorsqu'il est nécessaire. Ce processus peut être arrêté, et les variables conservées uniquement en mémoire disparaissent avec lui. [5][6]
Cela entraîne des conséquences directes.
Un code comme :
let activeSessions = new Map();
let currentTab = null;
let isEnabled = false;
ne peut pas être l'unique source de vérité si ces données sont nécessaires après le redémarrage du service worker.
L'état doit être divisé en trois groupes :
- état temporaire pouvant disparaître sans risque ;
- état reconstructible à partir des onglets actifs ou du DOM ;
- état persistant devant être enregistré dans
storage.local,storage.sessionou un emplacement adapté.
L'enregistrement des listeners est tout aussi important. Chrome recommande de déclarer les gestionnaires d'événements dans la portée globale du script afin qu'ils soient enregistrés de manière synchrone au réveil du service worker. Un listener créé uniquement après une initialisation asynchrone peut manquer précisément l'événement ayant déclenché le réveil. [10]
Il est facile de ne pas le remarquer pendant le développement. DevTools ouvert peut influencer le cycle de vie du service worker, tandis que le bug n'apparaît qu'en utilisation normale. Google a décrit des problèmes similaires dans une analyse des migrations MV3 : la suspension du processus peut interrompre les timers et supprimer l'état conservé en mémoire. [11]
La règle principale est donc la suivante :
Le processus d'arrière-plan d'une extension doit être conçu comme un gestionnaire d'événements interruptible, et non comme un petit serveur Node.js fonctionnant sans arrêt.
Leçon 3 : Chrome est un bon point de départ, mais un mauvais environnement de test unique
Chrome constitue une base naturelle pour un projet Manifest V3. Il dispose d'une documentation riche, de nombreux exemples, de bons outils de développement et du plus grand écosystème d'extensions.
Tester uniquement dans Chrome crée toutefois un faux sentiment de compatibilité.
Si le code fonctionne dans Chrome et Edge, cela confirme principalement sa compatibilité avec Chromium. Cela ne confirme pas :
- son bon fonctionnement dans Gecko,
- son bon fonctionnement dans WebKit,
- sa résistance à un cycle de vie de pop-up différent,
- son comportement avec un autre modèle de permissions,
- la bonne mise à l'échelle des icônes,
- le comportement correct de l'audio,
- une sémantique d'API identique.
Le Chrome Web Store exige qu'une extension ait un objectif unique, étroit et facile à comprendre. Il exige également le minimum de permissions, une justification pour chacune et des déclarations cohérentes sur les pratiques de données. Des permissions trop larges peuvent entraîner un refus. Manifest V3 interdit également de charger et d'exécuter du code hébergé à distance. [12]
Ces exigences influencent l'architecture plus tôt qu'on ne le pense. Ajouter « au cas où » l'accès à tous les sites n'est pas neutre. Il faudra ensuite le justifier auprès du réviseur et de l'utilisateur. Une meilleure solution peut être activeTab, des permissions d'hôte facultatives ou un accès limité à des domaines précis.
Chrome nous a donc appris deux choses opposées :
- il constitue une bonne base pour définir la variante MV3 principale ;
- il ne devrait pas déterminer toutes les hypothèses de la couche commune.
Leçon 4 : Edge est le portage technique le plus simple, mais pas un canal de distribution gratuit
Microsoft décrit officiellement le portage d'une extension Chrome existante vers Edge comme un scénario distinct et pris en charge. Les deux navigateurs utilisant Chromium, les différences dans le code principal sont souvent faibles. [13]
Dans nos projets, Edge a généralement demandé le moins de modifications techniques. Cela ne signifiait toujours pas une publication « en un clic ».
L'extension est envoyée dans un Partner Center séparé où il faut :
- téléverser un package ZIP dédié,
- définir la disponibilité et les marchés,
- choisir une catégorie,
- décrire l'objectif unique,
- justifier chaque permission,
- déclarer l'utilisation de code distant,
- décrire les pratiques relatives aux données,
- préparer les fiches dans plusieurs langues,
- téléverser des visuels et captures distincts,
- ajouter des notes pour l'équipe de certification. [14]
Comme Google, Microsoft exige un objectif clairement défini et l'ensemble de permissions le plus réduit possible. Partner Center précise également que des déclarations incomplètes ou incohérentes peuvent prolonger la certification ou entraîner un refus. [14]
Edge illustre donc bien la différence entre le coût du portage et le coût du canal.
Le code peut être presque identique, mais il faut toujours maintenir :
- une fiche produit séparée,
- un statut de validation séparé,
- des métadonnées séparées,
- des variantes graphiques séparées,
- un processus de mise à jour distinct,
- la cohérence des déclarations de confidentialité.
Pour une seule extension, cette charge reste modérée. Pour neuf produits, elle devient un processus opérationnel régulier.
Leçon 5 : Firefox détecte le mieux le code écrit « accidentellement uniquement pour Chrome »
Firefox utilise WebExtensions, mais n'est pas Chromium. C'est précisément ce qui en fait un environnement de test aussi précieux.
La différence la plus connue concerne le namespace et le code asynchrone. Chrome utilisait historiquement chrome et des callbacks, alors que Firefox et Safari utilisaient browser et des promises. Dans Manifest V3, Chrome et Edge ont ajouté la prise en charge des promises pour de nombreuses méthodes, mais toutes les différences n'ont pas disparu. Mozilla recommande d'écrire avec browser et des promises, en utilisant WebExtension browser API Polyfill si nécessaire. [8][15]
Il vaut mieux éviter de disperser dans le projet des conditions comme :
if (isFirefox) {
// ...
} else {
// ...
}
autour de chaque appel d'API. Il est préférable de créer une petite couche d'adaptation :
export const extensionApi = {
storageGet: (keys) => browser.storage.local.get(keys),
storageSet: (value) => browser.storage.local.set(value),
sendMessage: (message) => browser.runtime.sendMessage(message),
};
et de résoudre les différences à un seul endroit.
Firefox impose également davantage de discipline dans la publication. Les extensions doivent être signées, même lorsqu'elles sont distribuées indépendamment dans la configuration standard. AMO valide le package, et du code minifié ou difficile à lire peut nécessiter l'envoi de l'ensemble des sources. Mozilla peut aussi réaliser une inspection manuelle après la publication et rejeter la version actuelle ou une version antérieure. [16][17][18]
Du point de vue de la qualité, c'est un avantage. Le processus oblige le build à être :
- reproductible,
- documenté,
- dépourvu d'obfuscation inutile,
- conforme au comportement déclaré,
- vérifiable sans connaissances internes de l'auteur.
Firefox a également révélé des problèmes dans des situations où Chromium était plus permissif. Ils concernaient le traitement des messages, l'attente d'une réponse promise, les différences de pop-up et les hypothèses concernant la présence de certaines propriétés d'API.
La leçon principale :
Firefox n'est pas une case supplémentaire à cocher une fois le projet terminé. C'est un deuxième runtime indépendant qui doit intégrer les tests tôt.
Leçon 6 : Safari n'est pas un simple portage de boutique, mais une étape produit distincte
Apple prend en charge WebExtensions et fournit un outil de conversion depuis Chrome, Firefox ou Edge. Malgré cela, le processus Safari est fondamentalement différent. [3][4]
L'extension devient une partie d'une application créée dans Xcode. Il faut gérer :
- le projet de l'application,
- la cible de l'extension,
- le bundle identifier de l'application,
- un bundle identifier séparé pour l'extension,
- la signature,
- les profils et certificats,
- l'icône de l'application,
- les entitlements,
- l'archivage,
- App Store Connect,
- la validation App Store.
Apple exige que l'extension fonctionne avec la version actuelle de Safari pour le système concerné, qu'elle n'interfère pas avec l'interface de Safari et qu'elle ne demande pas l'accès à plus de sites que nécessaire. [19]
C'est ici que sont apparus les problèmes les plus concrets de notre portefeuille.
Loudly : l'interface fonctionnait, mais le son n'était pas amplifié
Loudly est une extension qui intervient sur l'audio lu dans une page. Dans la version Safari, l'interface s'affichait, les réglages pouvaient être modifiés et le produit semblait fonctionner. Le signal audio réel n'était toutefois pas amplifié.
Ce type de bug est pire qu'un crash évident. Le smoke test « le pop-up s'ouvre » réussit, mais la promesse principale du produit n'est pas tenue.
Conclusion : pour les extensions multimédias, le test fonctionnel doit mesurer l'effet final et non seulement l'état de l'interface.
TabZoo : un écran blanc à la place de l'application
La version Safari de TabZoo pouvait afficher une page blanche vide. Un tel problème peut provenir d'un échec d'initialisation, des chemins d'assets, d'un mauvais contexte ou d'une exception avant le rendu.
Dans Chromium, une erreur comparable peut rester invisible si l'ordre de démarrage ou la disponibilité de l'API est différent.
Conclusion : chaque vue de l'extension devrait disposer d'un mécanisme de diagnostic minimal et d'une gestion des erreurs d'initialisation. Une page blanche ne peut pas être l'état final du produit.
LoopIt : un clic ouvrait deux interfaces
Dans LoopIt, cliquer sur l'icône dans Safari pouvait faire apparaître un pop-up et un panneau d'extension supplémentaire. C'était un conflit entre un pop-up configuré de manière déclarative et une gestion manuelle du clic.
Dans un navigateur, le comportement semblait correct ; dans un autre, il provoquait une double réaction.
Conclusion : il faut définir clairement qui possède le clic sur le bouton. Soit le bouton dispose d'un default_popup, soit l'événement est traité par le code. Combiner les deux mécanismes exige une conception très consciente.
Ces trois cas montrent pourquoi exécuter le convertisseur d'Apple ne termine pas le portage. Le convertisseur transfère la structure. Il ne confirme pas la sémantique du comportement.
Leçon 7 : les extensions vidéo et audio sont plus complexes que le pop-up ne le laisse penser
Quatre de nos produits dépendent fortement des médias :
- AutoScroll,
- LoopIt,
- Loudly,
- TubePilot.
Chacun doit coordonner plusieurs mondes :
- le DOM de la page,
- l'élément audio ou vidéo,
- le content script,
- le pop-up,
- le processus en arrière-plan,
- les réglages enregistrés,
- la navigation SPA,
- le changement d'onglet actif.
La majorité des bugs apparaît non pas dans une fonction unique, mais à la frontière entre ces contextes.
Exemple de scénario :
- l'utilisateur ouvre le pop-up ;
- le pop-up demande l'état actuel au content script ;
- la page vient de changer de vidéo sans rechargement complet ;
- l'ancien élément vidéo a été supprimé ;
- le content script conserve une référence vers l'ancien élément ;
- le pop-up affiche une valeur correcte, mais modifie un lecteur inactif.
Ajouter un setTimeout supplémentaire ne résout pas le problème. Il faut un modèle d'état explicite :
- identifier l'élément actif,
- observer les changements du DOM,
- reconnecter les gestionnaires,
- confirmer l'exécution de la commande,
- synchroniser l'interface seulement après une réponse,
- restaurer l'état après le redémarrage du processus d'arrière-plan.
Pour des services comme YouTube, il faut également tenir compte de la navigation SPA. Changer de vidéo ne recharge pas toujours le document complet, une initialisation unique du content script est donc insuffisante.
Leçon 8 : l'icône et le pop-up font partie de l'implémentation, pas seulement de l'identité visuelle
Un build multiplateforme peut être techniquement correct tout en ayant une mauvaise apparence.
Pendant nos tests, certaines icônes étaient :
- trop grandes pour leur cadre,
- dépourvues de marge de sécurité,
- collées aux bords,
- visuellement plus petites que d'autres icônes malgré des dimensions de fichier identiques,
- correctes dans la boutique, mais illisibles dans la barre du navigateur.
Les dimensions du PNG ne résolvent pas le problème. Ce qui compte est la taille visuelle du symbole à l'intérieur du canvas.
Il en va de même pour les pop-ups. La même largeur CSS peut être rendue différemment selon :
- le rendu des polices,
- les styles par défaut des contrôles de formulaire,
- le comportement de la scrollbar,
- le calcul de la hauteur,
- les marges système,
- l'échelle de l'écran,
- le mode clair ou sombre.
Nous préparons donc désormais les assets comme un ensemble, et non comme une seule image :
- SVG source,
- variantes raster requises par le manifest,
- zone de sécurité contrôlée,
- icône de l'application Safari,
- visuels de fiche,
- captures pour les boutiques.
L'interface de l'extension doit également posséder des dimensions minimales et maximales et rester lisible lorsque les traductions sont plus longues.
Leçon 9 : la publication est un produit opérationnel distinct
La plus grande erreur d'organisation serait de considérer que le travail se termine après la génération du ZIP.
Chaque boutique a son propre modèle de confiance.
Chrome Web Store
Chrome exige un objectif unique et clair, des permissions minimales, des déclarations sur les données et une politique de confidentialité. Le code exécuté à distance est interdit avec Manifest V3. [12]
La description du produit doit correspondre au comportement réel. Si la fiche promet une fonction que le réviseur ne peut pas utiliser, ce n'est pas un problème de marketing mais de certification.
Firefox Add-ons
AMO valide automatiquement le package, peut exiger les sources du code compilé et conserve la possibilité d'un contrôle manuel ultérieur. Il vaut la peine de corriger les avertissements de sécurité et de confidentialité même lorsqu'il est techniquement possible de poursuivre. [16][17]
Microsoft Edge Add-ons
Partner Center exige des informations séparées pour la disponibilité, la confidentialité et les fiches linguistiques. Chaque permission devrait être justifiée et les informations doivent correspondre au manifest et au comportement de l'extension. [14]
Safari et l'App Store
Safari ajoute une couche application, la signature, les métadonnées App Store et les règles applicables aux applications Apple. L'extension doit fonctionner sur la version actuelle de Safari et limiter l'accès aux sites au strict nécessaire. [19]
Ainsi, neuf extensions ne signifient pas neuf fiches. Avec une couverture complète de quatre plateformes, on peut atteindre 36 combinaisons produit-navigateur, sans compter les versions linguistiques et les variantes séparées pour les appareils Apple.
C'est une raison suffisante pour stocker les métadonnées dans le repository.
Leçon 10 : il faut une matrice de tests, pas une note « vérifier l'extension »
Pour chaque extension, nous conservons le même noyau de tests, puis nous ajoutons les cas propres à ses fonctionnalités.
Smoke test minimal
Après chaque build, nous vérifions :
- l'installation propre ;
- la mise à jour depuis la version précédente ;
- l'apparition de l'icône ;
- l'ouverture du pop-up ;
- l'absence d'erreur d'initialisation ;
- l'enregistrement et la relecture des réglages ;
- le fonctionnement après fermeture et redémarrage du navigateur ;
- le fonctionnement après suspension du background ;
- le comportement sans permission pour la page ;
- le comportement sur une page non prise en charge.
Test de communication
Nous testons séparément :
- pop-up → background,
- pop-up → content script,
- content script → background,
- réponse asynchrone,
- absence d'onglet actif,
- rechargement de l'onglet,
- navigation SPA,
- réinjection du content script,
- fermeture du pop-up avant la réponse.
Test propre à la fonctionnalité
Pour Loudly, déplacer le curseur ne suffit pas. Il faut confirmer le changement du niveau audio.
Pour LoopIt, il faut vérifier que la lecture revient réellement dans la plage choisie.
Pour AutoScroll, il faut tester différents layouts, le plein écran, le changement de vidéo et le défilement manuel.
Pour ClickClean, il faut vérifier l'effet sur de vraies données de test, et pas seulement le message « terminé ».
Pour DevScope et LeakLens, le rapport doit être validé sur des sites présentant différentes politiques CSP, différents nombres de frames et différents ensembles d'en-têtes.
Test spécifique à la plateforme
Enfin viennent les cas propres au navigateur :
- redémarrage du service worker dans Chromium,
- package Firefox signé,
- permissions d'hôte dans Firefox,
- installation et activation de l'extension dans Safari,
- versions macOS et iOS si le produit les prend en charge,
- mise à jour via la boutique plutôt que seulement par sideload local.
Leçon 11 : les tests automatisés ne remplacent pas une véritable QA dans les navigateurs
L'automatisation est nécessaire, mais elle a des limites.
Les tests unitaires couvrent bien :
- la logique métier,
- la validation,
- les migrations de stockage,
- le formatage des données,
- les règles d'activation des fonctions.
Les tests d'intégration peuvent vérifier :
- le manifest,
- la présence des fichiers,
- la communication entre modules,
- les appels d'API de base,
- la complétude des traductions.
Les tests E2E dans Chromium détectent une grande partie des régressions d'interface et de content scripts.
Ils ne confirment cependant pas que :
- Firefox interprète toutes les permissions de la même manière,
- Safari modifie correctement l'audio,
- l'App Store lance la bonne cible,
- le pop-up ne s'ouvre pas deux fois,
- l'icône est correcte dans la barre système.
Les bugs les plus coûteux de nos portages n'étaient pas faciles à détecter avec de simples tests unitaires. Il s'agissait de problèmes d'intégration entre l'extension, le navigateur, le site et le système d'exploitation.
Leçon 12 : la couche de compatibilité doit décrire les capacités, pas les noms des navigateurs
Au début d'un projet, une condition comme celle-ci est tentante :
if (browserName === "safari") {
// comportement spécial
}
Elle est parfois inévitable, mais ne devrait pas constituer le modèle architectural principal.
De meilleures questions sont :
- l'environnement prend-il en charge cette API ?
- le pop-up est-il déclaratif ?
- le background est-il un service worker ?
- une méthode donnée est-elle disponible ?
- la fonction nécessite-t-elle un conteneur natif ?
- la page expose-t-elle l'élément média dont nous avons besoin ?
Un code basé sur les capacités résiste mieux aux changements de version et se teste plus facilement.
Une couche plateforme peut par exemple exposer :
export const capabilities = {
supportsSessionStorage: Boolean(browser.storage?.session),
supportsSidePanel: Boolean(browser.sidePanel),
usesServiceWorker: import.meta.env.BACKGROUND_MODE === "service-worker",
isSafariContainer: import.meta.env.TARGET === "safari",
};
Cela n'élimine pas les différences. Cela les rend explicites et les concentre à un seul endroit.
Leçon 13 : une approche privacy-first simplifie réellement la publication
Les extensions POLPROG sont conçues sans analytics, tracking ni backend externe lorsque ceux-ci ne sont pas nécessaires.
Ce n'est pas uniquement un choix d'image. Cela simplifie :
- la description des flux de données,
- la politique de confidentialité,
- la justification des permissions,
- la revue du code,
- le risque de fuite,
- la cohérence entre la fiche et le code.
Cela ne signifie pas qu'une extension respectueuse de la vie privée n'a pas besoin de documentation. Si une extension lit le contenu d'une page, l'historique, les onglets ou les cookies, elle doit clairement expliquer pourquoi, même si rien n'est envoyé à un serveur.
Chrome et Edge exigent de justifier les permissions et de déclarer les pratiques relatives aux données. Firefox exige des informations sur les permissions liées à la collecte pour les nouvelles extensions. Safari recommande de limiter l'accès aux sites au minimum nécessaire. [9][12][14][19]
Concevoir dès le départ avec des permissions minimales est bien plus simple que de supprimer des droits excessifs après un refus.
Leçon 14 : la pipeline de release doit produire des produits, pas une archive final-final.zip
Avec neuf extensions, le packaging manuel cesse rapidement d'être sûr.
Une bonne pipeline devrait :
- vérifier la version ;
- valider le manifest de base ;
- générer le manifest de la plateforme ;
- construire le code ;
- copier les bonnes icônes ;
- vérifier l'absence de code distant ;
- exécuter les tests ;
- générer des artefacts séparés ;
- créer des checksums ;
- préparer le changelog et les informations de boutique.
Exemple de résultat :
dist/
loudly/
chrome/loudly-1.4.0.zip
edge/loudly-1.4.0.zip
firefox/loudly-1.4.0.xpi
safari/Loudly.xcarchive
La version doit provenir d'une source unique de vérité, mais les artefacts n'ont pas besoin d'être identiques.
Il vaut également la peine de vérifier automatiquement :
- les fichiers interdits,
- les sourcemaps,
- les secrets,
- les permissions inutilisées,
- les traductions manquantes,
- un mauvais chemin d'icône,
- les incohérences de version entre manifests.
L'automatisation ne supprime pas la validation des boutiques. Elle supprime les erreurs qui ne devraient jamais leur parvenir.
Quel navigateur s'est révélé le plus simple ?
La réponse dépend de ce que l'on mesure.
Le début le plus simple : Chrome
Le plus de documentation, d'exemples et d'outils. Un bon choix pour construire la variante MV3 de base.
Le portage de code le plus simple : Edge
Si la version Chrome est correcte, les différences techniques sont souvent faibles. Le travail de publication séparé demeure.
Le meilleur test d'indépendance : Firefox
Ce n'est pas toujours le plus simple, mais il apporte beaucoup de valeur. Il révèle le code accidentellement lié à Chromium et impose une discipline de build.
La charge totale la plus élevée : Safari
Pas seulement à cause de l'API. Xcode, l'application, la signature, l'App Store et les tests sur les appareils Apple s'ajoutent. Les problèmes de Loudly, TabZoo et LoopIt ont aussi montré qu'une interface fonctionnelle ne signifie pas qu'une fonction marche réellement.
Que ferions-nous différemment en commençant aujourd'hui ?
1. Safari ferait partie du plan dès le premier sprint
Il ne devrait pas nécessairement disposer immédiatement de toutes les fonctions. En revanche, il devrait exister un squelette Xcode fonctionnel et des tests réguliers du flux principal.
2. Firefox intégrerait la CI plus tôt
Pas uniquement le build, mais aussi les tests exécutés dans un vrai Firefox.
3. Chaque fonction aurait un propriétaire de l'état
Avant l'implémentation, nous définirions si l'état appartient au content script, au background, au pop-up ou au stockage. De nombreux bugs de communication proviennent d'une réponse floue à cette question.
4. Les permissions seraient conçues avec la fonctionnalité
Chaque nouvelle permission devrait posséder :
- une justification technique,
- un texte pour la boutique,
- un scénario de refus,
- un test du comportement sans autorisation.
5. La fiche ferait partie du repository
La description, le résumé court, les release notes, les mots-clés de recherche, la politique de confidentialité et les visuels devraient avoir un historique de modifications.
6. Nous mesurerions les données dès la première publication
La prochaine étape du rapport devrait inclure :
- le temps entre l'envoi et la publication,
- le nombre de refus,
- la raison de chaque refus,
- le nombre de changements propres aux plateformes,
- le nombre de régressions détectées uniquement hors de Chrome,
- le temps nécessaire pour préparer une mise à jour,
- la taille du code partagé et du code spécifique.
Nous ne reconstruisons pas aujourd'hui ces chiffres de mémoire, car nous voulons séparer l'expérience des statistiques. À l'avenir, ils permettront des rapports encore plus précis.
Architecture recommandée d'une extension multiplateforme
src/
core/
domain/
storage/
validation/
platform/
api.ts
capabilities.ts
messaging.ts
background/
content/
popup/
options/
locales/
manifests/
chrome.json
edge.json
firefox.json
safari.json
store/
chrome/
edge/
firefox/
safari/
tests/
unit/
integration/
e2e/
manual/
Le plus important est la répartition des responsabilités :
corene connaît aucun navigateur précis ;platformtraduit WebExtensions vers une interface commune ;- les interfaces utilisateur sont partagées lorsque cela reste pertinent ;
- les manifests et les ressources de boutique sont explicitement propres à chaque plateforme ;
- les tests manuels possèdent des scénarios écrits et n'existent pas uniquement dans la mémoire de l'auteur.
Verdict
La leçon la plus importante tirée de neuf extensions est simple :
WebExtensions permet de maintenir un produit commun, mais ne dispense pas de créer quatre versions correctes.
Chrome fournit le meilleur point de départ. Edge est généralement le portage technique le plus simple. Firefox est le test le plus précieux de compatibilité réelle. Safari exige de traiter l'extension comme une partie d'une application Apple, et non comme une autre variante ZIP.
Les plus gros problèmes ne sont pas apparus lorsqu'une méthode d'API n'avait pas d'équivalent. Ils sont apparus à l'intersection de :
- cycle de vie du processus,
- état,
- site web,
- interface,
- permissions,
- packaging,
- validation de la boutique.
Une équipe efficace ne commence donc pas par la question : « Comment copier l'extension Chrome dans trois autres boutiques ? » Elle commence par :
- quel est le noyau commun du produit ?
- quelles capacités dépendent de la plateforme ?
- où l'état est-il conservé ?
- comment vérifierons-nous l'effet réel d'une fonctionnalité ?
- comment limiterons-nous les permissions ?
- comment reproduirons-nous chaque build ?
- comment testerons-nous une mise à jour, et pas seulement une installation propre ?
Après ces expériences, nous considérons toujours qu'une base de code unique est la bonne stratégie. Nous ne prétendons simplement pas que quatre navigateurs constituent un environnement unique.
C'est précisément l'acceptation des différences qui permet de partager le code sans diminuer la qualité.
À lire ensuite
Sources
- POLPROG, page d'accueil et informations sur le portefeuille d'extensions
- POLPROG, profil d'éditeur sur Firefox Add-ons
- Apple Developer, Safari Extensions
- Apple Developer, Meet Safari Web Extensions
- Chrome for Developers, Migrate to a service worker
- Chrome for Developers, Extension service worker lifecycle
- MDN, Chrome incompatibilities
- MDN, Differences between API implementations
- MDN,
browser_specific_settings - Chrome for Developers, Events in extension service workers
- Chrome for Developers, Testing MV3 service worker suspension
- Chrome for Developers, Chrome Web Store privacy fields and permissions
- Microsoft Learn, Port a Chrome extension to Microsoft Edge
- Microsoft Learn, Publish a Microsoft Edge extension
- MDN, Build a cross-browser extension
- Mozilla Extension Workshop, Submitting an add-on
- Mozilla Extension Workshop, Signing and distribution overview
- Mozilla Extension Workshop, Add-on Policies
- Apple Developer, App Review Guidelines, Safari extensions
- Mozilla, Approach to Manifest V3
- Pantelaios et Kapravelos, Manifest V3 Unveiled
- Polčák et al., Developers Insight on Manifest V3

