Overriding C++ xpcom objects using Javascript

New account types in Javascript for Thunderbird (Part 2)

One of the more difficult challenges in adding new Javascript-based accounts to Thunderbird is the problem of overriding functions of C++ objects in Javascript. C++ objects typically rely on the object inheritance structure of C++, and do not reliably use QueryInterface to force use of xpcom and xpconnect functionality. Even if they did, Javascript does not pass a reference to the underlying Javascript xpconnect object to the C++ object when it finds a C++ object in the prototype chain of an xpcom call. Instead, it passes the C++ object from the prototype chain, so any attempts that you make to override the C++ object with a Javascript object get lost. What all of this means is that you really cannot rely on either Javascript or C++ inheritance to work as you expect.

In Skinkglue (the C++ portion of my project to allow JavaScript-based accounts in Thunderbird), I get around this by adding a layer of C++ code between the underlying Skink object (such as nsMsgIncomingServer) and the js-based component that will implement functions (such as a particular nsIMsgIncomingServer method) that will allow account-type-specific functionality. I want to describe that layer here and how it is used.

The C++ part of the override layer

Skinkglue has a standard implementation of many of the Skink objects, which simplifies the process of adding new accounts. So, for example, the Skinkglue implementation of nsIMsgIncomingServer is in a C++ object called msqSgIncomingServer. This object derives from the base Skink C++ object nsMsgIncomingServer like this:

class msqSgIncomingServer : public nsMsgIncomingServer,
                            public msqISgIncomingServer

The additional interface msqISgIncomingServer adds a few small additional methods that are needed to setup the generic version of an incomingserver, but are hard coded in the original Skink design.

C++ users of this object then derive directly from msqSgIncomingServer without difficulty. But for javascript callers, we add an additional “overridable” layer:

class msqSgIncomingServerOverridable : public msqSgIncomingServer,
                                       public msqIOverride

This msqIOverride interface contains specific information that is used to override base class functionality in Javascript. That interface is:

interface msqIOverride : nsISupports
{
 /// @parameter aMethodName  string name of a C++ function that
 ///                         will be overridden by javascript
 void override(in ACString aMethodName);

 /// the javascript-based xpcom object that overrides methods
 attribute nsISupports jsParent;

 /// the base class used to implement default functionality
 readonly attribute nsISupports base;
};

Essentially the way this works is that the Overridable class is the most specific object type in C++, and therefore all C++ objects see it and its inherited base classes. But the Overridable class knows when it should really use a Javascript override, because you tell it specifically which classes to override. So we make C++ happy, but still allow Javascript to be used.

The Overridable C++ layer is rather interesting, but probably not of much interest to most users who want to create Javascipt accounts. So I won’t discuss it further here.

The Javascript glue part of the override layer

Typically the way that Skink works is that specific types of objects that are needed for different account types are loaded using a standard Contract Id for the object, which is defined using a single protocol type. In Skink, that type might be “imap” or “pop3″, but in the demo TweeQuilla extension I use “twitter”. So when Skink needs to create an incoming server for a “twitter” protocol, it tries to create an object using the Contract Id “@mozilla.org/messenger/server;1?type=twitter” There are many of these types of components that are needed by Skink. I also used the same concept to add a couple more additional types that are used to create Skinkglue-derived versions of objects. All of these needed objects are layed out in the file twitterSkinkGlue.js As an example, for the twitter incoming server, I have the component registration:

type = "twitter";

function IncomingServer() {
 Cu.import("resource://tweequilla/twitterIncomingServer.jsm");
 TwitterIncomingServer.call(this);
}

IncomingServer.prototype = {
 classDescription: "Twitter Incoming Server",
 classID:          Components.ID("{A6CA1F3D-73F3-4764-A6DC-61D495618533}"),
 contractID:       "@mozilla.org/messenger/server;1?type=" + type,
 QueryInterface:   XPCOMUtils.generateQI([Ci.nsIMsgIncomingServer,
                                          Ci.msqISgIncomingServer]),
}

The implementation of the object is in a separate module, because I wanted to keep the component registrations separate from the detailed implementations of each object type. That implementation needs to follow a particular pattern in order to provide all of the needed hooks between the Javascript and C++ layers. Here are the main features, using a simplified example that  is partially derived from twitterIncomingServer.jsm:

function TwitterIncomingServer()
{
 let server = Cc["@mesquilla.com/sgincomingserver;1"]
                .createInstance(Ci.nsIMsgIncomingServer);
 server instanceof Ci.msqISgIncomingServer;
 server instanceof Ci.msqIOverride;
 this.__proto__.__proto__ = server;

 // define the overrides
 this.jsServer = new TwitterIncomingServerOverride(server);
 server.jsParent = this.jsServer;

 // initializations
 server.override("msqSgIncomingServerOverridable::PerformBiff");
}

So when a new TwitterIncomingServer component is instantiated, the Javascript module create a base Skinkglue C++ xpcom object, and sets that object as the __proto__.__proto__ of the Javascript component.

A separate Javascript class TwitterIncomingServerOverride is used to describe the overridden functions, and any local functions that may be needed. An instance of this is created in the main object constructor, and a reference to that object given to the underlying C++ class so that it may use the Javascript class to override requested methods. Any C++ methods that will be overridden with Javascript must be declared with override() calls. In this example, I am overriding the nsIMsgIncomingServer method PerformBiff with a Javascript version.

The Javascript Implementation part of the override layer

The actual overriding functions are implemented in the Override Javascript object:

function TwitterIncomingServerOverride(aIncomingServer) {
 // initialization of member variables
 this.wrappedJSObject = this;
 this.baseServer = aIncomingServer;
}

TwitterIncomingServerOverride.prototype =
{
 QueryInterface:   XPCOMUtils.generateQI([Ci.nsIMsgIncomingServer]),

 // **** nsIMsgIncomingServer overrides
 performBiff: function _performBiff(aMsgWindow)
 {
  // do something to implement Biff
  // ...

  // call the base function if needed
  base = this.baseServer.base.QueryInterface(Ci.nsIMsgIncomingServer);
  base.performBiff(aMsgWindow);
 },

 // **** local methods
 sendStatusUpdate: function _sendStatusUpdate(text)
 {
 this.serverHelper().statuses.update(this.normalCallback,
                    this.errorCallback, null, 'json', text);
 },
}

When the Override function is defined, it is given a reference to the base object, that it should save for use when calling base functionality. Also, it is good practice to add this.wrappedJSObject = this; With this, you can get access to local javascript functions in the server, when you are given an xpcom link to the the server object, like this:

  server.QueryInterface(msqIOverride)
        .jsParent
        .wrappedJSObject
        .sendStatusUpdate(text);

So far, all of this seems to be working for me. While the glue may seem obscure, really it is all boilerplate and not all that hard to use.

Of course, we still have other “interesting” issues to deal with, such as message URLs and libmime. Stay tuned.

Appendix: Solvable but Annoying Problems

In several cases, the underlying Skink C++ will pass a null pointer to a C++ object that is expecting to give a result to xpconnect. The C++ can be designed to handle this, but it always causes Javascript implementations of xpconnect to fail. An example is this code:

nsMsgDBFolder::GetChildNamed(const nsAString& aName, nsIMsgFolder **aChild)
{
 NS_ENSURE_ARG_POINTER(aChild);
 GetSubFolders(nsnull);

For each of these that I find, I have to put a special override in the C++ Overridable layer to prevent crashes and assertions from xpconnect. That’s a real pain – and would be unnecessary if the core code followed appropriate behavior. I hope that when Skinkglue gets implemented in core, we can fix some of these unnecessary annoyances that greatly complicate Skinkglue.