Crear una extensión personalizada de Firefox con el Mozilla Build System

Hubo errores de script en esta página. Mientras los editores del sitio lo solucionan, puedes ver un contenido parcial debajo.

 

 

Nota: Todas las instrucciones de este artículo sólo son aplicables a la rama 1.8 de Mozilla (p.e. Firefox 1.5). Intentaré mantener actualizada la versión estable aunque de hecho no deberías asumir que esto funcionará con la rama 1.5 (p.e, Firefox 1.0) o anteriores.

Existe una infinidad de documentación sobre la creación de extensiones para Firefox. Sin embargo, actualmente todos esos documentos asumen que estás desarrollando tu extensión utilizando únicamente XUL y JavaScript. Para las extensiones complejas puede ser necesario crear componentes en C++ que proporcionen funcionalidades adicionales. Las razones por las que podrías querer agregar componentes C++ en tu extensión incluyen:

  • La necesidad de un alto rendimiento más allá de lo que te ofrece el código JavaScript.
  • El uso de bibliotecas de terceras partes escritas en C o en C++.
  • El uso de interfaces de Mozilla que no están disponibles vía XPCOM (p.e, NSPR).

Este artículo describe cómo configurar el entorno de desarrollo para una extensión de Firefox grande y compleja utilizando parcial o totalmente los requerimientos ya mencionados. El proceso de reunir esta información ha sido de algún modo un calvario debido a la falta de información publicada sobre este tema, aunque varios miembros de la comunidad de desarrolladores de Mozilla han colaborado en su creación. Ellos han mostrado una paciencia extraordinaria respondiendo preguntas típicas de novatos ignorantes. Me quedo corto si digo que estoy lejos de ser un experto de Mozilla, aunque voy mejorando. Puede que muchas partes de este documento sean imprecisas, confusas o simplemente incorrectas. De hecho, una de mis metas al escribir esto es afinar dichas instrucciones hasta que constituyan una guía definitiva para los hackers del núcleo que quieran ampliar la plataforma Firefox. Si tú eres uno de los muchos que saben más que yo sobre esto, tu ayuda mejorando este artículo será gratamente recibida.

También debería enfatizar que no tienes por qué compilar Mozilla o utilizar el sistema de compilación de Mozilla si quieres crear componentes C++ para Mozilla. Si sólo buscas crear un componente XPCOM o dos, este artículo será algo excesivo y puede que te interese mirar esta guía en su lugar. Por otra parte, si tienes experiencia desarrollando o dirigiendo un equipo y sabes que tienes que desarrollar una extensión grande y compleja, probablemente hagas bien en considerar la aproximación descrita en este artículo.

Una última nota: sólo he probado dichas técnicas dentro de Firefox aunque probablemente funcionarán más o menos bien en otras plataformas basadas en Gecko como Thunderbird o SeaMonkey. Si alguien puede confirmar esto y/o suministrar guías de estilo para aquello en lo que difiere actualizaré el artículo para incorporar esta información.

Y Bambi y Mozilla se encontraron...

Nada en este artículo es para cobardes. En particular el primer paso conlleva la compilación de Mozilla, lo cual es un gran... no, un inmenso proyecto. Muchos desarrolladores inteligentes han sido llevados al borde de la locura intentando compilarlo por  primera vez. Si no tienes experiencia desarrollando C++ no deberías ni preocuparte. Sigue con JavaScript.

Plataformas Windows

La primera vez que compilé Mozilla utilicé esta guía. Ni siquiera puedo recordar por qué, pero me quedé pillado unas cuantas veces, aunque la odisea llevó más tiempo de lo que me había imaginado al principio. Me cargué los muebles y me arranqué el pelo. Aquí tienes una guía comprensiva rápida, bien redactada. Sigue cada paso metódicamente y probablemente todo irá bien. Céntrate en el hecho de que una vez hayas completado la compilación, posiblemente el resto del trabajo no conllevará esfuerzo alguno. Posiblemente.

Otras plataformas

En otras plataformas, léase Linux y MacOS, el proceso es mucho más sencillo. Todas las herramientas para compilar están disponibles de forma integrada y por tanto todo lo que tienes que hacer es ejecutar algunas órdenes en el terminal. Puedes encontrar las instrucciones completas para casi cualquier SO aquí.

Estructurar tu proyecto

Mozilla incorpora un número de extensiones complejas que son integradas en su proceso de compilación. Es así que ha sido necesario resolver todos los problemas inherentes a la creación y registro de componentes XPCOM, a la compilación de ficheros JAR y los manifiestos; instalando el montón en el directorio extensions de Firefox y así sucesivamente. Así pues, lo mejor para nosotros es aprovecharnos de esta infraestructura para construir nuestra extensión.

Antes que nada, elige un nombre con gancho para tu extensión y crea un directorio con ese nombre bajo el directorio /mozilla/extensions/. Utiliza sólo minísculas. Deberías ver un montón de otros directorios (inspector/, reporter/, etc...) al mismo nivel del árbol de directorios.

Date cuenta  que antes de realmente construir nada, el sistema de desarrollo de Mozilla llama a un proceso que genera los makefiles usados por la compilación a partir de plantillas de makefiles llamadas Makefile.in. Los makefiles reales tienden a ser muy parecidos si no idénticos a las plantillas, pero la flexibilidad extra adquirida al obtener los makefiles generados dinámicamente es una de las cosas que hacen al sistema de compilación tan potente.

Anatomía de una simple extensión en C++

Asumiremos que estamos usando C++ para escribir componentes XPCOM que pueden ser usados tanto desde otros componentes C++ o desde JavaScript. El proceso de crear un componente es, en realidad, algo relativamente simple cuando se utiliza Mozilla Build System.

El caso más simple ocurre cuando un componente va a consistir en un único directorio principal con dos subdirectorios, public/ y src/. El directorio principal y cada subdirectorio deben contener un Makefile.in (a partir de ahora me referiré a este fichero como un makefile aunque, como sabemos, en la práctica es usado para generar los makefiles reales). Este makefile dice dos cosas: lo primero: lista los subdirectorios que componen la extensión por lo que el sistema de compilación conoce dónde buscar los makefiles adicionales. Después dà las instrucciones para compilar el sistema que crea una nueva extensión, en lugar de copiar los componentes directamente en el directorio de binarios de Firefox. La principal ventaja de utilizar una extensión es que es fácil empaquetarlo todo e instalarlo en otra máquina.

Así pues, aquí tienes el makefile principal más básico y simplón que te puedas encontrar (Makefile.in en el directorio principal de la extensión):

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

La descripción detallada del proceso de creación, describiendo las opciones clave de este makefile pueden ser encontradas aquí. Tanto MODULE como XPI_NAME están asignados al nombre de tu extensión; deben aparecer repetidos en todos los makefiles del proyecto para que todos los ficheros acaben en el mismo lugar en el área de pruebas del XPI (ver más adelante). INSTALL_EXTENSION_ID es el ID único de tu extensión. Puede ser un GUID aunque el formato mostrado antes es más bonito y, asumámoslo, mucho más fácil de recordar. No tienes porqué proporcionar un XPI_PKGNAME aunque si creas un fichero XPI susceptible de ser distribuido, será creado automáticamente en el directorio raíz del área de pruebas del XPI (/mozilla/$(MOZ_OBJDIR)/dist/xpi-stage/).

Toda extensión debe incluir un fichero install.rdf que le diga a Firefox cómo instalarla. Este fichero debería estar ubicado en el directorio principal de la extensión y tener este aspecto:

<?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>
	
    <!-- front-end metadata -->
    <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>

Existe una descripción detallada del formato del fichero install.rdf. Utiliza la variable DIST_FILES del makefile para decirle a make que copie el fichero en el directorio de la extensión y (opcionalmente) el fichero XPI.

Interfaces públicas

El directorio public/ contiene todas las interfaces necesarias para que otros módulos puedan utilizarlas. Éstas pueden ser ficheros IDL que describan interfaces XPCOM, los cuales son utilizados para generar ficheros de cabecera normales de C++ para incluirlos en los ficheros fuente. También pueden ser ficheros de cabecera normales de C++ que son utilizados directamente por otros módulos. La forma más fácil de hacer esto último es usar implementaciones en línea para todos los métodos, por lo que no tendrás ninguna dependencia de enlazado adicional. En otro caso tendrás que utilizar enlazado estático en tu módulo si utilizas estas cabeceras públicas en otros módulos. Personalmente desaconsejo esta práctica (entre otras cosas porque el enlazado estático significa que el mismo código se carga más de una vez en memoria y el código no estará disponible desde JavaScript u otros lenguajes diferentes a C++) y animo a usar XPCOM siempre que sea posible.

El makefile en el directorio public/ debería parecerse a este modelo:

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 es el nombre del fichero XPT generado que contiene información de tipos sobre tus interfaces IDL. Si tienes múltiples módulos, asegúrate de que utilizas un valor diferente de XPIDL_MODULE para cada uno. En caso contrario, el fichero XPT del primer módulo será sobrescrito por el segundo y obtendrás errores del tipo NS_ERROR_XPC_BAD_IID cuando intentes acceder a sus interfaces IDL desde el código. Los ficheros bajo EXPORTS son copiados directamente al directorio /mozilla/$(MOZ_OBJDIR)/dist/include/myextension/ siendo así accesibles desde otros módulos (el valor de MOZ_OBJDIR se define en /mozilla/.mozconfig). XPIDLSRCS es ejecutado a través del procesador IDL y las cabeceras generadas de C++ son copiadas en el mismo directorio include. Además, se genera un fichero XPT (biblioteca de tipos) que se copia en el subdirectorio components/ de tu extensión.

Ficheros fuente

Ahora es cuando hay que crear el makefile y los ficheros-fuente en el subdirectorio src/. Si estás implementando interfaces y las estás describiendo con IDL, la forma más fácil de hacer esto es dejando vacío el directorio src/ y ejecutando make sólo en el directorio public/. Esto serà explicado en breve.

Luego abre el fichero de cabecera generado para tu interfaz en /mozilla/$(MOZ_OBJDIR)/dist/include/myextension/. Este fichero tendrá algunas plantillas para los ficheros de componentes .H y .CPP que puedes copiar y pegar en tus ficheros de implementación. Todo lo que tienes que hacer es rellenar los huecos del fichero C++ con la implementación real y estarás listo para continuar.

A continuación se muestra un ejemplo del makefile que necesitas colocar en tu directorio 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)

# NOTA: si estás codificando contra la versión 1.8.0 branch (no 1.8 branch o trunk),
# la línea anterior no funcionará debido a problemas de modificadores del enlazador.
# En su lugar utiliza las siguientes variables:
#
# EXTRA_DSO_LDOPTS += \
#   $(MOZ_COMPONENT_LIBS) \
#   $(NULL)
#
# Por desgracia, usando MOZ_COMPONENT_LIBS se enlaza contra xpcom_core, lo que
# significa que tus componentes no funcionarán en futuras versiones de Firefox.

La sección REQUIRES le dice a make qué módulos utilizan tus componentes. Esto provoca que los subdirectorios relevantes de /mozilla/$(MOZ_OBJDIR)/dist/include/ sean añadidos a la ruta include del compilador de C++. Si estás incluyendo las cabeceras de Mozilla y el compilador es incapaz de encontrarlas, podría ser que no hayas listado todos los módulos necesarios aquí. CPPSRCS enumera los ficheros fuente que necesitan ser compilados.

En este ejemplo, los dos primeros ficheros contienen la implementación de los dos componentes de la extensión. El fichero final myExtension.cpp contiene el código necesario para registrar dichos componentes, como se describirá en la siguiente sección.

Registrar tus componentes

Para poder usar tus componentes desde otros módulos de C++ y JavaScript primero tienes que registrarlos. Para hacerlo, tu extensión necesita implementar una clase que exponga la interfaz nsIModule la cual posee métodos para acceder a los componentes definidos en un módulo. Afortunadamente, esto puede ser llevado a cabo mediante el uso de unas pocas y simples macros por lo que no tienes que embarrarte con los detalles de qué está pasando internamente.

El primer paso es definir un CID, un contract ID (o ID de contrato) y un nombre de clase para cada uno de tus componentes. Pega el siguiente código (adaptando los #defines según convenga) en la cabecera de cada componente que tenga que ser instanciado utilizando el administrador de componentes:

// {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	"My First Component"

Obviamente necesitas completar el CID con un GUID real. Bajo Windows puede ser generado utilizando guidgen.exe. Los Unixeros pueden utilizar uuidgen (viene de modo predeterminado en la mayoría de distribuciones de Unix y Linux).

Ahora crea el fichero myExtension.cpp así:

#include "nsXPCOM.h"
	
#include "nsIGenericFactory.h"
	
/**
 * Components to be registered
 */
#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 crea el objeto de módulo apropiado suministrando acceso a todos los componentes listados en el array nsModuleComponentInfo.

Compilación

Como se dijo antes, probablemente querrás compilar tu extensión inmediatamente tras crear tus ficheros IDL para poder generar las plantillas C++ para las implementaciones del componente. Estoy asumiendo que ya has podido compilar Firefox sin problemas. Si no es así, vuelve inmediatamente al inicio de este artículo y no regreses hasta que hayas generado un firefox.exe funcional. Creo que es una casilla del monopoly: Do not pass go. Do not collect $200.

¿Sigues ahí? Bien, ahora vamos a modificar tu .mozconfig (en el directorio raíz /mozilla/ para que tu extensión se compile junto con Mozilla. Añade la siguiente línea al final del fichero:

ac_add_options --enable-extensions=default,myextension

Ahora ejecuta make en el directorio raíz de Mozilla:

make -f client.mk build

Incluso aunque hayas compilado una versión actualizada de Firefox, tendrás que esperar un momento para que make pueda recorrer el árbol de Mozilla por completo buscando nuevo material (en mi máquina, que es bastante rápida, esto tarda unos 10-15 minutos). Finalmente alcanzará tu extensión y generará un montón de material bajo el directorio /mozilla/$(MOZ_OBJDIR)/:

  • Varios ficheros de cabecera exportados y ficheros de cabecera generados (desde IDL) en dist/include/myextension/
  • Bibliotecas estáticas para tus módulos en dist/lib/ (en el caso de que otros módulos quieran enlazar estáticamente a tu material en lugar de utilizar XPCOM).
  • Un fichero XPI en dist/xpi-stage/myextensión.xpi.
  • Los makefiles generados para tus proyectos en extensions/myextension/ (recuerda que estamos bajo el directorio /mozilla/$(MOZ_OBJDIR)/.
  • Todo lo demás en dist/bin/extensions/myextension@mycompany.com/.

Gran parte de este material no será creado en la primera pasada ya que make se impacientará cuando no encuentre los ficheros fuente para tus componentes. No te preocupes por eso, todo lo que necesitas son los ficheros de cabecera generados que contengan las plantillas de implementación en C++. Vuelve atrás y rellena la implementación en C++ de tus componentes para que puedan ser compilados la siguiente vez. Recuerda que no deberías modificar jamás, nunca, ningún fichero generado. Modifica siempre los ficheros utilizados para generarlos y vuelve a ejecutar make. Quizá haya excepciones a esta regla, pero si estás cambiando los ficheros generados directamente muy probablemente estás metiendo la pata.

El proceso de recorrer el árbol completo de Mozilla lleva bastante tiempo. Si ya has compilado Mozilla puedes evitar esto creando un makefile para tu extensión directamente. Ve al directorio raíz de tu $(MOZ_OBJDIR) y (desde un shell compatible con bash) introduce:

../build/autoconf/make-makefile extensions/myextension

Si tu $(MOZ_OBJDIR) está ubicado fuera de tu $(TOPSRCDIR), necesitarás hacer:

$(TOPSRCDIR)/build/autoconf/make-makefile -t $(TOPSRCDIR) extensions/myextension

para que el script sepa dónde están tus fuentes (usará la ruta de la extensión que des relativa al directorio actual para resolver dónde quieres que vayan tus makefiles).

Esto generará el makefile apropiado para tu extensión. Tanto si quieres compilar el árbol completo de Mozilla como si tomas este atajo, puedes compilar de ahora en adelante yendo a /mozilla/$(MOZ_OBJDIR)/extensions/myextension/ y escribiendo "make" en la línea de órdenes. Esto debería compilar tu componente sin distraerse con el resto de Mozilla. Si todo va bien, verás tu fichero XPI en el área de pruebas de XPI. También verás la versión descomprimida (es decir, la estructura de directorios descomprimida) bajo /mozilla/$(MOZ_OBJDIR)/dist/bin/extensions (si algo va mal, averigua qué es, corrígelo y luego vuelve aquí y añádelo a este artículo).

Para asegurarte de que la compilación ha finalizado realmente, lanza Firefox y comprueba que tu extensión aparece en la lista cuando selecciones Herramientas / Complementos. Si estás usando Firefox como tu navegador habitual (y si no, ¿por qué no?), puede que te moleste el hecho que de que tendrás que cerrar tu Firefox normal antes de ejecutar tu versión personalizada. Si es así, prueba a establecer la variable de entorno MOZ_NO_REMOTE a "1" antes de ejecutar la versión de desarrollo de Firefox. También necesitarás usar un perfil diferente para tu versión de desarrollo:

firefox -P desarrollo

Donde desarrollo se sustituye con el nombre del perfil extra que has creado. Esto te permitirá ejecutar ambas versiones de Firefox simultáneamente, ahorràndote montones de tiempo a lo largo del ciclo de compilación/prueba.

No hay nada como estar en chrome

¡Yuhuuuu! Ahora ya tienes una extensión que no hace absolutamente nada. Es hora de hacer algo con esos geniales componentes que implementaste y registraste. La forma más fácil de hacer esto es escribiendo algo de código JavaScript y XUL. En este punto sería de mucha utilidad tener algo de experiencia escribiendo extensiones "normales" (p.e., sin utilizar componentes de C++ personalizadas). Si nunca has hecho esto, te recomiendo encarecidamente que pienses una idea guay para algo simple que siempre hayas querido hacer para Firefox y lo escribas. Mostrar sólo un nuevo elemento de menú que abra un cuadro de diálogo "¡Hola mundo!" sería ya un gran ejercicio de precalentamiento.

Suponiendo que sabes cómo escribir extensiones en XUL/JavaScript, estarás al corriente de que la parte más importante va en el directorio chrome/ de tu extensión. Bueno, el hecho de que estés utilizando también componentes C++ no cambia eso ni una pizca. Por tanto, ahora necesitas crear los directorios normales content/, locale/ y skin/ en los que has de poner tus ficheros chrome. Personalmente me gusta ubicar dichos directorios directamente bajo el directorio raíz de mi módulo pero supongo que no habrá diferencia si prefieres ponerlos bajo el subdirectorio chrome/ o el que sea. ¡Viva la libertad!

Una vez has escrito los ficheros chrome necesarios (por ejemplo, un overlay que añade un elemento de menú para instanciar y utilizar uno de tus componentes), necesitas empaquetarlo como parte de tu extensión. Esto es llevado a cabo a través del uso de un manifiesto JAR. Para nuestro ejemplo de extensión simple este fichero podría tener este aspecto:

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)

Pon este código en un fichero llamado jar.mn en el directorio raíz de tu extensión, asegurándote de que las rutas en los paréntesis apuntan a los ficheros reales (relativos al directorio raíz). Además tienes que hacer un pequeño cambio al makefile del mismo directorio, añadiendo la siguiente línea:

USE_EXTENSION_MANIFEST = 1

Esto le dice a make que cree un único fichero de manifiesto llamado chrome.manifest en lugar de crear manifiestos separados con nombres tontos para cada paquete.

Ahora ejecuta make de nuevo. Deberías ver un subdirectorio chrome en tu extensión (/mozilla/$(MOZ_OBJDIR)/dist/bin/extensions/myextension@mycompany.com/). Observa que el directorio chrome contiene un fichero JAR (o sea, ZIP) con todos los ficheros chrome listados en jar.mn además de una estructura de directorio completa reflejo de la del fichero JAR. La estructura de directorio sin embargo está vacía. ¿Por qué? No lo sé. No te preocupes por esto, los ficheros en el JAR son los que realmente se usan.

Manteniéndolo complejo

Si estás desarrollando extensiones realmente complejas con un montón de componentes XPCOM, probablemente desees dividir tu código en módulos más pequeños.

Extensiones moderadamente complejas

Para una extensión moderadamente compleja, probablemente bastará con dividir el código en módulos de un solo nivel. Supongamos que tienes un módulo base/ que define un manojo de componentes XPCOM básicos y un módulo advanced/ que define algunos componentes chrome así como otros componentes que usan a los básicos. La estructura del directorio debería lucir más o menos así:

  • myextension
    • base
      • public
      • src
    • advanced
      • content
      • locale
        • en-US
        • ...other locales...
      • public
      • skin
        • classic
        • ...other skins...
      • src

Más allá de eso, nada cambia. Los makefiles en los directorios base/ y advanced/ deberían tener más o menos el mismo aspecto que el makefile original del directorio raíz, sin olvidar cambiar la variable DEPTH para reflejar el hecho de que se han movido a un nivel más de profundidad respecto de la raíz de mozilla. También debes quitar la variable DIST_FILES porque va a estar en el makefile de nivel superior. Cada makefile que genere algo debería definir la variable XPI_NAME para asegurarse que los archivos generados van dentro de tu extensión y no dentro del directorio global components/. Define esto en cada makefile para asegurarte. Puedes usar el mismo MODULE para ambos casos, base/ y advanced/, y así todos los archivos include generados irán al mismo directorio, pero asegúrate de no usar el mismo XPIDL_MODULE en los dos directorios public/ o una de las bibliotecas de componentes (es decir, archivos XPT) sobrescribirá al otro y todo se echará a perder.

Cada módulo debe tener también un valor diferente para la variable LIBRARY_NAME. Éste es el nombre de la biblioteca dinámica generada, así que si llamamos a las bibliotecas "myBase" y "myAdvance" entonces tendremos los archivos myBase.dll y myAdvance.dll (por lo menos así es en Windows). Y cada uno de estos módulos va a tener un archivo C++ separado para registrar componentes, así que va a haber dos archivos que lucirán como myExtension.cpp en el ejemplo original, digamos Base.cpp y Advanced.cpp. Por último, cada módulo tendrá, obviamente, su propio jar.mn, aunque pueden referenciar al mismo nombre de archivo JAR y nombre de paquete si quieres que todos los archivos chrome estén organizados en un mismo paquete y archivo JAR. El único que realmente permanece es install.rdf, que existe una vez y sólo una vez en el directorio raíz de la extensión.

En cuanto al makefile de nivel superior, ahora tendrá este aspecto:

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
Extensiones realmente complejas

En algún momento, hasta un solo módulo puede crecer hasta un punto en que desees dividirlo en submódulos. La diferencia entre tener módulos diferentes y tener un módulo dividido en submódulos es que todos los submódulos comparten el mismo archivo para registrar componentes (el famoso archivo myExtension.cpp) y cuando lo compilas genera una sola biblioteca dinámica. La decisión para dividir un módulo en submódulos es sólo cuestión de organización del código, no afecta al producto final.

Para dividir un módulo en submódulos primero debes crear un directorio para cada submódulo. Luego debes crear un directorio adicional llamado build/. Cada submódulo será configurado para crear una biblioteca estática y el directorio build/ las unirá para crear una sola biblioteca dinámica. ¿Confundido? Aquí hay un ejemplo mostrando la subrama advanced/ del directorio myextension/:

  • advanced
    • build
    • intricate
      • public
      • src
    • multifarious
      • public
      • src
    • content
    • locale
      • en-US
      • ...otros idiomas...
    • skin
      • classic
      • ...otros skins...

Como puedes ver, hemos dividido advanced/ dentro de dos submódulos: intrincate/ y multifarious/ y hemos añadido un directorio build/ adicional. Hemos dejado los directorios chrome directamente bajo advanced/, los cuales no están enlazados a ningún submódulo específico. Esto significa que jar.mn estará en el mismo lugar.

Los makefiles intricate/ y multifarious/ lucirán casi igual al makefile original advanced/, pero necesitamos modificarlos un poquito. Como siempre, debemos ajustar la variable DEPTH porque el makefile está más profundo en la estructura de directorios. Y deberíamos cambiar LIBRARY_NAME para indicar que estamos generando una biblioteca estática para cada submódulo. Por convenio, se usa el sufijo "_s" para este propósito. Así que las llamaremos "myIntricate_s" y "myMultifarious_s". Finalmente definimos la variable FORCE_STATIC_LIB para que quede un makefile que comience más o menos así:

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
	
...más cosas aquí...

El makefile build une las bibliotecas estáticas generadas por los submódulos y crea una única biblioteca dinámica de componentes:

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)

El makefile en el directorio advanced/ debería listar los directorios intricate/, multifarious/ y build/ en su variable DIRS. Asegúrate de que build/ esté al final porque no puede crear la biblioteca hasta que los otros makefiles hayan sido completados.

Otros temas

Agregar archivos de datos a tus extensiones

En algunos casos, puedes desear incluir archivos adicionales en tus extensiones que no pertenecen al subdirectorio chrome/. Algunos ejemplos pueden ser archivos de bases de datos o esquemas XML. Esto puede conseguirse añadiendo, en el makefile, código personalizado que copie los ficheros desde el árbol de origen hacia el directorio de destino de la extensión.

Copiar archivos de datos al directorio de destino

Supongamos que tienes algunos ficheros de datos que contienen información estadística que deseas incluir en tu extensión y que esté disponible para tus componentes. Tienes estos archivos, cuya extensión es .TXT, dentro del subdirectorio stats/ bajo el directorio de tu extensión en el árbol del código fuente. Puedes usar el siguiente makefile para copiar los ficheros al directorio de destino de la extensión:

libs::
	if test ! -d $(FINAL_TARGET)/stats; then \
		$(NSINSTALL) -D $(FINAL_TARGET)/stats; \
	fi
	$(INSTALL) $(srcdir)/*.txt $(FINAL_TARGET)/stats
Acceder a ficheros de datos desde los componentes

El truco para acceder a los ficheros es averiguar dónde está el directorio home de tu extensión. Los rumores dicen que en el futuro esto será posible a través del interface nsExtensionManager o alguna cosa similar. Mientras tanto, hay un modo simple y fiable de hacer esto. En la implementación de cualquier componente JavaScript XPCOM hay un símbolo especial __LOCATION__ (dos subrayados delante y dos detrás) que apunta al fichero de implementación del componente. Así, puedes escribir un componente simple que deduzca el directorio raíz de tu extensión extrapolando desde su ubicación.

Este artículo explica cómo crear un componente XPCOM en JavaScript. Necesitarás un fichero IDL para un interfaz que tenga un aspecto más o menos así:

interface myILocation : nsISupports
{
    readonly attribute nsIFile locationFile;
};

Sitúa el fichero IDL en el directorio public/ del proyecto o subproyecto. En el directorio src/ coloca el fichero JavaScript que implementa el componente. La implementación del componente incluirá los métodos para solicitar la ruta o el fichero para el directorio home de la extensión.

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;
  }
}

Esto asume que el componente se encuentra en un subdirectorio del directorio de la extensión (por convenio, este directorio se llama components/). La propiedad parent de __LOCATION__ devuelve components/, y el valor parent de éste es el directorio de la extensión.

El último paso es modificar el makefile del directorio de orìgen donde colocaste el fichero JavaScript para que sea copiado dentro de la ubicación apropiada en la extensión.

libs::
	$(INSTALL) $(srcdir)/*.js $(FINAL_TARGET)/components

Ahora puedes instanciar este componente y usar la propiedad locationFile para obtener un interface nsIFile que apunte al directorio home de tu extensión.

Usar otras bibliotecas

Para extensiones más sofisticadas, tal vez desees integrar bibliotecas de terceros que proveerán funciones especializadas para conectividad de bases de datos, procesamiento de imágenes, funciones de red y otras tareas. Si quieres que tu extensión se ejecute en todas las plataformas de FireFox, necesitarás el código fuente de la biblioteca en cuestión, así que asumiré que está disponible.

La mejor forma de hacerlo, desde el punto de vista del ciclo de desarrollo, es crear un makefile estilo-Mozilla para la biblioteca. Esto funciona bien para bibliotecas que tienen un proceso de compilación sencillo sin demasiada configuración. Un buen ejemplo de esto es la biblioteca SQLite incluida en el árbol de compilación de Mozilla en db/sqlite. Adaptando el makefile de esta manera, la biblioteca se crea como una parte estándar del proceso de compilado de Mozilla, lo cual elimina pasos adicionales. El lado malo es que necesitarás actualizar el makefile modificado cada vez que se publique una nueva versión de la biblioteca.

Para bibliotecas que tienen un proceso de configuración complejo, que use un compilador no-estándar, o que tengan alguna otra característica especial, quizá no sea viable crear un makefile compatible con el estilo Mozilla. En este caso, recomendaría colocar la distribución completa de la biblioteca dentro del proyecto o subproyecto que la utiliza. Por ejemplo, si la biblioteca acmelib se usa dentro del subproyecto multifarious/ en el ejemplo de arriba, se colocaría como un subdirectorio bajo ese proyecto (en el mismo nivel que public/ y src/).

Por supuesto, esto significa que tendrás que compilar acmelib manualmente en cada plataforma antes de lanzar Mozilla. Pero por lo menos luego puedes referirte a los archivos include y las bibliotecas importadas desde tus componentes usando rutas relativas.

Compilar para múltiples plataformas

POR HACER

Información del Documento Original

{{ languages( { "en": "en/Creating_Custom_Firefox_Extensions_with_the_Mozilla_Build_System", "fr": "fr/Cr\u00e9ation_d\'extensions_pour_Firefox_avec_le_syst\u00e8me_de_compilation_de_Mozilla", "it": "it/Creare_Estensioni_personalizzate_per_Firefox_con_il_sistema_di_sviluppo_di_Mozilla", "ja": "ja/Creating_Custom_Firefox_Extensions_with_the_Mozilla_Build_System" } ) }}

Etiquetas y colaboradores del documento

Última actualización por: DoctorRomi,