How to embed the JavaScript engine

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.

The code differs for each SpiderMonkey version, please choose right version for your SpiderMonkey.

SpiderMonkey 24

// 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,
    JS_PropertyStub,
    JS_DeletePropertyStub,
    JS_PropertyStub,
    JS_StrictPropertyStub,
    JS_EnumerateStub,
    JS_ResolveStub,
    JS_ConvertStub,
};

int main(int argc, const char *argv[])
{
    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;

    { // Scope for our various stack objects (JSAutoRequest, RootedObject), so they all go
      // out of scope before we JS_DestroyContext.

      JSAutoRequest ar(cx); // In practice, you would want to exit this any
                            // time you're spinning the event loop

      JS::RootedObject global(cx, JS_NewGlobalObject(cx, &global_class, nullptr));
      if (!global)
          return 1;

      JS::RootedValue rval(cx);

      { // Scope for JSAutoCompartment
        JSAutoCompartment ac(cx, global);
        JS_InitStandardClasses(cx, global);

        const char *script = "'hello'+'world, it is '+new Date()";
        const char *filename = "noname";
        int lineno = 1;
        bool ok = JS_EvaluateScript(cx, global, script, strlen(script), filename, lineno, rval.address());
        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;
}

SpiderMonkey 31

// 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,
    JS_PropertyStub,
    JS_DeletePropertyStub,
    JS_PropertyStub,
    JS_StrictPropertyStub,
    JS_EnumerateStub,
    JS_ResolveStub,
    JS_ConvertStub,
    nullptr,
    nullptr,
    nullptr,
    nullptr,
    JS_GlobalObjectTraceHook
};

int main(int argc, const char *argv[])
{
    JS_Init();

    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;

    { // Scope for our various stack objects (JSAutoRequest, RootedObject), so they all go
      // out of scope before we JS_DestroyContext.

      JSAutoRequest ar(cx); // In practice, you would want to exit this any
                            // time you're spinning the event loop

      JS::RootedObject global(cx, JS_NewGlobalObject(cx, &global_class, nullptr, JS::FireOnNewGlobalHook));
      if (!global)
          return 1;

      JS::RootedValue rval(cx);

      { // Scope for JSAutoCompartment
        JSAutoCompartment ac(cx, global);
        JS_InitStandardClasses(cx, global);

        const char *script = "'hello'+'world, it is '+new Date()";
        const char *filename = "noname";
        int lineno = 1;
        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;
}

SpiderMonkey 38

// 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,
    nullptr,
    nullptr,
    nullptr,
    nullptr,
    nullptr,
    nullptr,
    nullptr,
    nullptr,
    nullptr,
    nullptr,
    nullptr,
    JS_GlobalObjectTraceHook
};

int main(int argc, const char *argv[])
{
    JS_Init();

    JSRuntime *rt = JS_NewRuntime(8L * 1024 * 1024);
    if (!rt)
        return 1;

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

    { // Scope for our various stack objects (JSAutoRequest, RootedObject), so they all go
      // out of scope before we JS_DestroyContext.

      JSAutoRequest ar(cx); // In practice, you would want to exit this any
                            // time you're spinning the event loop

      JS::RootedObject global(cx, JS_NewGlobalObject(cx, &global_class, nullptr, JS::FireOnNewGlobalHook));
      if (!global)
          return 1;

      JS::RootedValue rval(cx);

      { // Scope for JSAutoCompartment
        JSAutoCompartment ac(cx, global);
        JS_InitStandardClasses(cx, global);

        const char *script = "'hello'+'world, it is '+new Date()";
        const char *filename = "noname";
        int lineno = 1;
        JS::CompileOptions opts(cx);
        opts.setFileAndLine(filename, lineno);
        bool ok = JS::Evaluate(cx, global, opts, script, strlen(script), &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;
}

SpiderMonkey 45

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

/* The class of the global object. */
static JSClass global_class = {
    "global",
    JSCLASS_GLOBAL_FLAGS,
    nullptr,
    nullptr,
    nullptr,
    nullptr,
    nullptr,
    nullptr,
    nullptr,
    nullptr,
    nullptr,
    nullptr,
    nullptr,
    JS_GlobalObjectTraceHook
};

int main(int argc, const char *argv[])
{
    JS_Init();

    JSRuntime *rt = JS_NewRuntime(8L * 1024 * 1024);
    if (!rt)
        return 1;

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

    { // Scope for our various stack objects (JSAutoRequest, RootedObject), so they all go
      // out of scope before we JS_DestroyContext.

      JSAutoRequest ar(cx); // In practice, you would want to exit this any
                            // time you're spinning the event loop

      JS::RootedObject global(cx, JS_NewGlobalObject(cx, &global_class, nullptr, JS::FireOnNewGlobalHook));
      if (!global)
          return 1;

      JS::RootedValue rval(cx);

      { // Scope for JSAutoCompartment
        JSAutoCompartment ac(cx, global);
        JS_InitStandardClasses(cx, global);

        const char *script = "'hello'+'world, it is '+new Date()";
        const char *filename = "noname";
        int lineno = 1;
        JS::CompileOptions opts(cx);
        opts.setFileAndLine(filename, lineno);
        bool ok = JS::Evaluate(cx, opts, script, strlen(script), &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;
}

SpiderMonkey 52

#include "jsapi.h"
#include "js/Initialization.h"

static JSClassOps global_ops = {
    nullptr,
    nullptr,
    nullptr,
    nullptr,
    nullptr,
    nullptr,
    nullptr,
    nullptr,
    nullptr,
    nullptr,
    nullptr,
    JS_GlobalObjectTraceHook
};

/* The class of the global object. */
static JSClass global_class = {
    "global",
    JSCLASS_GLOBAL_FLAGS,
    &global_ops
};

int main(int argc, const char *argv[])
{
    JS_Init();

    JSContext *cx = JS_NewContext(8L * 1024 * 1024);
    if (!cx)
        return 1;
    if (!JS::InitSelfHostedCode(cx))
        return 1;

    { // Scope for our various stack objects (JSAutoRequest, RootedObject), so they all go
      // out of scope before we JS_DestroyContext.

      JSAutoRequest ar(cx); // In practice, you would want to exit this any
                            // time you're spinning the event loop

      JS::CompartmentOptions options;
      JS::RootedObject global(cx, JS_NewGlobalObject(cx, &global_class, nullptr, JS::FireOnNewGlobalHook, options));
      if (!global)
          return 1;

      JS::RootedValue rval(cx);

      { // Scope for JSAutoCompartment
        JSAutoCompartment ac(cx, global);
        JS_InitStandardClasses(cx, global);

        const char *script = "'hello'+'world, it is '+new Date()";
        const char *filename = "noname";
        int lineno = 1;
        JS::CompileOptions opts(cx);
        opts.setFileAndLine(filename, lineno);
        bool ok = JS::Evaluate(cx, opts, script, strlen(script), &rval);
        if (!ok)
          return 1;
      }

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

    JS_DestroyContext(cx);
    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):

# If you're using debug build of SpiderMonkey, you need -DDEBUG in addition to the command below.
# if you're using version other than SpiderMonkey 31, please change -lmozjs-XX to your version.

[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