Files
echo-du-huit/docs/solutions/2026-06-29-squelette-de-l-extension-firefox-cubi-solution.md
T

4.7 KiB

Solution : Squelette de l'extension Firefox

Problème résolu

Poser la structure minimale d'une extension Firefox (Manifest V3, sans build step, vanilla JS) chargeable en module temporaire via about:debugging sur NixOS (pas de signature requise en dev). Le clic sur l'icône doit ouvrir une page dédiée autonome — la future liste agrégeant l'Atelier du Huit et la ville de Cugnaux. Ce squelette ne récupère aucune donnée : il câble la chaîne icône → page.

Approche choisie

Entre les deux options du corps de tâche (action.default_popup ou page dédiée), on retient la page dédiée ouverte dans un onglet via background.js :

  • « liste autonome » = vraie page plein écran, lisible, rechargeable, non contrainte aux ~600 px d'un popup qui se ferme à la perte de focus.
  • Cela donne un rôle concret à background.scripts (clé Firefox explicitement demandée) : écouter action.onClicked et faire tabs.create.
  • Contrainte MDN confirmée : action.onClicked ne se déclenche pas si default_popup est défini → on n'ajoute pas default_popup.

Pas de dépendance runtime. Tests de forme avec bun test à la racine, web-ext lint en contrôle dev optionnel via bunx (zéro dépendance ajoutée).

Décisions clés

  • Sous-dossier extension/ : isole l'extension chargeable (ce que voit about:debugging) du tooling bun resté à la racine (tests, package.json). Séparation des concerns nette.
  • strict_min_version: "127.0" : avant Firefox 127, les host_permissions étaient optionnelles (non accordées à l'install). Le seuil 127 garantit qu'elles sont accordées dès l'installation.
  • Namespace browser.* (API Firefox native, basée promesses) — jamais chrome.*. Cohérent dans tout le code.
  • Permission tabs volontairement omise : tabs.create ne la requiert pas. Principe du moindre privilège.
  • Icône SVG unique mappée sur les tailles 48/96 : accepté par Firefox, low-tech.
  • browser_specific_settings.gecko.id présent : identité stable de l'extension.

Patterns à réutiliser

Listener onClicked au niveau racine (synchrone) — l'event page MV3 peut être déchargée ; enregistrer le listener à la racine de background.js (jamais dans un callback async) garantit qu'il est rattaché au réveil :

// extension/background.js
browser.action.onClicked.addListener(() => {
  browser.tabs.create({ url: browser.runtime.getURL("liste.html") });
});

browser.runtime.getURL(...) plutôt qu'une URL concaténée : robuste et sûr.

Test de forme du manifeste (garde-fous structurels) — bun test valide la structure sans navigateur, dont le garde-fou anti-régression default_popup absent :

const manifest = await Bun.file(join(extensionDir, "manifest.json")).json();
test("ne définit PAS default_popup (sinon onClicked devient muet)", () => {
  expect(manifest.action.default_popup).toBeUndefined();
});
test("demande les host_permissions des deux sources", () => {
  expect(manifest.host_permissions).toEqual([/* URLs exactes, ordre vérifié */]);
});

Vérifier aussi l'existence des ressources référencées (existsSync).

Pièges à éviter

  • default_popup + onClicked incompatibles : ajouter default_popup rend onClicked muet → la page ne s'ouvre plus. Protégé par un test dédié.
  • host_permissions < FF127 : sans strict_min_version: "127.0", elles ne sont pas accordées à l'install.
  • Listener dans un callback async : peut être manqué au réveil de l'event page. Toujours au niveau racine.
  • Module temporaire volatil : un add-on chargé via about:debugging disparaît au redémarrage de Firefox → à recharger (nature dev, documentée dans le README).
  • Ressource d'icône manquante : si l'icône SVG n'est pas fournie, retirer les clés icons/action.default_icon sinon erreur de ressource au lint.
  • Worktree sans baseline : le worktree n'héritait pas du README/docs/ canoniques. Résolu par rebase sur main une fois la baseline réelle commitée (328df2d). Penser à vérifier la baseline avant de dupliquer des fichiers.
  • Warning MISSING_DATA_COLLECTION_PERMISSIONS (web-ext) : non bloquant ici, mais Firefox recommande désormais gecko.data_collection_permissions: { "required": ["none"] } pour une extension sans collecte — à prévoir pour les prochaines.
  • Diagnostics IDE bun:test : bun test tourne sans tsconfig, mais l'éditeur signale Cannot find module 'bun:test' faute de bun-types. Purement cosmétique (aucun impact runtime).

Tags

tags: [firefox-extension, manifest-v3, webextension, vanilla-js, bun-test, web-ext, low-tech, nixos]