< April 2006 >
SuMoTuWeThFrSa
       1
2 3 4 5 6 7 8
9101112131415
16171819202122
23242526272829
30      
Tue, 25 Apr 2006:

Somebody asked me to explain how the kartwheels engine hooked into the Mozilla js engine. But I don't have that code, but I still have code left over from my previous hacks from sometime before foss.in, when Tarique trolled about greasmonkey for links. The hack got dumped because the DOM in elinks is free'd immediately after rendering and the render heirarchy is what remains after the first few seconds. But that code is reincarnated here to serve as a first base for the average C programmer trying to embed javascript in his *unthreaded* application .

Basically the embedding API uses two constructs for reentrant code - JSRuntime and JSContext. These hold the equivalent positions as the ILExecEngine and ILExecProcess in dotgnu. Basically, if I wanted to use a single instance to run two scripts without polluting namespaces, I'd use a single runtime and two contexts. After creating these, the task gets easier and easier (you wish !).

declaring a function: All the functions have a bounding object, which for the seemingly unbound ones is the global method. In a browser the global object is the "window". The declaration for the global method is basically cut and paste and very similar to the other class object declarations so I'll just leave it out for now. The functions are declared using JS_DefineFunctions .

static JSFunctionSpec glob_functions[] = {
    {"print",           Print,          0},
}

JSObject * glob = JS_GetGlobalObject(ctx);
assert(JS_DefineFunctions(ctx, glob, glob_functions) == JS_TRUE);

popping arguments: The arguments are received by the function (which is in C land) as a set of jsval* pointers and an argument count. Each function is assumed to handle its own argument handling, leaving us with the option of handling variable number of arguments. My Print function does just that - printing out all the arguments it received.

JS_STATIC_DLL_CALLBACK(JSBool)
Print(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
{
    uintN i, n;
    JSString *str;
    for (i = n = 0; i < argc; i++)
    {
        str = JS_ValueToString(cx, argv[i]);
        if (!str) return JS_FALSE;
        fprintf(stdout, "%s%s", i ? " " : "", JS_GetStringBytes(str));
    }
    fputc('\n', stdout);
    return JS_TRUE;
}

declaring objects : Now every object needs a class. More accurately, I haven't figured out how to just construct a simple hash from spidermonkey land. But it suits my purposes to use objects and accessors to implement what I need right now. One of the first things I did in elinks was to put a hook on the <img> element which infact has very little effect on the renderer core. The following code is just pulled out and has the JS_GetPrivate/JS_SetPrivate code removed.

 
static JSClass image_class = {
    "image", JSCLASS_HAS_PRIVATE | JSCLASS_NEW_RESOLVE,
    JS_PropertyStub, JS_PropertyStub,
    JS_PropertyStub, img_setProperty,
    JS_PropertyStub, JS_ResolveStub,
    JS_ConvertStub,  JS_FinalizeStub
};

static JSBool img_setProperty(JSContext *ctx, JSObject * obj, jsval id,
                            jsval *vp)
{
    char * name = JS_GetStringBytes(JS_ValueToString(ctx, id));
    char * value = JS_GetStringBytes(JS_ValueToString(ctx, *vp));

    fprintf(stderr, "<image %p>.%s => %s\n", obj, name, value);
    return JS_TRUE;
}

Having declared our class, we proceed to write a few lines of code to construct a simple object from the class and attach our bound methods to the object. Technically, I can attach the methods to a prototype and provide that to the class definition. But this way seemed easier while I was at it than go through a hierarchy of prototype hashes while debugging method call resolution.

JS_STATIC_DLL_CALLBACK(JSBool)
img_Show(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
{
    JSBool show;
    if(argc == 0) return JS_FALSE;

    assert(JS_ValueToBoolean(cx, argv[0], &show) == JS_TRUE);
    fprintf(stdout, "<image %p>.show(%s)\n",obj, show ? "TRUE" : "FALSE");
    return JS_TRUE;
}

static JSFunctionSpec img_functions[] = {
    {"show",          img_Show,          0},
};

int defineImage(JSContext * ctx, const char * id)
{
    JSBool ok;
    JSObject * img = JS_DefineObject(ctx, JS_GetGlobalObject(ctx),
                        id, &image_class,
                        NULL, 0);
    ok = JS_DefineFunctions(ctx, img, img_functions);
    return (img != NULL);
}

compiling & running js: The easiest way to run the javascript is to use the following two step approach - JS_CompileFileHandleForPrincipals and JS_ExecuteScript. The first function accepts a FILE * and gives you a JSScript* and the second one obviously executes the script you just compiled. With that, your js engine embedding is complete. Oh, and don't forget to call defineImage while parsing the DOM tree.

There are a lot more nuances in there, like GC rooting. The engine GC does not scan your address space and cannot preserve objects which are only referred from your code space. To work around this, you could create a GC root by hand and attach your objects there so that you can prevent them from being GC'd till you're done with it. But remember to just cleanup the GC root when you're done with the object - this works sort of like a pool, only the crashes might be a few minutes later instead of immediately after you free the root (FUN !! ).

But none of them really matter to a newbie taking a few baby steps into VM embedding land and I didn't really do it proper when I did kartwheels either. Matters not, here's the sample code for people to play around with. Also take a good long look at spidermonkey api docs and the MDC Javascript Embedder's guide - they basically cover all my code, though in a different level of detail.

[gopal@phoenix spidermonkey]$ make
gcc -ggdb -L/usr/lib64/mozilla-1.7.3 .... testjs.c -o testjs

[gopal@phoenix spidermonkey]$ export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:.
[gopal@phoenix spidermonkey]$ ./testjs hello.js
Hello !
<image 0x50f8c0>.src => /tmp/x.png
<image 0x50f8c0>.show(TRUE)
<image 0x50f8c0>.show(FALSE

That's basically how it all begins and then after you've been debugging for four hours syncing/locking JS threads with the gtk+ event-loop ... Zzzz

--
The more I want to get something done, the less I call it work.
               -- Richard Bach

posted at: 12:14 | path: /hacks | permalink | Tags: ,