Avec Jetpack, si tu sais cuisiner un site Web, tu peux aussi bidouiller Firefox
Par Clochix le dimanche 8 novembre 2009, 17:03 - Lézarderies - Lien permanent
Si tu sais cuisiner une page Web, tu peux créer une extension pour Firefox. Ainsi pourrait être résumée la philosophie de Jetpack, un projet actuellement développé par les Laboratoires Mozilla et qui devrait devenir d'ici un an la base du nouveau mécanisme d'extensions de Firefox 4. Le but est de permettre à tout développeur Web de pouvoir bidouiller Firefox sans devoir apprendre un nouveau langage. Petit tour d'horizon à l'occasion de l'arrivée de la version 0.6
Simplifier le bricolage de navigateur
Il n'y a à vrai dire pas beaucoup de différences entre Firefox et une application Web riche. Les deux reposent sur quatre technologies:
- un langage de description d'interface: HTML dans un cas, XUL dans l'autre, mais les deux sont de proches cousins. XUL est juste un peu plus spécialisé, fournit plus d'éléments d'interface riche que HTML[1];
- des feuilles de styles pour gérer le rendu. Ce sont exactement les mêmes. Un thème Firefox n'est qu'un ensemble de feuilles de style et d'images. Personas, intégré à Firefox 3.6, en est une illustration : on peut modifier le look de son navigateur aussi simplement que celui d'un site Web avec Stylish;
- JavaScript, un langage pour rendre le tout dynamique et interagir avec l'utilisateur;
- une plate-forme offrant des fonctionnalités de bas niveau et s'occupant de mettre en musique les trois précédents composants. Dans le cadre d'une application Web, cette plate-forme est le navigateur. Dans le cadre de Firefox, c'est son moteur interne, XulRunner. Mais en fait, c'est la même chose, puisqu'au cœur de Firefox, le même moteur s'occupe aussi bien de l'affichage du Web que de l'interface du navigateur.
La principale différence entre une application Web et Firefox, c'est le langage de description d'interface. Mais avec les évolution de HTML et des bibliothèques de composants JavaScript, HTML peut remplacer XUL dans de nombreux cas. Au passage, Flex d'Adobe utilise exactement la même pile : MXML, un cousin de XUL, CSS, ActionScript (grand frère de JavaScript) et le lecteur Flash comme moteur.
Dès lors, étendre Firefox ne devrait pas être beaucoup plus difficile que développer une application Web, et à la portée de quiconque a des notions de HTML, CSS, JavaScript. En étudiant les extensions les plus populaires sur AMO, des membres des MozLabs se sont aperçu que la vaste majorité ne faisait pas d'appels complexes à des composants bas niveau de la plateforme, et n'avaient pas besoin de toute la puissance du mécanisme actuel d'extension, puissance qui se paie par une petite complexité : même si réaliser une extension n'est pas très compliqué, cela nécessite un apprentissage, aussi bien de nouvelles techniques que d'un environnement de développement spécifique. Jetpack est là pour abaisser cette barrière, rendre beaucoup plus facile, accessible, l'ajout de nouvelles fonctionnalités à Firefox. Il proposera une alternative pour bricoler facilement son navigateur. A terme, le but est que la majorité des extensions utilise cette architecture, le système actuel restant bien sûr disponible pour les développeurs ayant besoin de fonctions plus poussées.
Ainsi, Jetpack permettra à n'importe quel développeur Web d'étendre son navigateur en utilisant les outils qu'il connaît : HTML, CSS, JavaScript, JQuery et Firebug. L'environnement est familier, tout se passe dans Firefox, les mises à jour sont instantanées sans relancer le logiciel. Par ailleurs, JetPack devrait également apporter un gain de sécurité: actuellement, toutes les extensions disponible sur AMO sont contrôlées afin de s'assurer qu'elles ne contiennent pas de code malveillant. C'est un travail long et fastidieux. Jetpack devrait simplifier ce travail, et augmenter le nombre d'internautes qui auront les compétences pour lire le code et s'assurer de son innocuité.
Premiers pas
L'architecture est très similaire à celle de GreaseMonkey : des scripts chargés depuis une page Web s'exécutent dans un bac à sable et interagissent avec le navigateur via une API.
Attention, Jetpack est en plein développement, et certaine des informations indiquées ici seront sans doute obsolète dans quelques mois. Même si les versions se succèdent assez vite, Jetpack est encore assez instable. Il peut ne pas fonctionner avec certaines versions de Firefox, avoir des problèmes de compatibilité avec certaines versions de Firebug, et planter avec certains OS. Comme il embarque des binaires, par exemple pour la gestion de l'audio et de la vidéo, vous risquez d'avoir des soucis sous GNU/Linux en 64bits. C'est parfois frustrant, j'ai eu du mal à le faire fonctionner, mais je suis content de m'être acharné. Par ailleurs, Jetpack étant codé en JavaScript, on peut facilement accéder au code pour comprendre comment il fonctionne et le patcher. Les modifications elles-mêmes sont prises en compte instantanément. Les extraites que je donne plus bas ont été testés avec Jetpack 0.6 alpha et Iceweasel 3.5.3 sous Debian GNU/Linux[2].
Une fois Jetpack installé,
vous accédez à sa tour de contrôle en tapant about:jetpack dans la
barre d'url. Vous deviez avoir quelque chose comme ça: 
Si les onglets ne s'affichent pas, c'est qu'il y a un problème, regardez la console d'erreur, allez faire un tour dans le code ou viendez sur IRC.
Les deux principaux onglets sont le 2° et le 3°:
- Develop fournit un éditeur où développer votre code.
Attention, contrairement aux apparences ce n'est pas encore Bespin mais une
simple
textarea[3]; - Intalled Features liste les extensions installées et vous permet de les gérer.
Une fois votre script développé, il ne vous reste plus qu'à l'installer.
Pour cela, rien de plus simple, comme pour une commande Ubiquity il vous suffit
de créer une page HTML contenant un lien vers le fichier : <link
rel="jetpack" href="toto.js">. En visitant cette page, Firefox vous
proposera d'installer l'extension.
L'API
Toutes les fonctions de Jetpack sont regroupées dans un seul objet, qui sert
d'espace de nom de base. On appelle ensuite les propriétés et les méthodes des
modules disponibles. Par exemple, on obtiendra l'onglet courant avec
jetpack.tabs.focused, soit la propriété focused du
module tabs. Pour l'instant, une dizaine de modules sont déjà
disponibles "en standard", qui ne cessent de s'enrichir. Les plus intéressants
sont:
jetpack.infopermet d'obtenir des informations sur le système;jetpack.jsonpour encoder et décoder des données en JSON (euh, pourtant c'est implémenté nativement depuis FF 3.5 ?);jetpack.notifications.show(message)affiche une notification;jetpack.tabspermet d'intéragir avec les onglets du navigateur : obtenir l'onglet courant, ouvrir une page dans un nouvel onglet, et attacher des fonctions à des évènements. On pourra par exemple obtenir le document en cours avecjetpack.tabs.focused.contentDocument;jetpack.statusBar.append(obj)pour ajouter des objets à la barre de status;jetpack.sessionStorageenregistre des informations dans la session.
Outre jetpack, quelques autres objets sont disponibles dans le
bac à sable, les plus utiles étant jQuery et console,
un dérivé de la fonction du même nom de Firebug. Jetpack utilisera la console
de Firebug s'il la détecte, ou à défaut celle du système.
Extensibilité
Jetpack est prévu pour pouvoir être facilement étendu avec des bibliothèques
tierces. L'espace de nom jetpack.lib est réservé pour cet usage. A
titre d'exemple, une bibliothèque de communication avec Twitter est déjà
fournie. Ces bibliothèques peuvent elle-même communiquer avec des
composants binaires externes, via un système de membranes.
Le futur
Jetpack veut être à la fois une plate-forme stable et un lieu d'expérimentation. Comme en Python, on peut donc utiliser des fonctionnalités expérimentales en les important. Elles resteront dans cet espace le temps que leur API se stabilise. La JEP 13 définit une syntaxe très simple:
jetpack.future.import("toto");
var titi = jetpack.toto.tata();
Une vingtaine de modules sont en travaux, n'hésitez pas à participer à leur élaboration ou si vous ne trouvez pas votre bonheur, à proposer une JEP. Voici un petit tour de quelques fonctionnalités en cours d'implémentation:
La slidebar
La slidebar est une réinvention de la sitebar qui existe
depuis très longtemps. Pour l'instant on y accède via une petite flèche à
gauche du premier onglet. Les slides se présentent comme des onglets
verticaux:
Elle peut contenir de nombreux
slides. Chacun est un simple objet que l'on ajoute à la barre. Par
exemple:
jetpack.future.import("slideBar");
jetpack.slideBar.append({
icon: "", // URL de l'icône
html: "", // contenu du slide
url: "", // pour charger le contenu depuis une URL distante
width: 300, // largeur du slide
persist: true, //
autoReload: true, //
onClick: function(slide){},
onSelect: function(slide){},
onReady: function(slide){}
});
Du multi-média
Le module audio permet d'accéder au micro de l'ordinateur. Le résultat est soit enregistré dans un fichier au format OGG/Vorbis, soit accessible sous forme de flux. Le module vidéo en fera de même pour la vidéo. Quant à Music, il permettra d'accéder aux fichiers multimédias locaux, en fonctions des interfaces de chaque système d'exploitation (par exemple iTunes sur Mac). On pourra par exemple demander à Firefox de jouer l'Hymne avec ces simples lignes:
jetpack.future.import('music');
let tracks = jetpack.music.search('Free Software Song');
jetpack.music.playTrack(tracks0);
Et le stockage local ?
Jetpack implémentera plusieurs moyens pour enregistrer des données. Un
premier, live, permet déjà d'enregistrer des données dans la
session (mais qui seront perdues au redémarrage):
jetpack.storage.live.myData = {hello: "world"};
Pour enregistrer localement tout type de données, un premier module est en cours de développement. C'est une simple base de clés/valeur :
jetpack.storage.simple.prop1 = "toto";
jetpack.storage.simple.prop2 = {a: 'b', b: 'a'};
jetpack.storage.simple.prop3 = [1, 3, 3, 7];
Jetpack 0.6 offre les débuts du module de gestion des paramètres, défini par la JEP 24. L'implémentation est balbutiante, mais la spécification prometteuse: il suffit de déclarer au début de votre script un manifeste décrivant les paramètres. Jetpack créera automatiquement la boîte de dialogue pour les gérer (accessible depuis la liste des extensions installée):
var manifest = {
settings: [
{id:"autosave",
type:"range",
label:"Autosave",
min: 10,
max: 600,
default: 30
}
]
};
(...)
var toto = jetpack.storage.settings.autosave;
Je pense que d'autres modules de stockage devraient rapidement voir le jour, par exemple pour s'interfacer avec des base SQLite ou CouchDB.
Et encore
- la JEP 10 définit l'accès au presse-papier via des getter/setter;
- on accède à la sélection via le module
jetpack.selectiondécrit par la JEP 12; - pageMods permet d'associer des scripts à des pages, en fonction d'expressions régulières, comme avec GreaseMonkey;
- enfin, grosse nouveauté de la 0.6, la gestion de menus qui permet très facilement de rajouter des items dans la barre de menu ou le menu contextuel. Rajouter une action dans le menu contextuel des images est aussi simple que:
jetpack.menu.context.page.on('img').add(function(context)({
label: "Mon menu",
command: function(target) {}
}));
Allez voir la vidéo de présentation dans l'annonce de la sortie de la 0.6. Elle est assez bluffante !
A venir
D'autres JEP sont en cours de rédaction. Par exemple pour gérer les barres d'outil. Et bien sûr, chacun est invité, s'il ne trouve pas son bonheur dans l'API existante, à écrire une proposition et une implémentation d'exemple.
Assez causé, du code !!
Quelques bouts de code pour vous donner une idée de sa simplicité. Jetpack permet vraiment de faire beaucoup de choses en quelques lignes. Pour tester différents modules, j'ai commencé à coder une extension stockant des extraits de pages dans la slidebar. Ce sont mes tâtonnements, un travail en cours juste pour apprendre, donc pas très propres ni au point. Pour en voir plus, vous devriez allez jeter un œil aux nombreuses démos disponibles sur le site. Une galerie devrait également voir le jour très prochainement.
On commence par importer tout ce dont on aura besoin. A terme, une fois que l'API de ces modules sera figée, ils seront disponibles par défaut et on pourra se passer de cette phase
jetpack.future.import("menu");
jetpack.future.import("selection");
jetpack.future.import("slideBar");
jetpack.future.import("storage.settings");
jetpack.future.import("storage.simple");
Le manifeste décrivant les paramètres de l'extension. Jetpack va créer
automatiquement une fenêtre de configuration. En l'état actuel, elle n'est pas
encore complètement fonctionnelle. J'utilise ici un range, nouveau
type de champ de formulaire défini par HTML5.
var manifest = {
settings: [
{id:"autosave", type:"range", label:"Autosave", min: 10, max: 600, default: 30 }
]
};
On crée d'un slide dans la slidebar. Pour ne pas
surcharger le code, le HTML correspondant est dans un fichier à part, qui
contient en outre quelques fonctions Javascript pour gérer les extraits. (le
code du slide contient un <div
id="accordion"></div> à l'intérieur duquel je stocke les
extraits). J'utilise ici le stockage local pour initialiser le slide
en rechargeant les extraits précédemment enregistrés, et je déclenche une
sauvegarde régulière, le délai entre deux enregistrements étant défini dans les
paramètres.
var slider = jetpack.slideBar.append({
url: "snips.html", // la description est dans un fichier séparé
width: 500, // largeur du slide
persist: true, // pour qu'il reste ouvert
autoReload: false, // on ne recharge pas son contenu à chaque fois
onReady: function(slide){ // fonction appelée à la création du slide
current = slide;
var doc = current.contentDocument;
// on recharge le contenu depuis le stockage local :
$('#accordion', doc).html(jetpack.storage.simple.content);
// fonction pour sauvegarder périodiquement le contenu de la diapo
interval = setInterval(function(){
jetpack.storage.simple.content = $('#accordion', doc).html();
}
, jetpack.storage.settings.autosave);
}
});
On ajoute à présent un item dans le menu contextuel pour créer un extrait à partir de la sélection:
jetpack.menu.context.page.add(function(context)({
label: "Create snippet",
command: function(target) {
// on appelle une méthode pour enregistrer l'élément courant
// dans une nouvelle diapo
// la variable current référence notre slide
// astuce : pour accéder aux objets JS définis dans le code HTML
// de la diapo, il faut utiliser wrappedJSObject
current.contentDocument.defaultView.wrappedJSObject.accordionize(jetpack.selection.html, context.document.location.href, null, 'Snippet');
// on sauvegarde le nouveau contenu du slide
jetpack.storage.simple.content = $('#accordion', current.contentDocument).html();
}
}));
Et c'est tout !
Je me suis amusé à proposer une méthode alternative de sélection de contenus : en cochant une case dans la barre de status, la page passe en édition, c'est à dire qu'on peut créer un extrait à partir de tout élément possédant un identifiant. Voici le code pour ajouter une case à cocher dans la barre de status:
jetpack.statusBar.append({
html: 'Snip <input type="checkbox" />',
onReady: function(widget){
// lorsqu'un onglet est sélectionné, on décoche la case
jetpack.tabs.onFocus(function(){
$("input", widget).attr('checked', false);
});
// Gestion du click de la case
$("input", widget).click(function(){
// recherche de tous les éléments qui ont un id
var elmts = $(jetpack.tabs.focused.contentDocument).find('*[id]');
if( this.checked ){
elmts.bind('mouseenter', snipable);
} else {
elmts.unbind('mouseenter', snipable);
}
});
}
})
Ca n'a rien à voir avec Jetpack, mais voici juste le code de la fonction utilisée pour sélectionner les éléments:
function snipable() {
var elmt = $(this);
var css = elmt.css('border');
elmt.css('border', '1px solid red');
elmt.bind('mouseleave', function(){
$(this).css('border', css).unbind('mouseleave').unbind('dblclick');
}).bind('dblclick', function(e){
e.stopPropagation();
console.log('click : ' + elmt.attr('id'));
if (current) {
current.slide();
current.contentDocument.defaultView.wrappedJSObject.accordionize(elmt.html(), tabs.focused.url, null, 'Snippet');
}
return false;
});
}
Et pour installer mon extension, un simple fichier HTML suffit. Si votre extension nécessite d'autres fichiers, référencés dans le script, ils devront être situés au même endroit que le script (pour ce que j'en ai compris, Jetpack cache dans une base SQLite locale les extensions installées. Au moment de l'installation, il vous demande s'il doit se connecter régulièrement au serveur pour récupérer des mises à jour)
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="fr" lang="fr"> <head> <title>Snips</title> <link rel='jetpack' href='snips.js' /> </head> <body></body> </html>
Bon, ça suffit maintenant monsieur
Techniquement, je trouve Jetpack moins révolutionnaire qu'Ubiquity. Ce n'est au final qu'un Greasemonkey complètement intégré à Firefox. Certes très bien intégré, mais le concept n'est pas neuf (le mécanisme d'extensions de Chrome est je crois bâti sur le même modèle). Je ne peux donc m'empêcher de regretter que ce mariage de GreaseMonkey et du Panda n'ait pas eu lieu plus tôt. Car l'idée est excellente et surtout impressionnante de puissance et de simplicité. C'est je crois cette simplicité qui provoque réellement une rupture et mon enthousiasme. Tout développeur Web connaissant JavaScript peut réellement bidouiller le navigateur en quelques minutes. La technique s'efface de plus en plus, pour laisser chacun faire ce qu'il veut de l'outil, pour en démultiplier les possibilités. Et Jetpack est un beau terrain de jeu pour réfléchir et expérimenter de nouvelles pistes pour rendre le bidouillage de Web toujours plus simple, toujours plus accessible à de plus en plus d'internautes. Au final c'est bien ça qui compte. Bon alors, qu'est-ce que vous attendez pour aller essayer ?
Notes
[1] à vrai dire, HTML n'est pas fait pour décrire des interfaces mais des documents. Malheureusement, aucun autre langage spécifique pour les interfaces n'ayant été normalisé, il a été utilisé par défaut et s'est imposé. HTML5 valide cette évolution;
[2] à noter que Jetpack étant prévu pour fonctionner avec
toute application basée sur XulRunner, un test est pour l'instant réalisé "en
dur" pour différencier Firefox et Thunderbid. Si vous utilisez des variantes
portant d'autres nom de ces logiciels, comme par exemple Iceweasel, il vous
faudra modifier le fichier modules/xulapp.js situé dans le
répertoire de l'extension.
[3] Bespin n'est pour l'instant activé que dans la version Mac OS X, mais j'ai réussi à la faire fonctionner sans trop de problèmes sous Nunux.
Commentaires
Très bon article. Tu pourrais mettre le fichier snips.js entier en téléchargement stp ?
Les exemples sur le site web ne sont pas tous à jour pour Jetpack 0.6, je viens de proposer de les mettre à jour... Comme tu le précises c'est assez instable encore et on a vite du code a retouché entre versions.
Je n'ai pas joint le fichier snip.js car le code JS dans le fichier HTML que j'utilise pour la slidebar est buggy, et je doute avoir le temps de me replonger dedans avant le ouikende prochain. J'ai un peu de mal avec la sandbox, et la communication entre le code dans la slidebar et celui dans le script. Et pour tout avouer le code ne fonctionnera pas avant la correction de ce ticket : https://bugzilla.mozilla.org/show_bug.cgi?id=526850 (permettre d'utiliser une url relative comme source du contenu de la slidebar). C'est simple, j'ai soumis un patch, mais donc ça ne fonctionnera pas avec la 0.6. Et si met tout le HTML de la slidebar dans mon script, j'ai d'autres sushis.
Hey :-),
Bonne article en français, c'est très bien.
Toutefois, c'est bien Bespin qui est utilisé comme éditeur de texte (voir extensions/jetpack@labs.mozilla.com/content/js/ext/bespin-embedded.js) :-).