# How Ladybird Browser Handles JavaScript Execution and C++ Binding

> Discover how Ladybird Browser executes JavaScript using LibJS and binds C++ objects through WebIDL-generated bindings. Learn about its VM, Realm, and PlatformObject implementation for seamless integration.

- Repository: [Ladybird/ladybird](https://github.com/LadybirdBrowser/ladybird)
- Tags: internals
- Published: 2026-03-05

---

**Ladybird Browser executes JavaScript through the LibJS engine by creating a `JS::VM` and `JS::Realm`, while exposing native C++ objects via WebIDL-generated bindings that inherit from `Web::Bindings::PlatformObject` and register prototypes in the global object.**

Ladybird Browser implements a standalone web stack including its own JavaScript engine called LibJS. The architecture separates ECMAScript execution logic from browser API bindings, allowing tight integration between script code and native C++ implementations through a sophisticated platform object system.

## JavaScript Execution Flow in Ladybird

Ladybird’s JavaScript execution follows a structured pipeline from source parsing to runtime evaluation. The process centers on the `LibJS::VM` class which manages all execution contexts and heap allocation.

### VM Initialization and Realm Creation

Execution begins with VM instantiation. In [`Utilities/js.cpp`](https://github.com/LadybirdBrowser/ladybird/blob/main/Utilities/js.cpp), the global VM is created via:

```cpp
g_vm_storage.get() = JS::VM::create();

```

This singleton VM manages all JavaScript execution for the process. After creation, the system allocates a `JS::Realm` that serves as the global environment. In browser contexts, this realm uses `Web::Bindings::WindowObject` as its global object, while test harnesses may use simplified globals.

The realm encapsulates the global environment record and provides the intrinsics table where all built-in prototypes reside.

### Parsing and Bytecode Compilation

Source code enters the system through `Parser::parse` in [`Libraries/LibJS/Parser/Parser.cpp`](https://github.com/LadybirdBrowser/ladybird/blob/main/Libraries/LibJS/Parser/Parser.cpp), which constructs an Abstract Syntax Tree (AST). When bytecode debugging is enabled, the AST compiles to bytecode via [`Libraries/LibJS/Bytecode/Generator.cpp`](https://github.com/LadybirdBrowser/ladybird/blob/main/Libraries/LibJS/Bytecode/Generator.cpp). Otherwise, the VM may execute directly from the AST or other intermediate representations.

### Script Execution and Context Management

The `VM::run` method creates an `ExecutionContext` and pushes it onto the execution stack. This context carries the realm, lexical environment, and variable environment necessary for proper scope resolution. The VM then processes the compiled bytecode or AST nodes until completion.

Identifier resolution occurs through `VM::resolve_binding`, defined in [`Libraries/LibJS/Runtime/VM.h`](https://github.com/LadybirdBrowser/ladybird/blob/main/Libraries/LibJS/Runtime/VM.h). This method walks the lexical environment chain and checks the global object’s declarative record to locate variables. For example, the REPL implementation in [`Utilities/js.cpp`](https://github.com/LadybirdBrowser/ladybird/blob/main/Utilities/js.cpp) uses this mechanism for autocomplete functionality around lines 775-795:

```cpp
auto reference_or_error = g_vm->resolve_binding(variable_name,
                                                JS::Strict::No,
                                                &global_environment);

```

### Microtask Processing

After the main script completes, Ladybird processes queued promise jobs via `VM::run_queued_promise_jobs()`. This ensures that `Promise` resolutions and other microtasks execute before the next macrotask, maintaining ECMAScript compliance.

## C++ to JavaScript Binding Architecture

Exposing browser APIs to JavaScript requires bridging the gap between C++ implementations and ECMAScript semantics. Ladybird accomplishes this through WebIDL-generated bindings built on top of a unified platform object layer.

### WebIDL Code Generation

Ladybird uses a WebIDL compiler to generate C++ classes from interface definitions. Each IDL interface produces a corresponding C++ class that wraps the underlying implementation. For example, `Web::HTML::HTMLDivElement` in the DOM becomes a generated binding class that exposes the element’s methods and properties to scripts.

### The PlatformObject Base Class

All WebIDL-generated classes inherit from `Web::Bindings::PlatformObject`, located in [`Libraries/LibWeb/Bindings/PlatformObject.h`](https://github.com/LadybirdBrowser/ladybird/blob/main/Libraries/LibWeb/Bindings/PlatformObject.h). This class extends `JS::Object` and provides the bridge between JavaScript’s object model and C++ implementations:

```cpp
// Libraries/LibWeb/Bindings/PlatformObject.h – lines 35-42
class WEB_API PlatformObject : public JS::Object {
    JS_OBJECT(PlatformObject, JS::Object);
    // ...
};

```

The `WEB_PLATFORM_OBJECT` macro expands `JS_OBJECT` and adds virtual helpers for interface identification:

```cpp
// Libraries/LibWeb/Bindings/PlatformObject.h – lines 21-31
#define WEB_PLATFORM_OBJECT(class_, base_class) \
    JS_OBJECT(class_, base_class)                \
    virtual Bindings::InterfaceName interface_name() const override { ... } \
    virtual bool implements_interface(String const& interface) const override { ... }

```

### Global Object Initialization

When a realm initializes, `Web::Bindings::initialize_global_object()` populates the intrinsics table with prototypes for every platform object. This registration happens in [`Libraries/LibWeb/Bindings/WindowObject.cpp`](https://github.com/LadybirdBrowser/ladybird/blob/main/Libraries/LibWeb/Bindings/WindowObject.cpp), ensuring that `window.Element`, `window.Node`, and other constructors exist before script execution begins.

### Native Function Wrappers

C++ methods callable from JavaScript use the `JS_DEFINE_NATIVE_FUNCTION` macro. These functions extract the `this` value as a `PlatformObject*`, perform the operation, and return `JS::Value` instances. For example, a simplified window alert implementation:

```cpp
JS_DEFINE_NATIVE_FUNCTION(WindowObject::alert)
{
    auto* window = TRY_OR_FAIL(verify_self<WindowObject>(vm, this_value));
    // C++ implementation细节...
    return JS::js_undefined();
}

```

The `verify_self` template function ensures type safety by validating that the JavaScript `this` value points to the expected C++ class.

### Property Access Hooks

`PlatformObject` overrides internal object methods like `internal_get_own_property` and `internal_set` to forward property accesses to generated C++ getters and setters. This mechanism respects WebIDL’s "expose" rules and implements attribute reflection automatically.

## Practical Implementation Examples

### Running JavaScript from C++

The [`Utilities/js.cpp`](https://github.com/LadybirdBrowser/ladybird/blob/main/Utilities/js.cpp) file demonstrates standalone script execution:

```cpp
// VM already initialized: g_vm_storage.get() = JS::VM::create();
g_vm->heap().set_should_collect_on_every_allocation(gc_on_every_allocation);

// Create execution context and realm
auto execution_context = JS::create_simple_execution_context<ScriptObject>(*g_vm);
auto& realm = *execution_context->realm;

// Execute source
String source = "console.log('Hello from Ladybird');";
if (!TRY(parse_and_run(realm, source, "inline"sv, /*parse_only=*/false)))
    return 1;

// Process microtasks
g_vm->run_queued_promise_jobs();

```

### Exposing Custom C++ Classes

To expose a C++ class to JavaScript, inherit from `PlatformObject` and register native methods:

```cpp
// MyObject.h
#pragma once
#include <LibWeb/Bindings/PlatformObject.h>

namespace MyNamespace {

class MyObject final : public Web::Bindings::PlatformObject {
    WEB_PLATFORM_OBJECT(MyObject, Web::Bindings::PlatformObject);
public:
    explicit MyObject(JS::Realm&);
    JS_DEFINE_NATIVE_FUNCTION(say_hello);
};

}

```

Implementation:

```cpp
// MyObject.cpp
#include "MyObject.h"

namespace MyNamespace {

MyObject::MyObject(JS::Realm& realm)
    : PlatformObject(realm)
{
    define_native_function("sayHello", say_hello, 0);
}

JS_DEFINE_NATIVE_FUNCTION(MyObject::say_hello)
{
    auto* self = TRY(verify_self<MyObject>(vm, this_value));
    dbgln("Hello from C++!");
    return JS::js_undefined();
}
}

```

Registration in the global object:

```cpp
auto* my_proto = heap().allocate<MyObject>(realm);
realm.intrinsics().my_object_prototype = my_proto;

```

JavaScript can then instantiate and call methods:

```javascript
let obj = new MyObject();
obj.sayHello(); // Logs to Ladybird console

```

## Key Source Files

Understanding the Ladybird JavaScript binding system requires familiarity with these specific files:

- **[`Utilities/js.cpp`](https://github.com/LadybirdBrowser/ladybird/blob/main/Utilities/js.cpp)** – Entry point for the standalone JS REPL; creates the VM, parses scripts, and implements `parse_and_run`.
- **[`Libraries/LibJS/Runtime/VM.h`](https://github.com/LadybirdBrowser/ladybird/blob/main/Libraries/LibJS/Runtime/VM.h)** – Defines `VM::resolve_binding`, execution context management, and promise job queues.
- **[`Libraries/LibWeb/Bindings/PlatformObject.h`](https://github.com/LadybirdBrowser/ladybird/blob/main/Libraries/LibWeb/Bindings/PlatformObject.h)** – Base class for all WebIDL-generated objects; implements the JavaScript object interface and `WEB_PLATFORM_OBJECT` macro.
- **[`Libraries/LibWeb/Bindings/WindowObject.cpp`](https://github.com/LadybirdBrowser/ladybird/blob/main/Libraries/LibWeb/Bindings/WindowObject.cpp)** – Sets up the global `window` object and registers all DOM prototypes during realm initialization.
- **[`Libraries/LibJS/Parser/Parser.cpp`](https://github.com/LadybirdBrowser/ladybird/blob/main/Libraries/LibJS/Parser/Parser.cpp)** – Transforms source text into an AST for subsequent execution.
- **[`Libraries/LibJS/Bytecode/Generator.cpp`](https://github.com/LadybirdBrowser/ladybird/blob/main/Libraries/LibJS/Bytecode/Generator.cpp)** – Compiles AST nodes to bytecode when debugging or optimization requires intermediate representation.

## Summary

- **Ladybird uses LibJS**, a standalone C++ ECMAScript implementation, managed by the `JS::VM` class for all script execution.
- **Execution flows** through VM creation, Realm allocation, AST parsing (via `Parser::parse`), and evaluation via `VM::run` with identifier resolution handled by `VM::resolve_binding`.
- **C++ binding** occurs through WebIDL-generated classes inheriting from `Web::Bindings::PlatformObject`, registered in the global realm’s intrinsics table.
- **Native functions** use the `JS_DEFINE_NATIVE_FUNCTION` macro and `verify_self` template to safely bridge C++ implementations to JavaScript calls.
- **Microtask processing** completes the event loop via `VM::run_queued_promise_jobs` after main script execution finishes.

## Frequently Asked Questions

### How does Ladybird create the JavaScript VM?

Ladybird instantiates the JavaScript VM through `JS::VM::create()`, typically stored in thread-local storage via `g_vm_storage.get()` as seen in [`Utilities/js.cpp`](https://github.com/LadybirdBrowser/ladybird/blob/main/Utilities/js.cpp). This VM singleton manages the heap, execution contexts, and global realms for the entire browsing session.

### What is PlatformObject in Ladybird?

`PlatformObject` is the abstract base class defined in [`Libraries/LibWeb/Bindings/PlatformObject.h`](https://github.com/LadybirdBrowser/ladybird/blob/main/Libraries/LibWeb/Bindings/PlatformObject.h) that all WebIDL-generated DOM classes inherit from. It extends `JS::Object` and provides the necessary infrastructure for JavaScript to recognize and interact with C++ browser implementations, including interface name resolution and property access hooks.

### How are C++ methods exposed to JavaScript?

C++ methods become callable JavaScript functions through the `JS_DEFINE_NATIVE_FUNCTION` macro, which creates a wrapper that extracts the `this` value using `verify_self`, executes the native code, and returns a `JS::Value`. These functions are registered on prototypes during global object initialization in [`WindowObject.cpp`](https://github.com/LadybirdBrowser/ladybird/blob/main/WindowObject.cpp).

### Where does identifier resolution happen in Ladybird?

Identifier resolution occurs in `VM::resolve_binding` within [`Libraries/LibJS/Runtime/VM.h`](https://github.com/LadybirdBrowser/ladybird/blob/main/Libraries/LibJS/Runtime/VM.h). This method traverses the lexical environment chain and checks the global declarative record to locate variables, supporting both strict and non-strict mode resolution rules according to ECMAScript specifications.