Skip to content

The document

Checked against e2d8fb4 on 2026-11-10. The files it covers are unchanged since.

Everything in niso goes through one DOM: a real document with HTML parsing, CSS, selectors, layout and events, whether a JavaScript page or a native program builds it. There is one document per app, created by the host (niso_dom_new) and alive for the app’s life.

The API is the DOM’s own, as WebIDL declares it. niso’s IDL in idl/ is the part of the web platform it implements, and crates/niso-idl generates the bindings from it on every build: the C functions in niso.h (niso_element_set_attribute, niso_node_append_child, …), the JS binding’s host functions, and the JVM classes. So a page’s element.setAttribute(), a C program’s niso_element_set_attribute() and a Java app’s element.setAttribute() are the same call into the same core. What exists is listed per interface in the Web API reference, with each member’s C function.

What the IDL can’t express (creating a DOM, the event loop, rendering, input, storage drivers) is the hand-written runtime header, niso_runtime.h (C API reference).

Objects cross the API as handles: 64-bit ids, never pointers. Handles are generational, so a handle to a node that’s gone is an error (NISO_INVALID_HANDLE), never a different node. 0 is null.

Lifetime follows the DOM spec. The document owns its tree; a detached node lives while something holds it: a detached tree another node holds, or a handle the app retains with niso_retain (and lets go with niso_release). The DOM collects everything else between tasks (niso_dom_collect), so a native app that keeps a handle across callbacks either keeps the node in the document or retains it. Bindings with a garbage collector (JavaScript, Java) map their own wrapper objects onto this with weak references and finalizers; cycles through listeners are each binding’s concern (Listener cycles).

The rules every caller follows are at the top of the C API (Conventions):

  • Calls are synchronous and happen on the thread that created the DOM, the main thread next to the native toolkit. Reading layout (getBoundingClientRect) flushes style and layout first, as in a browser.
  • Strings are UTF-8 plus a length. Strings and arrays returned to the caller stay valid until the next call into the same DOM.
  • A function returns 0 or an error code, a DOMException name or one of a few more (niso_status), with the message in niso_dom_last_error.
  • Listeners and other callbacks are a function pointer, user data and a drop function, which the DOM calls exactly once when it no longer needs them.
  • any values cross as their structured serialization (Structured clone).

A page loaded from a URL has that URL’s origin. A native app’s document has no URL: it’s at about:blank with the app’s origin for storage, https://<app>.app.niso from the executable’s name, or what the host sets with niso_dom_set_app_origin (iOS and Android use the bundle id or package name). See Storage.

An app in any language is a library that exports niso_app_main(dom) and builds its document there: elements, a <style> element with its CSS, listeners, timers. After that its code runs only in those callbacks, and the host runs the event loop exactly as for a JavaScript page (niso_app.h, Native apps).