web developer & system programmer

coder . cl

ramblings and thoughts on programming...


mozilla extensions with xpcom

published: 31-07-2010 / updated: 31-07-2010
posted in: c, c++, programming, projects, tips
by Daniel Molina Wegener

The XPCOM API can allow you to create low level extensions — I mean written in C++, with support for C and assembler through C++ — and plug ins for Mozilla products. For example, you can create an extension to browse IMAP folders from Mozilla Firefox. This API is analogous to the COM+ API on Micro$oft Windows. One of my most recent projects, is a Firefox extension to handle SNMP protocol requests from JavaScript, so my extension is applied from the JavaScript within the HTML in the browser, allowing Firefox to act as an SNMP client. The extension is working fine, without memory leaks and allows you to do get and walk request. Possibly it will be expanded to more requests on the future.

interface definition

JavaScript requires class definitions to work. So, you need to create IDL definitions of your scriptable classes. The syntax for the XPIDL is quite standard, so isn’t difficult to create interfaces with this IDL variant. To allow you to use IDL defined interfaces on JavaScript, interfaces must be defined as scriptable and must be derived from nsISupports interface, with an unique uuid. Let me show you an example:

#include "nsISupports.idl"
#include "nsIMyProtocolRequest.idl"
#include "nsIMyProtocolResponse.idl"
#include "nsIMyProtocolError.idl"
#include "nsIMyProtocolStatus.idl"
#include "nsIMyProtocolService.idl"

[scriptable, uuid(3455c51a-3323-4343-844f-2342526113c2)]
interface nsIMyProtocolService : nsISupports
{
    readonly attribute nsIMyProtocolError error;
    nsIMyProtocolResponse get(in nsIMyProtocolRequest req);
    nsIMyProtocolStatus getStatus(in nsIMyProtocolRequest req);
};

This will create a XPCOM object wrapped in JavaScript similar to the following example:

var nsIMyProtocolService = {
    error: new nsIMyProtocolError(),
    get: function (req) {
    },
    getStatus: function (req) {
    },
};


class definition

Here, error is a read only attribute, and get() and getStatus() are methods. But this isn’t enough, that is just the interface definition. The implementation must be done in C++ so the interface defintion is transformed to C++ code — a C++ header specifically — which must be included in the class implementation C++ code file. Where need to consider a special note on class attributes and methods: Every method implementation has the form: nsresult MethodName(…input parameters if any…, …output parameters if any…), where nsresult is the type of the returned status to the XPCOM API to let it know how was executed the method, so you can return NS_OK on execution correctness, NS_ERROR_NULL_POINTER for invalid input as null pointers, NS_ERROR_INVALID_POINTER for invalid variable input and so on. Well, the class definition inside the implementation file, should look as follow:

class nsMyProtocolService : public nsIMyProtocolService
{
public:
    nsMyProtocolService();
    ~nsMyProtocolService();

    nsresult GetError(nsIMyProtocolError **out);
    nsresult Get(nsIMyProtocolRequest *req,
                 nsIMyProtocolResponse **out);
    nsresult GetStatus(nsIMyProtocolRequest *req,
                 nsIMyProtocolStatus **out);
private:
    int statusCode;
    int errorCode;
    char *errorString;
    nsIMyProtocolError *currentError;
}


class implementation

You must be a two star programmer to know how to implement the argument passing and method output, but I know that you are a good C and C++ programmer, so this is not an obstacle and you can act as a three star programmer without problems. So, the implementation, for example for the GetError() method, which has been mapped to the error attribute on the nsIMyProtocolService interface, should look as follows:

nsresult
nsMyProtocolService::GetError(nsIMyProtocolError **out)
{
    nsMyProtocolError *rout;
    result = NS_ERROR_NULL_POINTER;
    if (out == NULL || out == nsnull) {
        return result;
    }
    rout = new nsMyProtocolError();
    if (rout == NULL) {
        result = NS_ERROR_NOT_INITIALIZED;
        return result;
    }
    if (currentError != NULL && currentError != nsnull) {
        NS_RELEASE(currentError);
        delete currentError;
    }
    rout->SetErrorCode(errorCode);
    rout->SetErrorString(errorString);
    NS_ADDREF(rout);
    *out = rout;
    result = NS_OK;
    return result;
}

On the example above, we have created a new pointer to the nsIMyProtocolError interface implementation. That pointer, before is being returned to the JavaScript and wrapped by the XPCOM API, must have an increased reference count, so Firefox do not remove that object. But as any language which uses reference counting in its garbage collection tasks, requires a reference counting decrement before the object is released. So you need to release those references using the NS_RELEASE macro. As any software development — with or without a garbage collector behind — you must be careful with the memory usage. Any object with a reference count different from zero, will hang Firefox, so you need to release reference counting on destructors too.

nsMyProtocolService::~nsMyProtocolService()
{
    if (currentError != NULL && currentError != nsnull) {
        NS_RELEASE(currentError);
        delete currentError;
    }
}


The example above shows how the destructor releases the reference counting for the currentError object. But think a little. Every time you call GetError(), in other words, you request the error attribute on the nsIMyProtocolService instance, you are increasing references which are left without being decreased. You need an storage for those pointers, so you can use any collection class that you want and is supported by your platform, but is recommended that you use the kind of storage classes which are present on the XPCOM API. How to create a collection?


nsCOMPtr<nsIMutableArray> container;
container = do_CreateInstance(NS_ARRAY_CONTRACTID);

The nsIMutableArray provides an array class which can be used inside your Firefox extension and also it can be exposed as a JavaScript array, it is an scriptable class, and also provides methods for pushing and extracting elements as the JavaScript array does, so you can use methods such as GetLength(), which is mapped to the length attribute, AppendElement(), which is mapped to the append() method, QueryElementAt() which is mapped to the array[index] operator. The do_CreateInstance template will allow you create instances of you interfaces, when you are requesting them from other extensions. For example if you want to use the WebDAV extension from your C++ extension, you need to use do_CreateInstance template.


component declaration

Finally, you need to register and expose your components to the JavaScript interface and the XPCOM API, so you need to define a contract identifier and add some static code to you extension.

#define NS_MYSVC_CONTRACTID         "@mozilla.org/myprotocol/service;1"
#define NS_MYSVC_CID                                            
    { /* 3455c51a-3323-4343-844f-2342526113c2 */                
        0x3455c51a,                                             
            0x3323,                                             
            0x4343,                                             
            { 0x84, 0x4f, 0x23, 0x42, 0x52, 0x61, 0x13, 0xc2 }  
    }

NS_GENERIC_FACTORY_CONSTRUCTOR(nsMyProtocolService)
NS_DECL_CLASSINFO(nsMyProtocolService)
static const nsModuleComponentInfo components[] =
{
    { "My Protocol Service", NS_MYSVC_CONTRACTID, NS_MYSVC_CID,
      nsMyProtocolServiceConstructor,
      NULL, NULL, NULL,
      NS_CI_INTERFACE_GETTER_NAME(nsMyProtocolService),
      NULL,
      &NS_CLASSINFO_NAME(nsMyProtocolService)
    }
};

NS_IMPL_NSGETMODULE(nsJxSNMPService, components)

This can be used as template, where the components array contains the interface definitions and interface implementation references that allow you to use those classes from JavaScript. This code will:

NS_GENERIC_FACTORY_CONSTRUCTOR
Will declare nsMyProtocolServiceConstructor to be used as default constructor by the do_CreateInstance() template.
NS_DECL_CLASSINFO
Will declare class information referencing the nsMyProtocolService class to allow you to use that class information from JavaScript to be exposed to the XPCOM API.
NS_MYSVC_CONTRACTID
Contract (interface) identifier, it will be required by your JavaScript to instantiate you class, using the interface as was defined in nsIMyProtocolService.
NS_MYSVC_CID
This is the unique uuid identifier for your component. Remember that those components are quite analogous to the COM+ components on the Window$ platform.
NS_IMPL_NSGETMODULE(nsJxSNMPService, components)
This line finally will expose — through static code — your component to the XPCOM API, and will allow your class to being used from JavaScript or your Firefox extension. Remember that components is an array which can hold multiple interface and class definitions.


component exposition

If you followed the article with a conscious reading, the following lines of JavaScript will bring you the proper guide to instantiate your own component. I’m assuming that you are an experienced system programmer and web developer to handle this article:

const C = Components;
const CI = C.interfaces;
const CL = C.classes;

function getContract(contract) {
  try {
    return C.classes[contract];
  } catch (e) {
    alert(e.message);
  }
}

function getService(contract, iface) {
  try {
    return getContract(contract).getService(CI[iface]);
  } catch (e) {
    alert(e.message);
  }
}

function getInterface(contract, iface) {
  try {
    return getContract(contract).createInstance(CI[iface]);
  } catch (e) {
    alert(e.message);
  }
}

try {
    var myService = getService("@mozilla.org/myprotocol/service;1",
                               "nsIMyProtocolService");

    var myRequest = getService("@mozilla.org/myprotocol/request;1",
                               "nsIMyProtocolRequest");

    myRequest.first_argument = 200;
    myRequest.second_argument = "hello world!";
    var myResponse = myService.get(myRequest);
    if (typeof(myService.error) == "object"
        && myService.error.status != 0) {
        alert(myService.error.toString());
    }
} catch (e) {
    alert(e.message);
}


interesting code to look

You can start downloading the Firefox source code and take a look on the following interfaces:

  • extensions/webdav, this defines the "@mozilla.org/webdav/service;1" interface, allowing you to create WebDAV clients from JavaScript.
  • extensions/webservices, this defines the "@mozilla.org/xmlextras/proxy/webserviceproxy;1," and similar interfaces, allowing you to create WebService client proxies from JavaScript.
  • extensions/sql, this folder stores "@mozilla.org/sql/connection;1" and similar interfaces, so Firefox can be used directly as client to query some RDBMS engines — such as PostgreSQL and SQLite.

You can go on deeper reading code and references to enhance your XPCOM component development ;)


references


No coments yet.

post a comment

XHTML: You can use these tags: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>