As we mentioned, one advantage of using XPCOM is that it separates the implementation from the interface so you can write a component in a language-agnostic manner. The services your component provides are available to all other components despite the language used to implement it. This means, for example, that you can use JavaScript not only to access the services of an XPCOM component, but also to create those services. As described in Chapter 5, using JavaScript as a modularized application programming language provides the deepest level of scripting in Mozilla.
In your Mozilla build or distribution, you will find a subdirectory named components. Inside this directory, you will see many compiled components. You will also see a number of JavaScript components. If you look at the source of these components, you can get an idea of how a JavaScript component is created. For example, look at the files nsFilePicker.js and nsSidebar.js. These JavaScript components are used in the Mozilla distribution.
JavaScript XPCOM components have the advantage over regular scripts of being fast, reusable, and globally accessible to any caller. They also have the advantage over C++-based XPCOM components of being easier to write and maintain. The next few sections describe the creation of a JavaScript-based XPCOM component. If you would rather do your work in C++, then skip to the C++ implementation section in this chapter.
The complete source code for the nsISimple.idl interface file is:
#include "nsISupports.idl" [scriptable, uuid(ce32e3ff-36f8-425f-94be-d85b26e634ee)] interface nsISimple : nsISupports { attribute string yourName; void write( ); void change(in string aValue); };
#include "nsISupports.idl" [scriptable, uuid(ce32e3ff-36f8-425f-94be-d85b26e634ee)] interface nsISimple : nsISupports { attribute string yourName; void write( ); void change(in string aValue); };
function SimpleComponent( ) {}
With the function declared, we start defining the JavaScript class prototype.
SimpleComponent.prototype = { mName : "a default value",
get yourName( ) { return this.mName; }, set yourName(aName) { return this.mName = aName; },
simple.yourName='foo';
Or similarly read values from the attribute:
var foo = simple.yourName;
write : function ( ) { dump("Hello " + this.mName + "\n"); },
The void change( ) method is then implemented as follows:
change : function (aValue) { this.mName = aValue; },
Example 8-3 shows an implementation of QueryInterface specific to our new component. QueryInterface ensures that the correct interface (nsISimple) is used by matching the iid with the nsISimple interface that this component implements. If the interface doesn't match, then the argument is invalid. In this case, the exception Components.results.NS_ERROR_NO_INTERFACE is thrown, which maps to the error code number 2147500034, and code execution is stopped. If the interface identifier parameter matches the interface, then an instance of the implementation class object SimpleComponent with its interface is returned as a ready-to-use XPCOM component. In XPCOM, every component you implement must have a QueryInterface method.
var Module = { firstTime : true,
The Boolean firstTime is a flag used only when the component is initially registered:
registerSelf: function (compMgr, fileSpec, location, type) { if (this.firstTime) { dump("*** first time registration of Simple JS component\n"); this.firstTime = false; throw Components.results.NS_ERROR_FACTORY_REGISTER_AGAIN; }
dump(" ***** Registering: Simple JS component! ****\n"); compMgr.registerComponentWithType(this.myCID, "My JS Component", this.myProgID, fileSpec, location, true, true, type); },
getClassObject : function (compMgr, cid, iid) { if (!cid.equals(this.myCID)) throw Components.results.NS_ERROR_NO_INTERFACE; if (!iid.equals(Components.interfaces.nsIFactory)) throw Components.results.NS_ERROR_NOT_IMPLEMENTED; return this.myFactory; },
myCID: Components.ID("{98aa9afd-8b08-415b-91ed-01916a130d16}"), myProgID: "@mozilla.org/js_simple_component;1",
myFactory: { createInstance: function (outer, iid) { dump("CI: " + iid + "\n"); if (outer != null) throw Components.results.NS_ERROR_NO_AGGREGATION; return (new SimpleComponent( )).QueryInterface(iid); } },
canUnload: function(compMgr) { dump("****** Unloading: Simple JS component! ****** \n"); return true; } function NSGetModule(compMgr, fileSpec) { return Module; }
The code in Example 8-4 shows the implementation for the nsISimple interface in its entirety.
$ ls nsISimple.idl nsSimple.js $ PATH=$PATH:/usr/src/mozilla/xpcom/typelib/xpidl $ echo $PATH /sbin:/bin:/usr/sbin:/usr/bin:/usr/games:/usr/local/bin:/usr/X11R6/bin:/root/bin:/usr/src/mozilla/xpcom/typelib/xpidl $ export XPIDL_INC=/usr/src/mozilla/xpcom/base $ echo $XPIDL_INC /usr/src/mozilla/xpcom/base $ xpidl -m typelib -w -v -I $XPIDL_INC \ > -o nsISimple nsISimple.idl $ ls nsISimple.idl nsISimple.xpt nsSimple.js $ cp nsISimple.xpt nsSimple.js \ > /usr/src/mozilla/dist/bin/components/
TOP_SRC=/usr/src/mozilla INST_DIR=$(TOP_SRC)/dist/bin/components XPIDL=$(TOP_SRC)/xpcom/typelib/xpidl XPIDL_INC=$(TOP_SRC)/xpcom/base FLAGS=-m typelib -w -v -I $(XPIDL_INC) -o all: $(XPIDL)/xpidl $(FLAGS) \ nsISimple nsISimple.idl install: cp nsISimple.xpt nsSimple.js $(INST_DIR) clean: rm -rf *.xpt uninstall: rm -f $(INST_DIR)/nsISimple.xpt rm -f $(INST_DIR)/nsSimple.js
Remember that you must indent after your targets with a <tab>.
When you start up xpcshell, the Component Manager finds the new nsISimple component and registers it. The result of your test should look similar to Example 8-5.
<script type="application/x-JavaScript"> netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect"); var Simple=new Components.Constructor("@mozilla.org/js_simple_component;1", "nsISimple"); var s = new Simple( ); for(var list in s) document.write(list+"<br>\n"); </script>
To use this macro for the nsISimple interface, type:
NS_IMPL_ISUPPORTS1_CI(nsSimpleImpl, nsISimple)
The following lines define this macro:
#define NS_IMPL_ISUPPORTS1(_class, _interface) \ NS_IMPL_ADDREF(_class) \ NS_IMPL_RELEASE(_class) \ NS_IMPL_QUERY_INTERFACE1(_class, _interface)
Example 8-6 shows a reference implementation of the QueryInterface method in C++.
#define NS_SUCCEEDED(_nsresult) (!((_nsresult) & 0x80000000))
nsresult rv; nsCOMPtr<nsILocalFile> file(do_CreateInstance("@mozilla.org/file/local;1", &rv)); if (NS_FAILED(rv)) { printf("FAILED\n"); return rv; } if (NS_SUCCEEDED(rv)) { printf(" SUCCEEDED \n"); return rv; }
typedef PRUint32 nsresult;
nsresult rv = nsComponentManager::CreateInstance("@mozilla.org/file/local;1", nsnull, NS_GET_IID(nsILocalFile), (void **)&refp);
If a result is successful, the value of rv returns NS_OK, which is 0.
Another widely use type is nsnull, defined in nscore.h. Here is the definition:
#define nsnull 0
nsresult rv; nsCOMPtr<nsILocalFile> file = do_CreateInstance("@mozilla.org/file/local;1", &rv); if (NS_SUCCEEDED(rv)) { char* msg = "we successfully created an instance of file\n"; *_retval = (char*) nsMemory::Alloc(PL_strlen(msg) + 1); if (!*_retval) return NS_ERROR_OUT_OF_MEMORY; PL_strcpy(*_retval, msg); } else { *_retval = nsnull; }
If you look in the Mozilla C++ source code, you will see the macro NS_IMETHODIMP used frequently. This macro identifies the type of your interface implementation method. It is also defined in nscore.h, as shown in Example 8-7.
Example 8-8 shows a typical use of the NS_IMETHODIMP macro. All methods that implement an interface are of the type NS_IMETHODIMP.
The macro in Example 8-8 declares the method GetSomeString as an XPCOM implementation.
Here is how to create a raw pointer:
nsILocalFile *refp(nsnull); nsresult rv = nsComponentManager::CreateInstance("@mozilla.org/file/local;1", nsnull, NS_GET_IID(nsILocalFile), (void **)&refp); if (refp) printf("%p\n", (void*)refp);
After you create a new object that refp points to, refp is considered an owning reference, and any other pointers that point to it must be "refcounted." Example 8-9 uses anotherPtr and oneMorePtr to point to refp, and manually manages AddRef and Release.
In Example 8-9, if someCondition is false, anotherPtr is released and the function then returns (NS_OK). But what about oneMorePtr? In this instance, it is never released; if you remember, an object cannot be released from memory until our refcount is at zero. The refcount is out of sync, oneMorePtr is never decremented before the return, and the object is thus left dangling in memory. With the refcount off, the object leaks. Remember that Release( ) calls the C++ delete operator to free up the allocated XPCOM object only when the count is decremented to 0. If Release thinks there are still references to the object because the refcount hasn't been properly decremented, delete is never called. The correct code is shown below:
if (!someCondition) { NS_RELEASE(anotherPtr); // decrement refcount NS_RELEASE(oneMorePtr); // decrement refcount return NS_OK; }
As you can see, manual management of reference counting is prone to error. To alleviate this burden and extra code bloat, nsCOMPtr implements AddRef and Release for you and makes life much easier. Before the nsCOMPtr class is removed from the stack, it calls Release in its destructor. After all references are properly released, delete is called and the object is freed from memory. Example 8-10 shows a typical use of nsCOMPtr.
Earlier in this chapter, the section Section 8.2.1 showed you how to create an interface and implement it in JavaScript. However, you may need a C++ implementation to benefit from the better performance offered by a compiled language.
Most components used in Mozilla are written in C++. This section discusses how to create a C++ implementation for the nsISimple interface. A few more steps are involved, but as you will see, they are generally similar to the processes described in the JavaScript component section, facilitated to some extent by the available tools and templates discussed previously.
First, create a new directory and call it simple:
$ mkdir simple $ cd simple
#include "nsISupports.idl" [scriptable, uuid(ce32e3ff-36f8-425f-94be-d85b26e634ee)] interface nsISimple : nsISupports { attribute string yourName; void write( ); void change(in string aName); };
$ xpidl -m header -w -v -I $XPIDL_INC \ > -o nsISimple nsISimple.idl
The generated file is nsISimple.h and is shown in Example 8-11.
As you can see, the xpidl compiler can do a lot of work for you. The code generated in Example 8-11 is a C++ header file that declares the methods of nsISimple. It provides the class definition, macros for using the interface, and a template for the class implementation, which contains stubbed-out declaratory code that you can paste into your implementation file to quickly get started.
$ uuidgen 79e9424f-2c4d-4cae-a762-31b334079252
As part of the generated file nsISimple.h, all the code stubs you need to get started are ready to be copied and pasted into the C++ source files. You can use those stubs as a guide to implement the component. In a text editor, create a new file called nsSimple.h and enter the code shown in Example 8-12.
To maintain clarity, the C++ implementation class is named nsSimpleImpl, where the default class name generated by the xpidl compiler is nsSimple and the header file, nsSimple.h, is shown in Example 8-12.
Example 8-12 includes the ID-generated header file nsISimple.h, which holds the C++ declarations for the interface class nsISimple. It then takes the new UUID and breaks it into a class ID struct defined as NS_SIMPLE_CID. Next, it defines the contract ID for this implementation class.
The example uses a completely different class ID and contract ID than the one used for the JavaScript component because it's a different implementation class and needs to have it's own unique identification (even though it implements the same interface).
Now the example makes the class declaration of the implementation, called nsSimpleImpl, which inherits from nsISimple, defining the class constructor and virtual destructor. NS_DECL_ISUPPORTS is a macro that holds the declaration of our required QueryInterface, AddRef, and Release methods. NS_DECL_NSISIMPLE is created in the generated header file nsISimple.h. It expands to the used interface method declarations. Finally Example 8-12 shows the addition of the char* member variable identified as mName. This variable is used to hold the value of the interface attribute yourName, just as it did earlier in the JavaScript class implementation.
Once you have the header file, you are ready to start the implementation source file. With a text editor, create a new file called nsSimple.cpp. As in any C++ source file, you should add the header files required by the implementation:
#include "plstr.h" #include "stdio.h" #include "nsCOMPtr.h" #include "nsMemory.h" #include "nsSimple.h"
Start by adding the implementation of our class constructor and destructor:
// c++ constructor nsSimpleImpl::nsSimpleImpl( ) : mName(nsnull) { NS_INIT_REFCNT( ); mName = PL_strdup("default value"); } // c++ destructor nsSimpleImpl::~nsSimpleImpl( ) { if (mName) PL_strfree(mName); }
Then add the macro NS_IMPL_ISUPPORTS1_CI. As discussed earlier, this macro conveniently implements QueryInterface, AddRef, and Release:
NS_IMPL_ISUPPORTS1_CI(nsSimpleImpl, nsISimple);
Next you are ready to implement the actual nsISimple interface methods:
NS_IMETHODIMP nsSimpleImpl::GetYourName(char** aName) { NS_PRECONDITION(aName != nsnull, "null ptr"); if (!aName) return NS_ERROR_NULL_POINTER; if (mName) { *aName = (char*) nsMemory::Alloc(PL_strlen(mName) + 1); if (! *aName) return NS_ERROR_NULL_POINTER; PL_strcpy(*aName, mName); } else { *aName = nsnull; } return NS_OK; }
A C++ implementation of an IDL method is declared as the type NS_IMETHODIMP. The implementation starts with the getter method GetYourName, which takes a char** parameter for the method's return value. Return values in C++ XPCOM components are marshaled via method arguments because interface implementations must always return a numerical nsresult, as described earlier. To ensure that the aName parameter is a pointer, use the macro NS_PRECONDITION to warn if null, follow with a null test in the line below, and return the error result code NS_ERROR_NULL_POINTER. Then test whether the member variable mName holds a value. If it does, allocate the necessary memory to accommodate the size of the copy. Then by using PL_strcpy, you can assign the value to the parameter aName. Otherwise, mName is null and you can assign null into aName and return:
NS_IMETHODIMP nsSimpleImpl::SetYourName(const char* aName) { NS_PRECONDITION(aName != nsnull, "null ptr"); if (!aName) return NS_ERROR_NULL_POINTER; if (mName) { PL_strfree(mName); } mName = PL_strdup(aName); return NS_OK; }
After implementing the getter, implement the setter. Again, use NS_PRECONDITION and then a null test on the aName. If that parameter holds data, you can free it by using PL_strfree and calling PL_strdup. Then assign the new value to class member mName:
NS_IMETHODIMP nsSimpleImpl::Write( ) { printf("%s\n", mName); return NS_OK; } NS_IMETHODIMP nsSimpleImpl::Change(const char* aName) { return SetYourName(aName); }
Finally, implement the Write and Change methods by using printf to write the value of mName to stdout and set a new value to mName. Example 8-13 shows the C++ source code in its entirety.
As you needed to do with the JavaScript implementation, you must create the code for the module. The module code abstracts the implementation class and makes the implementation a component library. In your text editor, create a file called nsSimpleModule.cpp and enter the code shown in Example 8-14.
Once you have an interface file nsISimple.idl, a C++ source file nsSimple.cpp with its header file nsSimple.h, and a module file nsSimpleModule.cpp, you can create a Makefile like the one shown in Example 8-15. This Makefile can compile the sources into an XPCOM component.
A Makefile directs the Mozilla build system to build the sources and install them into the Mozilla dist/bin/components directory. To use the Makefile, run gmake to compile and install the component library file.
To test the newly compiled component, you can use xpcshell like you did for the JavaScript component. Example 8-16 shows a session with xpcshell that tests the new component.
nsCOMPtr<nsILocalFile> file(do_CreateInstance("@mozilla.org/file/local;1"));
You can also instantiate the object as follows:
nsresult rv; nsCOMPtr<nsILocalFile> file = do_CreateInstance("@mozilla.org/file/local;1", &rv); if (NS_FAILED(rv)) return rv;
Both techniques assign an nsCOMPtr to a newly allocated instance of an nsLocalFile object.
Example 8-17 accesses the public methods available from this component by using the pointer identifier file.
from xpcom import components
[scriptable, uuid(6D9F47DE-ADC1-4a8e-8E7D-2F7B037239BF)]
Components.classes["@foo.com/appSysUtils;1"]. getService(Components.interfaces.appISysUtils);
With these foundations, and assuming that you have to have a Python distribution on your system that Mozilla can access, you are ready to go! Example 8-18 shows a complete implementation of a PyXPCOM component. This file needs to be saved with a .py extension and put in the components directory and registered like any other component.
The special attributes defined in the appSysUtils class correspond to the special identifiers you must use in XPCOM to make your code a reusable component (see Section 8.1.5, earlier in this chapter). Table 8-3 describes these attributes.
Table 8-3. Special XPCOM attributes in Python
Attribute |
Description |
---|---|
_com_interfaces_ |
The interface IDs supported by this component. This attribute is required. It can be a single IID or a list, but you do not have to list base interfaces such as nsISupports. |
_reg_contractid_ |
The component's contract ID. Required. |
_reg_clsid_ |
The Class ID (CLSID) or progID of the component in the form: @domain/component;version.Required. |
_reg_desc_ |
A description of the component. Optional. |
Example 8-19 is the IDL file you also need to create a Python component.
Finally, Example 8-20 shows how this component might be used in script -- for example, in a function you define for an event handler in the XUL interface.