Using JavaScriptCore Directly
Access the raw JavaScriptCore C API to port code or use advanced features.
On this page
You can write code against the raw JavaScriptCore C API directly. This C API sits under the ultralight::js typed layer and ships fully supported with the SDK. Use it to port existing JavaScriptCore code or to access features the typed layer omits. For most tasks the typed layer is still the easier path.
Getting a Context
To get a raw context, call LockJSContext() on your View (passing an iframe's name attribute to access that iframe) and include <JavaScriptCore/JavaScript.h>.
#include <JavaScriptCore/JavaScript.h>
void MyApp::OnDOMReady(View* caller, uint64_t frame_id,
bool is_main_frame, const String& url) {
RefPtr<JSContext> context = caller->LockJSContext();
if (!context)
return;
JSContextRef ctx = context->ctx();
JSStringRef script = JSStringCreateWithUTF8CString("startGame()");
JSEvaluateScript(ctx, script, nullptr, nullptr, 0, nullptr);
JSStringRelease(script);
}
The call returns a RefPtr that locks the JavaScript VM until it goes out of scope (returning nullptr when the page cannot run scripts). Call ctx() on it to get the JSContextRef.
🚧 Contexts Reset on Navigation
The context resets on every navigation. You must get it again for each page.
LoadListener::OnWindowObjectReady()orLoadListener::OnDOMReady()are good places to set up state.
Retrieving From Typed Code
To get the underlying JSContextRef from an existing js::Context, call ulJSContextGetJSContextRef() (call this only on the Renderer's thread and never store the result).
js::Context ctx(caller);
JSContextRef jsc_ctx = ulJSContextGetJSContextRef(ctx.raw());
What the Fork Adds
The library uses a fork of JavaScriptCore that adds to the classic C API. You can find the details in the headers for these additions: JSObjectRef.h, JSValueRef.h and JSTypedArray.h.
| Addition | Description |
|---|---|
JSObjectMakeFunctionWithData |
Creates a real function object with a user-data pointer and a finalizer. Script methods like bind, call and apply work normally. |
| UTF-8 property functions | Get, set and define properties by UTF-8 name (JSObjectGetPropertiesUTF8, JSObjectSetPropertiesUTF8, JSObjectMakeWithPropertiesUTF8, JSObjectGetOwnEntriesUTF8, JSObjectDefinePropertyUTF8, JSObjectDefineAccessorUTF8). You can assign several properties in one call. JSObjectFreeze maps to Object.freeze. |
JSObjectMakeTypedError, JSValueGetErrorDetails |
Create an error of a specific type and read its type, message, source location and stack in one call. |
JSPromiseGetStatus, JSPromiseGetResult, JSPromiseMarkAsHandled |
Read a promise's state and result without scheduling a reaction. You can mark a rejection that you handled natively. |
JSArrayBufferDetach, JSArrayBufferIsDetached |
Free memory shared with a buffer so that later script access predictably fails. |
JSObjectSetExternalMemoryHint |
Tell the garbage collector how much native memory a small wrapper keeps alive. |
JSValueIsFunctionObject |
Returns true only for an actual function object (JSObjectIsFunction also returns true for anything callable like a callable Proxy). |
JSClassDefinition version 1000 |
Callbacks ending in Ex receive the JSClassRef, and you gain access to per-class private data (JSClassGetPrivate and JSClassSetPrivate). Version 0 remains unchanged. |
For example, you can create a frozen display object and define several properties in one call.
const char* names[] = { "width", "height" };
JSValueRef values[] = { JSValueMakeNumber(ctx, 1280),
JSValueMakeNumber(ctx, 720) };
JSObjectRef display =
JSObjectMakeWithPropertiesUTF8(ctx, names, values, 2, nullptr);
JSObjectFreeze(ctx, display, nullptr);
JSObjectRef global = JSContextGetGlobalObject(ctx);
JSObjectDefinePropertyUTF8(ctx, global, "display", display,
kJSPropertyAttributeReadOnly, nullptr);
Mixing Raw and Typed Code
There is no conversion step between a JSValueRef and a js::Value. The values meet on the page.
Raw values behave like ordinary page JavaScript so page script sees them as normal variables.
console.log(display.width); // 1280
display.width = 1920; // fails: display is frozen
The typed layer reads those values by name like any other page value.
js::Context ctx(caller);
double width = ctx["display"]["width"].Or(0.0);
🚧 Raw Ownership Rules
The raw layer follows classic ownership rules. Anything you create you must release (like
JSStringCreateWithUTF8CStringandJSStringRelease, orJSClassCreateandJSClassRelease). AJSValueRefkept past the current call needsJSValueProtectandJSValueUnprotect. The typed layer's handles manage themselves.
If you are porting JSHelpers or 1.4-era JavaScriptCore code, see the tables in Porting from 1.4 to 2.0.