# Web APIs Implemented in Lightpanda: XHR, Fetch, and DOM Support

> Explore Web APIs like XHR, Fetch, and DOM implemented in Lightpanda Zig for high-performance headless browsing. Integrate easily with JavaScript via V8.

- Repository: [Lightpanda/browser](https://github.com/lightpanda-io/browser)
- Tags: api-reference
- Published: 2026-03-14

---

**Lightpanda implements core browser Web APIs—XMLHttpRequest, fetch, and the full DOM—in Zig, exposing them to JavaScript via a V8 bridge for high-performance headless browsing.**

The **lightpanda-io/browser** repository provides a headless browser core written in Zig that supports the essential Web APIs modern web applications require. Unlike traditional browsers that embed heavy C++ engines, Lightpanda implements these APIs as lightweight Zig structs wired to the V8 engine through a `js.Bridge` layer. This architecture delivers zero-copy networking and arena-allocated DOM trees while maintaining full spec compliance for network requests and document manipulation.

## XMLHttpRequest Implementation

Lightpanda provides a complete **XMLHttpRequest** implementation following the W3C spec, including the full state machine, header management, and event dispatch model.

The implementation lives in `src/browser/webapi/net/XMLHttpRequest.zig`, where the `ReadyState` enum (lines 64‑70) tracks the request lifecycle from `UNSENT` to `DONE`. When JavaScript calls `xhr.send()`, the Zig struct hands the request to the singleton `HttpClient` (`page._session.browser.http_client`) and registers callbacks (`start_callback`, `header_callback`, `data_callback`, `done_callback`, `error_callback`) that feed data back into the object.

Key methods include:
- **`open(method, url)`** – Initializes the request and resets state.
- **`send(body)`** – Dispatches the request via `HttpClient.request()`.
- **`getStatus()`** – Returns the HTTP status code from the response.
- **`getResponse()`** – Parses and returns the response body based on `responseType`.

The bridge exposes properties like `status`, `response`, `onload`, and `onerror` through the `JsApi` struct nested within the same file (lines 150‑164), mapping Zig methods to JavaScript object descriptors.

```javascript
const xhr = new XMLHttpRequest();
xhr.open('GET', 'https://example.com/data.json');
xhr.responseType = 'json';
xhr.onload = function () {
    console.log('Status:', xhr.status);  // Mapped to getStatus() in XMLHttpRequest.zig
    console.log('JSON:', xhr.response);  // Mapped to getResponse()
};
xhr.onerror = function (e) {
    console.error('XHR error', e);
};
xhr.send();  // Triggers send() → HttpClient.request()

```

## Fetch API Implementation

For modern promise-based networking, Lightpanda implements **`window.fetch`** in `src/browser/webapi/net/Fetch.zig`, providing `Request` and `Response` objects built on the same `HttpClient` layer as XHR.

When `fetch()` is invoked from JavaScript, `Fetch.init` builds a `Request` object, creates a `Response` placeholder, and registers HTTP callbacks (`httpStartCallback`, `httpHeaderDoneCallback`, `httpDoneCallback`). The bridge creates a JavaScript promise using `page.js.local.?.createPromiseResolver()` (line 50) and resolves it in `httpDoneCallback` (line 73) when the network request completes.

The `Window` interface in `src/browser/webapi/Window.zig` exposes the global `fetch` entry point, acting as the bridge between the JavaScript global object and the Zig implementation.

```javascript
fetch('https://api.example.com/items', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ q: 'search' })
})
.then(resp => resp.json())
.then(data => console.log('Fetched data:', data))
.catch(err => console.error('Fetch error:', err));

```

## DOM API Implementation

Lightpanda implements a full **DOM** tree including `Document`, `Node`, `Element`, `HTMLDocument`, and related interfaces. These are plain Zig structs stored under `src/browser/webapi/` that maintain parent pointers, doubly-linked child lists, and `EventTarget` prototypes.

### Document and Node Trees

The **`Document`** struct in `src/browser/webapi/Document.zig` serves as the root of the DOM tree. It provides:
- **`createElement(tagName)`** (lines 40‑66) – Normalizes tag names and creates `Element` objects via `page.createElementNS`.
- **`getElementById(id)`** – Caches lookups in `_elements_by_id` for O(1) retrieval.
- **`querySelectorAll`** – Delegates to the selector engine in `src/browser/webapi/selector/Selector.zig`.

The **`Node`** base class in `src/browser/webapi/Node.zig` implements tree mutation methods including `appendChild`, `insertBefore`, and `replaceChildren` (lines 19‑55). These methods validate insertion constraints, handle cross-document adoption, and update the linked-list of children while firing mutation observer events.

### Element Interface

**`Element`** in `src/browser/webapi/Element.zig` extends `Node` with HTML-specific capabilities including attribute handling, ID reflection, and layout helpers. It integrates with the selector engine to support `querySelector` traversal.

```javascript
// Document.createElement → src/browser/webapi/Document.zig
const div = document.createElement('div');
div.id = 'msg';
div.textContent = 'Hello Lightpanda!';

// Node.appendChild → src/browser/webapi/Node.zig
document.body.appendChild(div);

// Document.getElementById → src/browser/webapi/Document.zig
const el = document.getElementById('msg');
console.log(el.textContent);  // Node.getTextContent() in Node.zig

```

## Architectural Overview: The Zig-to-V8 Bridge

All Web APIs connect to JavaScript through a unified **js.Bridge** system. Each API struct (e.g., `XMLHttpRequest`, `Fetch`, `Document`) contains a nested `JsApi` definition that generates V8 class IDs, prototypes, and property/method descriptors at compile time.

The **network layer** in `src/network/http.zig` and `src/HttpClient.zig` provides low-level HTTP handling—including cookies, redirects, and robots.txt filtering—that both XHR and fetch share. This avoids code duplication and ensures consistent networking behavior across APIs.

Request flow works as follows:
1. JavaScript calls `new XMLHttpRequest()` or `fetch()`.
2. The bridge instantiates the corresponding Zig struct via constructors like `XMLHttpRequest.init` or `Fetch.init`.
3. The request is handed to `HttpClient`, which manages the libcurl-based lifecycle.
4. Callbacks update the Zig object's internal state and dispatch DOM events (`load`, `error`, `readystatechange`).
5. For fetch, the promise resolves with a JavaScript value (`js.Value`) when `httpDoneCallback` fires.

## Summary

- **XMLHttpRequest** is fully implemented in `src/browser/webapi/net/XMLHttpRequest.zig` with complete state machine and event support.
- **Fetch API** provides modern promise-based networking in `src/browser/webapi/net/Fetch.zig`, sharing the underlying `HttpClient` with XHR.
- **DOM APIs** include `Document`, `Node`, and `Element` implementations with mutation methods, traversal, and a custom selector engine.
- **V8 Integration** occurs through the `js.Bridge` pattern, exposing Zig structs as native JavaScript objects without overhead.
- **Shared Infrastructure** means both networking APIs use the same cookie jar, redirect handling, and connection pooling from `src/HttpClient.zig`.

## Frequently Asked Questions

### Does Lightpanda support synchronous XHR requests?

No, the current implementation in `src/browser/webapi/net/XMLHttpRequest.zig` focuses on asynchronous operation. The `send()` method immediately returns control to JavaScript and dispatches events through the `ReadyState` state machine, matching modern browser behavior where synchronous XHR is deprecated.

### How does Lightpanda handle response parsing for fetch and XHR?

Both APIs delegate to the `HttpClient` layer in `src/HttpClient.zig`, which manages raw byte streams. For XHR, the `getResponse()` method parses JSON or text based on the `responseType` property. For fetch, the `Response` object in `src/browser/webapi/net/Fetch.zig` provides `.json()` and `.text()` methods that consume the stream and return promises.

### Can I use standard DOM methods like querySelector and getElementsByClassName?

Yes. Lightpanda implements `querySelector` and `querySelectorAll` through the custom selector engine in `src/browser/webapi/selector/Selector.zig`. `getElementById` is optimized with a hash map cache (`_elements_by_id`) in `src/browser/webapi/Document.zig`, while `getElementsByClassName` traverses the element tree maintained in `src/browser/webapi/Node.zig`.

### Is the DOM implementation compatible with server-side rendering frameworks?

Yes. Because the DOM is implemented as native Zig structs with V8 bindings rather than a separate process, Lightpanda offers high-performance manipulation suitable for SSR. The arena-allocated node trees and direct memory access patterns make it significantly faster than traditional browser automation tools for headless document generation and manipulation.