Firefox Add-on SDK

De Banane Atomic
Révision datée du 21 mai 2018 à 22:37 par Nicolas (discussion | contributions) (→‎Signer les add-ons)
(diff) ← Version précédente | Voir la version actuelle (diff) | Version suivante → (diff)
Aller à la navigationAller à la recherche

Liens

Tester sans redémarrer firefox à chaque fois

  1. installer Extension Auto-Installer
  2. Tools → Add-ons → Extension Auto-Installer → Preferences → Automatic Updates = On
Bash.svg
# créé le fichier xpi et fait la mise à jour dans firefox
jpm post --post-url http://localhost:8888/

# à chaque fois qu'un fichier est modifié, créé le fichier xpi et fait la mise à jour dans firefox
jpm watchpost --post-url http://localhost:8888/

Log

Par défaut, le niveau de log des extensions installé est à error.
Ce qui veut dire que les messages console.log ne seront pas affichés dans la Browser Console.

Changer le niveau de log pour toutes les extensions:

  1. about:config
  2. extensions.sdk.console.logLevel = all

Debug

about:debugging

ActionBouton

index.js
var buttons = require('sdk/ui/button/action');
var tabs = require("sdk/tabs");

var button = buttons.ActionButton({
  id: "mozilla-link",
  label: "Visit Mozilla",
  icon: {
    "16": "./icon-16.png",
    "32": "./icon-32.png",
    "64": "./icon-64.png"
  },
  onClick: handleClick
});

function handleClick(state) {
  tabs.open("https://www.mozilla.org/");
}
Bash.svg
mkdir data
# mettre dans le dossier data les images: icon-16.png, icon-32.png, icon-64.png

ToggleButton avec Panel

index.js
var { ToggleButton } = require('sdk/ui/button/toggle');
var panels = require("sdk/panel");
var self = require("sdk/self");

var button = ToggleButton({
  id: "my-button",
  label: "my button",
  icon: {
    "16": "./icon-16.png",
    "32": "./icon-32.png",
    "64": "./icon-64.png"
  },
  onChange: handleChange
});

function handleChange(state) {
  if (state.checked) {
    panel.show({
      position: button
    });
  }
}

var panel = panels.Panel({
  width: 250,
  height: 100,
  contentURL: self.data.url("panel.html"),
  contentStyleFile: self.data.url("panel.css"),
  onHide: handleHide
});

function handleHide() {
  button.state('window', {checked: false});
}

panel.port.on("panel_click", function(id) {
  // écoute les messages panel_click
});
data/panel.html
<!DOCTYPE html>

<html>
  <head>
    <meta charset="utf-8">
  </head>

  <body>
    <div id="1">Menu 1</div>
    <div id="2">Menu 2</div>
  </body>

</html>
data/panel.css
html, body {
    background-color: black;
    color: white;
}

div {
    border-bottom: solid 1px black;
    margin: 10px 0;
}
div:hover {
    border-color: red;
    cursor: pointer;
}
data/panel.js
/*
Listen for clicks in the panel.
*/
document.addEventListener("click", function(e) {
    if (e.target.tagName != "DIV") {
        return;
    }
    
    var id = e.target.getAttribute("id");
    // envoie le message panel_click au panel dans index.js
    self.port.emit("panel_click", id);
});

Modifier la page

index.js
var pageMod = require("sdk/page-mod");

var page = pageMod.PageMod({
  include: "*.mozilla.org",
  contentScriptFile: "./content-script.js"
});

// envoie du message Hello au fichier content-script.js
page.port.emit("Hello");
data/content-script.js
// écrase le contenu de la page
document.body.innerHTML = "<h1>Nouvelle page</h1>";

// écoute des messages Hello
self.port.on("Hello", function() { /* */ });
Si la page web récupéré ne correspond pas, il peut être nécessaire de mettre en place un timer pour attendre que la page soit complètement chargée: setTimeout(maFonction, 1000);
Erreur: workerFor(...) is undefined, recharger la page.

Content script

Déclaration

index.js
// un fichier: data/content-script.js
contentScriptFile: "./content-script.js",
// plusieurs fichiers
contentScriptFile: ["./jquery.min.js", "./content-script.js"],
// variables passées au script
contentScriptOptions: {
    var-id: "valeur",
    var-id2: data.url("fichier.ext")
},
// un fichier css: data/content-style.css
contentStyleFile: "./content-style.css",
data/content-script.js
// variables passées au script
console.log(self.options.var-id);

Tabs

Javascript.svg
var tabs = require("sdk/tabs");

// Listen for tab openings.
tabs.on('open', function onOpen(tab) {
    // à l'ouverture d'un nouvel onglet
});

// Listen for tab content loads.
tabs.on('ready', function(tab) {
    // une fois le contenu de la page chargé
    console.log('tab is loaded', tab.title, tab.url);
});

notifications

Javascript.svg
var notifications = require("sdk/notifications");
var data = require("sdk/self").data;

notifications.notify({
  title: "Titre",
  text: "message",
  iconURL: data.url("icon-16.png")
});

Écouter les changements d'url

Javascript.svg
const tabs = require("sdk/tabs");
const {viewFor} = require('sdk/view/core');
const {modelFor} = require('sdk/model/core');
const {getBrowserForTab, getTabForContentWindow} = require("sdk/tabs/utils");
const {Ci, Cu} = require("chrome");
Cu.import("resource://gre/modules/XPCOMUtils.jsm", this);

var progressListener = {
QueryInterface: XPCOMUtils.generateQI([Ci.nsIWebProgressListener, Ci.nsISupportsWeakReference]),
    onLocationChange: function(aProgress, aRequest, aURI) {
        // getTabForContentWindow cause le warning unsafe/forbidden CPOW usage
        var highLevelTab = modelFor(getTabForContentWindow(aProgress.DOMWindow));
        console.log("onLocationChange ", highLevelTab.url);

        // plus simplement, sans warning
        console.log("onLocationChange ", aURI.asciiSpec);
    }
};

pageMod.PageMod({
  include: "https://www.site.com/*",
  onAttach: startListening
});

function startListening(worker) {
    var lowLevel = viewFor(worker.tab);
    var browser = getBrowserForTab(lowLevel);
    browser.addProgressListener(progressListener);
}

// remove listeners on extension unload
exports.onUnload = function (reason) {
    var tab = tabs.activeTab
    var lowLevelTab = viewFor(tab);
    var browser = getBrowserForTab(lowLevelTab);
    browser.removeProgressListener(progressListener);
};

io/file

Javascript.svg
const fileIO = require("sdk/io/file");

var path = fileIO.join("/dossier1", "dossier2");  // /dossier1/dossier2

if (!fileIO.exists(path)) {
    fileIO.mkpath(path);  // créé tous le chemin des dossiers
}

// lister les fichiers d'un dossier
var allFiles = fileIO.list(path);
for (i = 0; i < allFiles.length; i++) { ... }

Ouvrir l'explorateur de fichier

Javascript.svg
var {Cc, Ci} = require("chrome");

var localFile = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsILocalFile);
localFile.initWithPath("/usr/bin/nemo");
var process = Cc["@mozilla.org/process/util;1"].createInstance(Ci.nsIProcess);
process.init(localFile);
process.run(false, Array(path), 1);  // ouvre l'explorateur de fichier à l'emplacement path

Télécharger

Javascript.svg
var {Cc, Ci, Cu} = require("chrome");
Cu.import("resource://gre/modules/Downloads.jsm");
Cu.import("resource://gre/modules/Task.jsm");

Task.spawn(function () {
    yield Downloads.fetch(pictureUrl, destination);
    console.log("image téléchargée!");
}).then(null, Cu.reportError);

icône

Utilise le fichier icon.png à la racine du dossier de l'add-on, sinon utilise le fichier package.json:

package.json
"icon": "resource://@your-addon-name/data/your-icon-name.png",
The icon may be up to 48x48 pixels in size

Préférences

simple-prefs

package.json
"preferences": [{
      "name": "destinationPath",
      "title": "Path where to save data",
      "type": "directory"
  },
  {
      "name": "fileExplorerPath",
      "title": "Path to the file explorer application",
      "type": "file"
  }]
Javascript.svg
var preferences = require("sdk/simple-prefs").prefs;

var destination = preferences.destinationPath;

jpm

run

Bash.svg
jpm run -b /usr/bin/firefox-developer --binary-args '-url google.fr -jsconsole'
# -b pour forcer le binaire à exécuter au lieu de firefox
# --binary-args pour passer des arguments comme l'url à utiliser et ouvrir la console web

xpi

Bash.svg
# créé le fichier xpi
jpm xpi

init

Bash.svg
# créer un dossier pour l'extension
mkdir MonExtension
# se rendre dans le dossier de l'extension
cd MonExtension

# créé les fichiers package.json, index.js, README.md, test/test-indes.js
jpm init

Installation

Bash.svg
# installer NodeJS Packet Manager
sudo pacman -S npm
# installe aussi nodejs

# installer jpm
sudo npm install jpm --global

Signer les add-ons

  1. Se créer un compte sur https://addons.mozilla.org
  2. Se rendre sur la page Developer Hub → Submit a New Add-on
  3. Envoyer l'add-on zippée sans le dossier .git et sans dossier contenant

Une fois l'add-on validée, un lien est donné pour permettre de télécharger l'add-on signé.

On peut signer un add-on sans le publier sur le site de mozilla.

Autoriser les add-on non-signés

about:config → xpinstall.signatures.required → false

Plus possible depuis la version 43.
Alternative: utiliser les versions ESR, Developer Edition ou Nightly