Créer son nsIProtocolHandler pour afficher un buffer (char *)
Par René-Luc D'Hont le jeudi 18 septembre 2008, 15:40 - Technologies Mozilla - Lien permanent
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 ;-)