/* The hand-written part of the niso C API: types and runtime functions that
 * do not come from WebIDL. The generated niso.h includes this file.
 *
 * Rules every caller follows:
 * - All calls are synchronous and happen on the thread that created the DOM.
 * - Strings are UTF-8 plus length, never NUL-terminated.
 * - Strings and arrays returned through out parameters stay valid until the
 *   next call into the same DOM. Copy them before calling again.
 * - Functions return 0 on success or a niso_status error code; the message is
 *   in niso_dom_last_error().
 * - Handles are generational: a stale handle is NISO_INVALID_HANDLE, never a
 *   different object. 0 is the null handle.
 * - A handle stays usable only while something holds it: the document (for
 *   connected nodes), a detached tree that something else holds, or a
 *   niso_retain. niso_dom_collect() drops everything else.
 * - Callbacks passed with a drop function are owned by the DOM, which calls
 *   drop exactly once when it no longer needs them. Drop functions must not
 *   call back into the DOM.
 * - A WebIDL dictionary is a struct in niso.h whose fields follow the order
 *   the IDL declares its members in, inherited members first. A member added
 *   later goes at the end, so existing fields keep their offsets: in
 *   niso_add_event_listener_options, capture, passive and once are where they
 *   were, and has_signal and signal (AbortSignal) come after them.
 */
#pragma once
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <string.h>

#ifdef __cplusplus
extern "C" {
#endif

typedef struct niso_dom niso_dom;
typedef uint64_t niso_handle;

typedef struct niso_str {
    const char* ptr;
    size_t len;
} niso_str;

typedef struct niso_bytes {
    const uint8_t* ptr;
    size_t len;
} niso_bytes;

typedef struct niso_handles {
    const niso_handle* ptr;
    size_t len;
} niso_handles;

typedef struct niso_strs {
    const niso_str* ptr;
    size_t len;
} niso_strs;

/* One item of a (Node or DOMString) list: a node when node is non-zero, else str. */
typedef struct niso_node_or_string {
    niso_handle node;
    niso_str str;
} niso_node_or_string;

/* A (BufferSource or USVString) value: UTF-8 text when is_string, else bytes. */
typedef struct niso_buffer_or_string {
    const uint8_t* ptr;
    size_t len;
    bool is_string;
} niso_buffer_or_string;

typedef void (*niso_drop_fn)(void* userdata);

/* A value the binding owns and the DOM holds by reference ([NisoHeld] any):
 * data is opaque to the DOM, which calls drop once when it lets go. A null
 * data is no value. Results hand back data, borrowed. */
typedef struct niso_held {
    void* data;
    niso_drop_fn drop;
} niso_held;

enum niso_status {
    NISO_OK = 0,
    NISO_INDEX_SIZE_ERROR = 1,
    NISO_HIERARCHY_REQUEST_ERROR = 3,
    NISO_WRONG_DOCUMENT_ERROR = 4,
    NISO_INVALID_CHARACTER_ERROR = 5,
    NISO_NO_MODIFICATION_ALLOWED_ERROR = 7,
    NISO_NOT_FOUND_ERROR = 8,
    NISO_NOT_SUPPORTED_ERROR = 9,
    NISO_INVALID_STATE_ERROR = 11,
    NISO_SYNTAX_ERROR = 12,
    NISO_INVALID_MODIFICATION_ERROR = 13,
    NISO_NAMESPACE_ERROR = 14,
    NISO_INVALID_ACCESS_ERROR = 15,
    NISO_IN_USE_ATTRIBUTE_ERROR = 10,
    NISO_SECURITY_ERROR = 18,
    NISO_ABORT_ERROR = 20,
    NISO_QUOTA_EXCEEDED_ERROR = 22,
    NISO_DATA_CLONE_ERROR = 25,
    NISO_CONSTRAINT_ERROR = 200,
    NISO_DATA_ERROR = 201,
    NISO_TRANSACTION_INACTIVE_ERROR = 202,
    NISO_READ_ONLY_ERROR = 203,
    NISO_VERSION_ERROR = 204,
    NISO_UNKNOWN_ERROR = 205,
    NISO_NOT_READABLE_ERROR = 206,
    NISO_OPERATION_ERROR = 207,
    NISO_TYPE_ERROR = 100,
    NISO_RANGE_ERROR = 101,
    NISO_INVALID_HANDLE = 102,
    NISO_INTERNAL_ERROR = 103,
    /* A callback threw, and the binding rethrows what it threw. */
    NISO_CALLBACK_ERROR = 104,
    /* WebAssembly's CompileError, LinkError and RuntimeError. */
    NISO_WASM_COMPILE_ERROR = 105,
    NISO_WASM_LINK_ERROR = 106,
    NISO_WASM_RUNTIME_ERROR = 107,
};

/* The event loop.
 *
 * The Window's setTimeout, setInterval and requestAnimationFrame
 * (niso_window_or_worker_global_scope_* and
 * niso_animation_frame_provider_* in niso.h) schedule callbacks in the DOM,
 * from any language. niso_dom_run runs every task due by until, in ms on the
 * niso_dom_now clock, each followed by the task hook: the tasks the DOM
 * queues (events such as popstate and hashchange, session history
 * traversal), the networking task source's tasks as they arrive (a
 * WebSocket's open, message, error and close events, its connections read on
 * the DOM's thread), the rendering update's script steps whenever they have
 * work (scroll and media events, resources that finished loading, resize
 * observations), and timers in the order they are due. Stylesheets and
 * other resources the network loaded are delivered only there, never while
 * a task runs. Animation frame callbacks run in the rendering
 * update (niso_render_frame), all of those requested before it with the
 * frame's time; when none comes by the first frame boundary after the first
 * request (boundaries are 16 ms apart, from the last rendering update),
 * niso_dom_run gives one itself at that boundary's time, so runs that
 * present no frames see them too, at the same times however fast the
 * machine is. The frame's time is also what CSS transitions and animations
 * and the document timeline read, through the frame's paint, until the next
 * frame, except that a later turn (a task with its microtasks, a task of the
 * rendering steps outside a frame such as a stylesheet's delivery, a resize
 * observation or a scroll event, input the host delivers (niso_dom_tap,
 * niso_dom_pointer, niso_dom_wheel, niso_dom_key and the niso_dom_*_input
 * and niso_dom_text_commit calls each start one), or what the host does
 * between a paint or a run and the next run, scripts it evaluates included)
 * that reads it a
 * frame or more after it moves it to the latest boundary; it then holds
 * still for the rest of that turn, as in browsers.
 * A task due
 * after now skips the clock ahead to it, so an until past now is virtual
 * time, except while a connection is open: then it waits for the network in
 * real time, until the task is due or until, whichever comes first. Tasks
 * scheduled while it runs wait for the next call when they are due after
 * until. It may return with work left: what native modules sent while it
 * ran waits for the next run, and once until has passed and it has run for
 * a frame's time (16 ms), it returns between batches of tasks that keep
 * queueing more (IndexedDB requests whose handlers make more), after the
 * rendering steps, so a run to now holds a host's frame for at most about
 * one frame. Whatever it leaves is a run's work: niso_dom_needs_frame holds
 * for the tasks and messages left, and niso_dom_next_timer is now for those
 * and for a timer that came due meanwhile, so a host checks both after
 * every run rather than taking a return for an idle page. It collects the
 * DOM first and after each task: what a caller holds across calls must be
 * retained.
 *
 * A host presenting frames runs the page on a display frame:
 *
 *   niso_dom_run(dom, niso_dom_now(dom));
 *   niso_render_frame(dom, &frame);
 *
 * and on the next one while niso_dom_needs_frame says so: an animation runs
 * (CSS or Web Animations), an animation frame callback waits, or there is
 * other work a run would do:
 * tasks the DOM queued (an IndexedDB request's events, one a run left for
 * the next), what a native module, another event loop or the network
 * delivered, a scroll, media or media query change event the rendering
 * steps fire. It holds exactly when a run or a frame has work, and
 * niso_dom_next_timer is now exactly when a run can make progress. Otherwise
 * it runs the page again when the user acts, at niso_dom_next_timer (ms on
 * the niso_dom_now clock: now while a run has work, 16 ms from now at the
 * latest while a secure WebSocket is open, which cannot wake the host,
 * infinity with no timer set), or when the wake hook says a native
 * module, a worker, another event loop or the network delivered something
 * (niso_dom_needs_frame also holds while such a delivery waits). The hook
 * may be called from any thread until niso_dom_free, so it must only
 * schedule a run and never call niso. niso_dom_set_wake_hook and
 * niso_dom_free wait for the calls other threads have in flight, so once
 * either returns the old hook's userdata may go. */
int32_t niso_dom_run(niso_dom* dom, double until);
bool niso_dom_needs_frame(niso_dom* dom);
double niso_dom_next_timer(niso_dom* dom);
/* The C surface of niso-host's Wake trait (waker.rs); a null hook is none. */
typedef void (*niso_wake_fn)(void* userdata);
void niso_dom_set_wake_hook(niso_dom* dom, niso_wake_fn hook, void* userdata);

/* A storage driver: where IndexedDB databases and localStorage areas keep
 * their data (docs/storage.md). It stores, per origin and endpoint, named
 * databases with a version, object stores and indexes (named in UTF-16 code
 * units), and records ordered by key. Keys arrive encoded so that comparing
 * their bytes (memcmp, then length) orders them as IndexedDB orders keys;
 * values and key paths are opaque bytes. All calls come from the DOM's
 * thread. Each function returns 0, NISO_QUOTA_EXCEEDED_ERROR when the
 * storage is full, or another nonzero status for any other failure.
 *
 * endpoint: 0 IndexedDB, 1 localStorage, 2 sessionStorage.
 * mode: 0 "readonly", 1 "readwrite", 2 "versionchange".
 * durability: 0 "default", 1 "strict", 2 "relaxed".
 *
 * A database has at most one "readwrite" or "versionchange" transaction at
 * a time, and a "readonly" one that runs beside it uses other object
 * stores, so a driver can run all of a database's transactions on one
 * underlying connection. */
typedef struct niso_storage_range_end {
    const uint8_t* key;
    size_t key_len;
    /* For an index, bounds the (key, primary key) pair; otherwise the key
     * alone, so all of the key's entries are in or out. */
    const uint8_t* primary;
    size_t primary_len;
    bool has_primary;
    bool open;
} niso_storage_range_end;

typedef struct niso_storage_range {
    niso_storage_range_end lower;
    niso_storage_range_end upper;
    bool has_lower;
    bool has_upper;
} niso_storage_range;

typedef struct niso_storage_store_info {
    uint64_t id;
    const uint16_t* name;
    size_t name_len;
    const uint8_t* key_path;
    size_t key_path_len;
    bool has_key_path;
    bool auto_increment;
} niso_storage_store_info;

typedef struct niso_storage_index_info {
    uint64_t id;
    const uint16_t* name;
    size_t name_len;
    const uint8_t* key_path;
    size_t key_path_len;
    bool unique;
    bool multi_entry;
} niso_storage_index_info;

/* How open reports a database's schema: store once per object store, then
 * index once per index. The pointers need only live for the call. */
typedef struct niso_storage_schema {
    void* ctx;
    void (*store)(void* ctx, const niso_storage_store_info* store);
    void (*index)(void* ctx, uint64_t store, const niso_storage_index_info* index);
} niso_storage_schema;

/* One entry of a scan: an object store record (primary is its key again) or
 * an index entry and the primary key of its record; value is the record's
 * value when the scan asked for values. Returns false to stop the scan. */
typedef bool (*niso_storage_visit_fn)(void* ctx, const uint8_t* key, size_t key_len, const uint8_t* primary,
                                      size_t primary_len, const uint8_t* value, size_t value_len, bool has_value);

typedef struct niso_storage_driver {
    void* userdata;
    /* Calls each with every database the origin has for the endpoint. */
    int32_t (*databases)(void* userdata, const char* origin, size_t len, uint8_t endpoint, void* ctx,
                         void (*each)(void* ctx, const uint16_t* name, size_t name_len, uint64_t version));
    /* Opens a database, creating it at version 0 with no object stores if
     * it does not exist, and reports its version and schema. */
    int32_t (*open)(void* userdata, const char* origin, size_t len, uint8_t endpoint, const uint16_t* name,
                    size_t name_len, uint64_t* db, uint64_t* version, const niso_storage_schema* schema);
    void (*close)(void* userdata, uint64_t db);
    /* Deletes a database that is not open. */
    int32_t (*delete_database)(void* userdata, const char* origin, size_t len, uint8_t endpoint,
                               const uint16_t* name, size_t name_len);
    int32_t (*begin)(void* userdata, uint64_t db, uint8_t mode, uint8_t durability, uint64_t* txn);
    /* Writes everything the transaction did, atomically. */
    int32_t (*commit)(void* userdata, uint64_t txn);
    /* Undoes everything the transaction did, version and schema included. */
    void (*abort)(void* userdata, uint64_t txn);
    /* Schema changes, only in a "versionchange" transaction. The core picks
     * the ids: unique among a database's object stores (or indexes), and
     * reused only after the transaction that used them aborted. */
    int32_t (*set_version)(void* userdata, uint64_t txn, uint64_t version);
    int32_t (*create_store)(void* userdata, uint64_t txn, const niso_storage_store_info* store);
    /* Deletes an object store, its records, its indexes and their entries. */
    int32_t (*delete_store)(void* userdata, uint64_t txn, uint64_t store);
    int32_t (*rename_store)(void* userdata, uint64_t txn, uint64_t store, const uint16_t* name, size_t len);
    int32_t (*create_index)(void* userdata, uint64_t txn, uint64_t store, const niso_storage_index_info* index);
    int32_t (*delete_index)(void* userdata, uint64_t txn, uint64_t index);
    int32_t (*rename_index)(void* userdata, uint64_t txn, uint64_t index, const uint16_t* name, size_t len);
    /* Stores a record, replacing the one with the same key after deleting
     * that one's index entries. */
    int32_t (*put)(void* userdata, uint64_t txn, uint64_t store, const uint8_t* key, size_t key_len,
                   const uint8_t* value, size_t value_len);
    /* Adds the entry (key, primary) to an index. */
    int32_t (*put_index)(void* userdata, uint64_t txn, uint64_t index, const uint8_t* key, size_t key_len,
                         const uint8_t* primary, size_t primary_len);
    /* Deletes the records in the range, and their index entries. */
    int32_t (*delete_range)(void* userdata, uint64_t txn, uint64_t store, const niso_storage_range* range);
    int32_t (*clear)(void* userdata, uint64_t txn, uint64_t store);
    /* Visits the entries of an object store (index false) or an index in the
     * range, in key order (then primary key order), or in reverse. */
    int32_t (*scan)(void* userdata, uint64_t txn, bool index, uint64_t source, const niso_storage_range* range,
                    bool reverse, bool values, void* ctx, niso_storage_visit_fn visit);
    int32_t (*count)(void* userdata, uint64_t txn, bool index, uint64_t source, const niso_storage_range* range,
                     uint64_t* out);
    /* An object store's key generator's current number: 1 when created. */
    int32_t (*generator)(void* userdata, uint64_t txn, uint64_t store, uint64_t* out);
    int32_t (*set_generator)(void* userdata, uint64_t txn, uint64_t store, uint64_t current);
    /* Called once with userdata when the DOM lets go of the driver; may be NULL. */
    niso_drop_fn drop;
} niso_storage_driver;

/* Storage (IndexedDB and localStorage; sessionStorage always lives in memory)
 * is keyed by the document's origin, and a document with an opaque origin has
 * none. A document whose URL matches about:blank (with any query or
 * fragment), as a native app's does, has the app's origin: the origin of the
 * URL given to niso_dom_set_app_origin, which must have a tuple origin (else
 * NISO_SYNTAX_ERROR) and come before script first uses storage (else
 * NISO_INVALID_STATE_ERROR); else https://<app>.app.niso, for the
 * executable's name. So a host that loads third-party HTML at about:blank
 * hands it the app's storage: give such a page its own URL. By default
 * storage goes to SQLite files under $NISO_STORAGE_DIR, else a directory
 * named for the executable in the platform's per-user data directory
 * (Library/Application Support/niso/<app> on Apple platforms,
 * %APPDATA%/niso/<app> on Windows, $XDG_DATA_HOME/niso/<app> or
 * ~/.local/share/niso/<app> elsewhere), else the temporary directory. Apps
 * that share an executable (every Android app; JVM apps run by java) share
 * these defaults, so their hosts set both. niso_dom_set_storage_directory
 * puts the files under path instead, or keeps everything in memory when len
 * is 0; niso_dom_set_storage_driver hands storage to the host's own driver,
 * which the DOM copies. Either must come before script first uses storage,
 * else NISO_INVALID_STATE_ERROR. Every function of the driver but drop must
 * be set, else NISO_TYPE_ERROR. The DOM owns the driver's userdata only once
 * niso_dom_set_storage_driver returns 0, and then calls drop when it lets go
 * of it; on an error the host still owns it. */
int32_t niso_dom_set_storage_directory(niso_dom* dom, const char* path, size_t len);
int32_t niso_dom_set_storage_driver(niso_dom* dom, const struct niso_storage_driver* driver);
int32_t niso_dom_set_app_origin(niso_dom* dom, const char* origin, size_t len);
/* Structured serialization (docs/structured-clone.md): how an IDL any value
 * crosses the C API (history.state, a PerformanceMark's detail, a message's
 * niso_message_event_serialized_data). A decoded serialization is a graph:
 * the value, and objects a value refers to by number, each with entries.
 * Every reference to one object has its number, so shared objects and
 * cycles can be rebuilt. Strings are UTF-8, with a JS string's lone
 * surrogates as U+FFFD. Decoding needs no DOM and works on any thread. */
typedef struct niso_structured_clone niso_structured_clone;

enum {
    NISO_CLONE_UNDEFINED = 0,
    NISO_CLONE_NULL = 1,
    NISO_CLONE_BOOLEAN = 2,         /* number: 0 or 1 */
    NISO_CLONE_NUMBER = 3,          /* number */
    NISO_CLONE_BIGINT = 4,          /* bytes: magnitude, least significant byte first; negative */
    NISO_CLONE_STRING = 5,          /* string */
    /* Objects, from here on: object is the object's number. */
    NISO_CLONE_BOOLEAN_OBJECT = 16, /* number: 0 or 1 */
    NISO_CLONE_NUMBER_OBJECT = 17,  /* number */
    NISO_CLONE_BIGINT_OBJECT = 18,  /* bytes, negative, as NISO_CLONE_BIGINT */
    NISO_CLONE_STRING_OBJECT = 19,  /* string */
    NISO_CLONE_DATE = 20,           /* number: ms since the epoch */
    NISO_CLONE_REGEXP = 21,         /* string: the source; entry "flags" */
    NISO_CLONE_ARRAY_BUFFER = 22,   /* bytes; number: a resizable one's max byte length, else -1 */
    NISO_CLONE_VIEW = 23,           /* string: "Uint8Array", "DataView", ...; bytes: what it views
                                       (none when its buffer was transferred); entry "buffer" */
    NISO_CLONE_MAP = 24,            /* entries: each key and value */
    NISO_CLONE_SET = 25,            /* entries: each value, its key undefined */
    NISO_CLONE_ERROR = 26,          /* string: "Error", "TypeError", ...; entries "message", "stack"
                                       and "cause", those it has */
    NISO_CLONE_ARRAY = 27,          /* number: its length; entries: its properties ("0", "1", ...) */
    NISO_CLONE_OBJECT = 28,         /* entries: its properties */
    NISO_CLONE_PLATFORM = 29,       /* string: its interface ("DOMException", "DOMRect", ...); entry "data" */
    NISO_CLONE_TRANSFERRED = 30,    /* number: its index among a message's ports */
};

typedef struct niso_clone_value {
    uint32_t kind;    /* a NISO_CLONE_* value */
    uint32_t object;  /* an object's number */
    double number;
    niso_str string;
    niso_bytes bytes;
    bool negative;
    size_t entries;   /* an object's entry count, for niso_structured_clone_entry */
} niso_clone_value;

/* Decodes a serialization, or returns NULL when the bytes are not one (or
 * nest objects more than 256 deep, or are NULL). What it hands out lives
 * until niso_structured_clone_free. */
niso_structured_clone* niso_structured_clone_decode(const uint8_t* bytes, size_t len);
void niso_structured_clone_free(niso_structured_clone* clone);
/* The serialized value; NISO_CLONE_UNDEFINED for a NULL clone. */
niso_clone_value niso_structured_clone_value(const niso_structured_clone* clone);
/* An object's entry: a property's key is a string, as are the named entries
 * above; a Map's is any value. Returns NISO_NOT_FOUND_ERROR when there is no
 * such entry, or no clone. */
int32_t niso_structured_clone_entry(const niso_structured_clone* clone, uint32_t object, size_t index, niso_clone_value* key,
                                    niso_clone_value* value);

/* The binding's hooks: the C surface of niso-core's DocumentHooks trait
 * (hooks.rs), which a Rust binding implements directly. Each setter sets
 * its own hook and leaves the others as they were; a null hook is none, and
 * the document does the work a hook needs only while it is set. The
 * userdata stays the binding's. */
/* Called after a call connects a subtree to the document or disconnects it
 * from the document: new_root is the root of the tree the subtree now belongs
 * to. Moves that leave it in the document, or out of it, are not reported.
 * The hook may call into the C API, but is not called again from inside
 * itself: the moves those calls make are delivered after it returns, in the
 * order they were made. */
typedef void (*niso_tree_hook_fn)(void* userdata, niso_handle subtree, niso_handle new_root);

/* Called at the end of each task the DOM runs: a timer, an animation frame
 * callback, a rendering update's scroll events, and a host's input
 * (niso_dom_tap and the other user input calls below). No binding code is on
 * the stack then, so a binding performs its microtask checkpoint here. */
typedef void (*niso_task_hook_fn)(void* userdata);
/* Loads the resource at url (a UTF-8 absolute URL) for the document: returns
 * 0 and its bytes in *out, valid until the loader is called again, or
 * nonzero when there is none. Loaders must not call back into the DOM. The
 * C surface of niso-core's ResourceLoader trait (loading.rs): a null loader
 * is none, and the document's own loads are then the network's. */
typedef int32_t (*niso_loader_fn)(void* userdata, const char* url, size_t len, niso_bytes* out);

/* Called after a call connects script elements that have not started, once
 * per script in tree order; the binding runs them. Scripts parsed by
 * niso_dom_load_html come with parser_inserted set, followed by a null
 * script once parsing has finished. Scripts parsed into innerHTML or
 * outerHTML never start. */
typedef void (*niso_script_hook_fn)(void* userdata, niso_handle script, bool parser_inserted);
typedef void (*niso_microtask_hook_fn)(void* userdata);
/* Called before a MessageEvent the platform made (a message from a port, a
 * worker, a BroadcastChannel, window.postMessage or a WebSocket) is
 * dispatched: the binding deserializes niso_message_event_serialized_data,
 * or takes niso_message_event_native_data (what the platform made the data
 * from, a WebSocket's frame) when there is no serialization,
 * into a value and sets it with niso_message_event_set_data. Returns false
 * when it cannot, and messageerror fires instead. Native code that is no
 * binding reads those two instead of data, with no hook. */
typedef bool (*niso_message_hook_fn)(void* userdata, niso_handle event);
/* An event handler the handler hook compiled, as an EventHandler setter
 * takes it. */
typedef struct niso_event_handler {
    int32_t (*fn)(void* userdata, niso_handle event);
    void* userdata;
    niso_drop_fn drop;
} niso_event_handler;
/* Compiles an event handler content attribute when its handler's value is
 * first needed: source is element's attribute name (like "onclick"), whose
 * event handler is target's, the element's or, for body and frameset, its
 * window's. form is the element's form owner, were it form-associated, or
 * 0. The binding compiles it with the element's scope, fills in out and
 * returns true, or returns false after reporting a compile error. With no
 * hook, content attributes are inert. */
typedef bool (*niso_handler_hook_fn)(void* userdata, niso_handle target, niso_handle element, niso_handle form, const char* name,
                                     size_t name_len, const char* source, size_t source_len, niso_event_handler* out);
/* The viewport is width x height device pixels at scale device pixels per CSS
 * pixel.
 *
 * Known limit: blitz walks a subtree that joins or leaves the document
 * recursively, so on an 8 MiB stack, removing a connected subtree more than
 * about 30k levels deep overflows it (repros/blitz-dom-subtree-walk-recursive).
 * Run the DOM on a thread with a larger stack for documents that deep. */
niso_dom* niso_dom_new(uint32_t width, uint32_t height, float scale);
void niso_dom_free(niso_dom* dom);
void niso_dom_set_viewport(niso_dom* dom, uint32_t width, uint32_t height, float scale);
/* The user's color scheme: dark 1 or light 0, what prefers-color-scheme
 * matches and the scheme system colors take where a page supports both.
 * Hosts set it at start and whenever the user changes it. Light until set. */
void niso_dom_set_color_scheme(niso_dom* dom, uint8_t dark);
/* The user's text size (Dynamic Type, Android's font scale), as the root font
 * scale: the size text of each CSS font size is laid out and drawn at.
 * points holds count (CSS px, px) pairs, the CSS px increasing and the px
 * never decreasing, between which sizes
 * are interpolated, and proportional to the nearest pair outside them, so a
 * linear scale s is one pair (1, s) and a nonlinear one (Android 14's
 * FontScaleConverter, iOS's text styles at the user's size) is a few. No
 * pairs, the default, draws text at its CSS size. Every computed font size
 * scales; ems, rems and other lengths keep resolving against the CSS sizes,
 * so nothing scales twice, and a line height given as a length scales with
 * its element's font. getComputedStyle reports the CSS sizes. Hosts set it at
 * start and whenever the user changes it; the page is laid out again. Template
 * text keeps its CSS size, which targets scale themselves (see
 * niso_render_surfaces). Fails (Range) unless the pairs are positive, the CSS
 * px increasing and the px never decreasing. */
int32_t niso_dom_set_text_scale(niso_dom* dom, const float* points, size_t count);
/* The platform's font weight adjustment, Android's
 * Configuration.fontWeightAdjustment (300 under Bold text): every font is
 * matched and drawn at its CSS weight plus this, within 1 to 1000, as the
 * platform's own text views draw theirs. getComputedStyle reports the CSS
 * weights. 0 until set. Hosts set it at start and whenever the user changes
 * it; the page is laid out again. Fails (Range) outside -1000 to 1000. */
int32_t niso_dom_set_font_weight_adjustment(niso_dom* dom, int32_t adjustment);
/* The weights the platform draws its system font (system-ui) at for CSS
 * weights 100, 200, ... 900, as it resolves that font itself: on Apple
 * platforms the wght of UIFont.systemFont(ofSize:weight:) in the current
 * trait collection, which Bold Text raises one named instance (Regular to
 * Semibold, Bold to Black). Weights between are interpolated. count is 9, or
 * 0 for the CSS weights, the default. Hosts set it at start and whenever the
 * user changes it; the page is laid out again. Fails (Range) unless the
 * weights are within 1 to 1000 and never decrease. */
int32_t niso_dom_set_system_font_weights(niso_dom* dom, const float* weights, size_t count);
/* The host's own user-agent style, UTF-8 CSS: the platform's palette, type
 * scale and shapes for the current color scheme, as custom
 * properties on :root (--apple-system-* on Apple platforms, Material's
 * --md-sys-* on Android, and --niso-safe-area-inset-* on both for a page
 * that covers the screen, see ROOT). It replaces the previous one; empty
 * removes it.
 * Hosts set it again whenever what it says changes.
 * A page that wants to look native asks for niso's platform sheet with
 * <meta name="niso-platform-style" content="auto">, the first such meta in
 * its head: ios, android or web name a platform, auto the one niso is built
 * for (the web elsewhere), and off or anything else none, the default. The
 * sheet is crates/niso-core/ua/niso.css's tokens (--niso-color-*,
 * --niso-font-* with --niso-tracking-*, --niso-corner-*, --niso-safe-*), the
 * platform style's properties under one set of names with web values where
 * it has none, then that platform's base sheet (ios.css, android.css,
 * web.css: the page's colors, type and margins, and overflow-wrap:
 * break-word), all in the niso-platform cascade layer of the author origin.
 * The meta holds it as a stylesheet link holds its sheet: the layer is
 * declared where the meta is, any unlayered rule of the page wins over it,
 * and the sheet follows the meta's content and goes with the meta. */
void niso_dom_set_platform_style(niso_dom* dom, const char* css, size_t len);
niso_handle niso_dom_document(niso_dom* dom);
niso_handle niso_dom_window(niso_dom* dom);
/* Milliseconds since the DOM was created: the time origin of event timestamps
 * and animations. niso_dom_advance_clock skips it ahead, for virtual time. */
double niso_dom_now(niso_dom* dom);
void niso_dom_advance_clock(niso_dom* dom, double ms);
/* Milliseconds since the Unix epoch on the same clock: the time origin
 * (performance.timeOrigin) plus niso_dom_now, so it skips ahead with it. What
 * a binding's Date.now() and new Date() read, so they agree with timers. */
double niso_dom_wall_clock(niso_dom* dom);
niso_str niso_dom_last_error(niso_dom* dom);
void niso_dom_set_tree_hook(niso_dom* dom, niso_tree_hook_fn hook, void* userdata);
void niso_dom_set_task_hook(niso_dom* dom, niso_task_hook_fn hook, void* userdata);
void niso_dom_set_script_hook(niso_dom* dom, niso_script_hook_fn hook, void* userdata);
/* Called when a call queues a microtask (to notify mutation observers or to
 * process the backup custom element queue): the embedder queues a microtask
 * that calls niso_dom_perform_microtask. */
void niso_dom_set_microtask_hook(niso_dom* dom, niso_microtask_hook_fn hook, void* userdata);
/* Processes the backup custom element queue, then delivers the
 * MutationObservers' records to their callbacks. */
int32_t niso_dom_perform_microtask(niso_dom* dom);
/* An embedder with microtasks brackets each microtask checkpoint it performs
 * (HTML's "perform a microtask checkpoint": after each task, and after each
 * callback the DOM calls when no script is running) with these. */
void niso_dom_begin_microtask_checkpoint(niso_dom* dom);
void niso_dom_end_microtask_checkpoint(niso_dom* dom);
/* Where the document's resources come from: stylesheets, fonts and other
 * resources its CSS references, and the scripts a binding runs. */
void niso_dom_set_loader(niso_dom* dom, niso_loader_fn loader, void* userdata);
/* Sets the document's URL, starting a new session history. */
int32_t niso_dom_set_url(niso_dom* dom, const char* url, size_t len);
/* Replaces the document's contents with the HTML document at url, parsed
 * from html. Stylesheets load through the loader; scripts go to the script
 * hook. */
int32_t niso_dom_load_html(niso_dom* dom, const char* html, size_t len, const char* url, size_t url_len);
/* The loader's bytes for url, valid until the next call into the DOM. */
int32_t niso_dom_load_resource(niso_dom* dom, const char* url, size_t len, niso_bytes* out);
/* A script's bytes, as niso_dom_load_resource gets them but with fetch's
 * options (niso_fetch_options, below): the request's client is the page,
 * or a module worker's creator until its script has run, and *type is the response's type as niso_net_take numbers it, opaque for
 * a script whose errors are muted. */
typedef struct niso_fetch_options niso_fetch_options;
int32_t niso_dom_load_script(niso_dom* dom, const char* url, size_t len, const niso_fetch_options* options, niso_bytes* out,
                             uint8_t* type);
void niso_dom_collect(niso_dom* dom);
size_t niso_dom_live_nodes(niso_dom* dom);

/* The render stream.
 *
 * niso_render_frame runs the rendering update (scroll events, then
 * animation frame callbacks, then style and layout) and returns what changed in the rendered box tree since
 * the previous call, as a byte stream valid until the next call into the DOM. The first frame, and the first after
 * niso_render_reset, describes the whole tree. Hosts keep one view per render
 * node and apply the ops in order.
 *
 * Values are little-endian. Geometry is in CSS pixels; colors are
 * non-premultiplied sRGB packed as 0xRRGGBBAA. Render node ids are the DOM
 * handles of the elements (or anonymous boxes) that generate the boxes.
 *
 *   1 FONT      u32 font, u32 collection index
 *               A font later TEXT ops use. niso_render_font returns its file
 *               bytes, valid for the life of the DOM. Hosts may call it while
 *               reading the frame: it leaves the frame's bytes alone.
 *   2 CREATE    u64 node
 *   3 DESTROY   u64 node
 *   4 CHILDREN  u64 node, u32 n, n x u64 child
 *               The node's children in paint order (back to front).
 *   5 FRAME     u64 node, f32 x, y, width, height
 *               The border box, relative to the parent's border box origin.
 *   6 BOX       u64 node, u32 background, f32 opacity,
 *               f32 transform a b c d e f (about the border box origin),
 *               u8 overflow: 0 visible; 1 clip (text and children are
 *               clipped to the padding box); 2 scroll (clipped, and the host
 *               scrolls the children in a native scroll view, see SCROLL),
 *               f32 border widths top right bottom left,
 *               u32 border colors top right bottom left,
 *               u8 border styles top right bottom left (0 solid, 1 dashed,
 *               2 dotted, 3 double, 4 two halves: groove and ridge),
 *               f32 corner radii top-left x y, top-right x y,
 *               bottom-right x y, bottom-left x y,
 *               u32 inner border colors top right bottom left
 *               A double border is two lines a third of the width each, at
 *               its edges. A side in two halves is drawn solid in its color
 *               from the outer edge to the middle of its width, and in its
 *               inner color from there in. Inset and outset borders come as
 *               solid ones; their colors and groove's and ridge's are shaded
 *               already, darker or lighter by side as Chrome shades them. A
 *               side's inner color is its color unless it is in two halves.
 *   7 TEXT      u64 node, u32 runs, then per run:
 *               u32 font, f32 size, u32 color, f32 synthetic skew degrees,
 *               u8 synthetic bold, u8 n, n x (u32 axis tag, f32 value),
 *               u32 glyphs, glyphs x (u32 glyph id, f32 x, f32 baseline y),
 *               u8 flags (1 right to left), f32 baseline y, f32 advance,
 *               u32 n, n x u16 text, u32 pieces, pieces x (u32 start, u32 end,
 *               f32 x)
 *               Glyph positions are relative to the node's border box origin.
 *               The text is what the run was shaped in (UTF-16): its shaped
 *               run on the line, so hosts that draw text instead of glyphs
 *               (Android's drawTextRun, which gets outline text) shape it the
 *               same way, kerning across a color change included. The pieces
 *               are ranges of it to draw, each from pen x on the baseline:
 *               the glyph run's own text, or one piece per cluster where
 *               letter-spacing moved clusters apart and per word (spaces
 *               left out) where justification or word-spacing moved words.
 *               A run whose text is unknown has none. The advance is the
 *               glyph run's width, which a host's own shaping of a single
 *               piece has to match for its positions to be the core's.
 *               Then u32 lines, per line: u8 style, u8 flags, u32 color,
 *               f32 x, y, width, thickness, f32 a, b, u32 gaps,
 *               gaps x (f32 from x, to x)
 *               The text decorations (underlines, overlines and
 *               line-throughs), each a line from x to x + width whose middle
 *               is at y, positioned like the glyphs. Styles: 0 solid; 1
 *               dashed, dashes a long with gaps b between them, starting
 *               with a dash at x; 2 dotted, round dots as wide as the line
 *               every b, the first centered half a thickness in from x,
 *               none past the end; 3 wavy, a stroke as thick as the line
 *               that leaves y downward, swinging a below and above it, in
 *               quadratic curves each b long (controls at 2a). A double line
 *               comes as two solid ones. Flag bit 0: the line paints over
 *               the text (a line-through), else under it, after the box's
 *               own background and before its glyphs. The gaps are ranges
 *               of x the line leaves out, where it would cross glyphs' ink
 *               (text-decoration-skip-ink, always auto: stylo parses the
 *               property only for Gecko); they may overlap.
 *   8 ROOT      u64 node, u32 canvas color, u8 flags, u8 scroll axes,
 *               f32 scroll width, height, f32 scroll x, y,
 *               u32 n, n x u64 fixed node
 *               The root of the tree (0 when nothing renders), the color the
 *               host fills the whole viewport with behind it (the canvas
 *               background: the root element's, or the body's, transparent
 *               when neither has one, over the host's base color, white as
 *               browsers paint it unless the embedder made the host's view
 *               transparent, like an overlay's), the flags, and the
 *               viewport's scrolling area and offset: the host scrolls the
 *               root natively. Fixed nodes are boxes fixed to the viewport,
 *               in paint order, painted above the root without scrolling;
 *               their frames are relative to the root's border box origin.
 *               Flag bit 0, cover: the page's viewport meta asks for
 *               viewport-fit=cover. The host then makes the viewport the
 *               whole window, under the status bar, cutouts and the home
 *               indicator or navigation bar, and gives the page the safe
 *               area as --niso-safe-area-inset-top, -right, -bottom and
 *               -left in its platform style (niso_dom_set_platform_style),
 *               since env(safe-area-inset-*) is 0 in stylo. Otherwise the
 *               viewport is the safe area and the host leaves them unset.
 *               The viewport units' variants, once stylo resolves them (it
 *               gives sv*, lv* and dv* the viewport size for now): all the
 *               viewport without cover; with it, large is the whole window,
 *               small the window less the system bars that can appear, and
 *               dynamic whichever is on screen, differing from large only
 *               while bars are transient (Android's immersive mode). The
 *               keyboard changes none of them, as in browsers by default.
 *   9 SCROLL    u64 node, u8 scroll axes, f32 scroll width, height,
 *               f32 scroll x, y
 *               A scroll container's scrolling area, measured from its
 *               padding box origin, and the offset to show. Scroll axes are
 *               those the user may scroll (bit 0 x, bit 1 y, from overflow
 *               auto or scroll); script may scroll either, so hosts show the
 *               offset on both but let gestures move only these. Children's frames
 *               are unscrolled: the native scroll view moves them. Sent when
 *               it changes, including when script scrolls.
 *  10 CONTROL   u64 node, u8 kind, u8 state, f32 x, y, width, height,
 *               f64 value, min, max, step; for kind 5, u32 n, n x (u8 flags,
 *               u32 n, n x u8 label (UTF-8))
 *               The node is a form control the host draws natively in the
 *               given content box, relative to the border box origin (the
 *               UA stylesheet sizes it as niso_dom_set_control_size says,
 *               so the control fits): kind 1 a
 *               switch (input type=checkbox switch), 2 a slider (input
 *               type=range), 3 a linear progress bar (progress), 4 a push
 *               button. Unlike the others a button keeps its TEXT and
 *               children, which the host shows as the button's label, in
 *               the content box. The button fills the border box, and its
 *               BOX's fill, border and radii are the native button's own,
 *               which the host draws it with instead of drawing the box: the
 *               stock style when they are the stock button's (see
 *               niso_dom_set_button_style), else a filled one, or a
 *               borderless one without a fill; 5 a radio group: a fieldset
 *               or role=radiogroup element whose radio buttons (two or more,
 *               outside groups nested in it) share a name and that has no
 *               other form controls. It is one native control with an
 *               option per radio button, labeled with the text of the label
 *               around or for it (else its value), flag 1 when that radio
 *               button is disabled: a row of up to 5 options of up to 15
 *               characters (the element is a flex container in a row) is a
 *               segmented control (iOS's segmented Picker, Android's
 *               connected button group), any other group a list (state
 *               bit 16: a menu Picker on iOS, a column of radio button rows
 *               on Android). Its box takes the control's height, see
 *               niso_dom_set_control_size, and loses its padding and border,
 *               so the control fills it; page styles on the radio buttons
 *               and labels never show. A group that holds more than its
 *               radio buttons and their labels, such as a legend or a hint,
 *               keeps it (state bit 32): the control takes only their area,
 *               its children from the first holding a radio button or a
 *               label for one to the last, which becomes one box of the
 *               control's height as wide as the group's content (sharing a
 *               row with what is beside it), and the group's box, its TEXT
 *               and its other children are drawn as any others, its fill no
 *               tint. Kind 0 when
 *               it stops being one (appearance: none, or a type change).
 *               State bits: 1 checked, 2 disabled, 4 indeterminate, 8 dark
 *               (the element's used color scheme is dark, as its
 *               color-scheme and niso_dom_set_color_scheme pick it: the host
 *               draws the control in its dark appearance), 16 a list, 32 a
 *               radio group's control that takes only part of its box. The
 *               value is a switch's checkedness (0 or 1), a slider's value
 *               between min and max in multiples of step from min (step 0:
 *               any), a progress bar's value between 0 and max, a radio
 *               group's selected option between 0 and max (n - 1), or -1
 *               for none. A control other than a button or a radio group
 *               with state bit 32 has no TEXT or children. Sent when any of
 *               it changes.
 *  11 TEXT_FIELD u64 node, u8 kind, u8 state, u8 keyboard,
 *               f32 x, y, width, height, f32 font size, u16 weight,
 *               u8 italic, u8 align, u32 color, u32 caret color,
 *               u32 n, n x u8 value, u32 n, n x u8 placeholder (UTF-8),
 *               u32 selection start, end, u8 direction
 *               The node is a text field the host edits natively: unless
 *               plain, its own stock field filling the border box (and
 *               drawn instead of the node's BOX), else a bare editor in the
 *               given content box. Kind 1 single-line (input types text,
 *               search, email, url, tel, password, number), 2 multi-line
 *               (textarea); 0 when it stops being one, with nothing after
 *               the keyboard byte. State bits: 1 disabled, 2 read-only,
 *               4 secure (password), 8 plain (the page gave it a box other
 *               than the stock field's, see niso_dom_set_field_style, which
 *               the host draws as the BOX says around a bare editor), 16
 *               focused, 32 caret (the page set caret-color: the caret is
 *               the caret color, even transparent), 64 dark (as for
 *               CONTROL). Keyboard: 0 text, 1 email,
 *               2 url, 3 phone, 4 decimal, 5 search. The text uses the
 *               system font at this size (CSS px), weight (100-900), slant,
 *               alignment (0 start, 1 center, 2 end) and color, with a
 *               caret of the caret color when the caret bit is set, else
 *               the platform's. The value is
 *               what the field shows: the host replaces its text with it
 *               only when it differs from what the host last reported. The
 *               selection is the field's, in UTF-16 code units of the value
 *               (start <= end, equal for a caret; direction 0 none, 1
 *               forward, 2 backward): the host selects it when it differs
 *               from what the host last reported, so script's select(),
 *               setSelectionRange() and value changes (which put the caret
 *               at the end) reach the native field. An edit the page
 *               refuses (a beforeinput listener canceled it) leaves the
 *               field as it was, and the next frame sends this op again,
 *               though nothing in it changed: the host shows the value and
 *               selection it sends, putting its native field back. Hosts report the
 *               user's selection with niso_dom_text_edit and
 *               niso_dom_text_select. (The selection came after the other
 *               fields: hosts written before it must now read its 9
 *               bytes.) A text field has no TEXT or
 *               children. Sent when any of it changes.
 *  12 IMAGE     u64 node, u32 image, f32 x, y, width, height,
 *               f32 clip x, y, width, height, u8 flags,
 *               u32 n, n x (u32 x, y, width, height)
 *               The node's picture (an img element's decoded image, or a
 *               canvas's bitmap), drawn scaled into the rect, which
 *               object-fit and object-position place, clipped to the clip
 *               rect (the content box), both relative to the border box
 *               origin, above the BOX and below the TEXT and children.
 *               niso_render_image returns the image's pixels; the same image
 *               keeps its id. Image u32 max when the node stops having one.
 *               Flags: 1 opaque (ignore the pixels' alpha: a canvas whose
 *               context has no alpha), 2 pixelated (image-rendering:
 *               pixelated or crisp-edges: scale with nearest-neighbor
 *               filtering, else smoothly), 4 live (a canvas's bitmap: its
 *               pixels change and its size may, so hosts update their copy
 *               rather than keep it). The rectangles are a live image's
 *               pixels that changed since the node's previous IMAGE, all of
 *               them when the host hasn't had the image at this size; the
 *               host copies those from niso_render_image. None for other
 *               images. Sent when any of it changes, including a live
 *               image's pixels. A canvas no script has drawn into has no
 *               image (it is transparent), and its fallback content does
 *               not render.
 *  13 MEDIA     u64 node, u32 load, u32 n, n x u8 source URL (UTF-8),
 *               u8 flags, f32 volume, f32 playback rate, f64 seek
 *               An audio element's player, which the host keeps whether or
 *               not the element renders. It plays the source when flag 1 is
 *               set and pauses otherwise; flag 2 mutes it and flag 4 loops
 *               it. A new load number means load the source again from the
 *               start. Seek is a position in seconds to jump to once, NaN
 *               for none. An empty source means stop and drop the player.
 *               Hosts report the player with niso_dom_media_update. Sent
 *               when any of it changes.
 *  14 NOW_PLAYING  u32 n, n x u8 title, then artist, album and artwork URL
 *               the same way, u8 playback state (0 none, 1 paused,
 *               2 playing), u32 actions, f64 duration, f64 position,
 *               f64 rate
 *               What the platform shows as now playing (lock screen,
 *               notification, control center) and the controls it offers:
 *               bit n of actions is niso_dom_media_session_action's action
 *               n. Sent when any of it changes. duration, position and rate
 *               are the page's position state (setPositionState), NaN
 *               without one: seconds, the duration infinite for a live
 *               stream, the position as of this frame, from which hosts
 *               advance it at the rate while the state is playing. Without
 *               a seekto action (bit 7) hosts offer no seeking.
 *  15 EFFECTS   u64 node, u32 n, n x shadow (u32 color, f32 x, y, blur,
 *               spread, u8 inset), u32 n, n x filter (u8 kind, f32 amount),
 *               u32 n, n x backdrop filter (the same)
 *               The box's box-shadow (in paint order, first on top, with
 *               the blur radius as CSS gives it, twice the Gaussian's
 *               standard deviation), filter and backdrop-filter. Filter
 *               kinds: 1 blur (the standard deviation, CSS pixels),
 *               2 brightness, 3 contrast, 4 grayscale, 5 hue-rotate
 *               (degrees), 6 invert, 7 opacity, 8 saturate, 9 sepia, each
 *               with its CSS amount (1 is 100%) and applied in order;
 *               drop-shadow() and url() are left out. A filter applies to
 *               the box with its text and children, as one layer; a backdrop
 *               filter to what is behind the box, inside its border box's
 *               curve. Sent when any of it changes; all three empty when the
 *               box stops having them. Boxes that never have any get none.
 *
 * Versions: ops carry no length, so a host reads every op and field of the
 * stream. Ops and their fields only grow at the end: a new op takes the next
 * number and a new field goes after an op's last, and niso_render_version
 * goes up with each, so a host updates by reading the additions. A host
 * checks niso_render_version() against the NISO_RENDER_VERSION it was
 * written for and reads no other version's stream.
 *
 * Hosts report the user's scrolling with niso_dom_scroll; the next
 * niso_render_frame fires the scroll events. They report the user's changes
 * to native controls with niso_dom_control_input and niso_dom_text_edit,
 * and show the state the next CONTROL or TEXT_FIELD op sends, which script
 * may have changed or refused. Native views that take focus report it with
 * niso_dom_focus_input; script's focus comes back in the focused bit.
 */
int32_t niso_render_frame(niso_dom* dom, niso_bytes* out);
/* The version of the render stream niso_render_frame writes: the ops as this
 * header describes them are NISO_RENDER_VERSION. */
#define NISO_RENDER_VERSION 2
uint32_t niso_render_version(void);

/* Surfaces and templates.
 *
 * Template targets (home screen widgets, Live Activities and the Dynamic
 * Island, ongoing notifications; later CarPlay and Android Auto) take only
 * native views and lay text out themselves, often in another process that
 * runs no niso. A page renders content for them as surfaces: elements with a
 * data-niso-surface attribute naming the surface, like
 *
 *   <div data-niso-surface="widget.small" style="width: 170px; height: 170px">
 *
 * A surface is laid out with the page, at the size its CSS gives it, but is
 * never part of the render stream and takes no taps (the UA stylesheet makes
 * it position: fixed at the viewport's top left, pointer-events: none). The
 * names hosts know:
 *
 *   widget.small, widget.medium   home screen widgets, by size class
 *   activity                       iOS Live Activity on the Lock Screen and
 *                                  the expanded Dynamic Island; Android's
 *                                  ongoing notification. Present = running.
 *   island.leading, island.trailing, island.minimal
 *                                  the compact and minimal Dynamic Island
 *
 * niso_render_surfaces lays the document out and returns the surfaces whose
 * template changed since the previous call, and those that went away (removed,
 * display: none or renamed), valid until the next call into the DOM; the first
 * call after niso_render_reset returns every surface. Hosts call it after
 * each niso_render_frame. It is a list of
 *
 *   name_len:u32 name:utf8[name_len] len:u32 template:u8[len]
 *
 * sorted by name, where len 0 means the surface is gone. When two surfaces
 * share a name the first in tree order wins.
 *
 * niso_render_template returns the template of any element, as the document
 * lays it out, in a buffer valid until the next call into the DOM: a server
 * rendering content for push updates calls it on a headless DOM.
 *
 * A template is the surface's box tree as the render stream would describe it,
 * reduced to what native template views take: boxes, text as strings, timers,
 * progress bars and images. Little-endian, lengths in CSS pixels (points on
 * iOS, dp on Android), colors non-premultiplied sRGB 0xRRGGBBAA:
 *
 *   template := 'N' 'T' version:u8 width:f32 height:f32 node
 *   node     := kind:u8 x:f32 y:f32 w:f32 h:f32 len:u16 body:u8[len]
 *               count:u8 node[count]
 *   body     := flags:u8 [background:u32]          flags & 1
 *               [border_width:f32 border_color:u32] flags & 2 (the top edge's)
 *               [radius:f32]                        flags & 4 (top left)
 *               [opacity:f32]                       flags & 8
 *               kind's fields                       flags & 16: clips children
 *
 * The root node is the surface, at (0, 0) and the template's size. A node's
 * frame is its border box in its parent's; children are in paint order. Boxes
 * that draw nothing are flattened away, their children moved to their parent,
 * so a template stays shallow. Kinds and their fields:
 *
 *   1 BOX       none
 *   2 TEXT      text_style string: an element's inline content, as one string
 *               in the style of its first run (whitespace collapsed, inline
 *               boxes dropped). A box with text may also have children.
 *   3 TIMER     text_style start:f64 end:f64: a <time> element with a
 *               data-timer-start (counting up from) or data-timer-end
 *               (counting down to) attribute, in ms since the Unix epoch, 0
 *               when absent. Targets show the platform's self-updating timer
 *               (SwiftUI Text(date, style: .timer), Android Chronometer)
 *               instead of the text the page shows, so the template does not
 *               change while it runs. A timer must be a box of its own, like
 *               a block or a flex item, to be one; inline, its text is part
 *               of its paragraph's. A <time> without these attributes (a
 *               paused timer) is TEXT.
 *   4 PROGRESS  value:f32 max:f32 tint:u32 track:u32 flags:u8 start:f64
 *               end:f64 period:f64: a <progress> element, tint its CSS
 *               background (0 for the platform's), track 0 for the
 *               platform's, flags 1 when indeterminate. With data-timer-start
 *               and data-timer-end (ms since the Unix epoch) the bar fills
 *               from empty at start to full at end by itself; with
 *               data-timer-start and data-timer-period (ms) it fills every
 *               period from start, then starts over; its value is then 0,
 *               for the target to compute. Times are 0 when absent.
 *   5 IMAGE     string: an <img>'s src, which targets resolve to an image
 *               they bundle or have stored.
 *   6 BUTTON    node:u64 flags:u8: a <button> (flags 1 when disabled), its
 *               own box its only child at (0, 0). Targets that can act on
 *               it (Android's ongoing notification, as its actions) show
 *               its text and report the user's action with
 *               niso_dom_activate(node), node meaning something only to the
 *               DOM that rendered it; targets that cannot leave it out (the
 *               iOS widgets and Live Activity, which run no page), and
 *               decoders older than it draw it as a box.
 *
 *   text_style := x:f32 y:f32 w:f32 h:f32 size:f32 weight:u16 color:u32
 *                 align:u8 font:u8 lines:u8
 *   string     := len:u16 utf8[len]
 *
 * text_style's rect is where the text's lines sit in the node: the content
 * box's left and width, the lines' top and height. size is the font size,
 * weight 1-1000, align 0 start, 1 center, 2 end; font's low two bits are the
 * design (0 the system font, 1 monospaced: monospace or ui-monospace, 2
 * rounded: ui-rounded, 3 serif: serif or ui-serif), 4 italic, 8 tabular
 * figures (font-variant-numeric: tabular-nums). The rect and lines are as the
 * core laid the text out at the user's text size (niso_dom_set_text_scale),
 * but size is the CSS font size: targets draw it at the platform's own text
 * scale (sp, Dynamic Type), which matches the core's when the template is
 * shown at the text size it was made at. lines is how many lines the rect
 * holds: all of them, or those that fit whole in a box that clips its
 * overflow (overflow other than visible, as with a max-height). Targets keep
 * to that many and end text that still does not fit with an ellipsis, never
 * shrinking or cutting it.
 *
 * Compatibility: an app and its widget extension update separately, and a
 * template from a newer app (or a server) can reach an older extension. A
 * decoder rejects a template whose magic or version differs from its own, and
 * shows nothing rather than guessing. Within a version, newer cores only
 * append fields to a body and add kinds; decoders read the fields they know,
 * skip the rest of the body by its length, and draw a node of an unknown
 * kind as a BOX (its children included). A change that cannot be expressed
 * that way is a new version. One change within version 1 could not be: text
 * lines used to be every line the core laid out, which targets kept to by
 * shrinking the text, and are now those a clipping box holds, which targets
 * keep to with an ellipsis. A target from before it shrinks a newer
 * template's clipped text to fit instead of ending it with an ellipsis. */
int32_t niso_render_surfaces(niso_dom* dom, niso_bytes* out);
int32_t niso_render_template(niso_dom* dom, niso_handle element, niso_bytes* out);
/* A user's tap at viewport point (x, y) in CSS pixels: fires trusted
 * mousedown, mouseup and click (with its activation behavior) at the topmost
 * element there, which *target receives (0 outside the viewport). A native
 * control's content box gets no events, a button's aside: the control
 * handles its own touches, reported with niso_dom_control_input. Taps on a
 * button are taps like any other, so they reach what its label holds. */
int32_t niso_dom_tap(niso_dom* dom, float x, float y, niso_handle* target);
/* The user acted on a template's BUTTON outside the page (a notification's
 * action): element is its node. Fires the trusted click a tap on it would,
 * with its activation behavior; none if it is disabled. */
int32_t niso_dom_activate(niso_dom* dom, niso_handle element);
/* The user scrolled a native scroll view: node is the scroll container's
 * render node (0 for the viewport), scrolled to (x, y) CSS pixels. */
int32_t niso_dom_scroll(niso_dom* dom, niso_handle node, float x, float y);

/* Pointer input for pages that handle their own (a canvas a page draws and
 * drives, like a remote desktop, and touches where touch-action is none, see
 * niso_dom_touch_action). kind: NISO_POINTER_DOWN, MOVE, UP or
 * CANCEL; id: the pointer, stable from down to up (a finger), and 1 for the
 * mouse, which is always active, as in Chrome (setPointerCapture(1) never
 * throws NotFoundError);
 * type: NISO_POINTER_MOUSE, TOUCH or PEN; primary: the first finger down or
 * the mouse; (x, y): the viewport point in CSS pixels; button: the button
 * that changed (0 main, 1 middle, 2 secondary; -1 for moves); buttons: the
 * pressed buttons as MouseEvent.buttons bits; pressure 0-1 (touch and pen);
 * modifiers: NISO_MOD_* bits. Fires the Pointer Events (over, enter, down,
 * move, up, cancel, out, leave) at the element there or the one capturing
 * the pointer (touches are captured by what they land on), with the
 * compatibility mouse events and click for the primary mouse, and a tap's
 * mouse events and click when a primary touch or pen lifts. *not_canceled
 * is false if a listener canceled the pointer event: a host that would also
 * scroll or zoom for the gesture should not. Hosts that report pointers this
 * way report no niso_dom_tap for the same touches. */
enum {
    NISO_POINTER_DOWN = 0,
    NISO_POINTER_MOVE = 1,
    NISO_POINTER_UP = 2,
    NISO_POINTER_CANCEL = 3,
    NISO_POINTER_MOUSE = 0,
    NISO_POINTER_TOUCH = 1,
    NISO_POINTER_PEN = 2,
    NISO_MOD_SHIFT = 1,
    NISO_MOD_CTRL = 2,
    NISO_MOD_ALT = 4,
    NISO_MOD_META = 8,
};
int32_t niso_dom_pointer(niso_dom* dom, uint8_t kind, int32_t id, uint8_t type, uint8_t primary, float x, float y,
                         int16_t button, uint16_t buttons, float pressure, uint32_t modifiers, bool* not_canceled);
/* The native gestures a touch starting at viewport point (x, y) may begin, as
 * NISO_TOUCH_* bits in *actions: what touch-action (Pointer Events) allows
 * there, for the element at the point and its ancestors (a scroll container
 * allows panning what it holds again, as in Chromium). Hosts ask when a touch goes down. Where it is 0
 * (touch-action: none) the page handles the touch itself: the host starts no
 * scrolling, zooming or tap of its own for it and reports it with
 * niso_dom_pointer, which also gives the tap's click when it lifts. */
enum {
    NISO_TOUCH_PAN_X = 1,
    NISO_TOUCH_PAN_Y = 2,
    NISO_TOUCH_PINCH_ZOOM = 4,
    NISO_TOUCH_DOUBLE_TAP_ZOOM = 8,
};
int32_t niso_dom_touch_action(niso_dom* dom, float x, float y, uint8_t* actions);
/* A mouse wheel or trackpad scroll at viewport point (x, y): a WheelEvent
 * there with the deltas in delta_mode units (0 pixels, 1 lines, 2 pages).
 * *not_canceled is false if a listener canceled it: don't scroll. */
int32_t niso_dom_wheel(niso_dom* dom, float x, float y, double delta_x, double delta_y, double delta_z,
                       uint32_t delta_mode, uint32_t modifiers, bool* not_canceled);
/* A hardware key pressed (down 1) or released (0): keydown or keyup at the
 * focused element, or the body, with UI Events key and code values (UTF-8)
 * and a KeyboardEvent location, then what the key does in the page: a key
 * that types a character (or Enter) fires keypress; Tab moves focus to the
 * next (with shift, the previous) element in sequential focus order; Enter
 * clicks a focused link, button or details summary, or submits a focused
 * input's form implicitly; Space released where it was pressed clicks a
 * focused button, checkbox, radio button or summary.
 *
 * *host_default is false if a listener canceled the key or the page did
 * something with it: the host must not do its own default for it. True
 * means the host does what it would for the key:
 * - A native text field keeps typing natively: the host inserts the
 *   character and reports the edit with niso_dom_text_input, and commits a
 *   single line on Enter with niso_dom_text_commit. False here means the
 *   page canceled the keydown or keypress, so nothing is typed.
 * - Focus is the DOM's: when Tab, a click or a script moves it, the host
 *   makes the focused element's native view first responder (or resigns it
 *   when nothing has focus), rather than moving native focus itself. Tab
 *   with nothing after (or, with shift, before) the focused element is true:
 *   the host decides whether to wrap round or leave the page.
 * - Space on a checkbox or radio button drawn as a native switch toggles it
 *   in the page (false here), so the host must not toggle the switch too;
 *   the next CONTROL op shows the new state. */
int32_t niso_dom_key(niso_dom* dom, uint8_t down, niso_str key, niso_str code, uint32_t location, uint8_t repeat,
                     uint32_t modifiers, bool* host_default);

/* The size in CSS pixels of the host's native control of a CONTROL op kind,
 * which the UA stylesheet gives those elements (until set: 51x31 switches,
 * 160x31 sliders, 160x4 progress bars). Sliders and progress bars stretch,
 * so their width is only a default. Kinds 6, 7 and 8 give radio groups,
 * which stretch too, their height when the page leaves it auto (and the
 * area of their radio buttons theirs, see CONTROL): 6 a
 * segmented control's (until set: 32), and a list's, 8 its own part (a
 * menu's button, until set: 34) plus 7 a row per option (until set: 0).
 * Hosts measure their stock controls and set each before running script. */
int32_t niso_dom_set_control_size(niso_dom* dom, uint8_t kind, float width, float height);
/* The host's text fields' font size in CSS pixels (until set: 17), which
 * text fields get from the UA stylesheet. Kinds 4 (single-line) and 5
 * (multi-line) of niso_dom_set_control_size size text fields (until set:
 * 200x34 and 200x80). */
int32_t niso_dom_set_control_font_size(niso_dom* dom, float size);
/* The host's stock text field's box, in CSS pixels: the inset of its text
 * from its sides (and a multi-line field's from its top), its fill, border
 * width and color and corner radius, colors 0xRRGGBBAA. The UA stylesheet
 * gives text fields this box, so an unstyled field is the stock one: a
 * field whose box differs is plain (see TEXT_FIELD). Until set: 7,
 * 0xffffffff, 1/3, 0x00000033, 6. */
int32_t niso_dom_set_field_style(niso_dom* dom, float padding, uint32_t background, float border_width,
                                 uint32_t border_color, float radius);
/* The host's stock push button, in CSS pixels: the padding beside its
 * label, its height with one line of label, the label's line height, font
 * size, weight (100-900) and 0xRRGGBBAA colors (enabled and disabled), and
 * the button's fill (enabled and disabled) and corner radius, at least half
 * its height for a capsule. The UA stylesheet gives these to <button>
 * elements, with vertical padding that centers one line in that height, so
 * an unstyled button is the stock one (until set: 12, 34, 22, 17, 400,
 * 0x007affff, 0x3c3c434d, 0x7878801f, 0x7878801f, 9999). */
int32_t niso_dom_set_button_style(niso_dom* dom, float padding_x, float height, float line_height,
                                  float font_size, uint16_t weight, uint32_t color, uint32_t disabled_color,
                                  uint32_t background, uint32_t disabled_background, float radius);
/* The stock push button's label colors and fills (enabled and disabled)
 * where a button's used color scheme is dark, which the UA stylesheet gives
 * with light-dark(). niso_dom_set_button_style sets them to its light ones;
 * call this after it. */
int32_t niso_dom_set_button_dark_colors(niso_dom* dom, uint32_t color, uint32_t disabled_color,
                                        uint32_t background, uint32_t disabled_background);
/* The user edited the text field drawn for node to the UTF-8 value, leaving
 * the selection from start to end (UTF-16 code units of the value, start <=
 * end, equal for a caret) with direction 0 none, 1 forward or 2 backward:
 * the field takes the value and the selection, then input fires, so input
 * listeners see where the caret is. Disabled fields ignore it. A read-only
 * field, or an unchanged value, takes only the selection, as
 * niso_dom_text_select. */
int32_t niso_dom_text_edit(niso_dom* dom, niso_handle node, const char* value, size_t len, uint32_t start,
                           uint32_t end, uint8_t direction);
/* niso_dom_text_edit for a host that can't read its field's selection: the
 * caret goes to the end of the value. Read-only fields ignore it. */
int32_t niso_dom_text_input(niso_dom* dom, niso_handle node, const char* value, size_t len);
/* The user moved the selection of the text field drawn for node without
 * editing it, as niso_dom_text_edit gives it. select fires when it selects
 * text. Disabled fields ignore it. */
int32_t niso_dom_text_select(niso_dom* dom, niso_handle node, uint32_t start, uint32_t end, uint8_t direction);
/* The user committed the text field (return on a single line): change fires
 * if input did since the last change. Losing focus does the same. */
int32_t niso_dom_text_commit(niso_dom* dom, niso_handle node);
/* The native view drawn for node gained (focused 1) or lost (0) focus: the
 * document's focus follows, with blur, focusout, focus and focusin. */
int32_t niso_dom_focus_input(niso_dom* dom, niso_handle node, uint8_t focused);
/* The user changed the native control drawn for node to value: a button
 * pressed (any value) is a tap in its middle (mousedown, mouseup, focus,
 * click); a radio group to option value checks that option's radio button
 * as a click on it does (input and change fire), unless it is disabled. A host whose page taps (niso_dom_tap) also see touches on its
 * button lets those be the press, and reports only presses no tap saw, such
 * as accessibility activations; a switch to
 * 0 or 1 (a trusted click toggles it, with input and change events, unless
 * a listener cancels the click), a slider to a value it sanitizes and fires
 * input for. done means the user let go of the slider: change fires if input
 * did since the last change. Ignored for disabled controls. */
int32_t niso_dom_control_input(niso_dom* dom, niso_handle node, double value, uint8_t done);
int32_t niso_render_reset(niso_dom* dom);
int32_t niso_render_font(niso_dom* dom, uint32_t font, niso_bytes* data, uint32_t* index);
/* An IMAGE op's image: *width x *height pixels as non-premultiplied RGBA8,
 * row by row, valid for the life of the DOM; a live image's (flag 4) are
 * its pixels now, valid until the next call into the DOM other than this
 * and niso_render_font. Hosts may call it while reading the frame. */
int32_t niso_render_image(niso_dom* dom, uint32_t image, niso_bytes* pixels, uint32_t* width, uint32_t* height);

int32_t niso_retain(niso_dom* dom, niso_handle handle);
int32_t niso_release(niso_dom* dom, niso_handle handle);
/* The most derived interface of a handle (a NISO_IFACE_* value), or 0 if the handle is invalid. */
uint32_t niso_object_interface(niso_dom* dom, niso_handle handle);
/* The node or window an object belongs to when the DOM makes it once for that
 * owner and hands it out again ([SameObject] attributes, a node's style sheet
 * and rules, the window's singletons), else 0. A binding keeps the object's
 * wrapper alive with its owner's, so its identity and expandos last as long
 * as the owner does. */
int32_t niso_object_get_owner(niso_dom* dom, niso_handle handle, niso_handle* owner);

/* The C surface of niso-core's WorkerHost trait (worker.rs): the runner is a
 * worker host that starts a thread itself. Setting the runner leaves the host
 * and the reverse; the host's start is used while it is set. Null is none,
 * and with neither set new Worker is NotSupported. The userdata stays the
 * host's. */
/* Runs a dedicated worker, on the worker's own thread, given its DOM: makes a
 * JS engine for it, runs the script niso_dedicated_worker_global_scope_script
 * returns (or reports niso_dedicated_worker_global_scope_script_failed), calls
 * niso_dom_worker_started, then runs niso_dom_run until
 * niso_dom_worker_closing, then frees the engine. The core frees the DOM. */
typedef void (*niso_worker_runner_fn)(niso_dom* dom);
/* A worker host, for a host that owns threads and their event loops: start
 * starts a thread with a realm of its own that calls
 * niso_worker_begin(worker) and returns at once, false if it cannot;
 * terminate stops the script of a worker whose thread began, even one that
 * never returns to its loop. Both are called on the thread of the DOM that
 * started the worker. */
typedef bool (*niso_worker_start_fn)(void* userdata, uint64_t worker);
typedef void (*niso_worker_terminate_fn)(void* userdata, uint64_t worker);

/* Workers. A DOM can start dedicated workers once a binding sets its worker
 * runner; workers the DOM starts inherit it. A worker's DOM has a
 * DedicatedWorkerGlobalScope for its window (niso_dom_window) and no
 * document to speak of. While a worker is active, niso_dom_run waits for it
 * in real time rather than skipping its clock ahead; a worker's own run
 * never skips ahead, and returns once its closing flag is set. Once its
 * workers are idle the window skips ahead again. A worker's time origin is
 * its parent's niso_dom_wall_clock when it starts, skips included. Its loads
 * (its script, importScripts, the scripts of the workers it starts) go
 * through the window's loader on the window's thread, which answers them
 * in niso_dom_run: a worker waits for its load while the window's host is
 * not calling niso_dom_run (the wake hook says when to), or is blocked in a
 * long call. Freeing a DOM ends its workers, waiting up to two seconds in
 * all; a worker's WebAssembly stops between slices of fuel when it ends. */
void niso_dom_set_worker_runner(niso_dom* dom, niso_worker_runner_fn runner);
/* Instead of a runner, a host starts the DOM's workers on threads of its
 * own; workers it starts do not inherit it, so it sets one on each worker's
 * DOM too. On the worker's thread niso_worker_begin makes its DOM there, or
 * returns NULL when the worker was terminated first or its script could not
 * be fetched (it has ended then). The host makes an engine for the DOM, runs
 * the script as a runner does and then the loop until
 * niso_dom_worker_closing, clears the wake hook, frees the engine and calls
 * niso_worker_end, which frees the DOM. Between runs the host says with
 * niso_dom_set_waiting when the worker waits for its wake hook, so its
 * parent stops waiting for it in real time. A host that runs a page in steps
 * of virtual time keeps the clock from skipping ahead while
 * niso_dom_workers_active, as niso_dom_run does. A start that returns false
 * fires error at the Worker. Before it frees the DOM, while its terminate
 * can still run, the host ends the DOM's workers with
 * niso_dom_terminate_workers. */
void niso_dom_set_worker_host(niso_dom* dom, niso_worker_start_fn start, niso_worker_terminate_fn terminate, void* userdata);
niso_dom* niso_worker_begin(uint64_t worker);
void niso_worker_end(niso_dom* dom);
void niso_dom_terminate_workers(niso_dom* dom);
void niso_dom_set_waiting(niso_dom* dom, bool waiting);
bool niso_dom_workers_active(niso_dom* dom);
void niso_dom_set_message_hook(niso_dom* dom, niso_message_hook_fn hook, void* userdata);
void niso_dom_set_handler_hook(niso_dom* dom, niso_handler_hook_fn hook, void* userdata);
/* The worker's script ran: messages from and to it start to flow. */
void niso_dom_worker_started(niso_dom* dom);
/* Whether the DOM is a worker's whose closing flag is set. */
bool niso_dom_worker_closing(niso_dom* dom);
/* Whether the DOM is a worker's whose parent terminated it: the binding's
 * engine aborts the running script when this becomes true. */
bool niso_dom_interrupted(niso_dom* dom);

/* The network: fetch's transport, for every language, which also loads the
 * document's images, stylesheets and fonts. Requests run off the DOM thread.
 *
 * niso_net_fetch starts a request with headers_len strings of header name
 * and value pairs and a body (body_len 0: none), and stores its id in *id.
 * It is the host's own request, which no page made: redirects are followed,
 * any HTTP status is a response, and neither CORS nor a referrer applies.
 *
 * niso_net_fetch_request starts one as the page's fetch() does, with the
 * document or worker as its client and the Fetch standard's options:
 * mode 1 same-origin, 2 no-cors, 3 cors (0, navigate, is no page's request
 * and an error); credentials 0 omit,
 * 1 same-origin, 2 include; redirect 0 follow, 1 error, 2 manual;
 * referrer_policy 0 "" (the client's), 1 no-referrer, 2
 * no-referrer-when-downgrade, 3 same-origin, 4 origin, 5 strict-origin,
 * 6 origin-when-cross-origin, 7 strict-origin-when-cross-origin,
 * 8 unsafe-url; destination 0 "", 1 script, 2 style, 3 worker, 4 image,
 * 5 font; use_cors_preflight, Fetch's use-CORS-preflight flag, sends a
 * CORS request's preflight whatever its method and headers (an
 * XMLHttpRequest with upload listeners); referrer "" for none,
 * "about:client" for the client, else a URL.
 * It is held to what fetch() holds a page to, whatever the host passes: a
 * forbidden method is a network error, forbidden request-headers are
 * dropped, a no-cors request keeps only no-CORS-safelisted headers and is
 * a GET, HEAD or POST, and a referrer of another origin is the client.
 *
 * niso_net_fetch_request_sync runs such a request to completion while the
 * calling thread waits, as a synchronous XMLHttpRequest does, and stores its
 * response in *out as niso_net_take encodes one, valid until the next call
 * into the DOM.
 *
 * niso_fetch_forbidden_request_header and
 * niso_fetch_cors_safelisted_request_header are Fetch's predicates for a
 * header name and value (UTF-8), for a binding's Headers guards.
 *
 * niso_net_take returns the responses that finished since the last call, as
 * bytes valid until the next call into the DOM: u32 count, then per response
 * u64 id, u16 status (0 for a network error and an opaque response), u8 type
 * (0 basic, 1 cors, 2 default, 3 error, 4 opaque, 5 opaqueredirect), u8
 * redirected (1 if a redirect led to it), string status text, string final
 * URL, u32 header count and that many name and value strings (names
 * lower-case), string error (empty unless the type is error), and the body
 * as a string of bytes. A string is a u32 length and that many bytes (UTF-8 but
 * for the body); numbers are little-endian. Hosts need not call it: a
 * language binding takes them in its task hook, and niso_dom_run gives the
 * hook a turn when responses wait and nothing else does, then drops the
 * ones it left.
 *
 * niso_net_cancel cancels a request niso_net_fetch started: its body stops
 * downloading and niso_net_take never returns it. */
int32_t niso_net_fetch(niso_dom* dom, const char* method, size_t method_len, const char* url, size_t url_len,
                       const niso_str* headers, size_t headers_len, const uint8_t* body, size_t body_len, uint64_t* id);
struct niso_fetch_options {
    uint8_t mode;
    uint8_t credentials;
    uint8_t redirect;
    uint8_t referrer_policy;
    uint8_t destination;
    bool use_cors_preflight;
    niso_str referrer;
};
int32_t niso_net_fetch_request(niso_dom* dom, const char* method, size_t method_len, const char* url, size_t url_len,
                               const niso_str* headers, size_t headers_len, const uint8_t* body, size_t body_len,
                               const niso_fetch_options* options, uint64_t* id);
int32_t niso_net_fetch_request_sync(niso_dom* dom, const char* method, size_t method_len, const char* url, size_t url_len,
                                    const niso_str* headers, size_t headers_len, const uint8_t* body, size_t body_len,
                                    const niso_fetch_options* options, niso_bytes* out);
int32_t niso_net_cancel(niso_dom* dom, uint64_t id);
int32_t niso_net_take(niso_dom* dom, niso_bytes* out);
bool niso_fetch_forbidden_request_header(const char* name, size_t name_len, const char* value, size_t value_len);
bool niso_fetch_cors_safelisted_request_header(const char* name, size_t name_len, const char* value, size_t value_len);

/* Media. A host reports its player for a MEDIA op's node: state 0 loading
 * or buffering, 1 playable (playing if the op says play), 2 paused by the
 * platform (an interruption), 3 ended, 4 failed; the position and duration in
 * seconds, NaN when unknown and infinite for a live stream. The media events
 * it causes fire at the next niso_render_frame. */
int32_t niso_dom_media_update(niso_dom* dom, niso_handle node, uint8_t state, double time, double duration);
/* An action from the platform's media controls: 0 play, 1 pause, 2 stop,
 * 3 seek backward, 4 seek forward, 5 previous track, 6 next track, 7 seek to
 * seek_time seconds. The page's handler gets it if it has one (see
 * niso_media_session_take_actions), else play and pause control the element
 * that last started playing. */
int32_t niso_dom_media_session_action(niso_dom* dom, uint8_t action, double seek_time);
/* The page's media session, for language bindings: the metadata (the
 * artwork URL is resolved against the document), the playback state (0 none:
 * as the playing element says, 1 paused, 2 playing) and the actions it
 * handles (bit n for action n). niso_media_session_set_position is
 * setPositionState(): the duration (infinite for a live stream), playback rate
 * and position in seconds as of now, from which the position advances at the
 * rate while the session plays; a NaN duration clears it. It fails with
 * NISO_TYPE_ERROR for what the Media Session spec rejects: a negative
 * duration, a position outside 0 to the duration, a rate of 0, or a position
 * or rate that is not finite. niso_media_session_take_actions returns the
 * actions due to the page's handlers since the last call, as bytes valid
 * until the next call into the DOM: u32 count, then per action u8 action and
 * f64 seek time. */
int32_t niso_media_session_set_metadata(niso_dom* dom, const char* title, size_t title_len, const char* artist,
                                        size_t artist_len, const char* album, size_t album_len, const char* artwork,
                                        size_t artwork_len);
int32_t niso_media_session_set_state(niso_dom* dom, uint8_t state, uint32_t handled);
int32_t niso_media_session_set_position(niso_dom* dom, double duration, double playback_rate, double position);
int32_t niso_media_session_take_actions(niso_dom* dom, niso_bytes* out);

/* Native modules: APIs an app's own native code gives its pages, declared in
 * WebIDL as [NisoModule] interfaces. niso-idl generates both sides from the
 * IDL file: the page's import and the native interface the app implements.
 * This channel between them knows neither side's language.
 *
 * Values cross it as value lists: tokens of tag 0 and an f64, or tag 1, a u32
 * byte length and that much UTF-8. Values flatten in IDL order: a boolean is
 * 0 or 1, numbers and enumeration values are themselves, a nullable value is
 * 0 for null or 1 and the value, a sequence its length and its items, a
 * dictionary its members in order (an optional member with no default 0 when
 * absent, or 1 and the value). A call's arguments flatten the same way, an
 * optional argument with no default like such a member. Little-endian.
 *
 * Members are numbered in IDL order: calls (each attribute's getter, then its
 * setter if it has one, and each operation) from 0, and the event handler
 * attributes (attributes of a callback function type) from 0.
 *
 * The module side: niso_dom_define_module gives the page module name, whose
 * call function runs on the DOM thread for every call, with the member, its
 * arguments and the call's id. The module answers with niso_module_settle:
 * status 0 and the result's value list, or a niso_status and a UTF-8 message
 * the page sees as the matching exception. A synchronous member settles
 * before its call function returns; a member returning a promise settles
 * whenever it is done, from any thread. niso_module_emit sends an event, with
 * its arguments' value list, from any thread. Both are queued as tasks the
 * DOM's event loop runs (niso_dom_run), so the page never sees them mid-script;
 * a module sending from another thread wakes its host to run them. The port
 * stays valid until niso_module_release, after the DOM too (what it sends then
 * is dropped); free runs with userdata when the DOM is freed.
 *
 * The binding side: niso_dom_module finds a module by name, and
 * niso_dom_module_call calls a member: a synchronous call stores the result's
 * value list in *result (valid until the next call into the DOM) or fails
 * with the module's error; otherwise *result is empty and the result comes to
 * the module hook as a task, with the call's id. Events come to the hook with
 * call 0. */
typedef struct niso_module niso_module;
typedef void (*niso_module_call_fn)(void* userdata, uint32_t member, const uint8_t* args, size_t len, uint64_t call);
typedef void (*niso_module_hook_fn)(void* userdata, uint32_t module, uint64_t call, uint32_t event, int32_t status,
                                    const uint8_t* value, size_t len);
int32_t niso_dom_define_module(niso_dom* dom, const char* name, size_t name_len, niso_module_call_fn call,
                               niso_drop_fn free, void* userdata, niso_module** port);
void niso_module_settle(niso_module* port, uint64_t call, int32_t status, const uint8_t* value, size_t len);
void niso_module_emit(niso_module* port, uint32_t event, const uint8_t* value, size_t len);
void niso_module_release(niso_module* port);
int32_t niso_dom_module(niso_dom* dom, const char* name, size_t len, uint32_t* module);
int32_t niso_dom_module_call(niso_dom* dom, uint32_t module, uint32_t member, const uint8_t* args, size_t len, bool sync,
                             uint64_t* call, niso_bytes* result);
void niso_dom_set_module_hook(niso_dom* dom, niso_module_hook_fn hook, void* userdata);

/* WebAssembly: the surrounding agent's store for the JS API, one per DOM,
 * run by an interpreter (wasmi). Modules have ids, and functions, tables,
 * memories and globals ("externs") share one id space where each store
 * address has exactly one id.
 *
 * Values cross as doubles: an i32, f32, f64 or reference is one (a funcref
 * is its function's extern id, an externref an id the binding gives its host
 * value, null is 0); an i64 is two, its low and high 32 bits as unsigned
 * numbers. A function type is a string: one character per parameter type, a
 * colon, one per result type, with i i32, I i64, f f32, F f64, v v128, r
 * funcref and e externref. A global's type is its value type, then m when
 * it is mutable; a table's is its element type, then I when it is 64-bit; a
 * memory's is i or I, its address type.
 *
 * Failures are NISO_WASM_COMPILE_ERROR, NISO_WASM_LINK_ERROR,
 * NISO_WASM_RUNTIME_ERROR (a trap), NISO_RANGE_ERROR (a failed allocation or
 * a stack overflow), NISO_TYPE_ERROR, or NISO_CALLBACK_ERROR when a host
 * function threw: then the binding rethrows what it threw.
 *
 * Host functions call the call hook synchronously, with the callback id they
 * were made with and their encoded arguments after one flag; the flag is 1
 * when niso_wasm_take_moved has memories. The hook returns 0 after setting
 * the results with niso_wasm_set_results, or nonzero when the function threw.
 * While it runs, any niso_wasm call works on the same store. A task queued
 * with niso_wasm_queue_task runs the task hook with its job from the DOM's
 * task queue. Numbers handed out stay valid until the next call. */
typedef int32_t (*niso_wasm_call_fn)(void* userdata, uint64_t callback, const double* args, size_t len);
typedef void (*niso_wasm_task_fn)(void* userdata, uint64_t job);
typedef struct niso_doubles {
    const double* ptr;
    size_t len;
} niso_doubles;

void niso_wasm_set_hooks(niso_dom* dom, niso_wasm_call_fn call, niso_wasm_task_fn task, void* userdata);
void niso_wasm_queue_task(niso_dom* dom, uint64_t job);
bool niso_wasm_validate(niso_dom* dom, const uint8_t* bytes, size_t len);
int32_t niso_wasm_compile(niso_dom* dom, const uint8_t* bytes, size_t len, uint32_t* module);
void niso_wasm_module_release(niso_dom* dom, uint32_t module);
/* Per import: module name, name, kind (function, table, memory, global) and type. */
int32_t niso_wasm_module_imports(niso_dom* dom, uint32_t module, niso_strs* out);
/* Per export, in the binary's order: name, kind and type. */
int32_t niso_wasm_module_exports(niso_dom* dom, uint32_t module, niso_strs* out);
/* The contents of the custom sections named name: u32 count, then per
 * section a u32 length and its bytes, little-endian. */
int32_t niso_wasm_module_custom_sections(niso_dom* dom, uint32_t module, const char* name, size_t name_len, niso_bytes* out);
/* An extern id per import in; an extern id per export out, in export order. */
int32_t niso_wasm_instantiate(niso_dom* dom, uint32_t module, const uint32_t* imports, size_t len, niso_doubles* exports);
/* A host function of type sig that runs the call hook with callback; name is
 * its index among the imports it is made for, the JS API's function name. */
int32_t niso_wasm_func_new(niso_dom* dom, const char* sig, size_t sig_len, uint64_t callback, uint32_t name, uint32_t* id);
int32_t niso_wasm_func_info(niso_dom* dom, uint32_t id, niso_str* sig, uint32_t* name);
/* Results: one flag as for the call hook, then the encoded results. */
int32_t niso_wasm_call(niso_dom* dom, uint32_t id, const double* args, size_t len, niso_doubles* results);
void niso_wasm_set_results(niso_dom* dom, const double* results, size_t len);
/* Memories whose data the binding took with niso_wasm_memory_data and that
 * moved or changed length since: it must stop using that data before it runs
 * script, as the JS API detaches their buffers. */
void niso_wasm_take_moved(niso_dom* dom, niso_doubles* memories);
/* Sizes are in pages; a negative maximum is none. */
int32_t niso_wasm_memory_new(niso_dom* dom, double initial, double maximum, bool is64, uint32_t* id);
int32_t niso_wasm_memory_data(niso_dom* dom, uint32_t id, uint8_t** ptr, size_t* len);
int32_t niso_wasm_memory_grow(niso_dom* dom, uint32_t id, double delta, double* old);
int32_t niso_wasm_memory_type(niso_dom* dom, uint32_t id, double* pages, double* maximum, bool* is64);
int32_t niso_wasm_table_new(niso_dom* dom, uint8_t element, double initial, double maximum, bool is64, const double* init,
                            size_t init_len, uint32_t* id);
int32_t niso_wasm_table_type(niso_dom* dom, uint32_t id, uint8_t* element, double* length, bool* is64);
int32_t niso_wasm_table_get(niso_dom* dom, uint32_t id, double index, niso_doubles* value);
int32_t niso_wasm_table_set(niso_dom* dom, uint32_t id, double index, const double* value, size_t len);
int32_t niso_wasm_table_grow(niso_dom* dom, uint32_t id, double delta, const double* value, size_t len, double* old);
int32_t niso_wasm_global_new(niso_dom* dom, uint8_t type, bool is_mutable, const double* value, size_t len, uint32_t* id);
int32_t niso_wasm_global_type(niso_dom* dom, uint32_t id, niso_str* type);
int32_t niso_wasm_global_get(niso_dom* dom, uint32_t id, niso_doubles* value);
int32_t niso_wasm_global_set(niso_dom* dom, uint32_t id, const double* value, size_t len);

#ifdef __cplusplus
}
#endif
