Implementing XPCOM components in JavaScriptFrom MozillaZine Knowledge Base(Difference between revisions)
Revision as of 21:09, 11 April 2006This page is part of the extension development documentation project. Ask your questions in MozillaZine Forums. Also try browsing example code. Note: development documentation is in process of being moved to Mozilla Development Center (MDC).
This article provides information about creating a Javascript XPCOM class. The hope is to help explain how to create a class, and what processes are used to register and iinstanciate it. It does not describe how to create interfaces for your class. How to Build an XPCOM Component in Javascript at developer.mozilla.org has example of doing this if you need more help in that area. There are also a couple of forum discussion with some explanations, example code, and troubleshooting tips:
About XPCOMMozilla applications are built from a collection of XPCOM (Cross-platform Component Object Model) components. They are used to perform certain tasks or to get specific functionality. For example, there is a directory service component that can be used to access files on your file system. Classes can be constructed using several programming languages, including: JavaScript, C++, and Python (See PyXPCOM). Mozilla then uses "interfaces" to describe what a component can do. The most basic interface nsISupports is used to retrieve the different interfaces that a component implements. Benefits of XPCOM
XPCOM Drawbacks
XPCOM ConceptsThere are basically 4 concepts you need to understand when developing XPCOM objects. They are components, interfaces, modules, and factories. I will give a brief summary of each, then go into more details later. ComponentsA component is simply an object that implements interfaces, and is registered with the component manager. In our case the implementation will be a Javascript object. InterfacesInterfaces are used to define what functions and attributes an XPCOM component provides. nsISupports is an interface that all classes must implement. It provides a function to request the interfaces on a class. FactoriesFactories are objects used to instantiate components. Each factory object implements the nsIFactory interface. ModulesMultiple classes can be defined in a file. A module is an object that is used to register, unregister and provide factories for classes. It implements the nsIModule interface. Example CodeFor our example, we are going to create a file that contains one class, one module, and one factory. The class will implement the required nsISupports interface, and the nsISupportsPriority interface. A simple example of an actual Mozilla component is the nsDictionary class. A complex example that implements multiple classes is the nsUpdateService. Note: This example implements pre-existing interfaces, and therefore does not address the creation or compilation of interface descriptions (IDL) for our component. A more comprehensive tutorial that covers creating an XPCOM in Javascript from scratch can be found here. ConstantsFirst lets add some constants to our file that will be used later. //interfaces we support const nsISupportsPriority = Components.interfaces.nsISupportsPriority; const nsISupports = Components.interfaces.nsISupports; //class constants const CLASS_ID = Components.ID("{1C0E8D86-B661-40d0-AE3D-CA012FADF170}"); const CLASS_NAME = "My Supports Priority Component"; const CONTRACT_ID = "@mozillazine.org/example/priority;1"; Class ConstructionNext we will add a javascript function which will be used for our class construction. //class constructor function MyPriority() { this._priority = nsISupportsPriority.PRIORITY_LOWEST; }; Class DefinitionNow we will use javascript prototyping to define our class. The class will expose the methods and properties from our selected interfaces. //class definition MyPriority.prototype = { _priority: null, //property of nsISupportsPriority interface get priority() { return this._priority; }, set priority(aValue) { this._priority = aValue; }, //method of nsISupportsPriority interface adjustPriority: function(aDelta) { this._priority += aDelta; }, //method of nsISupports interface QueryInterface: function(aIID) { if (!aIID.equals(nsISupportsPriority) && !aIID.equals(nsISupports)) throw Components.results.NS_ERROR_NO_INTERFACE; return this; } }; Factory ObjectNow we will add a javascript object that implements the nsIFactory interface. var MyPriorityFactory = { createInstance: function (aOuter, aIID) { if (aOuter != null) throw Components.results.NS_ERROR_NO_AGGREGATION; return (new MyPriority()).QueryInterface(aIID); } }; Module ObjectThis object implements the nsIModule interface. //module definition (xpcom registration) var MyPriorityModule = { _firstTime: true, registerSelf: function(aCompMgr, aFileSpec, aLocation, aType) { if (this._firstTime) { this._firstTime = false; throw Components.results.NS_ERROR_FACTORY_REGISTER_AGAIN; }; aCompMgr = aCompMgr.QueryInterface(Components.interfaces.nsIComponentRegistrar); aCompMgr.registerFactoryLocation(CLASS_ID, CLASS_NAME, CONTRACT_ID, aFileSpec, aLocation, aType); }, unregisterSelf: function(aCompMgr, aLocation, aType) { aCompMgr = aCompMgr.QueryInterface(Components.interfaces.nsIComponentRegistrar); aCompMgr.unregisterFactoryLocation(CLASS_ID, aLocation); }, getClassObject: function(aCompMgr, aCID, aIID) { if (!aIID.equals(Components.interfaces.nsIFactory)) throw Components.results.NS_ERROR_NOT_IMPLEMENTED; if (aCID.equals(CLASS_ID)) return MyPriorityFactory; throw Components.results.NS_ERROR_NO_INTERFACE; }, canUnload: function(aCompMgr) { return true; } }; Module FunctionLast we will add a function used to locate the module object. //module initialization function NSGetModule(aCompMgr, aFileSpec) { return MyPriorityModule; } Finished CodeBelow is how the finished code may look. // constants const nsISupportsPriority = Components.interfaces.nsISupportsPriority; const nsISupports = Components.interfaces.nsISupports; const CLASS_ID = Components.ID("{1C0E8D86-B661-40d0-AE3D-CA012FADF170}"); const CLASS_NAME = "My Supports Priority Component"; const CONTRACT_ID = "@mozillazine.org/example/priority;1"; //class constructor function MyPriority() { this._priority = nsISupportsPriority.PRIORITY_LOWEST; }; //class definition MyPriority.prototype = { _priority: null, get priority() { return this._priority; }, set priority(aValue) { this._priority = aValue; }, adjustPriority: function(aDelta) { this._priority += aDelta; }, QueryInterface: function(aIID) { if (!aIID.equals(nsISupportsPriority) && !aIID.equals(nsISupports)) throw Components.results.NS_ERROR_NO_INTERFACE; return this; } }; //class factory var MyPriorityFactory = { createInstance: function (aOuter, aIID) { if (aOuter != null) throw Components.results.NS_ERROR_NO_AGGREGATION; return (new MyPriority()).QueryInterface(aIID); } }; //module definition (xpcom registration) var MyPriorityModule = { _firstTime: true, registerSelf: function(aCompMgr, aFileSpec, aLocation, aType) { if (this._firstTime) { this._firstTime = false; throw Components.results.NS_ERROR_FACTORY_REGISTER_AGAIN; }; aCompMgr = aCompMgr.QueryInterface(Components.interfaces.nsIComponentRegistrar); aCompMgr.registerFactoryLocation(CLASS_ID, CLASS_NAME, CONTRACT_ID, aFileSpec, aLocation, aType); }, unregisterSelf: function(aCompMgr, aLocation, aType) { aCompMgr = aCompMgr.QueryInterface(Components.interfaces.nsIComponentRegistrar); aCompMgr.unregisterFactoryLocation(CLASS_ID, aLocation); }, getClassObject: function(aCompMgr, aCID, aIID) { if (!aIID.equals(Components.interfaces.nsIFactory)) throw Components.results.NS_ERROR_NOT_IMPLEMENTED; if (aCID.equals(CLASS_ID)) return MyPriorityFactory; throw Components.results.NS_ERROR_NO_INTERFACE; }, canUnload: function(aCompMgr) { return true; } }; //module initialization function NSGetModule(aCompMgr, aFileSpec) { return MyPriorityModule; } How it WorksStartup and RegistrationDuring the startup process if the file compreg.dat is missing or the file .autoreg exists, the application knows that it needs to register components. It then loads files from each of the components folders, including the components folder in each extension directory. It then calls the function NSGetModule on each file to get the object that implements the nsIModule interface. //module initialization function NSGetModule(aCompMgr, aFileSpec) { return MyPriorityModule; } The module object is then used to register your module. This is used to simplify the process of retrieving the module later in class creation. The registerSelf function is called to do the actual registering. When removing a class, the unregisterSelf function is called. registerSelf: function(aCompMgr, aFileSpec, aLocation, aType) { if (this._firstTime) { this._firstTime = false; throw Components.results.NS_ERROR_FACTORY_REGISTER_AGAIN; }; aCompMgr = aCompMgr.QueryInterface(Components.interfaces.nsIComponentRegistrar); aCompMgr.registerFactoryLocation(CLASS_ID, CLASS_NAME, CONTRACT_ID, aFileSpec, aLocation, aType); }, Class CreationUsing the code below to access your class, calls the getClassObject on your module. getClassObject: function(aCompMgr, aCID, aIID) { if (!aIID.equals(Components.interfaces.nsIFactory)) throw Components.results.NS_ERROR_NOT_IMPLEMENTED; if (aCID.equals(CLASS_ID)) return MyPriorityFactory; throw Components.results.NS_ERROR_NO_INTERFACE; }, The code within your module is used to return a class factory. The factory then has a method called createInstance which is used to create the actual class object. createInstance: function (aOuter, aIID) { if (aOuter != null) throw Components.results.NS_ERROR_NO_AGGREGATION; return (new MyPriority()).QueryInterface(aIID); } Accessing your ClassYou can now access your class by using the following code. // instanciate component object var oMyPriority = Components.classes['@mozillazine.org/example/priority;1']. createInstance(Components.interfaces.nsISupportsPriority); // lower priority oMyPriority.adjustPriority(10); Mozilla also provides a component construction process to easy creation with javascript. It can be accessed with the following code. //component constructor var Priority = new Components.Constructor('@mozillazine.org/example/priority;1','nsISupportsPriority'); //instanciate component objects var oMyPriority1 = new Priority(); var oMyPriority2 = new Priority(); //lower priority oMyPriority1.adjustPriority(10); oMyPriority2.priority = oMyPriority1.priority; |