Saturday, July 30, 2005

XPConnect is the name assigned by mozilla.org to infrastructure that allows JavaScript to interface with XPCOM-based components. XPCOM is mozilla's component architecture, similar to (and based upon) Microsoft COM. In a nutshell, functions like the onclick() handler that I described previously can only do so much given the support provided by the JavaScript engine. To make them truly powerful, they need to be able to instantiate and call external objects created in a language like C++.

For example, I might want to, from my onclick() handler, read the value of a text field in my UI, and store this value somewhere in an external database. JavaScript alone does not provide database functionality, and it is not realistic, nor is it practical to build into JavaScript all the functionality that an application might need, even if this functionality could be anticipated (and it cannot). To talk to a database, JavaScript code would need to call external, C++ code that knows how to interface with the database. The way to extend JavaScript to interface with this external code is some mechanism like Mozilla's XPConnect that acts as a bridge between the two bodies of code at runtime.

As we saw with Dump(), JavaScript can be made to call C++ functions. Say that we have a component that implements the following C++ class:


class Foo {
public:
Foo();
~Foo();
void SomeFunc(int a, string& b);
};


and we want to call that function from JavaScript. Recall the C++ function prototype for Dump():


JS_STATIC_DLL_CALLBACK(JSBool)
Dump(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)


In the mozilla JavaScript engine, all functions invoked from JavaScript must adhere to this function prototype. So, the function prototype for SomeFunc() in my class would be the same:


JS_STATIC_DLL_CALLBACK(JSBool)
SomeFunc(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)


But, clearly, I don't want to force component authors to implement classes that have member functions corresponding to this function prototype.

One solution for mapping calls from JavaScript to native methods would be to create, for each function that we might want to be able to call in the component from JavaScript, a wrapper or proxy function. The job of the wrapper would then be to extract the arguments that were passed in, convert each them from its JavaScript type to a corresponding C++ type, invoke the C++ function, obtain the result upon the return of the C++ function, convert this result from its C++ type to a corresponding JavaScript type, and then return control back to JavaScript.

I spent a lot of time over the past few evenings studying this problem in search of a reasonably powerful, yet easily describable solution. Part of that study involved taking a look at XPConnect, which, as I mentioned above, is the mozilla technology that provides the bridge between JavaScript and objects in externally written C++ components. XPConnect uses IDL files (xpidl) and typeinfo files to dynamically bind C++ components to JavaScript (and in the other direction as well; JS objects can be called from C++ if desired).

While using XPConnect is straightforward, describing it is far too involved for me to cover in this book. As I did with the layout engine, I have opted to implement a simpler (if perhaps less functional) scheme of my own design, one that is inspired by XPConnect.

The scheme that I envision, and plan to implement in the coming few weeks, builds on top of the SIL files previously described in a way that is somewhat analogous to the use of xpidl files by XPCOM/XPConnect, but is easier to describe, and thus more suitable for use in my book.

Here is the basic plan:
  • component authors create a shared library or DLL that implements classes of their choosing
  • the component, and the classes implemented by that component that are to be called from JavaScript, are described in a SIL file
  • a special tool reads the SIL file and creates source code and a header for each class that wraps the classes in that component. Each method that is described in the SIL file will have a corresponding function that wraps the actual function implemented by the component. The wrappers marshal arguments and return values; the function prototype for each of these wrappers will be:
STATIC_DLL_CALLBACK(JSBool)
WrapperFunc(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
  • Component developers will deliver the component shared library, the wrapper shared library, and the SIL file for each component.
  • All the components will be stored in a components directory accessible to the application. The layout engine will, at start up, iterate this directory, and process each SIL file it encounters.
  • For each class defined by a component and instantiated by JavaScript, the layout engine will instantiate an instance of the corresponding wrapper class (and the class it wraps), and a table of JS function objects will be created to map the names of functions callable from JavaScript to methods implemented in the wrapper.
More details will emerge as I flesh out the implementation, I am sure.