xulfr.org

8.1 Interfaces XPCOM

Écrit par Neil Deakin. Traduit par Maximilien (24/07/2004).
Page originale : http://www.xulplanet.com/tutorials/xultu/xpcom.html xulplanet.com

Dans cette section, nous allons faire une brève présentation de XPCOM ("Modèle de composants objets multi plate-forme"), qui est le système d'objets utilisé par Mozilla.

Appel des objets natifs

En utilisant XUL, nous pouvons construire des interfaces utilisateurs complexes. En y joignant des scripts, on peut modifier l'interface et réaliser des actions. Cependant, il y a un certain nombre de choses qui ne peuvent être réalisées directement en javascript. Par exemple, si nous voulons créer une application gérant des courriels, nous avons besoin d'écrire des scripts permettant de se connecter au serveur de courriels, afin de les retirer ou d'en envoyer. Le langage Javascript ne permet pas de faire ce genre de choses.

Le seul moyen pour le faire est d'écrire du code natif implémentant ces fonctionnalités avancées. Nous avons aussi besoin d'un moyen pour pouvoir appeler ce code natif aisément à partir de nos scripts. Mozilla fournit une telle possibilité en utilisant XPCOM.

À propos d'XPCOM

Mozilla est construit à partir d'une multitude de composants, chacun d'eux réalise une tâche précise. Par exemple, il y a un composant pour chaque menu, bouton et élément. Ces composants sont construits à partir de plusieurs définitions appelées interfaces.

Une interface dans Mozilla est une définition d'un ensemble de fonctions que peuvent implémenter des composants. Les composants sont ce qui permet au code de Mozilla de réaliser des traitements. Chaque composant implémente les fonctions conforme à une interface. Un composant peut implémenter plusieurs interfaces. Et plusieurs composants peuvent implémenter la même interface.

Prenons l'exemple d'un composant de fichier. Une interface sera créée décrivant les propriétés et les fonctions que l'on veut pouvoir appliquer sur un fichier. Les propriétés seront le nom du fichier, sa date de dernière modification ou sa taille. Les fonctions permettront d'effacer, de déplacer ou de copier le fichier.

L'interface "Fichier" décrit uniquement les caractéristiques du fichier, elle ne les implémente pas. L'implémentation est laissé au composant. Celui-ci contiendra le code qui permettra de récupérer le nom du fichier, sa date, sa taille. Il contiendra également le code pour le copier ou le renommer.

On n'a pas à s'intéresser à la manière dont l'implémentation est faite par le composant, du moment qu'il respecte l'interface correctement. Bien sûr, nous aurons une implémentation différente pour chaque plateforme. Les versions Macintosh ou Windows pour gérer les fichiers seront très différentes. Cependant ils implémentent la même interface et par conséquent on peut accéder au composant en utilisant les fonctions de cette interface.

Dans Mozilla, les interfaces sont préfixées par nsI ainsi elles sont facilement reconnaissables. Par exemple, nsIAddressBook est l'interface qui interagit avec le carnet d'adresses, nsISound est celle utilisée pour écouter des fichiers et nsILocalFile pour manipuler des fichiers.

Typiquement, les composants XPCOM sont implémentés nativement, ce qui signifie qu'ils font des choses que le langage Javascript ne peut réaliser. Par contre, on peut les appeler à partir de scripts. C'est ce que l'on va voir maintenant. Nous pouvons appeler n'importe laquelle des fonctions fournies par le composant, tel qu'il est décrit par les interfaces qu'il implémente. Par exemple, vous avez un composant à votre disposition, vous vérifiez alors s'il implémente l'interface nsISound, et si c'est la cas, vous pouvez jouer un son grâce lui.

Le processus d'appel de composant XPCOM à partir de script se nomme XPConnect : une couche qui traduit les objets du script en objets natifs.

Créer des objets XPCOM

L'appel de composant XPCOM se fait en trois étapes :

  1. Récupérer un composant.
  2. Récupérer la partie du composant qui implémente l'interface que l'on veut utiliser.
  3. Appeler la fonction que l'on a besoin.

Une fois que l'on a effectué les deux premières étapes, on peut effectuer la dernière autant de fois que nécessaire. Prenons le cas du renommage d'un fichier. La première étape est de récupérer le composant "fichier". Puis on interroge ledit composant pour récupérer la portion qui implémente l'interface nsILocalFile. Enfin, on appelle les fonctions fournies par l'interface. Cette interface est utilisée pour représenter un unique fichier.

Nous avons vu que les noms d'interfaces commencent toujours par nsI. Par contre, la désignation des composants utilise la syntaxe URI. Mozilla stocke une liste de tous les composants disponibles dans son propre registre. Un utilisateur peut installer de nouveaux composants si besoin est. Cela fonctionne comme les plugins.

Mozilla fournit un composant "fichier" c'est à dire implémentant nsILocalFile. Ce composant est désigné par l'URI @mozilla.org/file/local;1. Le schéma URI component: permet de spécifier un composant. D'autres composants peuvent être désignés de manière similaire.

L'URI du composant peut être utilisé pour récupérer le composant. Voici en Javascript le code correspondant.

var aFile = Components.classes["@mozilla.org/file/local;1"].createInstance();

Le composant "fichier" est récupéré et stocké dans la variable aFile. Components dans l'exemple fait référence à un objet fournissant des fonctions sur les composants. Ici on récupère la classe d'un composant en utilisant la propriété classes. Cette propriété est un tableau de tous les composants disponibles. Pour obtenir un composant différent, il suffit de remplacer l'URI par celui du composant voulu. Finalement, une instance est crée avec la fonction createInstance.

Vous devez vérifier que la valeur de retour de createInstance est différente de null, dans le cas contraire cela indique que le composant n'existe pas.

Pour l'instant, nous n'avons qu'une référence sur le composant "fichier". Pour appeler des fonctions, nous avons besoin de récupérer une de ces interfaces, dans notre cas nsILocalFile. Une seconde ligne est ajoutée à notre code comme suit :

 var aFile = Components.classes["@mozilla.org/file/local;1"].createInstance();
 if (aFile)   aFile.QueryInterface(Components.interfaces.nsILocalFile);
   

La fonction QueryInterface est fournie par tous les composants, elle permet d'obtenir une interface particulière du composant. Elle prend un seul paramètre : le nom de l'interface que l'on veut. La propriété interfaces de Components contient une liste de toutes les interfaces des composants. Ici on utilise l'interface nsILocalFile que l'on passe en paramètre à QueryInterface. Ainsi aFile fait référence à la partie du composant qui implémente nsILocalFile.

Ces deux lignes de Javascript peuvent être utilisées pour obtenir n'importe quelle interface de n'importe quel composant. Il suffit de remplacer le nom du composant et le nom de l'interface que l'on veut utiliser. On peut bien sûr choisir n'importe quel nom pour la variable. Par exemple si l'on veut utiliser l'interface pour le son, notre code peut être comme suit :

         var sound = Components.classes["@mozilla.org/sound;1"].createInstance();
         if (sound) sound.QueryInterface(Components.interfaces.nsILocalFile);
      

Les interfaces XPCOM peuvent hériter d'autres interfaces. L'interface héritière possède ses propres fonctions mais aussi toutes celles des interfaces parentes. Ainsi toute interface hérite de l'interface principale nsISupports qui fournit la fonction QueryInterface. Comme tout composant doit implémenter nsISupports, la fonction QueryInterface est disponible sur tous les composants.

Plusieurs composants peuvent implémenter la même interface. Typiquement ce sont des sous-classes de l'original mais pas nécessairement. N'importe quel composant peut implémenter les fonctionnalités de nsILocalFile. Et un composant peut implémenter plusieurs interfaces. C'est pour ces raisons que l'on doit procéder en deux étapes pour appeler les fonctions d'une interface.

Cependant, il existe un raccourci pour réduire ces deux étapes en une seule ligne de code.

 var aLocalFile = Components.classes["@mozilla.org/file/local;1"].createInstance(Components.interfaces.nsILocalFile);
      

Cela élimine le besoin de créer une instance et ensuite de l'interroger pour une interface précise, en deux étapes séparées.

Un appel à QueryInterface sur un objet qui ne fournit pas l'interface demandée lance une exception. Si vous n'êtes pas sûr que le composant supporte une interface, vous pouvez utiliser l'opérateur instanceof comme suit :

         var aFile = Components.classes["@mozilla.org/file/local;1"].createInstance();
         if (aFile instanceof Components.interfaces.nsILocalFile){
           // faire quelque chose si il s'agit d'une instance du bon type
         }
      
   

L'opérateur instanceof renvoie true si aFile implémente l'interface nsILocalFile et de manière similaire à la méthode QueryInterface, rend l'objet aFile compatible avec l'interface nsILocalFile .

L'emploi des fonctions de l'interface

Maintenant que nous avons un objet qui fait référence à un composant avec l'interface nsILocalFile, nous pouvons appeler les fonctions de celle-ci à travers l'objet. La liste suivant montre quelques propriétés et méthodes de l'interface nsILocalFile.

initWithPath
Cette méthode est utilisée pour initialiser le chemin et le nom du fichier pour l'interface nsILocalFile. Le premier paramètre doit être le chemin du fichier, comme par exemple /usr/local/mozilla.
leafName
Le nom du fichier sans son chemin complet.
fileSize
La taille du fichier.
isDirectory()
renvoie true si nsILocalFile représente un répertoire.
remove(recursif)
Efface un fichier. Si le paramètre recursif est true, le répertoire et tous ses fichiers et sous-répertoires sont effacés.
copyTo ( repertoire, nouveauNom )
Copie un fichier dans un autre répertoire, optionnellement renomme le fichier. La variable repertoire doit être un objet nsILocalFile représentant le répertoire où l'on veut copier le fichier .
moveTo ( repertoire, nouveauNom )
déplace le fichier dans un autre répertoire ou le renomme. La variable repertoire doit être un objet nsILocalFile représentant le répertoire où l'on va mettre le fichier .

Pour effacer un fichier, on doit d'abord assigner le fichier à un objet nsILocalFile. Nous appelons la méthode initWithPath pour indiquer le fichier en question, en indiquant juste le chemin de celui-ci. Puis on appelle la fonction remove avec le paramètre recursif à false. Voici le code correspondant :

         var aFile = Components.classes["@mozilla.org/file/local;1"].createInstance();
         if (aFile instanceof Components.interfaces.nsILocalFile){
              aFile.initWithPath("/mozilla/testfile.txt");
              aFile.remove(false);
         }
      

Ce code prend le fichier /mozilla/testfile.txt et l'efface. Essayez cet exemple en ajoutant le code à un gestionnaire d'évènements. Vous devez changer le nom du fichier pour qu'il corresponde à un fichier existant sur votre poste local et que vous voulez effacer.

Dans la liste du dessus, nous avons vu deux fonctions copyTo et moveTo. Ces fonctions sont utilisées pour respectivement copier et déplacer des fichiers. Notez qu'elles ne prennent pas en paramètre une chaîne de caractères pour désigner un répertoire mais un objet nsILocalFile. Cela veut dire que l'on doit récupérer deux composants "fichier". L'exemple suivant montre comment copier un fichier.

function copyFile(sourcefile,destdir) {

      // récupérer un composant pour le fichier à copier
      var aFile = Components.classes["@mozilla.org/file/local;1"]
          .createInstance(Components.interfaces.nsILocalFile);
      if (!aFile) return false;

      // récupérer un composant pour le répertoire où la copie va s'effectuer.
      var aDir = Components.classes["@mozilla.org/file/local;1"]
        .createInstance(Components.interfaces.nsILocalFile);
      if (!aDir) return false;

      // ensuite, on initialise les chemins
      aFile.initWithPath(sourcefile);
      aDir.initWithPath(destdir);

      // Au final, on copie le fichier sans le renommer
      aFile.copyTo(aDir,null);
   }

   copyFile("/mozilla/testfile.txt","/etc");
   

Les services XPCOM

Il y a des composants spéciaux qu'on appelle services. On ne peut pas créer plusieurs instances d'un service parce qu'il doit être unique. Les services fournissent des fonctions manipulant des données globales ou effectuent des opérations sur d'autres objets. Au lieu d'utiliser createInstance, on appelle getService pour récupérer une référence sur le composant de type "service". À part ça, les services ne diffèrent pas des autres composants.

Un exemple de service fournit par Mozilla est le service pour les marque-pages. Il vous permet d'ajouter un marque-page à la liste courante des marque-pages de l'utilisateur. Voici un exemple :

   var bmarks = Components.classes["@mozilla.org/browser/bookmarks-service;1"].getService();

   bmarks.QueryInterface(Components.interfaces.nsIBookmarksService);
   bmarks.addBookmarkImmediately("http://www.mozilla.org","Mozilla",0,null);
      
   

Tout d'abord, le composant @mozilla.org/browser/bookmarks-service;1 est récupéré et son service est placé dans la variable bmarks. Nous utilisons QueryInterface pour récupérer l'interface nsIBookmarksService. La fonction addBookmarkImmediately fournis par cette interface peut être utilisée pour ajouter des marque-pages. les deux premiers paramètres de cette fonction sont l'URL et le titre du marque-page. Le troisième paramètre est le type de marque-page qui doit normalement être 0, et le dernier paramètre est l'encodage des caractères du document correspondant au marque-page, qui peut être nul.


Dans la section suivante, nous verrons quelques-unes des interfaces que l'on peut utiliser, fournies par Mozilla.