Web APIs Implemented in Lightpanda: XHR, Fetch, and DOM Support
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 viaHttpClient.request().getStatus()– Returns the HTTP status code from the response.getResponse()– Parses and returns the response body based onresponseType.
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.
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.
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 createsElementobjects viapage.createElementNS.getElementById(id)– Caches lookups in_elements_by_idfor O(1) retrieval.querySelectorAll– Delegates to the selector engine insrc/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.
// 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:
- JavaScript calls
new XMLHttpRequest()orfetch(). - The bridge instantiates the corresponding Zig struct via constructors like
XMLHttpRequest.initorFetch.init. - The request is handed to
HttpClient, which manages the libcurl-based lifecycle. - Callbacks update the Zig object's internal state and dispatch DOM events (
load,error,readystatechange). - For fetch, the promise resolves with a JavaScript value (
js.Value) whenhttpDoneCallbackfires.
Summary
- XMLHttpRequest is fully implemented in
src/browser/webapi/net/XMLHttpRequest.zigwith complete state machine and event support. - Fetch API provides modern promise-based networking in
src/browser/webapi/net/Fetch.zig, sharing the underlyingHttpClientwith XHR. - DOM APIs include
Document,Node, andElementimplementations with mutation methods, traversal, and a custom selector engine. - V8 Integration occurs through the
js.Bridgepattern, 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.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →