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.
One API, from WebIDL
Section titled “One API, from WebIDL”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).
Node ids and handles
Section titled “Node ids and handles”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).
How calls behave
Section titled “How calls behave”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 inniso_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.
anyvalues cross as their structured serialization (Structured clone).
The document’s origin
Section titled “The document’s origin”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.
Native apps
Section titled “Native apps”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).