This document describes adding new components to Mozilla, including the build system and component registration. It does not deal with extensions, only core components checked into the main tree. It assumes you already know how to write XPCOM components, and want to integrate them into Mozilla.
You will have to pick a good place for your component to live. Some general shared classes live in toolkit/components
, Firefox-specific classes live in browser/components
, a lot of random other stuff lives in extensions
and in other directories underneath the main mozilla
directory. If you are extending a component, you can just use that component's directories. Otherwise, ask your favorite Mozilla guru or on #developers
if you don't know where it should go.
Off of your component's directory, there is often:
public
Where your IDL files go. Sometimes, there are also header files that will be #included by other components.src
Where your implementation files go. For C++ components, you will typically have a .cpp/.h pair for each implementation class. If you are implementing a class in JavaScript, put it here as well.content
Where your user interface files go, including .xul and any associated .js files that are used at runtime. If you aren't building new UI specific for this component, you won't have one of these.Once you have your component written, you will need to write the makefiles. Your best bet is to copy the Makefile.in from another component similar to yours: most of the makefiles are very similar. These Makefile.in files are processed and the "real" Makefile is created. If you are using a separate directory tree for your object files, that final Makefiles will appear there.
For more details on Makefiles see How Mozilla's build system works.
Generally, you will have a top-level directory foo
for your component, which will then contain foo/public
, foo/src
, etc. subdirectories. The makefile in foo
will just reference the subdirecories and otherwise just needs the boilerplate:
DEPTH = ../.. # <-- SEE BELOW FOR THE MEANING OF THIS topsrcdir = @top_srcdir@ srcdir = @srcdir@ VPATH = @srcdir@ include $(DEPTH)/config/autoconf.mk DIRS = public src include $(topsrcdir)/config/rules.mk
Your other makefiles will actually include all of your files. In these other makefiles, you should see/add the lines:
MODULE = foo XPIDL_MODULE = foo
MODULE = foo
means that a directory dist/include/foo
will be created and your public files, such as .h files generated from .idl sources, will be placed there. Other modules will then be able to say REQUIRES = foo
to add that directory to their include paths and use your interface files. XPIDL_MODULE
controls the name of the .xpt file, which is a compiled version of your IDL files. The exact name of this doesn't matter much, and is generally the same as your MODULE
name.
Other interesting makefile sections:
toolkit/components/history/Makefile.in
would contain DEPTH = ../../..
MODULE=foo
line in the other module (which generates a dist/include/foo
directory that contains the header files).REQUIRES
instead which will set up the include path for you. The format is LOCAL_INCLUDES = -Isome_directory/somewhere
. For relative paths, use something like -I$(srcdir)/../some_component/src
($srcdir
identifies the current directory, which might actually be different than the current directory of the compiler).DEFINES = -DSOME_DEFINE -DOTHER_DEFINE=25
When you add a new makefile for an XPCOM component, or when you add new files to an existing makefile, you should also add the relevant .xpt
, .js
and .manifest
files to the appropriate section of the package-manifest.in
file for all the products that are going to include your new components or interfaces.
For example, if your makefile includes the following lines:
EXTRA_COMPONENTS = \ DownloadManagerUI.js \ DownloadManagerUI.manifest \ $(NULL)
You should also add the following lines to package-manifest.in
:
@BINPATH@/components/DownloadManagerUI.js @BINPATH@/components/DownloadManagerUI.manifest
You can then follow the package build instructions to create an installer and verify that your new component is included.
Put your JavaScript file in some directory such as foo/src
which is for XPCOM components (don't use content
which is for interface code). Then add your filename to the EXTRA_COMPONENTS=
section of the Makefile.in
.
At the bottom of your javascript implementation, you should create a function NSGetModule
, which just returns a reference to an nsIModule implementation:
function NSGetModule(compMgr, fileSpec) { return myModule; }
Here is an example nsIModule implementation, which creates objects of type myImplementation
:
var myModule = { mCID: Components.ID("{d1a83725-4a27-4a66-b212-915b14722c75}"), // (use your CID) mContractID: "@example.com/my-contract-id;1", // (use your contract ID) registerSelf: function (compMgr, fileSpec, location, type) { compMgr = compMgr.QueryInterface(Components.interfaces.nsIComponentRegistrar); compMgr.registerFactoryLocation(this.mCID, "My class that does something useful", // (use your description) this.mContractID, fileSpec, location, type); }, getClassObject: function (compMgr, cid, iid) { if (!cid.equals(this.mCID)) throw Components.results.NS_ERROR_NO_INTERFACE; if (!iid.equals(Components.interfaces.nsIFactory)) throw Components.results.NS_ERROR_NOT_IMPLEMENTED; return this.mFactory; }, mFactory: { createInstance: function (outer, iid) { if (outer != null) throw Components.results.NS_ERROR_NO_AGGREGATION; return (new myImplementation()).QueryInterface(iid); // (use your implementation object name) } }, canUnload: function(compMgr) { return true; } };
You can provide an initialization function for your class. This will be called immediately after your class is allocated and the constructor is called. The init function takes 0 arguments, returns an nsresult, and must be public. You can call it anything you like, just reference it from NS_GENERIC_FACTORY_CONSTRUCTOR_INIT
(discussed below).
The advantage of using the init function over the normal C++ constructor is that you can return an error from the init function. If an error is detected, the function will be deallocated and the caller requesting an instance of your class will get the error. Generally, you should perform all initialization that may fail in your class' Init function.
The components are registered as part of a module, the source for which usually lives in a directory called build
. For example, toolkit/components/build
is the module source for the components underneath toolkit/components/
. Likewise, there is a browser/components/build
, a widget/src/build
, etc.
First, find the Makefile.in and give it the name of your component. This should be the same name as the MODULE = foo
line in your component's makefile. This makes sure the include path contains dist/include/foo
where your component's generated header files are placed.
In the build directory, you will find a cpp file corresponding to the module, the naming of which is somewhat arbitrary. In this file, you will want to first include your component's header file. This is not the header generated from the IDL, but the header for the concrete implementation of the component (since this module is what will actually make instances of your implementation).
Typically, your implementation header file (required here) lives in your component's directory and is not public. This means it is not copied/linked to from dist/include/foo
and just including your directory in the build REQUIRES
section is not sufficient. In this case, you'll need to add your implementation source directory to LOCAL_INCLUDES
. See the file toolkit/components/build/Makefile.in
for an example of how to do this.
Next, add a line:
NS_GENERIC_FACTORY_CONSTRUCTOR(nsYourConcreteClassName)
This will generate a function called nsYourConcreteClassNameConstructor that allocates your object (see xpcom/glue/nsIGenericFactory.h
). You can also use:
NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsYourConcreteClassName, Init)
which will call the function nsYourConcreteClassName->Init()
after the object is allocated (see The_init_function above).
nsModuleComponentInfo
for your classIn the module .cpp file, there should be a long list of nsModuleComponentInfo
structures corresponding to each of the classes the module implements. These structures generally look like this:
{ "Friendly class description for people to read", YOUR_CLASS_CID, YOUR_CLASS_CONTRACTID, nsYourConcreteClassNameConstructor }
There are a lot of optional arguments after this that are not often used. See the definition of nsModuleComponentInfo
in xpcom/glue/nsIGenericFactory.h
for the full list.
YOUR_CLASS_CID: (component ID) is a GUID that identified the implementation of your class. This is different than the GUIDs used to identify each of the interfaces. (It is possible, but not common, to create a class instance using this value instead of the contract ID.) The value is often defined in a somethingCID.h
file alongside the module .cpp file in the build directory. Sometimes, people declare them in their component's .h file instead. A typical definition is:
/* 2d96b3d0-c051-11d1-a827-0040959a28c9 */ #define NS_WINDOW_CID \ { 0x2d96b3d0, 0xc051, 0x11d1, \ {0xa8, 0x27, 0x00, 0x40, 0x95, 0x9a, 0x28, 0xc9}}
The contract ID: (the @example.com/...
part, not to be confused with the "CID" = component ID) is the string that people can pass to GetService
or CreateInstance
to get an instance of your class. Sometimes these are defined alongside the CID in the header file in the build directory, or there is a special public header file that defines it. Both of these methods are fine. Other times they are just hard-coded into the structure or are defined in the IDL file, but these are not recommended.
The constructor is the name of the constructor automatically generated by the NS_GENERIC_FACTORY_CONSTRUCTOR
line you added above. It will be the name of your concrete class followed by "Constructor".
If your class implements more than one contract: Define one nsModuleComponentInfo
structure for each interface your class implements. The CID and the constructor name will be the same for all of these (since your concrete implementation is the same), but you will have different contract IDs corresponding to each interface.