Lors du Bof d'avril 2010, nos équipes ont pu voir de leur propres yeux la rapidité avec laquelle il est possible de créer, installer, débugguer une extension à destination du navigateur Chrome.
En effet, à l'instar de Firefox, le navigateur de Google possède des api simples d'accès pour réaliser des extensions.

Nous allons voir tout au long de ce post comment faire pour créer une extension Chrome en partant de presque rien. 

Avant de plonger dans la partie technique il est nécessaire de bien définir ce que représente une extension pour Chrome. Une extension est vue dans son intégralité comme un onglet de navigation ce qui implique qu'il possède son propre processus système, les extensions ne font en définitive qu'exécuter du code Web (Javascript, Html et CSS) en proposant quelques API supplémentaires permettant des manipulations spécifiques au navigateur (comme la possibilité d'ajouter une vignette à une icône d'extension).

Intéressons-nous maintenant à notre objectif du jour. Le but de notre première extension sera de permettre de visualiser le statut des builds d'un serveur Hudson, serveur qui propose nativement des API Json et XML permettant de consulter son état. Par manque d'imagination et par soucis de rapidité nous donnerons à cette extension le nom barbare de Chrudson.

Une extension Chrome se présente sous la forme d'une archive (signée ou non) qui doit inclure un descripteur d'extension au format json. Ce fichier se nomme dans le jargon "chromien" le Manifest.
Voici à quoi pourrait ressembler notre premier manifest.json

{
  "name": "Chrudson",
  "version": "1.0",
  "description": "Gestion d'un serveur hudson depuis chrome",
  "permissions": ["http://*/*","https://*/*"]
}
Comme on le voit rapidement, il est possible de nommer, versionner, et décrire succintement son extension. Bien que notre extension n'ait aucun lien direct avec Maven, nous l'autorisons à faire des requêtes sur l'internet tout entier grâce à la ligne :

  "permissions": ["http://*/*","https://*/*"]

Une fois ce manifest écrit il est d'ores et déjà possible de l'installer sur un navigateur Chrome en utilisant pour cela le navigateur lui-même en passant par le panneau des extensions et en ouvrant le mode développeur. L'extension peut être chargée soit empaquetée (directement en la glissant dans le navigateur), soit directement depuis un dossier local (ce que nous utilisons en développement).

Outre le fichier manisfest, une extension chrome peut aussi se composer d'autres modules "actifs" comme une tâche de fond ou background_page (service venant alimenter les données locales ou mettre à jour l'état de l'extension), d'une page d'options ou options_page (page permettant de configurer l'extension) et enfin soit d'une action navigateur ou popup, soit d'une action de page ou page_action (icone cliquable ajoutée à la barre d'adresse, ex : icône rss).

Options_page


Cette partie de l'extension est disponible lorque l'utilisateur clique sur "options" dans le menu déroulant disponible sur l'icône d'extension.
Pour Chrudson, cette page permet de renseigner l'URL du serveur qui sera surveillée par l'extension. Une page d'options simplifiée de Chrudson ressemble à ceci :

<html>
    <head><title>Chrudson Options</title>
    <script type="text/javascript">

    // Saves options to localStorage.
    function save_options() {
      var url = document.getElementById("url");
      localStorage["hudson_url"] = url.value;
      // Update status to let user know options were saved.
      var status = document.getElementById("status");
      status.innerHTML = "Options Saved.";
      setTimeout(function() { status.innerHTML = "";}, 750);
    }

    //Restores select box state to saved value from localStorage.
    function restore_options() {
      var url = localStorage["hudson_url"];
      document.getElementById("url").value = url;
    }
    </script>
</head>
<body onload="restore_options()">
    Url :<input type="text" id="url">
    <button onclick="save_options()">Save</button>
    <span id="status"></span>
    </body>
</html>

On peut remarquer qu'il est possible de stoquer localement des données via la table localStorage, cette table étant accessible par toutes les parties javascript de l'extension. Tout comme une page web, l'évènement onload de la balise <body> est levé chaque fois que l'utilisateur affiche la page des options.

Pour définir cette page comme la page d'options il suffit d'ajouter la description suivante au manifest.json précédent : 

"options_page":"options.html"

Background_page


Ce module de l'extension va permettre à Chrudson de scruter à intervalles régulier l'état du serveur Hudson précédemment configuré dans la page d'options. Pour des raisons de synthèse, le code n'est pas au complet dans ce post. Quelques morceaux de cette page sont cependant intéressant

var pollInterval = DEFAULT_POLL_INTERVAL;
function onLoad() {
    chrome.browserAction.setTitle({title :"Hudson " + getHudsonUrl()});
    if(localStorage["poll_frequency"]){
        pollInterval = localStorage["poll_frequency"];
    }
    startRequest();
}

Cette fonction, exécutée au chargement de l'extension met à jour le tooltip disponible sur l'icône d'extension. Le localStorage est une fois de plus utilisé pour récupérer la fréquence de rafraîchissment des pages (fréquence que l'utilisateur pourra changer dans une version plus évoluée de notre page d'options). Dans notre exemple, cette valeur n'est jamais renseignée, la valeur par défaut est donc utilisée.

La tâche de fond peut aussi utiliser les APIs plus spécifiques aux extensions Chrome:

function setStatus(text, color) {
    chrome.browserAction.setBadgeText({text :text});
    chrome.browserAction.setBadgeBackgroundColor({color :color});
}

La fonction setStatus permet de changer l'état de l'icône de l'extension en lui affectant un badge de couleur à la manière de ce qui peut se trouver sur les iPhones (limité à quelques caractères).

Une fois la page html/javascript crée, il suffit de rajouter la ligne suivante au manifest.json pour s'assurer de le déclencher à chaque lancement du navigateur :

"background_page":"background.html"

Browser_action


L'icône d'extension à laquelle fait référence la background_page est définit par ce que l'on appelle une Browser_action. Cette page permettra lors d'un clic utilisateur sur l'icône de l'extension d'afficher une fenêtre qui contient en fait la page html définie comme popup pour la browser_action.

Pour cela il suffit d'ajouter la description de la browser_action au désormais bien connu manifest.json

"browser_action": { 
   "default_icon": "butler.png",
   "popup": "popup.html"
}

Dans notre cas, le fichier popup.html simplifé ressemble au code suivant :

<html>
<head>
<title>Chrudson Popup</title>
<script type="text/javascript">
    ...
    function onLoad() {
        url = localStorage["hudson_url"];
        titleDiv = document.getElementById("title");
        titleDiv.innerText = "Status hudson ("+url+")";
        statusDiv = document.getElementById("status");
        statusDiv.innerText = "loading";
        refreshStatus();
    }

    function refreshStatus(){
        var xmlUrl = url+"api/json";
        updateReq = new XMLHttpRequest();
        updateReq.open("GET",xmlUrl,true);
        updateReq.onload = parseResult;
        updateReq.send(null);
    }
    function parseResult(){...}
    ...

    function onClick(url){
        return function(){open_tab(url)};
    }

    function open_tab(url){
        chrome.tabs.create({"url": url});
    }
</script>
</head>
<body onload="onLoad();" class="status">
    <div id="title" class="title"/>
    <span id="status"/>
</body>
</html>

Il se contente d'afficher l'état des jobs listés sur le serveur dans des éléments HTML simples. Ces éléments peuvent être enrichis de styles CSS 3. On peut signaler dans ce code la possibilité d'ouvrir de nouveaux onglets Chrome lors d'un clic souris sur le nom d'un job par exemple. Il est cependant nécessaire de compléter les autorisations données à l'extension dans le fichier manifest comme suit :

"permissions": ["http://*/*","https://*/*","tabs"]

C'est bien beau tout ça ...


Tout ce qui nous avons vu jusqu'à présent montre à quel point il est simple de coder, installer et utiliser rapidement une extension. Cependant, que se passe-t-il lorsque l'un des traitements javascripts foire lamentablement, ou que certains styles CSS présentent finalement des problèmes d'alignement ? 

L'une des forces de Chrome sur ce point réside dans la possibilité d'inspecter dynamiquement chacune des parties de l'extension (background, options et popup) directement dans le navigateur. 
En effet, un lien d'inspection du background est disponible dans le menu des extensions, la page d'options est quant à elle inspectable comme n'importe qu'elle autre page Web, et la popup peut être inspectée en effectuant un clic-droit->inspecter sur l'icône de l'extension comme le montre la capture d'écran suivante (réalisée sous chrome 6.0.401.1) 



Le wrapping GWT


L'une des extensions les plus réussie de Chrome, livrée avec GWT 2.0, s'appelle Speed Tracer. Cette extension a été codée en grande partie avec le Framework GWT. Pour cela, les développeurs ont réalisé une implémentation partielle des API d'extension chrome pour GWT. Si vous êtes un fervent admirateur de GWT ou simplement un petit curieux, je vous invite à explorer le code par vous même pour vous faire une idée de la puissance de l'engin (l'implémentation des api d'extension se trouve dans le package com.google.gwt.chrome.crx.*). 
Cette variation d'utilisation du Google Web Toolkit sera d'ailleurs abordée au Google I/O cette semaine http://code.google.com/events/io/2010/sessions/gwt-linkers-webworkers-extensions.html

Vous savez maintenant tout ce qu'il faut savoir pour coder votre première extension chrome, vous savez aussi qu'il sera bientôt possible de coder des extensions avec GWT de manière encore plus intuitive pour les Java-istes qui lisent ce post.

Ressources 

SpeedTracer  http://code.google.com/p/speedtracer/
Guide & APIs  http://code.google.com/chrome/extensions/devguide.html
Projet Git d'où nos exemples sont librement inspirés http://github.com/sanitz/hudson-chrome-extension