About JavaScript Interop
Expose native functions to the page, call JavaScript from C++, and pass data across the bridge.
Ultralight offers a safe, high-performance, type-checked JavaScript bridge for C++20 and low-level C. You can use it to expose application data and functions, make calls into JavaScript, and more.
π§ Preview API
The JavaScript API is a preview. Names and behavior may still change after 2.0.
JavaScript API Quick-Start
Start by defining an "API" (think of it like a JavaScript namespace for all of your application's data/functions), attach it to a View, then load your content per-usual:
// Include "Ultralight/JS.h" to access the optional JS API headers
#include <Ultralight/JS.h>
js::API app("app");
app["version"] = "2.1.0";
app["add"] = [](double a, double b) { return a + b; };
if (app.AttachTo(view.get()))
view->LoadURL("file:///app.html");
Page script sees a app namespace on window:
app.add(2, 3); // 5
π§ Only Local Content Can Access Your Custom API
By default, only local pages (eg,
file://) can access native API objects (remote HTTP/HTTPS websites are blocked). To whitelist other URLs, pass origin rules toAttachTo()(see Choosing Which Pages Get the API).
Type-Checked on Both Sides
The JavaScript API is strongly typed. The types you declare are checked on both sidesβ once, when you build your C++ code and again, during every runtime JavaScript call.
- At build time, a bad type-conversion in C++ will throw a compiler error with a
static_assertexplaining the problem. - At call time, a page calling
myApp.add(2, "loud")throws a JavaScriptTypeErrorexplaining the expected type (expected number, got 'loud').
Rules to Know
- Call the API on the Renderer's thread.
API::Emit()is the exception and works from any thread. Copying, moving, and destroying a handle is safe from any thread too. - A handle never keeps its page alive. Once the page navigates away (or its View is destroyed), the handle's page is gone and anything you do with it fails safely.
- The API never throws C++ exceptions. A call that can fail returns a
js::Result, which holds either the value or ajs::Error.
π Build Requirements
The bridge requires C++20β on Windows, that means Visual Studio 2022 (MSVC 19.30) or newer. Automatic struct and enum conversion needs Clang, GCC, or MSVC 19.40+ (they don't convert automatically on MSVC 19.30 through 19.39). Every file that uses the bridge must use the same C++ exception setting.
Where to Go
- Extending JavaScript with Native API β Create an API, bind functions and values, and attach it to a View.
- Calling into the Page β Call functions on the page, run scripts, and receive typed results.
- Working with JavaScript Values β Convert types, inspect objects, and read arrays using
js::Value. - Passing Data Across the Bridge β Convert standard and library types automatically, and pass binary buffers and DOM elements.
- Custom Type Conversions β Teach the bridge to convert custom native value types with
js::TypeTraits. - Async Callbacks β Return Promises to the page, use C++20 coroutines, and run tasks on worker threads.
- Exposing Native Classes β Bind C++ classes that the page can instantiate with
new, and manage instance ownership. - Emitting Events β Push events from native code to listeners on the page.
- API Schemas and TypeScript β Export an API schema as JSON and generate TypeScript declarations for editor support.
- JavaScript Errors and Diagnostics β Throw structured errors into the page, inspect error codes, and catch typos with diagnostics.
- Using JavaScriptCore Directly β Work directly with the raw JavaScriptCore C API underneath, which remains supported and included.
Coming from JSHelpers or raw JavaScriptCore code? The porting tables live in Porting from 1.4 to 2.0. And if your UI needs no page script at all, the DOM API controls pages without any JavaScript.