Firefox 3 e la libreria FUEL

Uno dei motivi del successo di Firefox è dato dalla grande quantità di estensioni disponibili, estensioni ragionevolmente facili da scrivere perché basate su tecnologie standard quali CSS, XML e Javascript.
Il collante di queste tecnologie è poi XUL che altro non è che un dialetto XML per creare interfacce grafiche.

L’accesso al filesystem, ai segnalibri, alle risorse web viene fatto tramite XPCOM che forse è l’elemento più ostico da gestire avendo al suo interno centinaia di chiamate di libreria spesso anche mal documentate.

Compiti frequenti come leggere una configurazione richiedono l’accesso ad un notevole numero di API e Firefox 3 tenta di migliorare le cose con la libreria FUEL.


Librerie Javascript per XUL

FUEL (Firefox User Extension Library), presente nel prossimo Firefox, non è il primo tentativo di wrappare XPCOM per semplificare la vita allo sviluppatore, basti ricordare JSLib oppure Io.

Mentre la libreria Io semplifica l’input/output, invece JSLib copre molti più campi: RDF, Zip, file locali ed altro ancora.
Il loro successo però, per motivi differenti, è stato alquanto tiepido.
Io wrappa solo l’accesso ai file quindi di utilità ben delimitata, JSLib ha il problema opposto ovvero troppo ricca e troppo pesante in termini di Kb.
Può sembrare assurdo parlare di pesantezza per qualche centinaio di Kb ma da sempre si è cercato di creare estensioni di dimensioni contenute.

Un altro problema che ha frenato l’adozione di queste librerie è stato quello dei conflitti di versione.
JSLib può essere installata come estensione a sé stante e per fare riferimento ad essa è sufficiente includere il corrispondente chrome://
nei propri file XUL come riportato di seguito.

<script type="application/x-javascript"
         src="chrome://jslib/content/jslib.js" />

Questo però ha causato problemi ad estensioni che richiedevano versioni diverse di JSLib.

Alla fine la soluzione consisteva nel copiare JSLib nel singoli contesti chrome delle estensioni oppure molto più spesso si copiavano solo i “pezzi” utilizzati.

Perché FUEL dovrebbe funzionare?

Osservando l’esperienza sulle librerie citate si potrebbe essere scettici sul futuro di FUEL ma le cose sono abbastanza diverse considerando che:

  • è manutenuta direttamente da Mozilla
  • è il primo tentativo serio di semplificare lo sviluppo delle estensioni
  • sta dentro Firefox 3 quindi sono (meglio dire dovrebbero essere) scongiurati clash di versione
  • semplifica l’accesso ad API il cui errato utilizzo nel passato ha causato molti memory leak
  • offre un’interfaccia Javascript omogenea
  • è scritta tenendo presente l’efficienza del codice

Cosa offre FUEL

Prima di vedere cosa mette a disposizione è doveroso sottolineare che FUEL non vuole e almeno nella versione attuale non può sostituire le librerie come JSLib che quindi continueranno ad esistere con tutti i pregi e difetti discussi sopra.
FUEL offre i seguenti oggetti:

Lista degli oggetti FUEL
Nome oggetto JS Descrizione
Annotations accesso ad informazioni addizionali sui segnalibri definite dallo sviluppatore
Application accesso al singleton tramite il quale usare FUEL
Bookmark accesso ad uno specifico segnalibro
BookmarkFolder accesso a gerarchie di segnalibri
BrowserTab accesso ai tab di una finestra del browser
Console accesso alla console
EventItem usato da Events
EventListener presente nella documentazione non nel codice
Events accesso agli eventi associati all’applicazione, non sono eventi DOM
Extension accesso alle informazioni su una specifica estensione
Extensions accesso alla lista delle estensioni installate
Preference accesso alle informazioni su una preference
PreferenceBranch accesso ad un “ramo” di preference
SessionStorage accesso alle informazioni usate dell’oggetto extension
Window accesso alle informazioni su window, tab aperti, eventi

Usare FUEL

FUEL ha un unico entry point rappresentato dal singleton Application, tramite esso si accede a tutto.

Le informazioni più semplici messe a disposizione dal singleton permettono di conoscere l’id, il nome e la versione dell’applicazione in esecuzione come mostrato in Figura 1.

Informazioni applicazione

Figura 1. Informazioni applicazione in esecuzione

Una delle attività più frequenti per uno sviluppatore consiste nel leggere le preference e tramite FUEL questo è molto semplice. Per leggere il nome del tema corrente è sufficiente accedere
all’oggetto PreferenceBranch.

var currentThemeName = Application
    .prefs  // Oggetto di tipo PreferenceBranch
    .getValue("general.skins.selectedSkin", "defaultValue");

Vale la pena di confrontare l’accesso diretto tramite XPCOM

var currentThemeName =
    Components.classes['@mozilla.org/preferences-service;1']
    .getService()
    .QueryInterface(Components.interfaces.nsIPrefBranch)
    .getCharPref("general.skins.selectedSkin");

Per ottenere la lista delle estensioni installate è sufficiente scrivere

var extensionArray = Application.extensions.all;

var str = "";        
for (var ext in extensionArray) {
    str += extensionArray[ext].name
        + "(" + extensionArray[ext].id + ")\n";
}
alert(str);

Il codice nativo XPCOM richiede almeno una quarantina di linee di codice.

Accedere ai tab aperti nella finestra corrente

var tabArray = Application
                .activeWindow // oggetto di tipo Window
                .tabs;

for (var t in tabArray) {
    var tab = tabArray[t]; // Oggetto di tipo BrowserTab
    var uri = tab.uri; // Oggetto nsIURI XPCOM non wrappato
    alert(tab.uri.spec);
}
Lista finestre e tab

Figura 2. Lista finestre e tab

Prove tecniche di programmazione

Firefox 3 ad oggi non è stato rilasciato e la sua più recente incarnazione è la beta 3 quindi non è strano che qualcosa manchi, ad esempio
l’oggetto EventListener non esiste benchè documentato. Non è da escludere che nella versione finale FUEL contenga altri oggetti.

Gli eventi in FUEL

Chi dovesse imbattersi per la prima volta con gli eventi in FUEL arriverebbe alla conclusione che non funzionano.
In verità funzionano soltanto che non sono eventi… o meglio sono eventi FUEL.

Essi sono in realtà degli observer (nsIObserver), la scelta di “cambiargli” nome nasce dalla volontà di armonizzare le due cose per dare allo sviluppatore una visione più omogenea.

Operazione che non sembra proprio riuscita dato che si è abituati ad associare eventi a DOM (onclick, onload).

Ad ogni modo, tutti gli oggetti FUEL hanno una proprietà events ad esempio per essere avvertiti quando Firefox viene chiuso è sufficiente
aggiungere un listener sull’evento quit dell’oggetto Application

Application
    .events
    .addListener("quit",function(event)
        { alert("Non voglio morire");}
    );

oppure è possibile registrare un evento per quando si disabilita una estensione

Application
    .extensions
    .get("{9859aa37-9d17-4558-8ddd-11042c6f3390}")
    .events
    .addListener("disable", function(event)
        { alert("Richscrollbar disabled");}
    );

Stesso discorso vale per bookmark, sessionStorage, prefs e gli altri.
Lo sviluppatore può aggiungere i propri eventi custom, rimuoverli e fare dispatch. Una volta chiarito che non sono eventi DOM se ne
apprezza la compattezza espressiva rispetto al codice XPCOM equivalente.

Moduli JSM

Dando uno sguardo al codice sorgente si nota la pulizia, cosa non sempre presente nei sorgenti di Firefox.
Il design delle classi è semplice ma quello che salta all’occhio è una nuova istruzione in testa ai file.

Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");

Questa è una novità molto interessante di FF3, in pratica è possibile importare e condividere lo stesso
codice tra differenti contesti.

I file con estensione jsm sono moduli Javascript il cui contenuto viene messo in cache alla prima invocazione e che viene
ritornato alle successive richieste.

Si tratta di un singleton a livello di modulo, feature richiesta dagli sviluppatori da tempo.
Sino ad oggi infatti si era simulato il comportamento utilizzando hiddenDOMWindow
e mozIJSSubScriptLoader.

Maggiori informazioni su Components.utils.import sono disponibili su MDC

QueryInterface degli oggetti FUEL

Gli oggetti FUEL implementano tutti nsISupport.
Questo significa che è possibile fare il cast di un oggetto FUEL per verificare se implementa una particolare interfaccia, ad
esempio Application implementa nsIObserver.

Fin qui nulla di nuovo o interessante se non fosse per il fatto che accanto alle interfacce XPCOM standard nsIxxxx FUEL espone le proprie con prefisso fuelI.

Il vantaggio maggiore di questa scelta consiste nel poter testare la natura dell’oggetto in maniera semplice e soprattutto conforme ad XPCOM
senza dover fare inspect “esotici” che possono improvvisamente diventare non funzionanti cambiando versione.
Questo è un altro degli obiettivi di FUEL, ovvero evitare che lo sviluppatore sia costretto a soluzioni artificiose per conoscere la natura
di un oggetto.

In passato molti bug anche gravi sono nati da un uso improprio delle interfacce XPCOM, questo ovviamente non significa che le vulnerabilità
spariranno se si usa FUEL però magari l’effetto di un bug sarà meno dannoso.

Di seguito è riportato un esempio di QueryInterface su Application

var unknownObject = Application;

// Questo cast e' corretto dato che unknownObject e' di tipo Application
try {
    var app = unknownObject
        .QueryInterface(Components.interfaces.fuelIApplication);
    alert("Nome Applicazione " + app.name);
} catch (ex) {
    alert("Oggetto unknownObject non supporta fuelIApplication");
}

// Questo cast genera exception
try {
    var tab = unknownObject
        .QueryInterface(Components.interfaces.fuelIBrowserTab);
    alert("URI tab " + tab.uri);
} catch (ex) {
    alert("Oggetto unknownObject non supporta fuelIBrowserTab");
}
Interfacce FUEL
Nome oggetto JS Interfaccia
Annotations fuelIAnnotations
Application fuelIApplication
Bookmark fuelIBookmark
BookmarkFolder fuelIBookmarkFolder
BrowserTab fuelIBrowserTab
Console fuelIConsole
EventItem fuelIEventItem
Events fuelIEvents
Extension fuelIExtension
Extensions fuelIExtensions
Preference fuelIPreference
PreferenceBranch fuelIPreferenceBranch
SessionStorage fuelISessionStorage
Window fuelIWindow

Retro compatibilità

Come si deve comportare chi ha delle estensioni che devono funzionare su FF2 e vuole trarre vantaggio da FUEL?

La cosa migliore resta migrare lentamente e non toccare quello che funziona, in fondo FUEL è un wrapper e non ha senso
rischiare di introdurre delle regressioni.

Si parla di un backport su FF2 ma la cosa non è detto che avvenga e soprattutto che abbia senso.

Sperimentare con FUEL

Chi volesse sperimentare può farlo installando l’estensione Pannello Fuel di cui
si e’ visto qualche screenshot nelle Figure 1 e 2.
Dopo l’installazione il menu Strumenti (o Tools nella versione inglese) conterrà la nuova voce “Pannello test FUEL”.

La versione da usare e’ ovviamente l’ultima beta disponibile di Firefox 3 il cui download può essere effettuato dal repository nightly di Mozilla.

Conclusioni

FUEL non è una rivoluzione ma dopo tanti anni rappresenta un passo avanti per l’ingegnerizzazione della scrittura di estensioni, alcune scelte
sembrano discutibili e mancano oggetti importanti ad esempio per Places ma stando a quanto
dichiarato da Mozilla queste cose ci saranno.
Sono previsti anche oggetti lato interfaccia utente come animazioni ed altri effetti visivi, staremo a vedere.

Si parla di fare una cosa analoga per Thunderbird 3, il progetto si chiama STEEL ma ad oggi non risulta esistere del codice funzionante.

Policy per i commenti: Apprezzo moltissimo i vostri commenti, critiche incluse. Per evitare spam e troll, e far rimanere il discorso civile, i commenti sono moderati e prontamente approvati poco dopo il loro invio.