Google Gears : accélérer le chargement d'une application en cachant les fichiers statiques

21 août 2008Javascript, google-gears, performance
 Cet article a été rédigé il y a plusieurs années et peut ne plus être tout à fait à jour…

Vous souhaitez proposer aux utilisateurs de votre application Web de continuer à travailler avec celle-ci même lorsqu'ils ne sont pas connectés à Internet ?
C'est possible : prenez par exemple Google Docs, qui vous permet de modifier vos documents[1] sans être connecté !

Travailler hors ligne ?

Avant de commencer, alors que nous avons de plus en plus tendance à avoir accès à Internet où que nous allions, pourquoi se soucier de développer une application Web qui soit accessible hors-ligne ?

Introduction : accès Internet, ou isolement ?

Et bien... justement parce que nous avons de plus en plus accès à Internet : nous avons tellement l'habitude d'être connectés que nous utilisons de plus en plus de services Web quotidiennement, parfois même à la place des outils "déconnectés" que nous employions auparavant... Mais que se passe-t-il si nous sommes dans une situation où nous n'avons pas de connexion Internet (dans le train ? En vacances chez nos grand-parents qui ne connaissent pas encore Internet ? Au fond de la campagne sans connectivité 3G ? En réunion sans accès wifi ?) ?

Cela implique-t-il que nous n'ayons pas accès auxquels nous nous sommes habitués ? Cela signifie-t-il que nous n'avons plus accès à nos données ?
Cette situation est-elle acceptable pour vous ? Dans certains cas, pas pour moi !


Google Gears

Pour répondre au besoin qu'est la capacité pour une application de vous permettre de continuer à travailler hors-ligne, Google a développé Google Gears.

Je cite WikiPedia :

Gears est un prototype logiciel proposé par Google pour permettre l'accès hors-ligne à des services qui fonctionnent normalement en ligne. Il installe un moteur de base de donnée basé sur SQLite sur le système client pour cacher les données localement. Les pages pour lesquelles Google-Gears est activé utilisent les données en provenance de ce cache local plutôt que celles provenant du service en ligne. Si la connexion réseau n'est pas disponible, la synchronisation est ajournée jusqu'à ce que la connexion revienne. Gears permet donc à des applications web de fonctionner sans accès permanent au réseau.

Gears fonctionne sous la majorité des navigateurs Web que vos utilisateurs sont susceptibles d'utiliser[2] :

  • Firefox >= 1.5 (Windows, MacOS X, Linux)
  • Internet Explorer >= 6 (Windows)

Malheureusement, même si le support de Safari est prévu, il n'est pas encore disponible.
A vous de voir l'impact pour vous utilisateurs...


MAJ le 26/08/2008 : Une première version bêta de Gears est sortie pour Safari !

Je cite, tout de même :

This is BETA, it is not an official release, it might break your browser. Chances are it will break your browser. Please proceed with caution.

Pour plus d'informations, allez voir :



Une série d'articles sur Google Gears

Cet article est le premier d'une série - de trois, probablement, si je m'en tiens à mon plan - visant au développement d'une mini-application capable de vous permettre de travailler hors-ligne.

Le plan prévu est le suivant :

  • Premier article (celui-ci) : installation et activation de Google Gears, concepts de base, et utilisation pour stocker en cache, sur le poste utilisateur, les fichiers statiques utilisés par l'application.
  • Second article : travail hors-ligne, sans le moindre appel au serveur, une fois l'application en mode "déconnecté".
  • Troisième article (octobre 2008 ?) : synchronisation entre les données "en-ligne", et le travail effectué "hors-ligne", lors du retour en mode connecté.


Application utilisée pour cet article

Pour cet article, nous développerons une application des plus simple : une application de prise de notes.

Une application simple

L'idée est la suivante :

  • une note est composée d'un nom d'auteur, d'une date, et d'un texte
  • un formulaire permet de saisir une note
  • les trois champs sont obligatoires
  • la liste des notes saisies est affichée au-dessus du formulaire de saisie.

Au niveau de l'interface, on obtient quelque chose de ce genre : adding-a-note-1.png

Rien de très compliqué, donc ^^
(Les sources de l'application sont disponibles en téléchargement ; Cf la pièce jointe à ce billet, tout en bas de celui-ci)

Cette application est développée en PHP + SQLite, avec un brin de Javascript, CSS, et XHTML pour la couche présentation.
Pour faire simple, pas de MVC, pas d'orientation objet, juste un seul fichier .php pour le tout ; à vous de faire plus complet / plus organisé lorsque vous travaillerez sur une application réelle, et non un script d'exemple !


Des temps de chargement non satisfaisant ?

Au fur et à mesure que vous ajoutez des fonctionnalités à vos pages, vous ajouterez - probablement - des fichiers que le navigateur client devra télécharger : feuilles de styles, fichiers Javascript, images, ... Tout ceci finit par augmenter les durées de chargement des pages avec lesquelles vos visiteurs travaillent.

Pour la page toute simple que nous avons présenté plus haut, voici la liste des fichiers chargés depuis l'en-tête de la page :

<link href="css/style.css" media="screen" rel="stylesheet" type="text/css" />
<script type="text/javascript" src="js/lib/prototype.js"></script>
<script type="text/javascript" src="js/lib/scriptaculous/builder.js"></script>
<script type="text/javascript" src="js/lib/scriptaculous/effects.js"></script>
<script type="text/javascript" src="js/lib/scriptaculous/dragdrop.js"></script>
<script type="text/javascript" src="js/lib/scriptaculous/controls.js"></script>
<script type="text/javascript" src="js/application.js"></script>

Ceci qui donne le graphe de chargement de page suivant :

firebug-network-without-gears.png (Modulo un ou deux fichiers : cette capture correspond à un graphe de chargement de l'application complète)

Déjà pour cette page simple, qui n'inclut qu'une seule feuille de style et seulement quelques fichiers JS, le chargement de ces fichiers externes - et statiques - prend plus de la moitié du temps de chargement ! Plus de la moitié du temps que l'utilisateur passe à attendre que la page s'affiche correspond à des fichiers qui sont quasiment toujours les mêmes : ils ne seront modifiés que lors de mises à jour de l'application, et il est à parier que celle-ci sera utilisé plus souvent qu'elle ne sera MAJ[3] !

Gears va permettre de stocker ces fichiers directement sur les postes de vos utilisateurs, qui n'auront donc pas à les re-télécharger à chaque chargement de page.
Bien entendu, cela accélèrera le chargement de notre liste de notes... Mais, surtout, c'est un premier pas vers une version utilisable hors-ligne de notre site !

Quelques remarques :

  • Pour montrer l'intérêt de ce dont je parle dans cet article, j'ai volontairement inclus plus de fichiers JS que nécessaire pour cette application (j'ai inclus une partie de script.aculo.us) : pour une application un tant soit peu plus complexe, vous en inclurez probablement encore d'autres.
  • D'un autre côté - et ça vaut mieux considérant mes talents de graphiste - je n'ai pour ainsi dire pas inclus d'image ; disons que ça compense... Plus ou moins.
  • Bien entendu, dans un cas réel, votre serveur serait certainement configuré pour indiquer au navigateur qu'il doit conserver les fichiers css/js/images en cache, et ne pas les re-télécharger à chaque chargement de page ! Mais cela ne nous aiderait pas vraiment à rendre notre application accessible hors-ligne, n'est-ce pas ?


Premiers pas avec Google gears

Avant de réellement pouvoir travailler avec gears, il vous faut inclure un fichier Javascript fourni par Google, et, bien évidemment, vous assurer que l'utilisateur de votre site a installé le plugin attendu.

Inclusion du fichier gears_init.js

Pour inclure des fonctionnalités liées à Gears sur votre site, vous devez inclure sur vos pages le fichier "gears_init.js" fourni par Google : gears_init.js.

L'idée est la suivante :

  • vous téléchargez le fichier fourni par google,
  • vous le stockez sur votre propre serveur, avec vos autres fichiers Javascript,
  • et vous l'incluez sur vos pages, via une balise <script> tout ce qu'il y a de plus standard :
<script type="text/javascript" src="js/lib/gears_init.js"></script>

Ceci vous permettra d'accéder à l'outil Google gears depuis vos scripts.


Installation de Gears

Une fois le fichier gears_init.js inclut, vous pouvez commencer à travailler avec l'objet google.gears.
Typiquement, le premier pas sera de détecter si l'utilisateur de votre site a installé l'extension / le plugin[4] google gears, et, si ce n'est pas le cas, de lui proposer de l'installer.

Pour détecter si le plugin gears n'est pas installé, il suffit de deux tests Javascript :

if (!window.google || !google.gears) {
    // Extension google gears non installé
    // => Afficher un lien menant à son installation
}

Le lien proposant d'installer Google Gears doit pointer sur le site de Google ; plus précisément, sur la page permettant... l'installation du-dit plugin.
Il doit être construit de la manière suivante :
http://gears.google.com/?action=install&name=<NOM_APPLICATION>&message=<MESSAGE>&return=<URL_DE_RETOUR>&icon_src=<URL_ICONE>

Bien entendu, à vous d'assigner à chaque variable les valeurs qui correspondent à votre site / votre application :

  • action = install ou update. De manière générale, si google gears n'est pas installé, vous proposerez à votre visiteur de l'installer.
  • name = le nom de votre application ; au maximum, 150 caractères.
  • message = un message à afficher sur la page d'installation de gears ; au maximum, 150 caractères.
  • return : l'URL de la page de votre site vers laquelle l'utilisateur reviendra après installation du plugin.
  • icon_src : l'URL d'une icône à afficher sur la page d'installation de gears ; sa taille doit être de 48x48 pixels.

Un point intéressant à noter : pensez à échapper les caractères spéciaux de vos messages, url, ...
Mais cet échappement doit être conforme à la RFC 1738 ; cela signifie que le caractère espace ne doit pas être remplacé par un "+" !
Pour les développeurs PHP, notamment, cela signifie qu'il faut utiliser rawurlencode, et non urlencode !

Ci-dessous, quelques captures d'écran de l'installation de Google gears, sous Firefox 3.0, linux :

Le lien sur notre application proposant d'installer Gears, visible seulement lorsque nous avons détecté que l'extension n'était pas déjà installée : installer-google-gears-1.png

La page d'installation du plugin gears sur le site de Google : notez la présence des nom, message, et icône, que nous avions spécifiés dans le lien : installer-google-gears-2.png

Une fois que l'utilisateur a cliqué sur le bouton "Install gears", il doit accepter les conditions d'utilisation : installer-google-gears-3.png

Puis l'installation, sous Firefox, du moins, se fait comme pour n'importe quelle extension : installer-google-gears-4.png

Avec le redémarrage obligatoire de Firefox en fin de processus d'installation : installer-google-gears-5.png

Maintenant, lors de la prochaine visite de l'utilisateur (lorsqu'il aura redémarré son navigateur), nous détecterons que l'extension gears est installée, et nous pourrons commencer à exploiter ses fonctionnalités.


Activation de gears

Une fois l'extension / le plugin gears installé, nous allons pouvoir commencer à l'exploiter pour notre site.

Autorisation pour votre site

Pour cela, en premier lieu, le visiteur doit autoriser notre site à accéder aux fonctionnalités de gears. Sans cette autorisation, impossible pour vous de déposer des fichiers sur le poste utilisateur (même chose pour les autres fonctionnalités, que nous verrons au cours des prochains articles).

Automatiquement, lors de la première tentative d'accès aux fonctionnalités exportées par google gears, l'extension demande son autorisation à l'utilisateur de votre application : vous n'avez rien de particulier à gérer à ce niveau.
La seule liberté qui vous est offerte, en tant que de développeur, est de pouvoir déterminer si l'utilisateur a autorisé gears pour votre site :

if (google.gears.factory.hasPermission) {
    // gears est autorisé pour votre site
}


Instanciation des composants gears

Pour pouvoir utiliser la fonctionnalité de stockage de fichiers en local, qui est celle qui nous intéresse pour cet article, deux objets sont à instancier :

  • Le "LocalServer" en lui-même, qui permet à une application Web de stocker en cache, sur le poste client, des ressources (fichiers, notamment) HTTP, et de les servir sans connexion réseau,
  • Un "ManagedStore", qui permet de stocker ces fichiers, tout en gérant leur mise à jour régulière et (éventuellement) automatique.

Qu'est-ce que cela donne en terme de code ?

Et bien, commençons par déclarer les deux variables que nous utiliserons pour la suite de nos exemples :

var localServer;
var store;

Et instancions nos deux composants :

localServer = google.gears.factory.create('beta.localserver');
store = localServer.createManagedStore('test');

Tout d'abord, nous obtenons une instance de LocalServer, à partir de laquelle nous serons à même de stocker localement des fichiers auxquels nous accèderions normalement via une requête HTTP - requête qui sera interceptée par gears, qui renverra directement en retour le fichier stocké localement, sans effectuer l'aller-retour client/serveur.
Et, ensuite, nous créons au sein de notre serveur local un ManagedStore, qui contiendra les-dits fichiers, en se chargeant notamment de leur mise à jour.

Le paramètre donné à la méthode createManagedStore est le nom de notre espace de stockage ; c'est par ce nom que nous le retrouvons d'un chargement de page à l'autre.

A la première exécution de cette portion de code, google gears détectera que l'utilisateur n'a pas encore autorisé votre site à accéder à ses fonctionnalités, et lui affichera une demande d'autorisation, ressemblant à celle-ci :

gears-asking-for-permission.png (Le nom "http://tests" correspondant à celui de ma machine de développement sera naturellement remplacé par celui de votre site)

Vous ne pourrez exploiter les fonctionnalités de Google Gears au sein de votre application que si votre utilisateur a autorisé celle-ci à accéder à l'extension... ce qui signifie qu'il vous faudra former vos utilisateurs, en leur expliquant ce qu'est cette boite de dialogue, et ce qu'elle leur apportera !
(On répète tellement souvent qu'"il ne faut pas cliquer sur ,,oui j'accepte"" sans savoir de quoi il s'agit que ça commence à être compris... Ici, nous souhaitons justement que l'utilisateur accepte... L'expliquer - et expliquer pourquoi, cette fois, exceptionnellement, il faut dire "Oui" - ne peut être que bénéfique !)


Stocker les fichiers statiques sur le poste client

Alors... Nous avons vu comment indiquer à nos utilisateurs comment installer le plugin google gears ; nous avons vu comment obtenir des instances de classes LocalServer et ManagedStore ; et nous avons vu que, lors de la première utilisation de notre application, l'utilisateur devait autoriser google gears pour notre site.
Voyons maintenant (enfin ? ) comment stocker sur le poste client les fichiers statiques liés à notre application.


ManagedStore et fichier manifest

Pour déterminer quels sont les fichiers à télécharger et à stocker en local, le ManagedStore se base sur un fichier texte, contenant un objet au format JSON, contenant la liste des URLs des fichiers, ainsi qu'un numéro de version.
Ce fichier est dit fichier "manifest", et souvent nommé "manifest.json".

Dans le cas de notre application, le fichier manifest serait le suivant :

{
    "betaManifestVersion": 1,
    "version": "0.1",
    "entries":
    [
        {"url": "css/style.css"},
        {"url": "js/lib/gears_init.js"},
        {"url": "js/lib/prototype.js"},
        {"url": "js/lib/scriptaculous/builder.js"},
        {"url": "js/lib/scriptaculous/effects.js"},
        {"url": "js/lib/scriptaculous/dragdrop.js"},
        {"url": "js/lib/scriptaculous/controls.js"},
        {"url": "js/application.js"},
        {"url": "js/gestion-gears.js"},
        {"url": "img/error.png"},
        {"url": "img/tick.png"}
    ]
}

Le principe est simple :

  • betaManifestVersion correspond au numéro de version du format de fichier manifest utilisée ; actuellement, il n'existe qu'une version de format pour ce fichier ; on indique donc toujours 1 comme valeur.
  • version correspond au numéro de version du fichier en lui-même : pour déterminer si les fichiers stockés localement doivent être mis à jour, c'est sur ce numéro de version que le ManagedStore se basera. Autrement dit, à chaque fois que vous voudrez que les fichiers stockés localement sur les postes de vos utilisateurs soient mis à jour, il faudra modifier ce numéro de version. Attention : si vous ne modifiez pas ce numéro de version, il n'y aura pas de mise à jour effectuée !
  • Enfin, entries contient la liste de tous les fichiers qui devront être téléchargées, puis stockés sur les postes de vos utilisateurs.

Une fois ce fichier créé et déposé sur votre serveur, vous devez indiquer à l'instance d'objet ManagedStore avec laquelle vous travaillez qu'elle devra utiliser ce fichier.
Pour cela, affectez son chemin à l'attribut manifestUrl de l'instance d'objet ManagedStore :

store.manifestUrl = 'manifest.json';

Et enfin, pour rapatrier en local l'ensemble des fichiers renseignés dans le fichier manifest, il vous faudra utiliser la méthode checkForUpdate de l'instance d'objet ManagedStore :

store.checkForUpdate();

Il est à noter que cette méthode n'est normalement à appeler qu'une seule fois, pour forcer la récupération en local des fichiers au moment où l'utilisateur a activé l'utilisation de gears pour votre application.

A partir de là, votre application ne devrait plus effectuer de requête HTTP pour charger ces fichiers statiques : une fois qu'ils auront été stockés en local, c'est la version locale qui sera utilisée - du moins, tant que le numéro de version dans le fichier manifest n'aura pas été modifié.

Pour rappel, au niveau du code Javascript mis en place, tout tient en quelques lignes :

var localServer = google.gears.factory.create('beta.localserver');
var store = localServer.createManagedStore('test');
store.manifestUrl = 'manifest.json';
store.checkForUpdate();

C'est-à-dire :

  • Instanciation de la classe LocalServer,
  • Instanciation de la classe ManagedStore,
  • Renseignement du chemin vers le fichier manifest contenant la liste des fichiers statiques à stocker en local,
  • Et lancement de la mise à jour.


Résultat

Nous avions montré un peu plus haut une capture d'écran de l'onglet Réseau de Firebug, montrant les temps chargements respectifs de chacun des éléments de la page.
Une fois les fichiers statiques stockés en local, voici ce que nous obtenons :

firebug-network-with-gears-activated.png

Autrement dit, comme nous le souhaitions :

  • Nous n'avons plus d'autre chargement via le réseau que celui de la page en elle-même (celle-ci étant dynamique, affichant des informations stockées en base de données côté serveur, il sera plus difficile de se passer de ce chargement - ce sera l'objectif du prochain article de cette série, d'ailleurs)
  • N'ayant plus aucun chargement de fichier statique via le réseau, le temps de chargement total de la page est fortement diminué (je vous laisse comparer avec le même graphique, plus haut, qui incluait les temps de chargements des fichiers JS et CSS)


Suivre la mise à jour d'un ManagedStore

L'objet ManagedStore nous permet de brancher des méthodes qui seront appelées lors de trois types d'évènements :

oncomplete

La fonction branchée sur ce gestionnaire sera appelée lorsque la mise à jour d'un ManagedStore sera terminée.

Elle reçoit en paramètre un objet contenant une propriété newVersion, correspond au nouveau numéro de version indiqué dans le fichier manifest.

onprogress

La fonction branchée ici sera appelée (de manière générale, plusieurs fois : une fois pour chaque fichier mis à jour) pendant le processus de mise à jour du ManagedStore.

Elle reçoit en paramètre un objet porteur de deux attributs :

  • filesComplete : le nombre de fichiers mis à jour jusqu'à présent.
  • filesTotal : le nombre total de fichiers à mettre à jour.

onerror

La fonction branchée sur ce gestionnaire sera appelée en cas d'erreur pendant le processus de mise à jour.

Elle reçoit en paramètre un objet contenant la propriété message, contenant un message d'erreur sous forme textuelle.

Utiliser ces gestionnaires

Il est habituel de brancher un gestionnaire sur onprogress et oncomplete, de manière à pouvoir indiquer à l'utilisateur qu'une mise à jour est en cours, et où elle en est.
Typiquement, cela se fait à l'aide d'une portion de code de la forme suivante, placée après le renseignement du chemin vers le fichier manifest :

Pour afficher un message en fin de vérification de présence d'une mise à jour :

store.oncomplete = function(details) {
    if (!details.newVersion.blank()) {
        $('debug').innerHTML += "Mise à jour effectuée ; version : " + details.newVersion + "<br />";
    }
    else {
        $('debug').innerHTML += "Pas de nouvelle version...<br />";
    }
};

(En supposant que vous avez quelque part sur votre page un élément d'identifiant "debug", et que vous avez, tout comme moi pour mon exemple, inclut le framework prototype, qui met à votre disposition la fonction $ comme alias vers document.getElementById)

Pour la mise à jour en train de s'effectuer, à présent, cette portion affichera la nombre de fichiers mis à jours, et le nombre de fichiers restant à récupérer depuis le serveur :

store.onprogress = function(details){
    $('debug').innerHTML += "Récupération des fichiers : " + details.filesComplete + ' / ' + details.filesTotal + "<br />";
};

Et, finalement, pour afficher un message en cas d'erreur à la récupération d'une mise à jour :

store.onerror = function(error) {
    $('debug').innerHTML += "Erreur : " + error.message + "<br />";
};

Et voici le genre de résultat que ça permet d'obtenir sur notre application de tests :

gears-after-update-1.png

Bien entendu, à vous d'améliorer l'aspect de tout cela - typiquement, en rajoutant une barre de chargement ; discrète, dans l'idéal : la mise à jour se faisant en arrière-plan, il serait dommage de bloquer tout l'écran pour indiquer que quelque chose se passe ^^

Note : La détection de mise à jour se fait sur le numéro de version du fichier manifest, et non fichier par fichier. Lors d'une mise à jour, c'est donc l'ensemble des fichiers décris par le fichier manifest qui sont re-téléchargés, et non uniquement ceux qui ont réellement été modifiés.


Aller plus loin

Avant de terminer cet article par un rassemblage de tous les morceaux de code présentés jusqu'à présent, allons plus loin sur quelques points spécifiques.


Forcer la détection de mise à jour

Nous avons dit plus haut qu'utiliser un objet de type ManagedStore pour le stockage local permettait de disposer de mises à jour automatiques des fichiers locaux : régulièrement, gears va vérifier si le numéro de version indiqué dans le fichier manifest est différent de celui qui a été mémorisé en local.

Si le numéro de version a changé, les fichiers listés dans le fichier manifest seront téléchargés, et l'utilisateur travaillera avec cette nouvelle version lors du prochain chargement de la page (la mise à jour des fichiers locaux n'est prise en compte que lors d'un rechargement de page : gears ne peut pas "débrancher" l'ancienne version des fichiers, et "re-brancher" la nouvelle version à la place sans recharger la page - on peut comprendre les difficultés et les problèmes que cela pourrait causer).

Le ManagedStore vérifie périodiquement l'existence de mises à jour (en pratique, il semblerait que ce soit fait quelques secondes après chaque chargement de page), et vous n'avez pas à effectuer la vérification de mise à jour vous-même.
Au pire, il peut vous arriver de vouloir forcer la vérification de mise à jour (éventuellement, dans le cas d'une application qui n'entraine aucun rechargement de page, ou si vous tenez vraiment à ce que vos utilisateurs aient une version à jour).

Dans ce cas, il vous suffit d'appeler la méthode checkForUpdate de votre instance de ManagedStore, comme nous l'avions fait plus haut pour forcer le téléchargement des fichiers lors de l'activation du stockage local :

store.checkForUpdate();

Gears se chargera tout seul de vérifier la version indiquée dans le fichier manifest, et de, au besoin, rapatrier les fichiers pour les stocker, en version à jour, en cache local.

Attention : Encore une fois, pour qu'une mise à jour se fasse, il est impératif que le numéro de version indiqué dans le fichier manifest soit différent de celui qui s'y trouvait lors de la vérification précédente !
(Et ce même si vous utilisez manuellement la méthode checkForUpdate ! )


Désactiver (momentanément) l'utilisation du stockage local

Pour je ne sais quelle raison (rappel : c'est vous qui avez une application que vous voulez rendre utilisable hors-ligne ^^ ), vous pouvez vouloir désactiver momentanément l'utilisation du stockage local.

Cela est possible en exploitant la propriété enabled de votre instance de ManagedStore : en la passant à false, au prochain chargement de la page, ce ne seront plus les fichiers stockés en local qui seront utilisés, mais ceux téléchargés depuis le serveur :

store.enabled = false;

Et pour recommencer à utiliser les fichiers stockés localement, il suffit d'inverser la valeur de la même propriété :

store.enabled = true;

Un point intéressant à noter : lorsque vous recommencez à utiliser un stockage local, vous utilisez la version actuellement stockée en local des fichiers : ils ont été conservés par gears.
En particulier, cela signifie qu'ils ne seront pas retéléchargés, et donc, pas mis à jour, si le numéro de version dans le fichier manifest n'a pas été modifié !


Désactiver gears pour votre site

Lors de vos tests, il vous sera certainement nécessaire, un bon nombre de fois, de "repartir de zéro", comme si vous n'aviez jamais autorisé gears pour votre site...
Hors, nous venons de voir que désactiver un ManagedStore ne le purgeait pas.

Finalement, la meilleure façon de recommencer au tout début est de révoquer l'autorisation que vous aviez donné à gears de fonctionner pour votre site ; de la sorte, à la prochaine tentative d'utilisation d'une des fonctionnalités fournies par cette extension, il vous faudra re-donner votre autorisation, et, entre temps, tout ce qui avait été stocké en local aura été purgé.

Pour cela, rendez-vous dans le menu "options" de votre navigateur (ou équivalent), pour ouvrir la boite de dialogue "Google Gears Settings".
Celle-ci affiche la liste des sites qui ont le droit d'exploiter les fonctionnalités de gears :

gears-settings.png

Le votre est forcément dans cette liste, puisque vous l'avez autorisé plus haut.
Cliquez sur le lien "remove" de la ligne correspondant à votre site, validez... Et voila, vous repartez sur des bases vierges : lors de la prochaine utilisation d'une fonctionnalité fournie par gears, l'extension demandera à nouveau votre autorisation.

(Et cette méthode est sans le moindre doute plus rapide que de désinstaller l'extension, redémarrer le navigateur pour que cette désinstallation soit prise en compte, réinstaller l'extension, et redémarrer le navigateur à nouveau pour que la réinstallation soit prise en compte ^^ )


Recollons les morceaux

Nous avons vu les concepts permettant d'exploiter une partie des fonctionnalités offertes par Google Gears, pour stocker en local, sur le poste client, les fichiers statiques de notre application, ce qui était le but de cet article.

Avant de terminer, je pense qu'il serait nécessaire de reprendre quelques portions de code en les regroupant en méthodes utilisables, pour que vous puissiez voir en un seul coup d'oeil comment le tout fonctionne...


Squelette de page HTML

Pour commencer, l'application de test utilisée ici est composée d'une seule page HTML, découpée en plusieurs parties :

  • liens de contrôle de gears
  • affichage d'une liste de notes issues de la base de données (côté serveur)
  • formulaire permettant d'ajouter une note
  • et enfin, zone de débug

En somme :

<!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="en" lang="en">
<head>
    <title>Google Gears</title>

    <script type="text/javascript" src="js/lib/gears_init.js"></script>

    <link href="css/style.css" media="screen" rel="stylesheet" type="text/css" />
    <script type="text/javascript" src="js/lib/prototype.js"></script>
    <script type="text/javascript" src="js/lib/scriptaculous/builder.js"></script>
    <script type="text/javascript" src="js/lib/scriptaculous/effects.js"></script>
    <script type="text/javascript" src="js/lib/scriptaculous/dragdrop.js"></script>
    <script type="text/javascript" src="js/lib/scriptaculous/controls.js"></script>
    <script type="text/javascript" src="js/application.js"></script>

    <script type="text/javascript" src="js/gestion-gears.js"></script>

</head>
<body>
<?php
    $notes = array();
    // TODO récupérer les notes depuis la DB (PHP)
    // TODO gérer l'ajout d'une nouvelle note en DB depuis les saisies dans le formulaire (PHP)
?>

    <div id="control">
        <a href="http://gears.google.com/?action=install&amp;name=<?php
                echo rawurlencode('Google Gears, Tutorial');
            ?>&amp;message=<?php
                echo rawurlencode('Installez Google Gears pour accélérer le chargement des fichiers statiques de votre application !');
            ?>&amp;return=<?php
                echo rawurlencode('http://' . $_SERVER['HTTP_HOST'] . $_SERVER['SCRIPT_NAME']);
            ?>&amp;icon_src=<?php echo
                rawurlencode('http://tests/gears/faster-loading/application/img/icon-install-gears.png');
            ?>"
            id="install" style="display: none;">Installer Gears</a>
        <a href="#" id="activate" onclick="activateGears(); return false;" style="display: none;">Activer Gears</a>
        <a href="#" id="maj" onclick="majGears(); return false;" style="display: none;">Forcer la recherche de MAJ</a>
        <a href="#" id="deactivate" onclick="deactivateGears(); return false;" style="display: none;">Désactiver Gears</a>
    </div>

    <?php foreach ($notes as $note) : ?>
    <p class="note">
        Auteur : <?php echo htmlspecialchars($note->auteur); ?>
        <br />Date : <?php echo htmlspecialchars($note->date); ?>
        <br />Texte : <?php echo htmlspecialchars($note->texte); ?>
    </p>
    <?php endforeach ; ?>

    <form method="post" action="" onsubmit="return validation();">
        <p>
            Auteur : <input type="text" name="auteur" id="auteur" maxlength="64" value="<?php echo htmlspecialchars($auteur); ?>" />
            <br />Date : <input type="text" name="date" id="date" maxlength="10" value="<?php echo htmlspecialchars($date); ?>" />
            <br />Texte :
            <br /><textarea name="texte" id="texte" rows="5" cols="50"><?php echo htmlspecialchars($texte); ?></textarea>
            <br /><input type="submit" value="Hop !" name="ok" />
        </p>
    </form>

    <div id="debugTimer"></div>
    <div id="debug"></div>

</body>
</html>

Je passe sur les portions de code PHP : il ne s'agit pas du point important pour cet article... Vous trouverez le tout en pièce jointe à ce billet (Cf en bas de la page)

A noter : les liens en haut de la page, qui sont tous masqués initialement, et que nous rendrons visible à partir du code Javascript, en détectant si gears est installé ou non, autorisé ou non, ...


LocalServer et ManagedStore

Pour les besoins de notre application, nous avons besoin de créer une instance de classe LocalServer, et une instance d'objet ManagedStore, comme vu plus haut.

Elles seront mémorisées au sein des deux variables suivantes :

var localServer;
var store;

L'instanciation des classes en question viendra plus tard.

Note : dans un cas réel, vous encapsuleriez peut-être tout ça dans une autre classe, plutôt que de tout fourrer dans l'espace de nom global... A vous de voir : nous ne sommes ici pas dans un cas réel ^^


Actions au chargement de la page

Au chargement de la page, plusieurs actions sont à gérer :

  • Détecter si l'extension / le plugin Google gears est installé
  • Si non, afficher le lien menant à la page d'installation
  • Si oui,
    • Détecter si l'utilisateur a donné à gears l'autorisation de fonctionner pour notre site
    • Si non, afficher le lien permettant d'activer Gears pour notre site
    • Si oui,
      • Détecter si le ManagedStore est actif
      • Si non, afficher le lien permettant de l'activer
      • Si oui... c'est tout : notre application utilise déjà les fichiers stockés localement - ou est en train de les télécharger si c'est le premier affichage de la page depuis l'activation de gears.

Concrètement, tout ceci peut se traduire par la portion de code suivante :

Event.observe(window, 'load', function () {
    if (!window.google || !google.gears) {
        $('install').show();
    } else {
        if (google.gears.factory.hasPermission) {
            $('debug').innerHTML += "Permissions : OK<br />";
            createStore();
            if (store.enabled) {
                $('debug').innerHTML += "Store : actif<br />";
                $('maj').show();
                $('deactivate').show();
            } else {
                $('debug').innerHTML += "Store : non actif<br />";
                $('activate').show();
            }
        } else {
            $('debug').innerHTML += "Permissions : NON OK<br />";
            $('activate').show();
        }
    }
});

Vous noterez que j'ai choisir d'utiliser la méthode Event.observe du Framework prototype sur l'événement "load" pour que ma portion de code soit appelée une fois l'intégralité de la page chargée, afin d'être sûr que tous les éléments du DOM soient présents avant de commencer à exécuter du code.

Un autre point : pour simplifier les choses, la page HTML inclut de base tous les liens, masqués, et nous affichons en Javascript ceux qui nous sont nécessaires. Dans l'idéal, il serait probablement possible de n'inclure aucun lien dans la page, et de les créer à la volée... Ici encore, il y a une différence entre une mini-application de tests, et un cas réel !


Créer une instance de ManagedStore

En local, nos fichiers statiques seront stockés au sein d'un ManagedStore.
Pour pouvoir obtenir une instance de cette classe, comme vu plus haut, il faut passer par une instance de LocalServer :

var createStore = function () {
    localServer = google.gears.factory.create('beta.localserver');
    store = localServer.createManagedStore('test');
    store.manifestUrl = 'manifest.json';

    store.oncomplete = function(details) {
        if (!details.newVersion.blank()) {
            $('debug').innerHTML += "Mise à jour effectuée ; version : " + details.newVersion + "<br />";
            $('debug').innerHTML += "=&gt; Recharger la page<br />";
        }
        else {
            $('debug').innerHTML += "Pas de nouvelle version...<br />";
        }
    };
    store.onerror = function(error) {
        $('debug').innerHTML += "Erreur : " + error.message + "<br />";
    };
    store.onprogress = function(details){
        $('debug').innerHTML += "Récupération des fichiers : " + details.filesComplete + ' / ' + details.filesTotal + "<br />";
    };
}; // createStore

Nous profitons de l'instanciation de notre espace de stockage pour brancher nos gestionnaires sur les événements affectant son cycle de vie, notamment pendant les éventuelles mises à jour des fichiers stockés localement.


Activation de gears pour notre site

Lors du clic sur le lien d'activation de gears, nous devons :

  • créer le ManagedStore qui servira à stocker localement les fichiers distants,
  • forcer une mise à jour de celui-ci, pour que les fichiers soient récupérés depuis le serveur lors de la première activation de gears.

Nous en profitons pour nous assurer que le store soit bien activé, en passant à true sa propriété enabled :

var activateGears = function () {
    $('debug').innerHTML += "Activation de google gears<br />";
    createStore();
    store.enabled = true;
    majGears();
}; // activateGears

Cette méthode sera appelée lorsque l'utilisateur activera Gears pour notre site (ce qui explique qu'il faille forcer la MAJ), ainsi que lorsque l'utilisateur ré-activera Gears après l'avoir désactivé (ce qui explique qu'il faille nous assurer que l'attribut enabled est à true : il aura été basculé à false lors de la désactivation).


Mise à jour des fichiers stockés localement

La mise à jour des fichiers stockés localement est des plus simple : il suffit de faire appel à la méthode checkForUpdate de notre instance de ManagedStore :

var majGears = function () {
    if (store.enabled) {
        $('debug').innerHTML += "Lancement de la détection de Mises à jour...<br />";
        store.checkForUpdate();
    } else {
        $('debug').innerHTML += "Store désactivé =&gt; Pas de vérification de MAJ...<br />";
    }
}; // activateGears

Encore une (dernière ?) fois, je rappelle que même si des fichiers ont été mis à jours sur le serveur, la mise à jour ne se fera que si le numéro de version dans le fichier manifest a été modifié !


Fichier manifest

En parlant de fichier manifest... Il s'agit bien entendu du même que celui présenté plus haut, mais le reproduire ici ne peut faire de mal à personne :

{
    "betaManifestVersion": 1,
    "version": "0.1",
    "entries":
    [
        {"url": "css/style.css"},
        {"url": "js/lib/gears_init.js"},
        {"url": "js/lib/prototype.js"},
        {"url": "js/lib/scriptaculous/builder.js"},
        {"url": "js/lib/scriptaculous/effects.js"},
        {"url": "js/lib/scriptaculous/dragdrop.js"},
        {"url": "js/lib/scriptaculous/controls.js"},
        {"url": "js/application.js"},
        {"url": "js/gestion-gears.js"},
        {"url": "img/error.png"},
        {"url": "img/tick.png"}
    ]
}

Je rappelle :

  • le numéro de version du format de fichier,
  • le numéro de version du fichier en lui-même,
  • et la liste des fichiers que gears doit stocker en local.


Désactiver gears

Après ce que j'ai dit quelques lignes plus haut, la désactivation - temporaire - de gears, pour utiliser à nouveau des fichiers téléchargés depuis le serveur à chaque chargement de page, ne devrait pas poser de problème :

deactivateGears = function () {
    $('debug').innerHTML += "Désactivation de gears...<br />";
    $('debug').innerHTML += "=&gt; Recharger la page<br />";
    store.enabled = false;
}; // deactivateGears

En effet, il s'agit uniquement de mettre à false le flag enabled du ManagedStore utilisé pour stocker localement les fichiers.


Avant de passer à la suite...

Cet article était le premier d'une série ayant pour objectif de vous permettre de développer une application continuant à fonctionner hors-connexion, en utilisant Google gears.
Il nous a permis de découvrir les premiers concepts nécessaires à l'utilisation de Gears ; en particulier, comment installer l'extension, et détecter si l'utilisateur l'a autorisée à fonctionner pour notre site.

Bien entendu, stocker localement, sur le poste client, les fichiers statiques n'est qu'un premier pas : débranchez votre cable réseau, et vous réaliserez vite qu'il reste encore du travail à effectuer !

Avant de vous donner rendez-vous pour le second article de cette série, j'aimerais attirer votre attention sur un point : le concept offert par Gears - pouvoir continuer à travailler hors-ligne avec une application Web - est peut-être "révolutionnaire"... mais il est surtout complexe pour vos utilisateurs : il faut installer une extension / un plugin, il faut autoriser l'utilisation de celle-ci par votre site, il faut comprendre la notion de synchronisation, ...
Pour cette raison, j'estime que Gears ne doit pas devenir un pré-requis pour votre site : vous pouvez en utiliser les fonctionnalités, et proposer à vos utilisateurs n'en bénéficier... Mais votre site doit rester fonctionnel pour les utilisateurs n'ayant pas Gears installé !
Dans la même logique, Gears est sûrement plus adapté à un Back-office, réservé à un nombre restreint d'utilisateurs que vous pouvez facilement former, qu'à un Front-office ouvert à des milliers de visiteurs !


Notes

[1] Le travail hors-ligne sous Google Docs étant limité aux documents texte uniquement - du moins pour l'instant

[2] Cf What browsers and operating systems are supported by Google Gears?

[3] Si votre application est mise à jour plus souvent qu'elle n'est utilisée... C'est dommage ^^

[4] J'utiliserai parfois le terme "extension" (dénomination usuelle sous Firefox), et parfois le terme "plugin" (qui est plus habituel sous Internet Explorer). Les deux, pour cet article, sont à considérer comme synonymes.