Aller au contenu | Aller au menu | Aller à la recherche

Créer son nsIProtocolHandler pour afficher un buffer (char *)

Dans le cadre de la réalisation de mon premier composants XPCOM en C++, j'ai dû résoudre le problème suivant : Comment récupérer un buffer pour le manipuler en JavaScript et l'afficher via la définition d'un nouveau protocol ?

Voici mes découvertes :

nsIBinaryOutputStream

L'objet JavaScript suivant permet d'écriture dans un output stream :

Components.classes"@mozilla.org/binaryoutputstream;1"
  .createInstance(Components.interfaces.nsIBinaryOutputStream);

Un tel objet permet de récupérer le buffer générer au sein de mon composant C++ via la méthode writeByteArray qui prend en paramètre le buffer (char *) et la taille de celui-ci. J'ai donc créer une méthode saveToStream qui prend en paramètre un objet implémentant l'interface nsIBinaryOutputStream.
Par exemple pour écrire dans un fichier :

// création d'un fichier local
var aFile = Components.classes"@mozilla.org/file/local;1".createInstance(Components.interfaces.nsILocalFile);
aFile.initWithPath( "/tmp/buffer");
// création du OutputStream associé au fichier
var stream = Components.classes"@mozilla.org/network/file-output-stream;1".createInstance(Components.interfaces.nsIFileOutputStream);
stream.init(aFile, 0x04 | 0x08 | 0x20, 0600, 0);
// création du binary output stream
var bos = Components.classes"@mozilla.org/binaryoutputstream;1".createInstance(Components.interfaces.nsIBinaryOutputStream);
// cette étape va permettre d'écrire dans l'output stream du fichier
bos.setOutputStream(stream);
// écriture du buffer dans l'output stream du fichier
myComponent.saveToStream(bos);
// il n'y a plus qu'à fermer les flux pour finaliser l'écriture du buffer dans le fichier
bos.close();
stream.close();

Grâce au Binary Output Stream je suis capable d'écrire le buffer générer dans mon composant XPCOM dans un fichier.

nsIChannel et nsIPipe

Dans Mozilla, il est possible de définir son propre protocol. La principale étape est de définir la méthode newChannel qui prend en paramètre un objet implémentant l'interface nsIURI et doit retourner un objet implémentant l'interface nsIChannel. Dans mon cas c'est dans cette méthode que j'utilise mon composant et donc que je récupère un objet implémentant l'interface nsIOutputStream. Or un nsIChannel est basé sur un objet implémentant une interface nsIInputStream. Il fallait trouver le moyen d'obtenir le input stream associé à l'output stream.

L'objet JavaScript suivant permet de lié input stream et output stream :

Components.classes"@mozilla.org/pipe;1".createInstance(Components.interfaces.nsIPipe);

Enfin il fallait faire de l'input stream obtenu un objet implémentant l'interface nsIChannel, c'est ce que permet l'objet suivant :

Components.classes"@mozilla.org/network/input-stream-channel;1".createInstance(Components.interfaces.nsIInputStreamChannel);

La méthode newChannel s'implémente ainsi :

myProtocolHandler.prototype.newChannel = function(aURI) {
     // création du pipe
     var pipe = Components.classes"@mozilla.org/pipe;1".createInstance(Components.interfaces.nsIPipe);
     pipe.init(true,true, 0, 0, null);
     // création du binary output stream
     var bos = Components.classes"@mozilla.org/binaryoutputstream;1".createInstance(Components.interfaces.nsIBinaryOutputStream);
     // cette étape va permettre d'écrire dans l'output stream du pipe
     bos.setOutputSTream(pipe.outputStream);
     // écriture du buffer dans l'output stream du pipe
     myComponent.saveToStream(bos);
     // fermeture des output stream
     bos.close();
     pipe.outputStream.close();
     // il reste plus qu'à créer le channel
     var channel = Components.classes"@mozilla.org/network/input-stream-channel;1".createInstance(Components.interfaces.nsIInputStreamChannel);
     channel.setURI(aURI);
     channel.contentStream = pipe.inputStream;
     channel.QueryInterface(Components.interfaces.nsIChannel);
     return channel;
}

C'est bien mais c'est synchone...

nsIPipe et nsIThreadManager

La dernière étape consiste à faire en sorte que la création du buffer et l'écriture soit asynchrone. Ce qu'il faut savoir c'est que le pipe permet d'écrire dans un thread et de lire le résultat dans un autre, de plus tant que rien n'est écrit dans l'output stream du pipe rein n'est lu, enfin lorsque l'output stream est clos le channel et donc la requête qui a été créer à partir de l'input stream du pipe est considéré comme fini.

Il faut d'abord définir un thread d'exécution :

var workingThread = function(pipe, myComponent) {
   this.pipe = pipe;
   this.component = myComponent;
};
workingThread.prototype = {
  run: function() {
    try {
      var bos = Cc"@mozilla.org/binaryoutputstream;1".createInstance(Ci.nsIBinaryOutputStream);
      bos.setOutputStream(this.pipe.outputStream);
      this.component.saveToStream(bos);
      bos.close();
      this.pipe.outputStream.close();
    } catch(err) {
      Components.utils.reportError(err);
    }
  },
  
  QueryInterface: function(iid) {
    if (iid.equals(Ci.nsIRunnable) ||
        iid.equals(Ci.nsISupports)) {
            return this;
    }
    throw Components.results.NS_ERROR_NO_INTERFACE;
  }
};

Ensuite il suffit de déclarer un objet global permettant l'exécution en fond de tâche :

background = Components.classes"@mozilla.org/thread-manager;1".getService().newThread(0);

Il ne reste plus qu'à réécrire la méthode newChannel du protocol handler :

myProtocolHandler.prototype.newChannel = function(aURI) {
     // création du pipe
     var pipe = Components.classes"@mozilla.org/pipe;1".createInstance(Components.interfaces.nsIPipe);
     pipe.init(true,true, 0, 0, null);
     // appel en fond de tâche de la création du buffer et de l'écriture
     background.dispatch(new workingThread(pipe, myComponent), background.DISPATCH_NORMAL);
     // il reste plus qu'à créer le channel
     var channel = Components.classes"@mozilla.org/network/input-stream-channel;1".createInstance(Components.interfaces.nsIInputStreamChannel);
     channel.setURI(aURI);
     channel.contentStream = pipe.inputStream;
     channel.QueryInterface(Components.interfaces.nsIChannel);
     return channel;
}

En espérant que ça puisses vous servir ;-)