Fino a poco tempo fa, scrivere un'estensione per Firefox non era proprio facilissimo: crearla manualmente voleva dire copiarne una che avesse qualche funzionalità simile alla nostra e modificarla, cercando di capire concetti nuovi o non proprio immediati (chrome? manifest? File XML da modificare? XPCOM?). Oppure si poteva usare l'apposito tool online fornito da Mozilla proprio per creare da zero un'estensione, con il minimo sforzo necessario.
In ogni caso, si doveva poi passare parecchio tempo a carpire i segreti dello sviluppo di estensioni leggendo i (tantissimi) tutorial, partendo ovviamente da quello ufficiale.
Con Firefox 4 le cose migliorano, sia perché scrivere un'estensione è adesso più semplice, sia perché le estensioni vogliono diventare sempre più interessanti, proprio perché Mozilla, strategicamente, sta sempre più puntando sul fare del proprio browser una vera e proprio piattaforma di sviluppo.
In questo articolo esamineremo il cosiddetto "Add-on SDK" meglio conosciuto come JetPack, che si propone come strumento principe per la creazione di estensioni. Per essere precisi, fino alla versione 0.9 Jetpack supportava anche Firefox 3.x. Dalla versione 1 è invece stato deciso di abbandonare il supporto alla versione 3 e concentrarsi soltanto sulla versione 4.
L'occorrente per sviluppare estensioni con Jetpack
Per scrivere un'estensione con Jetpack non occorre imparare nuovo tecnologie o nuove convenzioni, ma soltanto un modo di lavorare. Le tecnologie infatti sono quelle che già conosciamo bene: tanto JavaScript, CSS e HTML.
Uno dei responsabili del progetto Jetpack, Kevin Dangoor, è anche il padre di due altri progetti piuttosto famosi: TurboGears e CommonJS e Jetpack ne risente in modo rilevante.
Per scrivere estensioni con Jetpack, prima di tutto dobbiamo scaricarlo e installarlo. Le istruzioni sono piuttosto semplici.
Il pacchetto installato ci mette a disposizione un unico programma command line (chiamato cfx) e un discreto numero di librerie JavaScript sotto forma di moduli CommonJS che vi permetteranno di avere accesso alle funzionalità e proprietà del browser in maniera elegante e standardizzata (ovvero, se capite cosa voglio dire, senza passare dall'interfaccia XPCOM).
cfx (scritto in Python e con dei paradigmi di utilizzo che torneranno familiari a chi ha usato TurboGears) è il sistema con il quale comanderete l'SDK di effettuare delle operazioni come:
Operazione | Descrizione |
---|---|
init |
Crea una nuova estensione |
test |
Permette di provare le estensioni |
run |
Manda in esecuzione l'estensione |
xpi |
Crea il file .xpi da distribuire a tutto il mondo |
Completiamo la configurazione di Jetpack: dobbiamo "attivare" l'ambiente di sviluppo e indicare a Jetpack dove si trovino le sue librerie. Per fare questo è sufficiente portarsi nella directory dove lo abbiamo scompattato ed eseguire il comando:
source bin/activate
e vedremo cambiare il prompt del terminale.
Un'estensione di prova
Entriamo nel vivo e realizziamo un'estensione che abbia come obiettivo quello di aggiungere una voce nel menu contestuale del browser. Cliccando sulla voce di menu si apriranno, in una nuova finestra, tutti i link presenti nella pagina corrente. La particolarità sarà però che i link saranno aperti in effetti in un nuovo tab e in background.
Creiamo una directory per il nostro progetto, entriamoci, ed eseguiamo il comando:
cfx init
Il comando creerà una struttura di file e directory: la directory data potrà contenere file html, immagini, file CSS o script JS. Nella directory lib ci sarà invece il file main.js: il punto di ingresso vero e proprio dell'estensione.
Apriamo il file con il nostro editor preferito; ciò che si trova all'interno è solo un piccolo esempio e non è necessario: cancellate tutto eccetto l'ultima riga, quella che contiene l'istruzione console.log()
.
Siamo già pronti per effettuare una prova. Per provare l'estensione man mano che la aggiorneremo dovremo usare ancora cfx
con il comando run
. Esso farà partire una nuova istanza di Firefox che avrà "magicamente" installata la nostra estensione.
Il browser partirà utilizzando un profilo temporaneo, generato volta per volta. Questo vuol dire che non saranno salvati i cookie, e che sarà inutile installarvi delle estensioni. È utile dare comunque un'occhiata all'output di:
cfx --help
per intervenire su questo aspetto. Se abbiamo installati sia Firefox3 sia Firefox4, per accertarci che cfx
usi la versione 4, utilizziamo il comando:
cfx run -b /usr/bin/firefox-4.0
Una volta che il browser sarà partito, nel terminale vedremo apparire la scritta "The add-on is running", il che vorrà dire che il nostro main.js
è stato correttamente interpretato. Eureka!
Come abbiamo detto all'inizio, Jetpack fa uso dello standard CommonJS per l'utilizzo (ed eventualmente la scrittura) di moduli JavaScript che forniscono funzionalità aggiuntive alle nostre applicazioni. Ogni modulo di cui avremo bisogno sarà incluso usando la funzione:
require(nome del modulo)
La prima cosa che faremo sarà quella di modificare il menu contestuale di ogni pagina web. Per fare questo avremo bisogno del modulo context-menu
, che includeremo in questo modo:
var cMenu = require("context-menu");
Per sapere quali e quanti moduli abbiamo a disposizione, possiamo sfogliare la (non ottima) documentazione alla voce API Overview, pagina che adesso è utile leggere.
Aggiungiamo dunque una nuova voce al menu contestuale con il seguente codice:
cMenu.Item({
label: "Open links in a new window",
});
Possiamo provare adesso e vedere la nuova voce di menu, assolutamente inutile. Il prossimo passo è quindi quello di assegnare qualche operazione al momento in cui la nuova voce viene selezionata, ovvero quando su di essa sarà rilevato l'evento click.
contentScript
Prima di assegnare delle azioni al nostro menu, occorre introdurre un concetto molto importante: il JavaScript dell'estensione (quello che stiamo scrivendo all'interno di main.js
) è completamente fuori dal contesto della pagina presente all'interno del vostro browser. Qualsiasi tentativo di accedere, per esempio, all'oggetto globale window
direttamente dal main.js
genererebbe un errore.
L'arcinota funzione alert()
è però all'interno dell'oggetto window
, come faremo ad eseguirla a fronte del click sulla nostra voce di menu? Il concetto con cui familiarizzare è dunque quello dei contentScript, ovvero codice JavaScript definito all'interno del nostro main.js
ma che viene eseguito nel contesto della nostra pagina web (il "content", appunto).
Ecco dunque come modificare il nostro codice:
cMenu.Item({
label: "Open links in a new window",
contentScript: 'on("click", function (node, data) {' +
' alert("Item clicked! " + node.tagName);' +
' console.log("Item clicked!");' +
'});'
});
Come vedete il codice è passato alla voce di menu come una stringa di JavaScript. Quel codice verrà subito eseguito al momento della creazione della voce stessa e non farà altro che impiantare un gestore per l'evento click
sulla voce stessa. Quel codice ha accesso all'oggetto window
e potrà dunque usare alert()
o tutto ciò che è presente all'interno della pagina.
Il parametro node, nella fattispecie, è il nodo DOM sul quale è stato effettuato il click per attivare il menu contestuale.
Infine il console.log()
serve per evidenziare un altro dettaglio: il nostro Firefox di test, fatto partire da cfx
, continuerà ad usare la console che ha il terminale come output e non la web console di Firefox come magari ci si sarebbe aspettati.
contentScriptFile
Ovviamente nella maggior parte dei casi non è pensabile scrivere del codice JavaScript all'interno di una stringa da passare come parametro alla funzione. Per ovviare a questo situazione useremo da adesso un altro parametro: il contentScriptFile. Il valore del parametro sarà ovviamente il file all'interno del quale scriveremo il nostro JavaScript, in tutta libertà.
Mettiamo i nostri file JavaScript nella directory data
, creata all'inizio dell'esempio, e poi useremo l'apposito helper fornito da Jetpack per sapere l'URL esatto del file stesso. Rivediamo dunque tutto il codice scritto finora:
var cMenu = require('context-menu'),
data = require('self').data;
cMenu.Item({
label: "Open links in a new window",
contentScriptFile: data.url('content1.js')
});
console.log("The add-on is running.");
Il contenuto di content1.js
sarà invece, per ora, il seguente (notate il metodo on comodamente a disposizione nel namespace globale):
on('click', function (node) {
alert("Item clicked! " + node.tagName);
console.log("Item clicked!");
});
Tutte le volte che faremo riferimento ad uno script (o un file CSS o HTML) della nostra estensione, presente nella directory data
, dovremo sempre usare l'helper data
fornito da Jetpack, così come fatto nell'esempio. Altrimenti la risorsa non verrà trovata.
In base a quello che è il nostro obiettivo, adesso andremo a prenderci tutti gli elementi A della nostra ipotetica pagina web e dirotteremo la loro apertura all'interno di un nuovo tab, in background.
Lavoriamo all'interno di content1.js
e scriviamo il codice per andare a dirottare tutti gli anchor. Le parti interessanti sono commentate:
on('click', function (node) {
var anchors = document.getElementsByTagName("a");
for (var i=0; i < anchors.length; i++) {
anchors[i].addEventListener('click', function(event) {
/* Scorciatoia per ottenere velocemente l'URL dell'anchor */
var url = "" + this;
console.log(url);
/* Interrompiamo l'evento */
event.preventDefault();
event.stopPropagation();
}, true);
}
});
Aprire tab in background
Il nostro prossimo obiettivo è quello di sostituire alla chiamata alla funzione "console.log", il codice che ci permetta di aprire il link in un nuovo tab, ma in background. Tuttavia, come è facile verificare, operazione, questa apparentemente banale, non è possibile usando una semplice chiamata alla window.open()
. Il link si potrebbe aprire sì in un nuovo tab, ma al "normale" JavaScript che gira all'interno di pagine web non è permesso aprire tab in background.
Per ottenere questo effetto dobbiamo usare dunque qualcosa di più potente, ovvero le funzionalità "interne" del nostro brower che sono a disposizione grazie appunto a Jetpack.
Dobbiamo dunque comunicare alla nostra estensione che vogliamo aprire un url in un nuovo tab. Questa comunicazione userà il metodo postMessage e l'evento message, in dotazione ormai da tempo dai browser moderni (compreso, per la cronaca, IE8). Postmessage
permette di spedire messaggi tra oggetti window
, che li potranno usare (se vogliono) intercettando il messaggio message
. In questo caso noi useremo direttamente dallo script il metodo postMessage()
e Jetpack penserà a recapitarlo al nostro main.js
, che dovrà essere messo in ascolto sull'evento message
.
Modifichiamo prima content1.js:
on('click', function (node) {
var anchors = document.getElementsByTagName("a");
for (var i=0; i < anchors.length; i++) {
anchors[i].addEventListener('click', function(event) {
var url = "" + this;
/* Spediamo al main.js l'URL che vogliamo aprire */
postMessage(url);
event.preventDefault();
event.stopPropagation();
}, true);
}
});
Adesso facciamo in modo che main.js
"ascolti" sul canale dei messaggi... (attenzione ai commenti)
var cMenu = require('context-menu'),
data = require('self').data,
/* Prendiamo l'helper per la gestione dei tab */
tabs = require("tabs");
cMenu.Item({
label: "Open links in a new window",
contentScriptFile: data.url('content1.js'),
/* Alla ricezione di un messaggio, prodotto dallo script "content1.js",
* apriamo l'url all'interno di un nuovo tab, ma in background!
*/
onMessage: function(text) {
tabs.open({
url: text,
inBackground: true
});
}
});
Obiettivo raggiunto!
Conclusioni
I concetti di base sono piuttosto semplici e le cose nuove da imparare non moltissime (se si ha una buona padronanza del JavaScript). Una volta terminato lo sviluppo della vostra estensioni non vi rimarrebbe altro che crearne un pacchetto distribuibile attraverso il comando "cfx xpi" che generera, appunto, il file .xpi installabile in qualsiasi Firefox4 (senza riavviare!).
Può essere interessante guardare questo video nel quale uno sviluppatore di Mozilla mostra come si può crare un'estensione in 5 minuti.
Per completezza occorre dire che esiste - oltre alla versione offline che ho presentato qui - anche un "Add-on builder" web based online, che usa gli stessi concetti e le stesse librerie di Jetpack (è il sistema usato dalla persona in quel video) ma permette di fare le operazioni di editing direttamente online usando ACE/Cloud9 (l'ex editor Bespin). Lo potete usare se avete un account con Mozilla. Per usarlo fino in fondo dovete anche installare questa estensione.
Link utili
- Maggiori informazioni sull'installazione
- Kevin Dangoor - uno dei responsabili del progetto Jetpack
- TurboGears e CommonJS - altri progetti di Kevin Dangoor