Création d'extensions pour Firefox avec le système de compilation de Mozilla
Un article de MDC.
Sommaire |
Il existe beaucoup d'informations sur la création d'extensions pour Firefox. Actuellement, tous ces documents supposent toutefois que vous développez votre extension en utilisant seulement XUL et JavaScript. Pour des extensions plus complexes, la création de composants en C++ fournissant des fonctionnalités supplémentaires peut s'avérer nécessaire. Les raisons pour inclure des composants C++ dans votre extension sont notamment :
- Besoin de haute-performance au delà de ce que peut faire du code JavaScript.
- Utilisation de bibliothèques tierces écrites en C ou C++.
- Utilisation d'interfaces Mozilla qui ne sont pas exposées par XPCOM (i.e. NSPR).
Cet article décrit comment est défini l'environnement de développement pour des grosses et complexes extensions Firefox comprenant ce qui a été décrit ci-avant. La collecte d'informations sur le sujet a été difficile par manque d'informations publiées sur le sujet, mais elle a été possible par les contributions de plusieurs membres de la communauté de développement Mozilla qui ont eu la patience de répondre aux questions des débutants. Cet article peut être incomplet ou erroné par endroit. Il ne constitue pas un guide pour les hackers confirmés qui veulent étendre la plateforme Firefox. Si vous en connaissez plus sur le sujet, votre aide dans la contribution de cet article sera grandement appréciée.
Je devrais également souligner que vous n'avez pas à compiler Mozilla ou utiliser le système de compilation de Mozilla si vous voulez créer des composants C++ pour Mozilla. Si vous ne cherchez qu'à créer un composant XPCOM ou deux, cela risque d'être insurmontable et vous devriez plutôt regarder ce guide. D'un autre côté, si vous êtes un développeur expérimenté et savez développer compiler une grosse et complexe extension, vous devriez considérer l'approche décrite dans cet article.
Un dernier point : j'ai seulement essayé ces techniques dans Firefox, mais elles devraient probablement fonctionner sans changements dans d'autres plateformes Gecko telles que Thunderbird ou Seamonkey. Si quelqu'un peut le confirmer et/ou indiquer les différences, je mettrai à jour cet article.
[modifier] Bambi rencontre Mozilla
Tout ceci n'est pas des plus simples. En particulier, l'étape initiale consistant à compiler Mozilla est en soit un projet gargantuesque. Si vous n'êtes pas un développeur expérimenté en C++, restez au JavaScript.
[modifier] Sur des plateformes Windows
La première fois que j'ai compilé Mozilla, je me suis servi de ce guide. Je ne me souviens plus avec précision le temps nécessaire pour y arriver, mais c'était plus que ce que j'avais prévu initialement. Voici un guide donnant de bons conseils. En suivant chaque étape méthodologiquement, vous devriez y arriver. Soyez certain que si vous réussissez l'étape de compilation, le reste vous semblera plus simple.
[modifier] Sur d'autres plateformes
Sur d'autres plateformes, nommées Linux et MacOS, le processus est plus simple. Tous les outils pour la compilation sont disponibles sous la forme de paquetages, et tout ce que vous avez à faire est de lancer quelques commandes dans le terminal. Vous trouverez toutes les instructions propre à chaque OS ici.
[modifier] Structurez votre projet
Mozilla comprend plusieurs extensions complexes qui sont intégrées dans son processus de compilation. Il a ainsi été nécessaire de résoudre tous les problèmes de création et enregistrement de composants XPCOM, de la création de fichiers JAR et manifestes, de leur installation dans le répertoire extensions/ de Firefox, etc. Donc il est nécessaire de décliner cette infrastructure pour créer notre extension.
Tout d'abord, pensez à un nom accrocheur pour votre extension et créez un répertoire portant ce nom dans le répertoire /mozilla/extensions/. N'utilisez que des lettres minuscules. Vous devriez voir plusieurs autres répertoires (inspector/, reporter/, etc.) au même niveau dans l'arbre de compilation.
Notez qu'avant toute étape de compilation, le système de compilation de Mozilla invoque un process qui génère les fichiers makefiles utilisés à partir des modèles appelés Makefile.in. Les fichiers makefiles sont similaires ou identiques aux modèles, mais cette souplesse accrue de makefiles générés dynamiquement est l'une des choses rendant le système de compilation si performant.
[modifier] Anatomie d'une extension C++ simple
Nous supposons que vous essayez d'utiliser le C++ pour écrire des composants XPCOM qui pourront servir à d'autres composants C++ ou depuis JavaScript. Le processus de création d'un composant est relativement strict lorsque le système de compilation de Mozilla est utilisé.
Dans le cas le plus simplifié, un composant consisterait en un seul répertoire principal avec deux sous répertoires, public/ et src/. Le répertoire principal et chacun des sous répertoires doit contenir un Makefile.in (jusqu'à présent, nous parlions de ce fichier comme d'un makefile en tant que tel, alors qu'il ne s'agit que d'un fichier qui servira à générer le vrai makefile final). Ce fichier Makefile.in indique deux choses. Premièrement, il liste les sous répertoires pour générer l'extension, en permettant au système de compilation de savoir où trouver les makefile additionnels. Deuxièmement, il précise au système de compilation de créer une nouvelle extension plutôt que de copier les composants directement dans le répertoire binaire de Firefox. L'avantage principal d'utiliser une extension est qu'il est très facile d'en faire un paquetage et de l'installer sur une autre machine.
Donc, voici votre fichier Makefile.in basique situé au niveau le plus élevé (dans le répertoire principal de l'extension) :
DEPTH = ../.. topsrcdir = @top_srcdir@ srcdir = @srcdir@ VPATH = @srcdir@ include $(DEPTH)/config/autoconf.mk MODULE = myextension DIRS = public src XPI_NAME = myextension INSTALL_EXTENSION_ID = myextension@mycompany.com XPI_PKGNAME = myextension DIST_FILES = install.rdf include $(topsrcdir)/config/rules.mk
Une description détaillée du processus de compilation, décrivant les fonctionnalités clés de ce makefile, se trouve ici. MODULE et XPI_NAME sont tous les deux définis avec le nom de votre extension ; ils doivent être répétés dans tous les makefiles du projet pour que tous les fichiers arrivent au même endroit dans l'espace de construction de XPI (voir ci-dessous). INSTALL_EXTENSION_ID est l'ID unique de votre extension. Il peut être un GUID, mais le format présenté ci-dessous est bien meilleur car plus facile à se souvenir. Vous n'avez pas à fournir un XPI_PKGNAME, mais si vous le faites, un fichier XPI propre à la distribution sera automatiquement créé à la racine de l'aire de construction XPI (/mozilla/$(MOZ_OBJDIR)/dist/xpi-stage/).
Chaque extension doit inclure un fichier install.rdf indiquant à Firefox comment l'installer. Ce fichier doit se situer dans le répertoire principal et ressembler à ceci :
<?xml version="1.0"?>
<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:em="http://www.mozilla.org/2004/em-rdf#">
<Description about="urn:mozilla:install-manifest">
<em:id>myextension@mycompany.com</em:id>
<em:version>0.1</em:version>
<em:targetApplication>
<!-- Firefox -->
<Description>
<em:id>{ec8030f7-c20a-464f-9b0e-13a3a9e97384}</em:id>
<em:minVersion>1.0+</em:minVersion>
<em:maxVersion>1.0+</em:maxVersion>
</Description>
</em:targetApplication>
<!-- métadonnées visibles -->
<em:name>My First Extension</em:name>
<em:description>Just an example.</em:description>
<em:creator>allpeers.com</em:creator>
<em:homepageURL>http://www.allpeers.com/blog/</em:homepageURL>
</Description>
</RDF>
Il existe une description détaillée du format de ce fichier install.rdf. Utilisez la variable DIST_FILES dans le makefile pour dire au compilateur de copier le fichier dans le répertoire de l'extension et le fichier XPI (optionnel).
[modifier] Interfaces publiques
Le répertoire public/ contient toutes les interfaces qui ont besoin d'être accessibles par d'autres modules. Celles-ci peuvent être des fichiers IDL décrivant des interfaces XPCOM, et servant à générer des fichiers d'entêtes C++ normaux pour leur insertion dans vos fichiers sources. Elles peuvent également être des fichiers d'entêtes C++ normaux utilisés directement par d'autres modules. Pour ce dernier cas, la méthode la plus simple est d'implémenter en ligne toutes les méthodes pour ne pas à avoir de dépendances liées supplémentaires. Autrement, vous devrez faire des liens statiques pour votre module si vous utilisez des entêtes publiques dans d'autres modules. Personnellement, je déconseille cette pratique (au delà du fait que les liens statiques signifient que le même code est chargé plus d'une fois en mémoire, et que le code ne sera pas disponible dans des langages JavaScript ou non-C++) et j'encourage l'utilisation de XPCOM à chaque fois que c'est possible.
Le makefile du répertoire public/ devrait ressembler à ce modèle :
DEPTH = ../../.. topsrcdir = @top_srcdir@ srcdir = @srcdir@ VPATH = @srcdir@ include $(DEPTH)/config/autoconf.mk MODULE = myextension XPIDL_MODULE = myextension XPI_NAME = myextension EXPORTS = \ myHeader.h \ $(NULL) XPIDLSRCS = \ myIFirstComponent.idl \ myISecondComponent.idl \ $(NULL) include $(topsrcdir)/config/rules.mk
XPIDL_MODULE est le nom du fichier XPT généré qui contient les informations des interfaces de votre IDL. Si vous avez plusieurs modules, assurez vous qu'ils aient tous une valeur différente pour XPIDL_MODULE. Autrement, le fichier XPT du premier module sera écrasé par le second, et vous obtiendriez des erreurs NS_ERROR_XPC_BAD_IID lorsque vous essaierez d'accéder aux interfaces IDL depuis votre code. Les fichiers en dessous de EXPORTS sont copiés directement vers le répertoire /mozilla/$(MOZ_OBJDIR)/dist/include/myextension/ et sont ainsi accessibles par d'autres modules (la valeur de MOZ_OBJDIR est définie dans /mozilla/.mozconfig). XPIDLSRCS sont exécutés dans le processus IDL, et les entêtes C++ générés sont copiées dans le même répertoire include. De plus, un fichier XPT (de type bibliothèque) est généré et placé dans le sous répertoire components/ de votre extension.
[modifier] Fichiers sources
Maintenant, il est temps de créer les fichiers makefile et sources dans le sous répertoire src/. Si vous implémentez des interfaces que vous décrivez par des IDL, la méthode la plus simple est de laisser le répertoire src/ vide et de lancer seulement un make sur le répertoire public/ ; ceci sera expliqué rapidement ci-après.
Ensuite, ouvrez le fichier d'entête généré pour votre interface depuis /mozilla/$(MOZ_OBJDIR)/dist/include/myextension/. Il contient des éléments pour les fichiers de composant .H et .CPP que vous pouvez copier et coller dans vos fichiers d'implémentation. Tout ce que vous avez à faire est de remplir les éléments d'implémentation dans le fichier C++.
Voici un exemple d'un makefile que vous devrez placer dans votre répertoire src :
DEPTH = ../../.. topsrcdir = @top_srcdir@ srcdir = @srcdir@ VPATH = @srcdir@ include $(DEPTH)/config/autoconf.mk IS_COMPONENT = 1 MODULE = myextension LIBRARY_NAME = myExtension XPI_NAME = myextension REQUIRES = xpcom \ string \ $(NULL) CPPSRCS = \ myFirstComponent.cpp \ mySecondComponent.cpp \ myExtension.cpp \ $(NULL) include $(topsrcdir)/config/rules.mk EXTRA_DSO_LDOPTS += \ $(XPCOM_GLUE_LDOPTS) \ $(NSPR_LIBS) \ $(NULL) # NOTE : Si vous codez sur la branche 1.8.0 (et non la branche 1.8 ou le tronc), # la ligne ne fonctionnera pas à cause du problème de liaison de drapeau. # Utilisez plutôt les variables suivantes : # # EXTRA_DSO_LDOPTS += \ # $(MOZ_COMPONENT_LIBS) \ # $(NULL) # # Malheureusement, l'utilisation de MOZ_COMPONENT_LIBS vous lie à xpcom_core, ce qui signifie # que vos composants ne fonctionneront plus dans des futures versions de Firefox.
La section REQUIRES indique à make quels modules votre composant utilise. Ce suppose l'ajout du sous répertoire /mozilla/$(MOZ_OBJDIR)/dist/include/ au chemin de recherche include du compilateur C++. Si vous incluez des entêtes Mozilla et que le compilateur ne les trouve pas, cela signifie que vous n'avez pas listé tous les modules nécessaires ici. CPPSRCS liste les fichiers sources devant être compilés.
Dans cet exemple, les deux premiers fichiers contiennent l'implémentation des deux composants de l'extension. Le fichier final, myExtension.cpp, contient le code nécessaire pour enregistrer ces composants comme décrit dans la prochaine section.
[modifier] Enregistrement de vos composants
Afin de pouvoir utiliser vos composants dans d'autres modules C++ et dans JavaScript, vous devez les enregistrer au préalable. Pour cela, votre extension doit implémenter une classe qui expose l'interface nsIModule qui contient des méthodes pour accéder aux composants définis dans un module. Heureusement, vous pouvez utiliser une macro très simple, sans avoir à vous souciez de tous les détails de ce qu'il se passe sous le capot.
La première étape est de définir une CID, contact ID et le nom d'une classe pour chacun de vos composants. Placez le code suivant (en adaptant les lignes #define) dans l'entête de chaque composant que le gestionnaire de composants devra instancier :
// {00000000-0000-0000-0000-000000000000}
#define MYFIRSTCOMPONENT_CID \
{ 0x00000000, 0x0000, 0x0000, \
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } }
#define MYFIRSTCOMPONENT_CONTRACTID "@mycompany.com/myfirst;1"
#define MYFIRSTCOMPONENT_CLASSNAME "Mon premier composant"
Évidemment, vous devrez remplir le CID avec un véritable GUID. Sous Windows, il peut être généré avec guidgen.exe. Les utilisateurs Unix peuvent utiliser uuidgen (fourni en standard dans la plupart des distributions Unix et Linux).
Maintenant, créez le fichier myExtension.cpp ressemblant à ceci :
#include "nsXPCOM.h"
#include "nsIGenericFactory.h"
/**
* Composants à enregistrer
*/
#include "myFirstComponent.h"
#include "mySecondComponent.h"
NS_GENERIC_FACTORY_CONSTRUCTOR(myFirstComponent)
NS_GENERIC_FACTORY_CONSTRUCTOR(mySecondComponent)
//----------------------------------------------------------
static const nsModuleComponentInfo components[] =
{
{
MYFIRSTCOMPONENT_CLASSNAME,
MYFIRSTCOMPONENT_CID,
MYFIRSTCOMPONENT_CONTRACTID,
myFirstComponentConstructor
},
{
MYSECONDCOMPONENT_CLASSNAME,
MYSECONDCOMPONENT_CID,
MYSECONDCOMPONENT_CONTRACTID,
mySecondComponentConstructor
},
};
NS_IMPL_NSGETMODULE(MyExtension, components)
La macro NS_IMPL_NSGETMODULE crée l'objet module approprié fournissant l'accès à tous les autres composants listés dans le tableau nsModuleComponentInfo.
[modifier] Compilation
Comme mentionné ci-dessus, vous aurez probablement envie de compiler votre extension immédiatement après avoir créer vos fichiers IDL dans le but générer les codes C++ pour les implémentations de votre composant. Je suppose que vous avez déjà réussi à compiler correctement Firefox. Si ce n'est pas le cas, retournez au début de cet article et ne revenez que lorsque vous aurez un firefox.exe fonctionnel.
Vous êtes encore là ? Donc, nous allons pouvoir modifier .mozconfig (dans le répertoire racine de /mozilla/) afin de permettre à votre extension d'être compilée en même temps que Mozilla. Ajoutez la ligne suivante à la fin du fichier :
ac_add_options --enable-extensions=default,myextension
Maintenant, lancez la commande make depuis le répertoire racine de Mozilla :
make -f client.mk build
Même si vous disposez d'une compilation de Firefox à jour, vous devrez attendre un moment pendant que le make parcours l'ensemble de l'arbre source de Mozilla pour vérifier les modifications (sur une machine relativement rapide, cela prend tout de même 10 à 15 minutes). Par la suite, il va atteindre votre extension et générer une série de fichiers dans /mozilla/$(MOZ_OBJDIR)/ :
- Fichiers d'entêtes exportés et fichiers d'entêtes générés (à partir des IDL) dans
dist/include/myextension/ - Bibliothèques statiques pour votre module dans
dist/lib/(au cas où d'autres modules voudraient se lier statiquement à votre code au lieu d'utiliser des XPCOM). - Fichier XPI dans
dist/xpi-stage/myextension.xpi. - Fichiers Makefile générés pour votre projet dans
extensions/myextension/(souvenez vous, nous sommes sous/mozilla/$(MOZ_OBJDIR)/). - Tout le reste se trouve dans
dist/bin/extensions/myextension@mycompany.com/.
Une partie de ceci ne sera pas créée lors de la première passe puisque make va hoqueter lorsqu'il ne trouvera pas les fichiers source de vos composants. Ne vous inquiétez pas ; tout ce dont vous avez besoin sont les fichiers d'entêtes générés contenant les extraits d'implémentation C++. Retournez en arrière et complétez votre implémentation C++ de vos composants jusqu'à ce que la compilation puisse s'achever. Souvenez vous que vous ne devez jamais modifier aucun de ces fichiers générés. Modifiez toujours les fichiers servant à les générer et relancez un make. Il existe des exceptions à cette règle, mais si vous modifiez les fichiers générés directement, vous risquez de faire des erreurs.
Le processus pour parcourir l'ensemble de l'arbre de Mozilla prend du temps. Si vous disposez déjà d'une compilation de Mozilla, vous pouvez éviter cette étape en créant directement un makefile pour votre extension. Allez à la racine de votre $(MOZ_OBJDIR) et entrez (à partir d'un terminal compatible bash) :
../build/autoconf/make-makefile extensions/myextension
Si votre $(MOZ_OBJDIR) ets situé en dehors de votre $(TOPSRCDIR), vous devrez alors faire :
$(TOPSRCDIR)/build/autoconf/make-makefile -t $(TOPSRCDIR) extensions/myextension
afin que votre script sachez où sont vos sources (il utilisera le chemin de l'extension que vous avez indiqué de manière relatif au répertoire courant pour indiquer où devront être mis vos makefiles).
Ceci génèrera le makefile approprié à votre extension. Que vous compiliez tout l'arbre Mozilla ou utilisiez ce raccourci, vous pourrez compiler dorénavant depuis ce point en allant dans /mozilla/$(MOZ_OBJDIR)/extensions/myextension/ et en tapant "make" en ligne de commande. Votre composant sera alors compilé sans le reste de Mozilla. Si tout fonctionne correctement, vous devriez voir apparaître votre fichier XPI dans l'endroit dédié. Vous verrez également la version non compressée du XPI (i.e. la structure du répertoire dézippée) sous /mozilla/$(MOZ_OBJDIR)/dist/bin/extensions. (Si quelque chose ne fonctionne pas, examinez la cause, corrigez la et revenez corriger cet article).
Pour vérifier que votre compilation est réellement terminée, lancez Firefox et vérifiez que votre extension est listée dans le gestionnaire des modules. Si vous utilisez Firefox comme votre navigateur habituel, il peut être ennuyeux pour vous de devoir le fermer à chaque nouvelle version compilée. Dans ce cas, définissez la variable environnement MOZ_NO_REMOTE à "1" avant de lancer la version de développement de Firefox. Vous devrez également ajouter un profil différent pour votre version de développement :
firefox -P development
Où development doit être remplacé avec le nom du profil supplémentaire que vous aurez créé. Cela vous permettra de lancer deux versions de Firefox simultanément, le temps de vos cycles de compilations et de tests.
[modifier] Aucun endroit ne ressemble au Chrome
Hourra ! Vous disposez maintenant d'une extension qui ne fait absolument rien. Il est temps de réaliser quelque chose avec ces composants que vous avez implémenté et enregistré. La méthode la plus simple consiste à écrire du code JavaScript et XUL. À ce stade, il peut être utile de posséder une certaine expérience dans l'écriture habituelle d'extensions (i.e. sans l'utilisation de composants C++). Si vous ne l'avez jamais fait, il est fortement conseillé de réfléchir à quelque chose de simple que vous aimeriez améliorer dans Firefox, et de l'écrire. Le simple affichage d'un menu ouvrant une boîte de dialogue "Bonjour le monde !" serait déjà un bel exercice d'apprentissage.
En supposant que vous savez écrire des extensions XUL/JavaScript, vous êtes au courant que les choses les plus importantes se situent dans le répertoire chrome/ de votre extension. Bien, le fait d'utiliser des composants C++ n'a aucune incidence là dessus. Donc, vous devez maintenant créer les répertoires habituels content/, locale/ et skin/ dans lesquels placer vos fichiers chrome. Personnellement, je préfère les placer directement dans le répertoire racine de mon module, mais je suppose que cela ne fait aucune différence si vous préférez les placer dans un sous répertoire chrome/.
Une fois que vous avez écrit les fichiers chrome nécessaires (par exemple, un overlay ajoutant un menu pour instancier et utiliser un de vos composants), vous devrez les empaqueter comme faisant partie de votre extension. Vous pouvez utiliser un manifeste JAR pour cela. Pour notre simple exemple d'extension, ce fichier ressemble à ceci :
myextension.jar: % content myextension %content/ % locale myextension en-US %locale/en-US/ % skin myextension classic/1.0 %skin/classic/ % overlay chrome://browser/content/browser.xul chrome://myextension/content/MyExtensionOverlay.xul content/MyExtensionOverlay.js (content/MyExtensionOverlay.js) content/MyExtensionOverlay.xul (content/MyExtensionOverlay.xul) locale/en-US/MyExtension.dtd (locale/en-US/MyExtension.dtd) locale/en-US/MyExtension.properties (locale/en-US/MyExtension.properties) skin/classic/MyExtension.css (skin/classic/MyExtension.css)
Placez ce code dans un fichier appelé jar.mn dans la racine du répertoire de votre extension, en vérifiant que les chemins entre parenthèses pointent vers les fichiers existants (lorsqu'ils sont interprétés relativement au répertoire racine). Vous devrez également faire une petite modification du fichier makefile dans le même répertoire, en ajoutant la ligne suivante :
USE_EXTENSION_MANIFEST = 1
Elle indique à make de créer un seul fichier manifeste appelé chrome.manifest plutôt que de créer des manifestes séparés avec des noms inadaptés pour chaque paquetages.
Maintenant, lancez la commande make une nouvelle fois, et vous devriez voir apparaître un sous répertoire chrome dans votre extension (/mozilla/$(MOZ_OBJDIR)/dist/bin/extensions/myextension@mycompany.com/). Notez que le répertoire chrome contient un fichier JAR (i.e. ZIP) avec tous les fichiers chrome listés dans jar.mn ainsi qu'une structure complète des répertoires égalé à celle du fichier JAR. Les répertoires de la structure sont toutefois vides. Pourquoi ? Je ne sais pas. Ne vous en préoccupez pas, car les fichiers dans le JAR sont les seuls utilisés.
[modifier] Conservez la complexité
Si vous développez une extension compliquée contenant beaucoup de composants XPCOM, vous voudrez probablement partager votre code en de plus petit modules.
[modifier] Extensions moyennement complexes
Pour une extension moyennement complexe, il est possible de diviser simplement le code en un seul niveau de modules. Supposons que vous ayez un module base/ qui définit une série de composants XPCOM de base et un module advanced/ qui définit du chrome ainsi que d'autres composants se servant des composants de base. La structure complète de votre répertoire ressemblerait à ceci :
- myextension
- base
- public
- src
- advanced
- content
- locale
- en-US
- ...autres langages...
- public
- skin
- classic
- ...autres thèmes...
- src
- base
Sans cela, rien ne change vraiment. Les makefiles dans les répertoires base/ et advanced/ devraient ressembler plus ou moins à votre makefile racine originel, en se souvenant de modifier la variable DEPTH pour tenir compte du fait qu'ils ont été déplacés d'un niveau plus haut dans la racine de Mozilla. Vous pouvez également supprimer la variable DIST_FILES puisqu'elle apparait dans le makefile du niveau le plus élevé. Chaque makefile qui génère le reste doit définir la variable XPI_NAME pour garantir que les fichiers générés sont créés dans votre extension et non dans le répertoire components/ global. En fait, il est plus sûr de le définir dans chaque makefile. Vous pouvez utiliser le même MODULE dans base/ et advanced/ pour les fichiers inclus générés aillent dans le même répertoire, mais vous ne devez pas employer le même XPIDL_MODULE dans deux répertoires public/, sinon les bibliothèques types du composant (i.e. fichiers XPT) écraseront les autres.
Chaque module doit également avoir une valeur différente pour la variable LIBRARY_NAME. Il s'agit du nom de la bibliothèque dynamique générée, donc si nous les appelions "myBase" et "myAdvanced", nous aurions au final myBase.dll et myAdvanced.dll (sous Windows au moins). Et chacun de ces modules doit avoir un fichier C++ séparé pour l'enregistrement des composants. Il y aura donc deux fichiers semblable au myExtension.cpp de l'exemple de base, appelés Base.cpp et Advanced.cpp. Enfin, chaque module aura évidemment son propre jar.mn, bien qu'il peut indiquer à la fois le nom de fichier JAR et le nom du paquetage si vous souhaitez que tous les fichiers chrome soient organisés dans un seul fichier JAR et paquetage. Le seul fichier devant réellement rester est install.rdf qui doit exister seul et dans la racine du répertoire de l'extension.
Le niveau haut du makefile devrait maintenant ressembler à ceci :
DEPTH = ../.. topsrcdir = @top_srcdir@ srcdir = @srcdir@ VPATH = @srcdir@ include $(DEPTH)/config/autoconf.mk MODULE = myextension DIRS = base advanced XPI_NAME = myextension INSTALL_EXTENSION_ID = myextension@mycompany.com XPI_PKGNAME = myextension DIST_FILES = install.rdf include $(topsrcdir)/config/rules.mk
[modifier] Extensions sérieusement complexes
Parfois, il arrive que même un module simple va grossir au delà d'un point où vous voudrez le diviser en plusieurs sous modules. La différence entre avoir des modules séparés et un unique module avec des sous modules séparés est que tous les sous modules partageront le même fichier d'enregistrement des composants (le fameux fichier myExtension.cpp), et une fois compilés, ils créeront une unique bibliothèque dynamique. La décision de scinder un module en sous modules ne concerne que l'organisation du code ; elle n'affecte pas du tout le produit final.
Pour scinder un module en sous modules, créez d'abord un sous répertoire pour chacun des sous modules. Ensuite créez un répertoire supplémentaire appelé build/. Chaque sous module sera configuré pour créer une bibliothèque statique, et les bibliothèques placées dans ce répertoire build/ serviront à créer une unique bibliothèque dynamique de composant. Confus ? Voici un exemple montrant simplement la sous branche advanced/ du répertoire myextension/ :
- advanced
- build
- intricate
- public
- src
- multifarious
- public
- src
- content
- locale
- en-US
- ...autres langues...
- skin
- classic
- ...autres thèmes...
Comme vous pouvez le voir, nous avons scindé advanced/ en deux sous modules : intricate/ et multifarious/, et nous avons ajouté un sous répertoire build/ supplémentaire. Les répertoires chrome ont été laissés dans advanced/ puisqu'ils ne sont liés à aucun sous module spécifique. Cela signifie que jar.mn restera à la même place.
Les makefiles de intricate/ et multifarious/ ressembleront au makefile originel de advanced/, mais vous devrez les modifier un peu. Comme toujours, vous devrez ajuster la variable DEPTH puisque les makefiles sont plus profonds dans la structure des répertoires. Et nous devons modifier la variable LIBRARY_NAME pour indiquer que nous générons une bibliothèque statique pour chaque sous module. Par convention, le suffixe "_s" est utilisé ici. Donc appelons-les "myIntricate_s" et "myMultifarious_s". Enfin, définissez la variable FORCE_STATIC_LIB, ce qui vous donne un makefile débutant comme ceci :
DEPTH = ../../../../.. topsrcdir = @top_srcdir@ srcdir = @srcdir@ VPATH = @srcdir@ include $(DEPTH)/config/autoconf.mk MODULE = myextension LIBRARY_NAME = myIntricate_s FORCE_STATIC_LIB = 1 XPI_NAME = myextension ...d'autres trucs ici...
Le makefile de build collecte ensemble les bibliothèques statiques générées par les sous modules et crée une unique bibliothèque (dynamique) de composants :
DEPTH = ../../../..
topsrcdir = @top_srcdir@
srcdir = @srcdir@
VPATH = @srcdir@
include $(DEPTH)/config/autoconf.mk
IS_COMPONENT = 1
MODULE = myextension
LIBRARY_NAME = myAdvanced
XPI_NAME = myextension
DEFINES += XPCOM_GLUE
SHARED_LIBRARY_LIBS = \
$(DIST)/lib/$(LIB_PREFIX)myIntricate_s.$(LIB_SUFFIX) \
$(DIST)/lib/$(LIB_PREFIX)myMultifarious_s.$(LIB_SUFFIX) \
$(DIST)/lib/$(LIB_PREFIX)xpcomglue_s.$(LIB_SUFFIX) \
$(DIST)/lib/$(LIB_PREFIX)xpcom.$(LIB_SUFFIX) \
$(DIST)/lib/$(LIB_PREFIX)nspr4.$(LIB_SUFFIX) \
$(DIST)/lib/$(LIB_PREFIX)plds4.$(LIB_SUFFIX) \
$(DIST)/lib/$(LIB_PREFIX)plc4.$(LIB_SUFFIX) \
$(NULL)
REQUIRES = \
xpcom \
string \
$(NULL)
CPPSRCS = \
Advanced.cpp \
$(NULL)
include $(topsrcdir)/config/rules.mk
LOCAL_INCLUDES += \
-I$(srcdir)/../intricate/src \
-I$(srcdir)/../multifarious/src \
$(NULL)
Le makefile dans le répertoire advanced/ doit lister les répertoires intricate/, multifarious/ et build/ dans sa variable DIRS. Assurez-vous que le build/ vienne en dernier puisqu'il ne peut créer la bibliothèque de composants que lorsque les autres makefile ont été achevés.
[modifier] Autres sujets
[modifier] Ajouter des fichiers de données à vos extensions
Dans certains cas, vous pouvez inclure des fichiers supplémentaires dans votre extension qui n'appartiennent pas au sous répertoire chrome/. Les exemples pourraient être des fichiers de bases de données ou des schémas XML. Cet objectif peut être atteint en ajoutant une étape personnalisée à votre fichier makefile qui copie les fichiers depuis l'arborescence source vers l'extension du répertoire cible.
[modifier] Copie des fichiers de données dans le répertoire cible
Admettons que vous disposiez de quelques fichiers de données statistiques que vous désirez inclure dans votre extension et les rendre disponible à vos composants. Vous avez placé ces fichiers, ayant pour extension .TXT, dans un sous répertoire stats/ dans le répertoire de votre extension de l'arborescence source. La règle suivante du makefile peut être utilisée pour copier ces fichiers vers le répertoire cible de l'extension :
libs:: if test ! -d $(FINAL_TARGET)/stats; then \ $(NSINSTALL) -D $(FINAL_TARGET)/stats; \ fi $(INSTALL) $(srcdir)/*.txt $(FINAL_TARGET)/stats
[modifier] Accès aux fichiers de données depuis les composants
L'astuce pour accéder à vos fichiers de données est de savoir où se trouve le répertoire principal de votre extension. Une rumeur signale que plus tard, il serait possible d'y accéder grâce à l'interface nsIExtensionManager ou quelque chose de similaire. Jusque là, il existe une astuce simple et efficace. Dans l'implémentation de n'importe quel composant XPCOM JavaScript, il existe un symbole spéciale __LOCATION__ (deux soulignés en préfixe et en suffixe) pointant vers le fichier d'implémentation du composant. Donc, vous pouvez écrire un simple composant qui déduit le répertoire racine de votre extension en l'extrapolation depuis sa position.
Cet article explique comment créer un composant XPCOM en JavaScript. Vous aurez besoin d'un fichier IDL pour une interface et qui ressemblera à ceci :
interface myILocation : nsISupports
{
readonly attribute nsIFile locationFile;
};
Placez le fichier IDL dans le répertoire public/ de votre projet ou sous projet. Dans le répertoire src/, placez le fichier JavaScript qui implémente le composant. L'implémentation du composant contiendra les méthodes pour récupérer le chemin ou le fichier du répertoire principal de l'extension :
myLocation.prototype =
{
QueryInterface: function(iid)
{
if (iid.equals(nsISupports))
return this;
if (iid.equals(myILocation))
return this;
Components.returnCode = Components.results.NS_ERROR_NO_INTERFACE;
return null;
},
get locationFile()
{
return __LOCATION__.parent.parent;
}
}
Cela suppose que le composant réside dans un sous répertoire de l'extension (par convention, ce répertoire est appelé components/). La propriété parent de __LOCATION__ retourne components/, et la propriété parent de ce dernier contient le répertoire de l'extension.
La dernière étape consiste à modifier le makefile du répertoire source où vous avez placé votre fichier JavaScript afin qu'il soit copié à l'emplacement approprié dans l'extension :
libs:: $(INSTALL) $(srcdir)/*.js $(FINAL_TARGET)/components
Maintenant, vous pouvez instancier une instance de ce composant et utiliser la propriété locationFile pour obtenir une interface nsIFile pointant vers le répertoire principal de votre extension.
[modifier] Utilisation de bibliothèques tierces
Pour des extensions plus sophistiquées, vous pouvez intégrer des bibliothèques tierces offrant des fonctionnalités spécialisées telles que la connectivité à des bases de données, du traitement d'image, les réseaux, etc. Si vous souhaitez que votre extension s'exécute sur toutes les plateformes Firefox, vous devrez disposez des codes sources des bibliothèques correspondants, en supposant qu'ils soient disponibles.
L'approche la plus pratique du point de vue du cycle de développement consiste à créer un makefile de style Mozilla pour la bibliothèque. Cela fonctionne bien pour les bibliothèques ayant un processus de compilation simple sans une configuration compliquée. Un bon exemple est représenté par la bibliothèque SQLite inclue dans l'arbre de compilation de Mozilla sous db/sqlite. En adaptant le makefile de cette manière, la bibliothèque est créée comme faisant partie du processus de compilation de Mozilla, ce qui élimine des étapes de compilation supplémentaires. L'inconvénient est que vous devez mettre à jour le makefile modifié à chaque nouvelle version de la bibliothèque.
Pour des bibliothèques qui ont des processus complexes de configuration, qui utilisent un compilateur non standard ou qui ont des caractéristiques spéciales, il peut être impossible de créer un makefile compatible Mozilla. Dans ce cas, il est recommandé de placer la totalité de la distribution de la bibliothèque dans le projet ou le sous projet qui l'utile. Donc si la bibliothèque acmelib est utilisée à l'intérieur du sous projet multifarious/ dans l'exemple ci-dessus, elle devrait être placée dans un sous répertoire de ce sous projet (au même niveau que public/ et src/).
Bien entendu, cela signifie que vous devrez compiler manuellement acmelib sur toutes les plateformes avant de lancer la compilation de Mozilla. Mais au moins, vous avez accès aux fichiers inclus et bibliothèques importées depuis votre composant en utilisant des chemins relatifs.
[modifier] Compilation pour de multiples plateformes
À FAIRE
[modifier] Information sur le Document Original
- Autheur : Matthew Gertner - 26 Juillet 2005.
- Permission accordée pour migrer en Janv 2006, incluant la permission de modifier la licence en CC:By-SA.
- Source Originale : http://www.allpeers.com/blog/creating-complex-firefox-extensions/