How to embed the JavaScript engine

This article is in need of a technical review.

See also JSAPI User Guide. In particular, it has more and better code examples!

A Bare Bones Tutorial

Hello World sample embedding application

The following code is a very simple application that shows how to embed SpiderMonkey and run a simple JavaScript script. See the instructions for building and running the sample below the code.

// This is an example for SpiderMonkey 31.
// For SpiderMonkey 24 or 38, see each comment.

// following code might be needed in some case
// #define __STDC_LIMIT_MACROS
// #include <stdint.h>
#include "jsapi.h"

/* The class of the global object. */
static JSClass global_class = {
    "global",
    JSCLASS_GLOBAL_FLAGS,
    // [SpiderMonkey 38] Following Stubs are removed. Remove those lines.
    JS_PropertyStub,
    JS_DeletePropertyStub,
    JS_PropertyStub,
    JS_StrictPropertyStub,
    JS_EnumerateStub,
    JS_ResolveStub,
    JS_ConvertStub
};

int main(int argc, const char *argv[])
{
    // [SpiderMonkey 24] JS_Init does not exist. Remove this line.
    JS_Init();

    // [SpiderMonkey 38] useHelperThreads parameter is removed.
    // JSRuntime *rt = JS_NewRuntime(8L * 1024 * 1024);
    JSRuntime *rt = JS_NewRuntime(8L * 1024 * 1024, JS_USE_HELPER_THREADS);
    if (!rt)
        return 1;

    JSContext *cx = JS_NewContext(rt, 8192);
    if (!cx)
        return 1;

    // [SpiderMonkey 24] hookOption parameter does not exist.
    // JS::RootedObject global(cx, JS_NewGlobalObject(cx, &global_class, nullptr));
    JS::RootedObject global(cx, JS_NewGlobalObject(cx, &global_class, nullptr, JS::FireOnNewGlobalHook));
    if (!global)
        return 1;

    JS::RootedValue rval(cx);

    {
      JSAutoCompartment ac(cx, global);
      JS_InitStandardClasses(cx, global);

      const char *script = "'hello'+'world, it is '+new Date()";
      const char *filename = "noname";
      int lineno = 1;
      // [SpiderMonkey 24] The type of rval parameter is 'jsval *'.
      // bool ok = JS_EvaluateScript(cx, global, script, strlen(script), filename, lineno, rval.address());
      // [SpiderMonkey 38] JS_EvaluateScript is replaced with JS::Evaluate.
      // JS::CompileOptions opts(cx);
      // opts.setFileAndLine(filename, lineno);
      // bool ok = JS::Evaluate(cx, global, opts, script, strlen(script), &rval);
      bool ok = JS_EvaluateScript(cx, global, script, strlen(script), filename, lineno, &rval);
      if (!ok)
        return 1;
    }

    JSString *str = rval.toString();
    printf("%s\n", JS_EncodeString(cx, str));

    JS_DestroyContext(cx);
    JS_DestroyRuntime(rt);
    JS_ShutDown();
    return 0;
}

Build and run the Hello World example

Build command line depends on the OS and the tools. Here are sample Mac and Linux command lines (where <objdir> is the directory where SpiderMonkey was built):

[Mac]
clang++ -std=c++11 -I<objdir>/dist/include -L<objdir>/dist/lib helloworld.cpp -o helloworld  -lmozjs-31 -lz
[Linux]
g++ -std=c++11 -I<objdir>/dist/include -L<objdir>/dist/lib helloworld.cpp -o helloworld  -lmozjs-31 -lz -lpthread -ldl

It should print "helloworld, it is TIME" (here TIME is the current time).

  1. Make sure the build computer has the prerequisites for building SpiderMonkey: Linux, Windows, Mac OS X, others. For Windows, the following steps will assume that you have installed the MozillaBuild package.
  2. Get the SpiderMonkey source code. You can download a source archive or use Mercurial (hg) to pull the SpiderMonkey repository. On Windows, do not install the SpiderMonkey source code under the MSYS root directory (which is usually c:\mozilla-build\msys). Instead use something like c:\mozjs-31.2.0
  3. Compile SpiderMonkey using the build instructions at SpiderMonkey Build Documentation. By default this will build a SpiderMonkey shared library that you will link into your application in a later step.
  4. Copy the code example above into a text editor and save the file as helloworld.cpp in the SpiderMonkey js\src directory. To get a copy of the code sample without line numbers, hover over the sample near the top until buttons appear. Then click the view source button, and copy the code from the window that appears.
  5. Compile the helloworld application and link to the SpiderMonkey library.
  6. Run the helloworld executable at the command line:
    ./helloworld

How to call C functions from JavaScript

Say the C function is named doit and it would like at least two actual parameters when called (if the caller supplies fewer, the JS engine should ensure that undefined is passed for the missing ones):

#define DOIT_MINARGS 2

// [SpiderMonkey 24] Use JSBool instead of bool.
static bool
doit(JSContext *cx, unsigned argc, jsval *vp)
{
  JS::CallArgs args = CallArgsFromVp(argc, vp);
  /*
   * Look in argv for argc actual parameters, set *rval to return a
   * value to the caller.
   *
   * ex. Add two arguments as integer.
   * args.rval().setInt32(args[0].toInt32() + args[1].toInt32());
   */
  return true;
}

Then to wire it up to JS, you could write:

ok = JS_DefineFunction(cx, global, "doit", doit, DOIT_MINARGS, 0);

Or, if you had a bunch of native functions to define, you would probably put them in a table:

static JSFunctionSpec my_functions[] = {
  JS_FN("doit", doit, DOIT_MINARGS, 0),
  /* etc... */
  JS_FS_END
};

JS_FS_END terminates the table. And say:

ok = JS_DefineFunctions(cx, global, my_functions);

How to call JavaScript functions from C

First, create arguments for the call, here I create arguments with 2 items:

// [SpiderMonkey 24] JS::AutoValueArray is not defined.
// JS::AutoValueVector argv(cx);
// argv.resize(2);
JS::AutoValueArray<2> argv(cx);
argv[0].setInt32(1);
argv[1].setInt32(2);

then call the function:

// [SpiderMonkey 24] Pass arguments length and the 'jsval *' pointer.
// JS_CallFunctionName(cx, global, "func", 2, argv.begin(), rval.address());
JS_CallFunctionName(cx, global, "func", argv, &rval);

Example

Say the click event is for the top-most or focused UI element at position (x, y):

JSObject *target, *event;
JS::AutoValueArray<1> argv(cx);

/*
 * Find event target and make event object to represent this click.
 * Pass cx to NewEventObject so JS_NewObject can be called.
 */
target = FindEventTargetAt(cx, global, x, y);
event = NewEventObject(cx, "click", x, y);
argv[0].setObjectOrNull(event);

/* To emulate the DOM, you might want to try "onclick" too. */
ok = JS_CallFunctionName(cx, target, "onClick", argv, &rval);

/* Now test rval to see whether we should cancel the event. */
if (rval.isBoolean() && !rval.toBoolean())
    CancelEvent(event);

Again, I've elided error checking (such as testing for !ok after the call), and I've faked up some C event management routines that emulate the DOM's convention of canceling an event if its handler returns false.

Original Document Information

  • Author: Brendan Eich

 

Document Tags and Contributors

Last updated by: kscarfone,
Hide Sidebar