Si vous avez suivi l'article votre première extension, vous avez déjà des notions élémentaires d'écriture une extension. Dans cet article on se propose d'écrire un module un peu plus complexe qui démontrera un peu plus les fonctionnalités de l'API.

L'extension ajoute un nouveau bouton à la barre d'outils Firefox. Lorsque l'utilisateur clique sur le bouton, nous affichons une fenêtre contextuelle qui leur permet de choisir un animal. Une fois qu'ils choisissent un animal, nous remplacerons le contenu de la page actuelle par une image de l'animal choisi.

 

Pour implémenter ce module, il nous faut:

  • définir une action du navigateur, matérialisée par un bouton dans la barre d'outil de Firefox.
    Pour ce bouton, nous avons besoin de:
    • une icône, nommée "beasts-32.png"
    • une popup qui s'ouvrira lorsque le bouton est cliqué. La popup sera constituée d'HTML, de CSS et de JavaScript.
  • définir l'icône de l'extension, nommée "beasts-48.png". Elle apparaîtra dans le gestionnaire de module.
  • écrire un content script, "beastify.js" qui sera injecté dans chaque page web.
    C'est le code qui va effectivement transformer les pages.
  • packager les images d'animaux, afin de remplacer les images de la page web.
    Nous définirons les images comme étant des "web accessible resources" (des ressources accessible par le web) de sorte que la page web puisse y accéder.

Voici une visualisation globale possible de la structure du module:

C'est une simple extension mais qui démontre plusieurs concepts élémentaires de l'API des WebExtensions:

  • ajout d'un bouton à la barre d'outil
  • définition d'une popup à l'aide de HTML, CSS et JavaScript
  • injection des content scripts dans chaque page web
  • communication entre les content scripts et le reste du module
  • packager les ressources dans le module qui peuvent ensuite être utilisées par les pages web

Le code source complet du module est disponble sur GitHub.

Afin d'écrire cet extension, il nous faut Firefox 45 ou plus récent.

Ecriture de l'extension

Créez un nouveau répertoire et positionnez vous dedans:

mkdir beastify
cd beastify

manifest.json

Créez un nouveau fichier nommé "manifest.json" directement dans le répertoire "borderfy" et tapez-y le contenu suivant:

{

  "manifest_version": 2,
  "name": "Beastify",
  "version": "1.0",

  "description": "Adds a browser action icon to the toolbar. Click the button to choose a beast. The active tab's body content is then replaced with a picture of the chosen beast. See https://developer.mozilla.org/en-US/Add-ons/WebExtensions/Examples#beastify",
  "homepage_url": "https://github.com/mdn/webextensions-examples/tree/master/beastify",
  "icons": {
    "48": "icons/beasts-48.png"
  },

  "permissions": [
    "activeTab"
  ],

  "browser_action": {
    "default_icon": "icons/beasts-32.png",
    "default_title": "Beastify",
    "default_popup": "popup/choose_beast.html"
  },

  "web_accessible_resources": [
    "beasts/frog.jpg",
    "beasts/turtle.jpg",
    "beasts/snake.jpg"
  ]

}
  • Les trois premières clés: manifest_version, name, et version, sont obligatoires et contiennent les métadatas élémentaires nécessaires à l'extension.
  • description and homepage_url sont optionnelles mais recommandées: elles apportent de l'information utile à propos de l'extension.
  • icons est optionnelle mais recommandée: elle permet de spécifier l'icône du module qui s'affichera dans le gestionnaire d'extension.
  • permissions liste les permissions nécessaires à cet extension. Ici, uniquement  la permission activeTab est demandée.
  • browser_action spécifie le bouton de la barre d'outil. Nous fournissons trois informations:
    • default_icon est obligatoire et référence l'icône du bouton
    • default_title est optionelle et s'affichera dans une bulle d'aide
    • default_popup est nécessaire si vous souhaitez qu'une popup s'affiche lorsque l'utilisateur clique sur le bouton. C'est notre cas, nous avons donc défini cette clé et l'avons faites pointer sur un fichier HTML inclus dans le module.
  • web_accessible_resources liste les fichiers qui doivent être accessibles aux pages web. Comme cet extension remplace le contenu de la page web par les images inclues dans cet extension, il faut les rendre accessible à la page.

Il est à noter que tous les chemins sont relatifs au fichier manifest.json.

L'icône

L'extension doit posséder une icône qui sera affichée à côté de la liste des extensions du gestionnaire des Add-ons module (vous pouvez afficher le gestionnaire en ouvrant l'URL "about:addons").  Le fichier manifest.json a déclaré une icône pour la barre d'outil, "icons/beasts-48.png".

Créer le répertoire "icons" et enregistrez-y une icône nommée "beasts-48.png".  Il vous est possible d'en utiliser une de notre exemple, provenant du jeu d'icône de Aha-Soft’s Free Retina et utilisable selon les termes de sa license.

Si vous décidez de fournir votre propre icône, sa taille devra être de 48 pixels par 48 pixels. Il vous est aussi possible de fournir une icône de taille 96 pixels par 96 pixels, adpatée aux affichages hautes résolutions, et, devra dans ce cas, être spécifiée par la propriété 96 de l'objet icon du manifest.json:

"icons": {
  "48": "icons/beasts-48.png",
  "96": "icons/beasts-96.png"
}

Le bouton de la barre d'outils

Une icône est nécessaire pour le bouton de la barre d'outils et le manifest.json déclare une icône "icons/beasts-32.png" pour la barre d'outils.

Enregistrez une icône nommée "beasts-32.png" dans le répertoire "icons". Il vous est possible d'en utiliser une de notre exemple, provenant du jeu d'icône IconBeast Lite icon et utilisable selon les termes de sa license.

Si vous ne mettez pas à disposition une popup, alors un événement "click" est propagé au module lorque l'utilisateur clique le bouton. Si vous mettez à disposition une popup l'évenement "click" n'est pas propagé, mais la popup s'ouvre à la place. Nous souhaitons une popup, alors créons là.

La popup

La but de la popup est de permettre à l'utilisateur de choisir une des trois bêtes.

Créez un nouveau répertoire nommé popup à la racine de l'extension. Ce sera l'emplacement du code de la popup. La popup sera constituée de trois fichiers:

  • choose_beast.html qui définit le contenu du panneau
  • choose_beast.css qui stylise le contenu
  • choose_beast.js qui gére le choix de l'utilisateur en exécutant un content script dans l'onglet actif

choose_beast.html

Voici le contenu du fichier HTML:

<!DOCTYPE html>

<html>
  <head>
    <meta charset="utf-8">
    <link rel="stylesheet" href="choose_beast.css"/>
  </head>

  <body>
    <div class="beast">Frog</div>
    <div class="beast">Turtle</div>
    <div class="beast">Snake</div>

    <script src="choose_beast.js"></script>
  </body>

</html>

Un seul élément est défini pour chaque animal. Il est à noter que le ficher CSS et le fichier JS sont inclus depuis ce fichier, tout comme une page web normale.

choose_beast.css

Le CSS fixe la taille de la popup, s'assure que les trois choix remplissent l'espace et les stylise de façon élémentaire:

html, body {
  width: 100px;
}

.beast {
  margin: 3% auto;
  padding: 4px;
  text-align: center;
  font-size: 1.5em;
  background-color: #E5F2F2;
  cursor: pointer;
}

.beast:hover {
  background-color: #CFF2F2;
}

choose_beast.js

Dans le JavaScript de la popup, nous écoutons les événements click. Si le click se produit sur un de nos trois choix d'animaux, nous injectons un content script dans l'onglet actif. Une fois le content script chargé, nous lui envoyons un message contenant le choix de l'animal:

/*
Given the name of a beast, get the URL to the corresponding image.
*/
function beastNameToURL(beastName) {
  switch (beastName) {
    case "Frog":
      return browser.extension.getURL("beasts/frog.jpg");
    case "Snake":
      return browser.extension.getURL("beasts/snake.jpg");
    case "Turtle":
      return browser.extension.getURL("beasts/turtle.jpg");
  }
}

/*
Listen for clicks in the popup.

If the click is on one of the beasts:
  Inject the "beastify.js" content script in the active tab.

  Then get the active tab and send "beastify.js" a message
  containing the URL to the chosen beast's image.

If it's on a button wich contains class "clear":
  Reload the page.
  Close the popup. This is needed, as the content script malfunctions after page reloads.
*/

// execute the script now so it can listen to the messages sent by the code below
browser.tabs.executeScript(null, { file: "/content_scripts/beastify.js" });


document.addEventListener("click", (e) => {
  if (e.target.classList.contains("beast")) {
    var chosenBeast = e.target.textContent;
    var chosenBeastURL = beastNameToURL(chosenBeast);



    var gettingActiveTab = browser.tabs.query({active: true, currentWindow: true});
    gettingActiveTab.then((tabs) => {
      browser.tabs.sendMessage(tabs[0].id, {beastURL: chosenBeastURL});
    });
  }
  else if (e.target.classList.contains("clear")) {
    browser.tabs.reload();
    window.close();
  }
});

Ce script utilise trois fonctions de l'API WebExtension:

  • browser.tabs.executeScript qui injecte le content script, de chemin  "content_scripts/beastify.js" dans l'onglet actif
  • browser.tabs.query pour récupérer l'onglet actif
  • browser.tabs.sendMessage pour envoyer un message au content scripts s'exécutant dans l'onglet actif. Le message contient l'URL pointant vers l'image de la bête choisie.

Le content script

Créez un nouveau répertoire sous la racine du module nommé "content_scripts" et créez un nouveau fichier nommé "beastify.js", contenant:

/*
beastify():
* removes every node in the document.body,
* then inserts the chosen beast
* then removes itself as a listener
*/
function beastify(request, sender, sendResponse) {
  removeEverything();
  insertBeast(request.beastURL);
  browser.runtime.onMessage.removeListener(beastify);
}

/*
Remove every node under document.body
*/
function removeEverything() {
  while (document.body.firstChild) {
    document.body.firstChild.remove();
  }
}

/*
Given a URL to a beast image, create and style an IMG node pointing to
that image, then insert the node into the document.
*/
function insertBeast(beastURL) {
  var beastImage = document.createElement("img");
  beastImage.setAttribute("src", beastURL);
  beastImage.setAttribute("style", "width: 100vw");
  beastImage.setAttribute("style", "height: 100vh");
  document.body.appendChild(beastImage);
}

/*
Assign beastify() as a listener for messages from the extension.
*/
browser.runtime.onMessage.addListener(beastify);

Le content script ajoute un listener sur les messages émis depuis le module (et spécifiquement depuis "choose_beast.js" ci-dessus). Dans le listener, il:

  • supprime tous les éléments dans le document.body
  • crée un élément <img> pointant sur l'URL définie et l'insère dans le DOM
  • supprime le listener du message.

Les bêtes

Enfin, nous devons inclure les images d'animaux.

Créez un nouveau répertoire nommé "beasts", et ajoutez-y les trois images, nommées de façon appropriée. Vous pouvez récuperer les images du dépôt GitHub, ou bien ci-après:

Test

D'abord, vérifiez de nouveau que les bons fichiers sont au bon endroit:

beastify/

    beasts/
        frog.jpg
        snake.jpg
        turtle.jpg

    content_scripts/
        beastify.js

    icons/
        beasts-32.png
        beasts-48.png

    popup/
        choose_beast.css
        choose_beast.html
        choose_beast.js

    manifest.json

A partir de Firefox version 45, il est possible d'installer les WebExtensions temporairement.

Ouvrez "about:debugging" dans Firefox, cliquez sur "Load Temporary Add-on", et choisissez le fichier manifest.json. Vous devriez voir apparaitre l'icône du module dans la barre d'outils de Firefox:

Ouvrez une page web et cliquez sur l'icône, sélectionnez une bête et observez la page web se modifier:

Développement depuis la ligne de commande

Il est possible d'automatiser l'installation temporaire de modules, étape par étape en utilisant l'outil web-ext tool. Essayez ainsi:

cd beastify
web-ext run

Étiquettes et contributeurs liés au document

Étiquettes : 
 Contributeurs à cette page : b1nj, hellosct1, pascalchevrel, Hell_Carlito, DeflatedBimbo
 Dernière mise à jour par : b1nj,