JSAPI Cookbook

  • Revision slug: SpiderMonkey/JSAPI_Cookbook
  • Revision title: JSAPI Cookbook
  • Revision id: 74016
  • Created:
  • Creator: Jorend
  • Is current revision? No
  • Comment /* Wanted */ oops, remove "wanted" item that's already in the document

Revision Content

This article shows the JSAPI equivalent for a tiny handful of common JavaScript idioms.

Basics

Defining a function

// JavaScript
function justForFun() {
    return null;
}
/* JSAPI */
JSBool justForFun(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
{
   *rval = JSVAL_NULL;
   return JS_TRUE;
}

...

/*
 * Add this to your JSContext setup code.
 * This makes your C function visible as a global function in JavaScript.
 */
if (!JS_DefineFunction(cx, JS_GetGlobalObject(cx), "justForFun", &justForFun, 0, 0))
    return JS_FALSE;

To define many JSAPI functions at once, use JS_DefineFunctions.

Creating an Array

// JavaScript
var x = [];  // or "x = Array()", or "x = new Array"
/* JSAPI */
JSObject *x = JS_NewArrayObject(cx, 0, NULL);
if (x == NULL)
    return JS_FALSE;

Creating an Object

// JavaScript
var x = {};  // or "x = Object()", or "x = new Object"
/* JSAPI */
JSObject *x = JS_NewObject(cx, NULL, NULL, NULL);
if (x == NULL)
    return JS_FALSE;

Calling a global JS function

// JavaScript
var r = foo();  // where f is a global function
/* JSAPI
 *
 * Suppose the script defines a global JavaScript
 * function foo() and we want to call it from C.
 */
jsval r;
if (!JS_CallFunctionName(cx, JS_GetGlobalObject(cx), "foo", 0, NULL, &r))
   return JS_FALSE;

Calling a JS function via a local variable

// JavaScript
var r = f();  // where f is a local variable
/* JSAPI
 *
 * Suppose f is a local C variable of type jsval.
 */
jsval r;
if (!JS_CallFunctionValue(cx, NULL, f, 0, NULL, &r)
    return JS_FALSE;

Returning an integer

// JavaScript
return 23;
/* JSAPI
 *
 * Warning: This only works for integers that fit in 31 (not 32) bits.
 * Otherwise, convert the number to floating point (see the next example).
 */
*rval = INT_TO_JSVAL(23);
return JS_TRUE;

Returning a floating-point number

// JavaScript
return 3.14159;
/* JSAPI */
jsdouble n = 3.14159;
return JS_NewNumberValue(cx, n, rval);

Exception handling

throw

// JavaScript
throw exc;
/* JSAPI */
JS_SetPendingException(cx, exc);
return JS_FALSE;

The usual idiom involves creating a new exception object and throwing that:

// JavaScript
throw new Error(message);
/* JSAPI */
jsval exc;

if (!JS_CallFunctionName(cx, JS_GetGlobalObject(cx), "Error", 1, &message, &exc))
    return JS_FALSE;
JS_SetPendingException(cx, exc);
return JS_FALSE;

The JSAPI code here is actually simulating throw Error(message) without the new, as new is hard to simulate using the JSAPI. In this case, unless the script has redefined Error, it amounts to the same thing.

catch

// JavaScript
try {
    // try some stuff here; for example:
    foo();
    bar();
} catch (exc) {
    // do error-handling stuff here
}
/* JSAPI */
    jsval exc;

    /* try some stuff here; for example: */
    if (!JS_CallFunctionName(cx, JS_GetGlobalObject(cx), "foo", 0, NULL, &r))
        goto catch_block;  /* instead of returning JS_FALSE */
    if (!JS_CallFunctionName(cx, JS_GetGlobalObject(cx), "bar", 0, NULL, &r))
        goto catch_block;  /* instead of returning JS_FALSE */
    return JS_TRUE;

catch_block:
    if (!JS_GetPendingException(cx, &exc))
        return JS_FALSE;
    JS_ClearPendingException(cx);
    /* do error-handling stuff here */
    return JS_TRUE;

finally

// JavaScript
try {
   foo();
   bar();
} finally {
   cleanup();
}

If your C/C++ cleanup code doesn't call back into the JSAPI, this is straightforward:

/* JSAPI */
    JSBool success = JS_FALSE;

    if (!JS_CallFunctionName(cx, JS_GetGlobalObject(cx), "foo", 0, NULL, &r))
        goto finally_block;  /* instead of returning JS_FALSE immediately */
    if (!JS_CallFunctionName(cx, JS_GetGlobalObject(cx), "bar", 0, NULL, &r))
        goto finally_block;
    success = JS_TRUE;
    /* Intentionally fall through to the finally block. */

finally_block:
    cleanup();
    return success;

However, if cleanup() is actually a JavaScript function, there's a catch. When an error occurs, the JSContext's pending exception is set. If this happens in foo() or bar() in the above example, the pending exception will still be set when you call cleanup(), which would be bad. To avoid this, your JSAPI code implementing the finally block must:

  • save the old exception, if any
  • clear the pending exception so that your cleanup code can run
  • do your cleanup
  • restore the old exception, if any
  • return JS_FALSE if an exception occurred, so that the exception is propagated up.
/* JSAPI */
    JSBool success = JS_FALSE;
    JSExceptionState *exc_state;

    if (!JS_CallFunctionName(cx, JS_GetGlobalObject(cx), "foo", 0, NULL, &r))
        goto finally_block;  /* instead of returning JS_FALSE immediately */
    if (!JS_CallFunctionName(cx, JS_GetGlobalObject(cx), "bar", 0, NULL, &r))
        goto finally_block;
    success = JS_TRUE;
    /* Intentionally fall through to the finally block. */

finally_block:
    exc_state = JS_SaveExceptionState(cx);
    if (exc_state == NULL)
        return JS_FALSE;
    JS_ClearPendingException(cx);

    if (!JS_CallFunctionName(cx, JS_GetGlobalObject(cx), "cleanup", 0, NULL, &r)) {
        /* The new error replaces the previous one, so discard the saved exception state. */
        JS_DropExceptionState(cx, exc_state);
        return JS_FALSE;
    }
    JS_RestoreExceptionState(cx, exc_state);
    return success;

Object properties

Getting a property

// JavaScript
var x = y.myprop;

The JSAPI function that does this is JS_GetProperty. It requires a JSObject * argument. Since JavaScript variables are stored in usually jsval variables, a cast or conversion is needed.

In cases where it is certain that y is an object (that is, not a boolean, number, string, null, or undefined), this is fairly straightforward. Use JSVAL_TO_OBJECT to cast y to type JSObject *.

/* JSAPI */
jsval x;

assert(JSVAL_IS_OBJECT(y)); 
if (!JS_GetProperty(cx, JSVAL_TO_OBJECT(y), "myprop", &x))
    return JS_FALSE;

That code will crash if y is not an object. That's often unacceptable. An alternative would be to simulate the behavior of the JavaScript . notation exactly. It's a nice thought—JavaScript wouldn't crash, at least—but implementing its exact behavior turns out to be quite complicated, and most of the work is not particularly helpful.

Usually it is best to check for !JSVAL_IS_OBJECT(y) and throw a TypeError with a nice error message.

/* '''JSAPI''' */ JSBool myThrowTypeError(const char *msg) { ... /* see examples for "throw" above for help with this */ return JS_FALSE; } ... jsval x; if (!{{mediawiki.internal('JSVAL_IS_OBJECT', "en")}}(y)) return myThrowTypeError("Parameter y must be an object."); if (!{{mediawiki.internal('JS_GetProperty', "en")}}(cx, JSVAL_TO_OBJECT(y), "myprop", &x)) return JS_FALSE;

Setting a property

// JavaScript
y.myprop = x;

See "Getting a property", above, concerning the case where y is not an object.

/* JSAPI */
assert(JSVAL_IS_OBJECT(y));
if (!JS_SetProperty(cx, JSVAL_TO_OBJECT(y), "myprop", &x))
    return JS_FALSE;

Checking for a property

// JavaScript
if ("myprop" in y) {
    // then do something
}

See "Getting a property", above, concerning the case where y is not an object.

/* JSAPI */
JSBool found;

assert(JSVAL_IS_OBJECT(y));
if (!JS_HasProperty(cx, JSVAL_TO_OBJECT(y), "myprop", &found))
    return JS_FALSE;
if (found) {
    // then do something
}

Wanted

  • Using JS_GetStandardClass in the throw new Error example, so it throws an actual Error even if the program has deleted or assigned to the global property Error.
  • Simulating for and for each.
  • Calling a constructor with new.

Revision Source

<p>This article shows the JSAPI equivalent for a tiny handful of common JavaScript idioms.
</p>
<h3 name="Basics"> Basics </h3>
<h4 name="Defining_a_function"> Defining a function </h4>
<pre class="eval">// <b>JavaScript</b>
function justForFun() {
    return null;
}
</pre>
<pre class="eval">/* <b>JSAPI</b> */
JSBool justForFun(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
{
   *rval = <a href="en/JSVAL_NULL">JSVAL_NULL</a>;
   return JS_TRUE;
}

...

/*
 * Add this to your JSContext setup code.
 * This makes your C function visible as a global function in JavaScript.
 */
if (!<a href="en/JS_DefineFunction">JS_DefineFunction</a>(cx, <a href="en/JS_GetGlobalObject">JS_GetGlobalObject</a>(cx), "justForFun", &amp;justForFun, 0, 0))
    return JS_FALSE;
</pre>
<p>To define many JSAPI functions at once, use <code><a href="en/JS_DefineFunctions">JS_DefineFunctions</a></code>.
</p>
<h4 name="Creating_an_Array"> Creating an Array </h4>
<pre class="eval">// <b>JavaScript</b>
var x = [];  // or "x = Array()", or "x = new Array"
</pre>
<pre class="eval">/* <b>JSAPI</b> */
JSObject *x = <a href="en/JS_NewArrayObject">JS_NewArrayObject</a>(cx, 0, NULL);
if (x == NULL)
    return JS_FALSE;
</pre>
<h4 name="Creating_an_Object"> Creating an Object </h4>
<pre class="eval">// <b>JavaScript</b>
var x = {};  // or "x = Object()", or "x = new Object"
</pre>
<pre class="eval">/* <b>JSAPI</b> */
JSObject *x = <a href="en/JS_NewObject">JS_NewObject</a>(cx, NULL, NULL, NULL);
if (x == NULL)
    return JS_FALSE;
</pre>
<h4 name="Calling_a_global_JS_function"> Calling a global JS function </h4>
<pre class="eval">// <b>JavaScript</b>
var r = foo();  // where f is a global function
</pre>
<pre class="eval">/* <b>JSAPI</b>
 *
 * Suppose the script defines a global JavaScript
 * function foo() and we want to call it from C.
 */
jsval r;
if (!<a href="en/JS_CallFunctionName">JS_CallFunctionName</a>(cx, <a href="en/JS_GetGlobalObject">JS_GetGlobalObject</a>(cx), "foo", 0, NULL, &amp;r))
   return JS_FALSE;
</pre>
<h4 name="Calling_a_JS_function_via_a_local_variable"> Calling a JS function via a local variable </h4>
<pre class="eval">// <b>JavaScript</b>
var r = f();  // where f is a local variable
</pre>
<pre class="eval">/* <b>JSAPI</b>
 *
 * Suppose f is a local C variable of type jsval.
 */
jsval r;
if (!<a href="en/JS_CallFunctionValue">JS_CallFunctionValue</a>(cx, NULL, f, 0, NULL, &amp;r)
    return JS_FALSE;
</pre>
<h4 name="Returning_an_integer"> Returning an integer </h4>
<pre class="eval">// <b>JavaScript</b>
return 23;
</pre>
<pre class="eval">/* <b>JSAPI</b>
 *
 * Warning: This only works for integers that fit in 31 (not 32) bits.
 * Otherwise, convert the number to floating point (see the next example).
 */
*rval = <a href="en/INT_TO_JSVAL">INT_TO_JSVAL</a>(23);
return JS_TRUE;
</pre>
<h4 name="Returning_a_floating-point_number"> Returning a floating-point number </h4>
<pre class="eval">// <b>JavaScript</b>
return 3.14159;
</pre>
<pre class="eval">/* <b>JSAPI</b> */
<a href="en/Jsdouble">jsdouble</a> n = 3.14159;
return <a href="en/JS_NewNumberValue">JS_NewNumberValue</a>(cx, n, rval);
</pre>
<h3 name="Exception_handling"> Exception handling </h3>
<h4 name="throw"> <code>throw</code> </h4>
<pre class="eval">// <b>JavaScript</b>
throw exc;
</pre>
<pre class="eval">/* <b>JSAPI</b> */
<a href="en/JS_SetPendingException">JS_SetPendingException</a>(cx, exc);
return JS_FALSE;
</pre>
<p>The usual idiom involves creating a new exception object and throwing that:
</p>
<pre class="eval">// <b>JavaScript</b>
throw new Error(message);
</pre>
<pre class="eval">/* <b>JSAPI</b> */
jsval exc;

if (!<a href="en/JS_CallFunctionName">JS_CallFunctionName</a>(cx, <a href="en/JS_GetGlobalObject">JS_GetGlobalObject</a>(cx), "Error", 1, &amp;message, &amp;exc))
    return JS_FALSE;
<a href="en/JS_SetPendingException">JS_SetPendingException</a>(cx, exc);
return JS_FALSE;
</pre>
<p>The JSAPI code here is actually simulating <code>throw Error(message)</code> without the <code>new</code>, as <code>new</code> is hard to simulate using the JSAPI.  In this case, unless the script has redefined <code>Error</code>, it amounts to the same thing.
</p>
<h4 name="catch"> <code>catch</code> </h4>
<pre class="eval">// <b>JavaScript</b>
try {
    // try some stuff here; for example:
    foo();
    bar();
} catch (exc) {
    // do error-handling stuff here
}
</pre>
<pre class="eval">/* <b>JSAPI</b> */
    jsval exc;

    /* try some stuff here; for example: */
    if (!JS_CallFunctionName(cx, JS_GetGlobalObject(cx), "foo", 0, NULL, &amp;r))
        goto catch_block;  /* instead of returning JS_FALSE */
    if (!JS_CallFunctionName(cx, JS_GetGlobalObject(cx), "bar", 0, NULL, &amp;r))
        goto catch_block;  /* instead of returning JS_FALSE */
    return JS_TRUE;

catch_block:
    if (!<a href="en/JS_GetPendingException">JS_GetPendingException</a>(cx, &amp;exc))
        return JS_FALSE;
    <a href="en/JS_ClearPendingException">JS_ClearPendingException</a>(cx);
    /* do error-handling stuff here */
    return JS_TRUE;
</pre>
<h4 name="finally"> <code>finally</code> </h4>
<pre class="eval">// <b>JavaScript</b>
try {
   foo();
   bar();
} finally {
   cleanup();
}
</pre>
<p>If your C/C++ cleanup code doesn't call back into the JSAPI, this is straightforward:
</p>
<pre class="eval">/* <b>JSAPI</b> */
    <a href="en/JSBool">JSBool</a> success = JS_FALSE;

    if (!JS_CallFunctionName(cx, JS_GetGlobalObject(cx), "foo", 0, NULL, &amp;r))
        goto finally_block;  /* instead of returning JS_FALSE immediately */
    if (!JS_CallFunctionName(cx, JS_GetGlobalObject(cx), "bar", 0, NULL, &amp;r))
        goto finally_block;
    success = JS_TRUE;
    /* Intentionally fall through to the finally block. */

finally_block:
    cleanup();
    return success;
</pre>
<p>However, if <code>cleanup()</code> is actually a JavaScript function, there's a catch.  When an error occurs, the <code>JSContext</code>'s <i>pending exception</i> is set.  If this happens in <code>foo()</code> or <code>bar()</code> in the above example, the pending exception will still be set when you call <code>cleanup()</code>, which would be bad.  To avoid this, your JSAPI code implementing the <code>finally</code> block must:
</p>
<ul><li> save the old exception, if any
</li><li> clear the pending exception so that your cleanup code can run
</li><li> do your cleanup
</li><li> restore the old exception, if any
</li><li> return JS_FALSE if an exception occurred, so that the exception is propagated up.
</li></ul>
<pre class="eval">/* <b>JSAPI</b> */
    <a href="en/JSBool">JSBool</a> success = JS_FALSE;
    <a href="en/JSExceptionState">JSExceptionState</a> *exc_state;

    if (!JS_CallFunctionName(cx, JS_GetGlobalObject(cx), "foo", 0, NULL, &amp;r))
        goto finally_block;  /* instead of returning JS_FALSE immediately */
    if (!JS_CallFunctionName(cx, JS_GetGlobalObject(cx), "bar", 0, NULL, &amp;r))
        goto finally_block;
    success = JS_TRUE;
    /* Intentionally fall through to the finally block. */

finally_block:
    exc_state = <a href="en/JS_SaveExceptionState">JS_SaveExceptionState</a>(cx);
    if (exc_state == NULL)
        return JS_FALSE;
    <a href="en/JS_ClearPendingException">JS_ClearPendingException</a>(cx);

    if (!JS_CallFunctionName(cx, JS_GetGlobalObject(cx), "cleanup", 0, NULL, &amp;r)) {
        /* The new error replaces the previous one, so discard the saved exception state. */
        <a href="en/JS_DropExceptionState">JS_DropExceptionState</a>(cx, exc_state);
        return JS_FALSE;
    }
    <a href="en/JS_RestoreExceptionState">JS_RestoreExceptionState</a>(cx, exc_state);
    return success;
</pre>
<h3 name="Object_properties"> Object properties </h3>
<h4 name="Getting_a_property"> Getting a property </h4>
<pre class="eval">// <b>JavaScript</b>
var x = y.myprop;
</pre>
<p>The JSAPI function that does this is <code><a href="en/JS_GetProperty">JS_GetProperty</a></code>.  It requires a <code>JSObject *</code> argument.  Since JavaScript variables are stored in usually <code>jsval</code> variables, a cast or conversion is needed.
</p><p>In cases where it is certain that <code>y</code> is an object (that is, not a boolean, number, string, <code>null</code>, or <code>undefined</code>), this is fairly straightforward.  Use <code><a href="en/JSVAL_TO_OBJECT">JSVAL_TO_OBJECT</a></code> to cast <code>y</code> to type <code>JSObject *</code>.
</p>
<pre class="eval">/* <b>JSAPI</b> */
jsval x;

assert(<a href="en/JSVAL_IS_OBJECT">JSVAL_IS_OBJECT</a>(y)); 
if (!<a href="en/JS_GetProperty">JS_GetProperty</a>(cx, <a href="en/JSVAL_TO_OBJECT">JSVAL_TO_OBJECT</a>(y), "myprop", &amp;x))
    return JS_FALSE;
</pre>
<p>That code will crash if <code>y</code> is not an object.  That's often unacceptable.  An alternative would be to simulate the behavior of the JavaScript <code>.</code> notation exactly.  It's a nice thought—JavaScript wouldn't crash, at least—but implementing its exact behavior turns out to be quite complicated, and most of the work is not particularly helpful.
</p><p>Usually it is best to check for <code>!<a href="en/JSVAL_IS_OBJECT">JSVAL_IS_OBJECT</a>(y)</code> and throw a <code>TypeError</code> with a nice error message.
</p><p><span class="comment">/* '''JSAPI''' */  JSBool myThrowTypeError(const char *msg)  {      ... /* see examples for "throw" above for help with this */      return JS_FALSE;  }    ...    jsval x;    if (!{{mediawiki.internal('JSVAL_IS_OBJECT', "en")}}(y))      return myThrowTypeError("Parameter y must be an object.");  if (!{{mediawiki.internal('JS_GetProperty', "en")}}(cx, JSVAL_TO_OBJECT(y), "myprop", &amp;x))      return JS_FALSE;</span>
</p>
<h4 name="Setting_a_property"> Setting a property </h4>
<pre class="eval">// <b>JavaScript</b>
y.myprop = x;
</pre>
<p>See "Getting a property", above, concerning the case where <code>y</code> is not an object.
</p>
<pre class="eval">/* <b>JSAPI</b> */
assert(<a href="en/JSVAL_IS_OBJECT">JSVAL_IS_OBJECT</a>(y));
if (!<a href="en/JS_SetProperty">JS_SetProperty</a>(cx, <a href="en/JSVAL_TO_OBJECT">JSVAL_TO_OBJECT</a>(y), "myprop", &amp;x))
    return JS_FALSE;
</pre>
<h4 name="Checking_for_a_property"> Checking for a property </h4>
<pre class="eval">// <b>JavaScript</b>
if ("myprop" in y) {
    // then do something
}
</pre>
<p>See "Getting a property", above, concerning the case where <code>y</code> is not an object.
</p>
<pre class="eval">/* <b>JSAPI</b> */
JSBool found;

assert(<a href="en/JSVAL_IS_OBJECT">JSVAL_IS_OBJECT</a>(y));
if (!<a href="en/JS_HasProperty">JS_HasProperty</a>(cx, <a href="en/JSVAL_TO_OBJECT">JSVAL_TO_OBJECT</a>(y), "myprop", &amp;found))
    return JS_FALSE;
if (found) {
    // then do something
}
</pre>
<h3 name="Wanted">Wanted</h3>
<ul><li> Using <code><a href="en/JS_GetStandardClass">JS_GetStandardClass</a></code> in the <code>throw new Error</code> example, so it throws an actual <code>Error</code> even if the program has deleted or assigned to the global property <code>Error</code>.
</li></ul>
<ul><li> Simulating <code>for</code> and <code>for each</code>.
</li></ul>
<ul><li> Calling a constructor with <code>new</code>.
</li></ul>
Revert to this revision