Erstellen von Firefox Erweiterungen mit dem Mozilla Build System

Hinweis: Alle Anweisungen in diesem Artikel gelten nur für Mozilla 1.8 (Firefox 1.5). Wir werden versuchen, die Änderungen aus neueren Versionen zu aktualisieren, aber Sie sollten nicht erwarten, dass dies mit Mozilla 1.7 (Firefox 1.0) oder älter funktioniert.

Es gibt sehr viel Informationsmaterial zur Erstellung von Erweiterungen für Firefox. Diese Artikel nehmen jedoch an, dass Sie Ihre Erweiterungen nur mit XUL und JavaScript entwickeln. Für komplexe Erweiterungen ist es unter Umständen nötig, Komponenten in C++ zu erstellen, die erweiterte Funktionen bereitstellten. Gründe für C++ Komponenten in Ihrer Erweiterung:

  • Bedarf an höchster Performance neben dem, was von JavaScript Code ausgeliefert werden kann.
  • Verwendung von Bibliotheken von Drittanbietern, die in C or C++ geschrieben sind.
  • Verwendung von Mozilla Schnittstellen, die nicht über XPCOM (e.g. NSPR) bereitgestellt werden.

Dieser Artikel beschreibt, wie Sie eine Entwicklungsumgebung für eine große und komplexe Firefox Erweiterung mit den oben genannten Anforderungen einrichten können. Das Auffinden von Informationen zu diesem Thema war sehr schwierig, da bisher kaum Informationen darüber veröffentlicht wurden, aber viele Mitwirkende aus der Mozilla Community haben dazu beigetragen, etliche Fragen von Neulingen zu beantworten. Ich kann nicht genug darauf hinweisen, dass ich noch weit entfernt von einem Mozilla Experten bin, obwohl ich Fortschritte mache. Es könnten daher Informationen in diesem Dokument vorhanden sein, die nicht komplett, nicht den richtige Weg aufzeigen oder einfach falsch sind. Wenn Sie mehr über dieses Thema wissen, sind Sie angehalten diesen Artikel zu verbessern.

Ich sollte auch betonen, dass Sie Mozilla nicht kompilieren müssen oder das Mozilla Build System verwenden müssen, wenn Sie C++ Komponenten für Mozilla erstellen wollen. Wenn Sie nur ein oder zwei XPCOM Komponenten erstellen wollen, ist dieser Weg vielleicht zu übertrieben Sie können stattdessen einen Blick auf diese Anleitung werfen. Wenn Sie auf der anderen Seite ein erfahrener Entwickler sind und wissen, dass Sie eine große komplizierte Erweiterung erstellen wollen, finden Sie in diesem Artikel nützliche Hinweise.

Ein letzter Hinweis: Die folgenden Techniken wurden nur mit Firefox getestet, allerdings werden Sie mit wenigen Änderungen auch in anderen Gecko-basierten Anwendungen wie Thunderbird oder Seamonkey funktionieren. Fall das jemand bestätigen kann und Informationen zur Anpassung hat, wäre ein kleiner Abschnitt in diesem Artikel dazu sehr hilfreich.

Bambi trifft Mozilla

Das ist nichts für schwache Nerven. Die ersten Schritte erfordern unter anderem das Kompilieren von Mozilla, was ein großen - nein gigantisches - Unterfangen ist. Viele schlaue Entwickler standen schon am Rande der Verzweiflung als Sie zum ersten mal versucht haben Mozilla zu kompilieren. Falls Sie kein erfahrener C++ Entwickler sind, würde ich es gar nicht erst versuchen. Bleiben Sie bei JavaScript.

Unter Windows Plattformen

Als ich das erste Mal Mozilla kompiliert habe, verwendete ich diese Anleitung. Ich weiß nicht mehr warum, aber ich bin an verschiedenen Stellen hängen geblieben und die ganze Sache hat länger gedauert als ich erwartet hatte. Viele Möbel wurden zerstört, viele Haare waren danach nicht mehr auf dem Kopf. Hier ist eine ausführliche Anleitung(engl.), welche gute Rezensionen erhalten hat. Folgen Sie jedem Schritt genau und vielleicht passt am Ende alles. Denken Sie daran, dass wenn Sie das Build einmal erfolgreich hin bekommen haben, es danach umso einfacher ist. Vielleicht.

Unter anderen Plattformen

Auf anderen Systemen, Linux und MacOS, ist der Prozess um einiges einfacher. Alle Tools zum Kompilieren sind eingebaut und daher können Sie mit einigen Befehlen im Terminal sehr einfach ein Build erstellen. Sie können die vollständigen Anweisungen für fast jedes Betriebssystem hier finden.

Strukturierung des Projekts

Mozilla enthält eine Reihe von komplexen Erweiterungen, die im Build-Prozess integriert sind. Das ist nötig, um alle Probleme bei der Erstellung und Registrierung von XPCOM Komponenten, der Kompilierung von JAR Dateien und Manifesten, der Installation des Firefox extensions/ Verzeichnisses und so weiter zu lösen. Wir werden diese Infrastruktur nutzen, um unsere Erweiterung zu erstellen.

Zunächst denken Sie sich einen schönen Namen für Ihre Erweiterung aus und erstellen ein Verzeichnis unter /mozilla/extensions/. Verwenden Sie nur Kleinbuchstaben. Sie sollten eine Reihe weitere Verzeichnisse (inspector/, reporter/ und so weiter) im gleichen Verzeichnis sehen.

Beachten Sie, dass bevor Sie irgendetwas kompilieren, ein Konfigurationsprozess für das Mozilla Build System erforderlich ist, welcher die Datei Makefile.in für das Build erstellt. Die makefile sollte ähnlich oder identisch zur Vorlage sein, aber die zusätzliche Flexibilität durch die dynamische Generierung ist eines der Dinge, welche das Mozilla Build System so mächtig machen.

Anatomie einer einfachen C++ Erweiterung

Wir nehmen an, dass Sie C++ verwenden, um XPCOM Komponenten zu schreiben, die entweder von anderen C++ Komponenten oder von JavaScript verwendet werden. Der Prozess der Erstellung eines Komponents ist eigentlich relativ unkompliziert, wenn das Mozilla Build System verwendet wird.

Im einfachsten Fall besteht eine Komponente aus einem Hauptverzeichnis mit zwei Unterverzeichnissen public/ und src/. Das Hauptverzeichnis und jedes Unterverzeichnis muss eine Makefile.in Datei enthalten (von jetzt beziehe ich mich auf diese Datei als makefile, obwohl wir wissen, dass diese nur verwendet werden, um die wirkliche makefile zu generieren). Diese makefile sagt zwei Sachen. Zuerst werden die Unterverzeichnisse aufgelistet aus denen die Erweiterung besteht, sodass das Build-System weiß, wo nach zusätzlichen makefiles gesucht werden muss. Als Zweites weist diese das Build-System an, eine neue Erweiterung zu installieren, als die Komponenten direkt in das binäre Verzeichnis von Firefox zu kopieren. Der größte Vorteil der Verwendung einer Erweiterung ist das einfache Packen und das installieren auf anderen Rechnern.

Hier ist also unsere Grundlage (Makefile.in im Hauptverzeichnis der Erweiterung):

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

Eine detaillierte Beschreibung des Prozesses und eine Beschreibung der Schlüsselfunktionen dieser makefile, kann hier gefunden werden. MODULE und XPI_NAME sind beides die Namen Ihrer Erweiterung; Sie sollten in allen Projekt-makefiles wiederholt werden, sodass alle Dateien in dem selben Ort des XPIs landen (siehe unten). INSTALL_EXTENSION_ID ist die einzigartige ID Ihrer Erweiterung. Das kann eine GUID sein, aber das obige Format ist schöner und besser zu behalten. Sie müssen kein XPI_PKGNAME angeben, aber wenn Sie eine XPI Datei erstellen, die zur Auslieferung geeignet sein soll, wird diese automatisch im Wurzelverzeichnis des XPIs erstellt (/mozilla/$(MOZ_OBJDIR)/dist/xpi-stage/).

Jede Erweiterung muss eine install.rdf Datei enthalten, die Firefox mitteilt wie die Erweiterung installiert werden soll. Diese Datei sollte im Hauptverzeichnis platziert werden und ungefähr so aussehen:

<?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>

Es gibt eine detaillierte Beschreibung des Formats der install.rdf Datei. Verwenden Sie die Variable DIST_FILES in der makefile, um anzugeben, dass eine Kopie der Datei in das Erweiterungsverzeichnis und (optional) der XPI Datei erstellt wird.

Öffentliche Schnittstellen

Das public/ Verzeichnis enthält alle Schnittstellen, auf die andere Module zugreifen wollen. Das können IDL Dateien für XPCOM Schnittstellen sein, welche verwendet werden, um normale C++ header Dateien in Ihre Quelldateien einzubinden. Es können auch normale C++ header Dateien sein, die direkt von anderen Modulen verwendet werden. Der einfachste Weg um das zu letzt genannte zu machen, ist Implemenierungen für alle Methode innerhalb vorzunehmen, sodass Sie keine zusätzlichen Verweisabhängigkeiten beachten müssen. Sonst müssen Sie statische Links auf Ihr Modul setzen, wenn Sie diese öffentlichen header in andern Modulen verwenden. Ich persönlich würde diese Praxis vermeiden (neben anderen Dingen, heißt statisches Verlinken, dass der gleiche Code mehrmals geladen wird und der Code nicht von JavaScript oder andern nicht-C++ Sprachen verfügbar ist) und immer wenn es möglich ist auf XPCOM zurückgreifen.

Die makefile Datei im public/ Verzeichnis sollte so aussehen:

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 ist der Name der generierten XPT Datei, welche Informationen über Ihre IDL Schnittstellen enthält. Wenn Sie mehrere Module haben, stellen Sie absolut sicher, dass Sie einen anderen Wert für jedes XPIDL_MODULE verwenden. Sonst wird die erste XPT Datei von der zweiten Überschrieben und Sie erhalten NS_ERROR_XPC_BAD_IID Fehler, wenn Sie versuchen die IDL Schnittstellen über Ihren Code zu erreichen. Die Dateien unter EXPORTS werden direkt nach /mozilla/$(MOZ_OBJDIR)/dist/include/myextension/ kopiert und sind daher für andere Module erreichbar (der Wert von MOZ_OBJDIR ist in /mozilla/.mozconfig definiert). XPIDLSRCS läuft durch den IDL Prozessor, und die generierten C++ header werden in das gleiche Verzeichnis kopiert. Außerdem wird eine XPT Datei generiert und im components/ Unterverzeichnis Ihrer Erweiterung abgelegt.

Quelldateien

Jetzt ist es an der Zeit, die makefile und Quelldateien im src/ Unterverzeichnis zu erstellen. Wenn Sie Schnittstellen implementieren, die Sie über IDL  beschrieben haben, ist der einfachste Weg das src/ Verzeichnis leer zu lassen und nur make im public/ Verzeichnis auszuführen; das wird kurz erklärt.

Dann öffnen Sie die generierte header Datei für Ihre Schnittstelle von /mozilla/$(MOZ_OBJDIR)/dist/include/myextension/. Es sind Stücke der Komponenten Dateien .H und .CPP enthalten, die Sie einfach mittels Kopieren und Einfügen in Ihre Implementierungsdateien hinzufügen können. Was Sie tun müssen, ist die Implementierungsstückchen in der C++ Datei zu füllen und Sie sind auf einem guten Weg.

Hier ist ein Beispiel der makefile, die Sie in Ihr src Verzeichnis packen müssen:

DEPTH		= ../../..
topsrcdir	= @top_srcdir@
srcdir		= @srcdir@
VPATH		= @srcdir@

include $(DEPTH)/config/autoconf.mk

IS_COMPONENT = 1
MODULE = myextension
LIBRARY_NAME =  myExtension
USE_STATIC_LIBS = 1

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: If you are coding against the 1.8.0 branch (not 1.8 branch or trunk), the
# above line won't work, due to linker flag issues. Use the following 
# variables instead:
#
# EXTRA_DSO_LDOPTS += \
#   $(MOZ_COMPONENT_LIBS) \
#   $(NULL)
#
# Unfortunately, using MOZ_COMPONENT_LIBS links against xpcom_core, which means
# your components will not work in future versions of Firefox.

Der Abschnitt REQUIRES teilt make mit, welche Module Ihre Komponente nutzt. Das bringt die Unterverzeichnisse von /mozilla/$(MOZ_OBJDIR)/dist/include/ dazu zum C++ Compiler Pfad hinzugefügt zu werden. Wenn Sie Mozilla header einfügen und der Compiler diese nicht findet, kann es bedeuten, dass Sie nicht alle nötigen Module hier aufgelistet haben. CPPSRCS listet die Quelledateien auf, die kompiliert werden sollen.

In diesem Beispiel enthalten die ersten beiden Dateien die Implementierung der zwei Erweiterungskomponenten. Die letzte Datei, myExtension.cpp, enthält den nötigen Code zur Registrierung der Komponenten, wie im nächsten Abschnitt beschrieben.

Komponenten registrieren

Um Ihre Komponenten in anderen C++ Modulen und JavaScript zu verwenden, müssen Sie diese registrieren. Um das zu machen, muss Ihre Erweiterung eine Klasse implementieren, welches die nsIModule Schnittstelle ausstellt, welche Methoden zum Zugang der Komponenten hat. Glücklicherweise kann dies durch einige einfache Makros erreicht werden, sodass Sie sich nicht um die unwichtigen Details kümmern müssen, was unter der Haube geschieht.

Der erste Schritt ist eine CID, contract ID und einen Klassennamen für jede Ihrer Komponenten zu definieren. Platzieren Sie den folgenden Code (mit den entsprechenden #defines) im header jeder Komponente, die Sie über den Komponentenmnager initialisieren möchten:

// {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"

Natürlich müssen Sie die CID mit einer echten GUID ersetzen. Unter Windows kann dies über die guidgen.exe geschehen. Unix Anwender können "uuidgen" verwenden (wird mit den meisten Distributionen ausgeliefert).

Jetzt erstellen Sie die myExtension.cpp Datei wie folgt:

#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)

Das NS_IMPL_NSGETMODULE Makro erstellt das passenden Modulobjekt, welches Zugang zu allen Komponenten bereitstellt, die im nsModuleComponentInfo Array aufgelistet sind.

Kompilieren

Wie oben genannt, möchten Sie Ihre Erweiterung wahrscheinlich direkt nach der Erstellung der IDL Dateien kompilieren, um die C++ stubs für Ihre Komponenten-Implementierung zu generieren. Ich nehme an, dass Sie Firefox bereits erfolgreich kompiliert haben. Wenn nicht gehen Sie sofort zum Anfang dieses Artikels zurück und kommen Sie nicht wieder bis Sie eine funktionierende firefox.exe haben. Gehen Sie nicht weiter. Sammeln Sie keine $200 ein.

Immer noch da? Gut, jetzt müssen wir Ihre .mozconfig (im /mozilla/ Verzeichnis) ändern, sodass Ihre Erweiterung mit Mozilla zusammen kompiliert wird. Fügen Sie die folgende Zeile am Ende der Datei an:

ac_add_options --enable-extensions=default,myextension

Starten Sie make vom Mozilla Root:

make -f client.mk build

Selbst wenn Sie ein aktuelles Firefox Build haben, müssen Sie warten bis make durch den kompletten Mozilla Quelltext gegangen ist und nach neuem Code gesucht hat (auf meinem Computer, welcher sehr schnell ist, hat dies gut 10-15 Minuten gedauert). Schließlich wird Ihre Erweiterung erreicht und eine Reihe von Dingen werden unter /mozilla/$(MOZ_OBJDIR)/ erstellt:

  • Exportierte header Dateien und generierte header Dateien (von IDL) in dist/include/myextension/
  • Statische Bibliotheken für Ihre Module in dist/lib/ (für den Fall, dass andere Module statisch auf Ihre verweisen wollen, anstatt mit XPCOM).
  • XPI Datei in dist/xpi-stage/myextension.xpi.
  • Generierte makefiles für Ihre Projekte in extensions/myextension/ (denken Sie daran wir sind unter /mozilla/$(MOZ_OBJDIR)/)
  • Alles weitere ist in dist/bin/extensions/myextension@mycompany.com/.

Ein Großteil dieser Sachen wird nicht erstellt, wenn make das erste Mal ausgeführt wird, es werden nicht alle Quelldateien Ihrer Komponenten gefunden. Kümmern Sie sich nicht darum. Alles was Sie brauchen, sind die genierten header Dateien, die die C++ Implementierungs-Stubs enthalten. Gehen Sie zurück und arbeiten Sie Ihre C++ Implementierung Ihrer Komponenten aus, sodass das Build beim nächsten Mal komplett ist. Denken Sie dran, niemals die generierten Dateien zu verändern. Ändern Sie immer die Dateien, die verwendet werden, um sie zu generieren und führen Sie make aus. Es können Ausnahmen dieser Regel existieren, aber wenn Sie die generierten Dateien direkt ändern, machen Sie wahrscheinlich irgendetwas falsch.

Der Vorgang, der das komplette Mozilla Verzeichnis durchgeht, dauert eine lange Zeit. Wenn Sie bereits ein Mozilla Build haben, können Sie dies vermeiden, indem Sie eine makefile für Ihre Erweiterung direkt erstellen. Gehen Sie zum Wurzelverzeichnis von $(MOZ_OBJDIR) und fügen Sie in eine Eingabeaufforderung folgendes ein:

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

Wenn sich Ihr $(MOZ_OBJDIR) außerhalb von $(TOPSRCDIR) befindet, müssen Sie folgendes schreiben:

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

Sodass das Skript weiß, wo sich Ihre Quelldateien befinden.

Das wird eine eigene makefile für Ihre Erweiterung erstellen. Egal, ob Sie Mozilla komplett kompilieren oder diese Abkürzung verwenden, Sie können nun zu /mozilla/$(MOZ_OBJDIR)/extensions/myextension/ wechseln, "make" in die Kommandozeile tippen und Ihr Build wird erstellt. Es sollte Ihre Komponente ungeachtet vom Rest kompiliert werden. Wenn alles funktioniert, werden Sie Ihre XPI sehen. Außerdem ist eine "exploded" Version des XPIs (das ungezippte Verzeichnis) unter /mozilla/$(MOZ_OBJDIR)/dist/bin/extensions zu finden.

Um sicher zu gehen, dass das Build wirklich fertig ist, starten Sie Firefox und überprüfen Sie, ob Ihre Erweiterung unter Extras/Erweiterungen angezeigt wird. Wenn Sie Firefox als Ihren normalen Browser verwenden (und wenn nicht, warum nicht?), nervt es Sie vielleicht, dass Sie Firefox öfters schließen müssen bevor Sie Ihre Version starten. Setzen Sie die MOZ_NO_REMOTE Umgebungsvariable auf "1" bevor Sie die Entwicklungsversion von Firefox starten. Sie müssen außerdem ein anderes Profil für Ihre Entwicklerversion verwenden:

firefox -P development

Wobei development der Name des zusätzlichen Profils ist. Dies erlaubt Ihnen nun beide Versionen gleichzeitig laufen zu lassen.

Kein Ort wie Chrome

Yippee-yay! Jetzt haben Sie eine Erweiterung, die absolut nichts kann. Es ist Zeit etwas mit den Komponenten zu machen, die Sie implementiert und registriert haben. Am einfachsten geht das über einfachen JavaScript und XUL Code. Jetzt wäre es hilfreich, wenn Sie bereits Erfahrung im Schreiben von "regulären" Erweiterungen (ohne C++ Komponenten) haben. Wenn Sie das noch nie getan haben, ist empfohlen dies zuerst zu tun. Zeigen Sie doch einfach mal ein neues Menü-Item an, welches eine "Hello, World!" Dialogbox öffnet. Dadurch lernen Sie die benötigten Grundlagen kennen.

Wenn Sie wissen wie man XUL/JavaScript Erweiterungen schreibt, wissen Sie auch, dass die wichtigste Teil im chrome/ Verzeichnis der Erweiterung liegt. Die Tatsache, dass Sie C++ Komponenten verwenden, ändert daran nichts. Also müssen Sie nun die Ordner content/, locale/ und skin/ erstellen, in welchen Sie Ihre Chrome-Dateien packen.

Sobald Sie die notwendigen Chrome-Dateien erstellt haben (zum Beispiel ein Overlay, welches einen Button hinzufügt, der Ihre Komponente aktiviert), müssen sie diese als Teil Ihrer Erweiterung packen. Das wird durch ein JAR Manifest erreicht. Für unser einfaches Beispiel, kann diese Datei in etwa so aussehen:

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)

Packen Sie diesen Code in eine Datei namens jar.mn in das Wurzelverzeichnis Ihrer Erweiterung und stellen Sie sicher, dass die Pfade in den Klammern wirklich auf die entsprechenden Dateien zeigen (relativ zum Wurzelverzeichnis). Sie müssen zudem eine kleine Änderung an der makefile im gleichen Verzeichnis vornehmen: Fügen Sie folgende Zeile hinzu:

USE_EXTENSION_MANIFEST = 1

Das teilt make mit eine einzelne Manifestdatei chrome.manifest zu erstellen, anstatt separate Manifestdateien für jedes Paket zu erstellen.

Nun führen Sie make noch einmal aus  und Sie sollten ein chrome Unterverzeichnis sehen (/mozilla/$(MOZ_OBJDIR)/dist/bin/extensions/myextension@mycompany.com/). Beachten Sie, dass das chrome Verzeichnis ein JAR (ZIP) Datei mit allen Chrome-Dateien, die in jar.mn aufgelistet sind und auch die komplette Verzeichnisstruktur der jar Datei, enthält. Die Verzeichnisstruktur ist jedoch leer. Warum? Ich weiß es nicht. Kümmern Sie sich nicht darum, denn die Dateien im JAR sind die, die gebraucht werden.

Komplizierte Erweiterungen strukturieren

Wenn Sie eine komplexere Erweiterung mit vielen XPCOM Komponenten entwickeln, möchten Sie Ihren Code womöglich in kleinere Module aufteilen.

Etwas komplexere Erweiterungen

Für mäßig komplexe Erweiterungen ist es wahrscheinlich ausreichend den Code einfach in einzelne Module aufzuteilen. Nehmen wir an, Sie haben ein base/ Modul, welches eine Reihe grundlegender XPCOM Komponenten bereitstellt und ein advanced/ Modul, welches einige Chrome und andere Module bereitstellt. Ihre komplette Verzeichnisstruktur könnte so aussehen:

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

Sonst ändert sich nicht wirklich etwas. Die makefiles in den base/ und advanced/ Verzeichnissen sollten mehr oder weniger genau so wie die originalen makefiles aussehen. Denken Sie daran die DEPTH Variable zu ändern, falls die Dateien ein Level weiter weg von dem Mozilla Verzeichnis verschoben wurden. Sie müssen außerdem die DIST_FILES Variable entfernen, weil das in der top-level makefile vorhanden sein wird. Jede makefile, die irgendetwas generiert, sollte die XPI_NAME Variable definieren, um sicher zu gehen, dass generierte Dateien in Ihre Erweiterung kommen und nicht in das globale components/ Verzeichnis. Definieren Sie das einfach in jeder makefile, um sicher zu gehen. Sie können das gleiche MODULE in beiden base/ und advanced/ Verzeichnissen verwenden, sodass die generierten Dateien in das gleiche Verzeichnis wandern, aber stellen Sie sicher, dass Sie nicht die gleichen für XPIDL_MODULE in den zwei public/ Verzeichnissen definieren oder eine in einen der Komponenten Bibliotheken (XPT Dateien), sonst wird das andere überschrieben und alles wird kaputt gehen.

Jedes Modul muss auch einen unterschiedlichen Wert für die LIBRARY_NAME Variable besitzen. Das ist der Name der generierten dynamischen Bibliothek, wenn wir also die Bibliotheken "myBase" und "myAdvanced" aufrufen, wird eine myBase.dll und myAdvanced.dll (unter Windows zumindest) erstellt. Jedes dieser Module hat eine einzelne C++ Datei zur Registrierung von Komponenten. Es wird also zwei Dateien, die wie myExtension.cpp aussehen, in diesem Beispiel Base.cpp und Advanced.cpp. Schließlich wird jedes Modul ein eigenes jar.mn haben, sodass sie auf die gleichen JAR und Paket Dateinamen zurückgreifen. Die einzige Datei, die so bleibt ist install.rdf, welche immer noch nur einmal in dem Wurzelverzeichnis der Erweiterung steht.

Vom top-level der makefile, sieht es nun so aus:

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
Wirklich komplexe Erweiterungen

Wenn ein einzelnes Modul sehr groß wird, wollen Sie dieses sicher in weitere Untermodule aufteilen. Der Unterschied zu einzelnen Modulen und einzelnen Untermodulen, ist, dass die Untermodule alle die gleiche Datei zur Registrierung von Komponenten haben (die berühmte myExtension.cpp Datei) und wenn kompiliert wurde, werden sie als einzelne Bibliothek erstellt. Die Entscheidung zur Unterteilung in Untermodulen liegt nur in der Organisation des Codes und betrifft das Endprodukt nicht wirklich.

Um ein Modul in Untermodule aufzuteilen, erstellen Sie zunächst einen Unterordner für jedes Untermodul. Dann erstellen Sie ein zusätzliches Verzeichnis, das build/ genannt wird. Jedes Untermodul wird so konfiguriert, dass eine statische Bibliothek erstellt wird und das build/ Verzeichnis wird diese Bibliotheken zusammenfügen, um eine einzige Komponentenbibliothek zu erstellen. Verwirrt? Hier ist ein Beispiel, welches das advanced/ Unterverzeichnis des myextension/ Verzeichnisses zeigt:

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

Wie Sie sehen, haben wir advanced/ in zwei Untermodule geteilt: intricate/ und multifarious/ und wir haben einen zusätzlichen Ordner build/ hinzugefügt. Wir haben die Chrome-Verzeichnisse direkt unter advanced/ belassen, das sie nicht an irgendwelche Untermodule gebunden sind. Das bedeutet, dass das jar.mn am gleichen Platz verweilt.

Die intricate/ und multifarious/ makefiles werden so wie die originale advanced/ makefile aussehen, aber wir müssen ein paar kleiner Anpassungen vornehmen. Wie immer müssen wir die DEPTH Variable anpassen, da sich die makefiles tiefer in der Verzeichnisstruktur befinden. Wir sollten außerdem die LIBRARY_NAMEs ändern, um anzugeben, das wir statische Bibliotheken für jedes Untermodul anlegen. Das "_s" Suffix ist für diesen Zweck geeignet. Wir nennen sie also "myIntricate_s" und "myMultifarious_s". Schließlich definieren wir FORCE_STATIC_LIB, was eine makefile ergibt, die ungefähr so aussieht:

DEPTH		= ../../../../..
topsrcdir	= @top_srcdir@
srcdir		= @srcdir@
VPATH		= @srcdir@
	
include $(DEPTH)/config/autoconf.mk
	
MODULE = myextension
LIBRARY_NAME = myIntricate_s
FORCE_STATIC_LIB = 1
USE_STATIC_LIBS = 1
	
XPI_NAME = myextension
	
...more stuff here...

Build makefile fügt die statischen Bibliotheken zusammen, die von jedem Untermodul generiert worden sind und erstellt eine einzelne (dynamische) Komponentenbibiliothek:

DEPTH		= ../../../..
topsrcdir	= @top_srcdir@
srcdir		= @srcdir@
VPATH		= @srcdir@
	
include $(DEPTH)/config/autoconf.mk
	
IS_COMPONENT = 1
MODULE = myextension
LIBRARY_NAME = myAdvanced
USE_STATIC_LIBS = 1
	
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)

Die makefile im advanced/ Verzeichnis sollte die intricate/, multifarious/ und build/ Verzeichnisse in der DIRS Variable auflisten. Stellen Sie sicher, dass  build/ als letztes genannt wird, da die Komponentenbibliothek nicht erstellt werden kann, solange die makefiles nicht komplett sind.

Weitere Themen

Data Dateien zur Erweiterung hinzufügen

In einigen Fällen möchten Sie vielleicht zusätzliche Dateien zu Ihrer Erweiterung hinzufügen, die nicht in das chrome/ Unterverzeichnis gehören. Beispiele sind Datenbankdateien oder XML-Schemata. Das erreicht werden, indem ein zusätzlicher Schritt zur makefile hinzugefügt wird, welcher diese Dateien in das Erweiterungsverzeichnis kopiert.

Data Dateien in ein Zielverzeichnis kopieren

Nehmen wir an, dass Sie einige Dateien mit statistischen Daten haben, die Sie in Ihre Erweiterung einfügen wollen und für Komponenten verfügbar machen wollen. Sie haben diese Datei mit der Endung .TXT in ein stats/ Unterverzeichnis getan. Die folgende makefile Regel kann verwendet werden, um die Dateien zu kopieren:

export::
	if test ! -d $(FINAL_TARGET)/stats; then \
		$(NSINSTALL) -D $(FINAL_TARGET)/stats; \
	fi
	$(INSTALL) $(srcdir)/*.txt $(FINAL_TARGET)/stats
Auf Data Dateien über Komponenten zugreifen

Der Trick, welcher Ihre Daten zugänglich macht, ist herauszufinden, wo Ihr Hauptverzeichnis Ihrer Erweiterung ist. Um das möglich zu machen, wird die  nsIExtensionManager Schnittstelle später benötigt. In der Zwischenzeit gibt es einen Hack, der das auf einfache Art und Weise ermöglicht. In der Implementierung von jeder JavaScript XPCOM Komponente gibt es ein spezielles __LOCATION__ (zwei führenden und zwei schließende Unterstriche) Symbol, welches auf die Komponentenimplementierungsdatei zeigt. Sie können also eine einfache Komponente schreiben, die auf das Wurzelverzeichnis Ihrer Erweiterung rückschließt.

Dieser Artikel(engl.) erklärt wie man eine XPCOM Komponente in JavaScript erstellt. Sie brauchen dazu eine IDL Datei für eine Schnittstelle, die ungefähr so aussieht:

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

Legen Sie die IDL Datei in public/ Verzeichnis Ihres Projekts ab. Im src/ Verzeichnis platzieren Sie die JavaScript Datei, die die Komponente implementiert. Die Komponentenimplementierung enthält Methoden, die den Pfad zum Hauptverzeichnis der Erweiterung abrufen können:

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

Es wird angenommen, dass sich die Komponente in einem Unterverzeichnis des Erweiterungsverzeichnis befindet (das Verzeichnis wird components/ genannt). Die Eigenschaft von __LOCATION__ gibt das components/ und das überliegende Verzeichnis, also das Hauptverzeichnis, zurück.

Der letzte Schritt ist die makfile anzupassen, sodass die JavaScript Datei an die richtige Stelle kopiert wird:

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

Jetzt können Sie eine Instanz dieser Komponente starten und die locationFile Eigenschaft verwenden, um eine nsIFile Schnittstelle, welche auf Ihr Hauptverzeichnis zeigt, zu bekommen.

Drittanbieter Bibliotheken verwenden

Für anspruchsvollere Erweiterungen möchten Sie Drittanbieter Bibliotheken einbinden, die Ihnen spezialisierte Funktionen bieten, zum Beispiel für Datenbankverbindungen, Bildprozesse, Netzwerkoperationen und vielem mehr. Wenn Sie möchten, dass Ihre Erweiterungen unter Firefox auf allen Plattformen läuft, müssen Sie den Quelltext für die entsprechenden Bibliotheken haben.

Der Beste Ansatz ist, eine makefile im Mozilla-style für diese Bibliothek zu erstellen. Das funktioniert gut für Bibliotheken, die keine intensive Konfiguration erfordern. Ein gutes Beispiel ist die SQLite Bibliothek im Mozilla Build Tree unter db/sqlite. Wenn die makefile so angepasst wird, wird die Bibliothek als des Standard Mozilla Build Prozesses erstellt, welches zusätzliches Schritte unnötig macht. Der Nachteil ist, dass Sie jedes Mal die geänderte makefile aktualisieren müssen, wenn eine neue Version der Bibliothek ausgeliefert wird.

Bei Bibliotheken mit komplexen Konfigurationen, die keine Standard-Compiler verwenden oder andere speziellen Charakteristiken haben, kann es numgänglich sein, eine Mozilla-kompatible makefile zu erstellen. In diesem Fall würde ich empfehlen, dass Sie die komplette Bibliothek innerhalb des Projekts platzieren. Wenn die Bibliothek acmelib innerhalb des multifarious/ Unterprojekts verwendet wird, würde diese als Unterverzeichnis unter diesem Unterprojekt auftauchen (auf dem gleichen Level wie public/ und src/).

Natürlich bedeutet das, dass Sie die Bibliothek acmelib manuell kompilieren müssen, bevor Sie das Mozilla Build starten. Aber immerhin können Sie sich auf die Dateien beziehen und Bibliotheken importieren, indem Sie relative Pfade verwenden.

Kompilierung für mehrere Plattformen

TODO

Informationen zum Originaldokument

 

Schlagwörter des Dokuments und Mitwirkende

Mitwirkende an dieser Seite: Luxo, Xxhellfirexx, fscholz
Zuletzt aktualisiert von: fscholz,