Module wasm::dom
wasm32 DOM and browser-API interop.
A wasm32-unknown-unknown module has no access to the page itself, so every browser operation here is an 'extern' js_* function that becomes a WebAssembly import (an undefined extern in a module linked with --allow-undefined is imported from "env"), implemented by the JS glue that ships beside this header: std/wasm/safec_dom.js. Load a module through it —
import { loadSafeC } from "./safec_dom.js";
const app = await loadSafeC(fetch("/app.wasm")); // instantiates, wires every import
app.exports.start();
— and every dom_* function below works; see safec_dom.js's header for running under Node with a fake document (that is how the tests run it).
ABI. Strings go to JS as (pointer, length) into the module's linear memory and are read synchronously. Strings come back through a caller buffer: the import copies at most 'cap' bytes and returns the full length (-1 for "absent"), and the wrappers here retry with a buffer of that size, so the dom_* API hands out ordinary owned Strings. Elements are integer handles into a JS-side table (-1: none); DOM_DOCUMENT and DOM_WINDOW name the document and window. A handle stays valid until dom_release() — release elements you looked up or created in a loop, or the table grows for the page's lifetime.
Callbacks (events, timers, animation frames, fetch completions) are slots in one table of DOM_MAX_CALLBACKS entries; JS invokes them by id through the exported std_dom_dispatch_* functions. A handler receives its userdata (the generic ?&T it was registered with) and, for events, a DomEvent whose accessors read the event being dispatched — valid only during the handler. Every wrapper pairs fn void(?&T, ...) with ?&T, so the type-erased storage is always called correctly.
Memory: the strings these wrappers return are heap Strings; free them. (std/wasm/wasm_rt.sc's allocator is a bump allocator whose free() is a no-op — a long-running page should keep per-event allocation small.)
Structs
| DomEvent | The event being dispatched. Accessors call into JS for the live event: only valid inside the handler that received it. |
| DomFetchResponse | A completed fetch, as its handler sees it. 'status' is 0 (and ok 0) on a network error; text() etc. are only valid inside the handler. |
Type aliases
| DomEventHandler | Stored type-erased (the table holds handlers with unrelated context types); the generic wrappers below are the only writers and always pair a handler with userdata of the matching type. A callback id is slot + DOM_MAX_CALLBACKS * generation, so an id whose slot was freed and reused (a timer that already fired, say) is recognized as stale. |
Functions
| js_get_element | Original surface (kept for existing glue). |
| js_query | Elements and tree. |
| js_get_text | Content, attributes, classes, style, properties. |
| js_add_listener | Events. |
| js_set_timer | Timers and time. |
| js_fetch | fetch(). |
| js_storage_get | localStorage / sessionStorage. |
| js_location | location / history / console. |
| wasm_strlen | Length of a NUL-terminated string. No libc 'strlen' on a freestanding wasm32 build — this is the whole reason dom.sc carries its own copy. |
| wasm_itoa | Minimal signed-decimal integer formatter — no snprintf on wasm32. 'buf' should hold 24 bytes (any 64-bit value's sign + digits + NUL); a shorter buf gets a truncated number. Always NUL-terminates. |
| dom_get_element | First element matching the CSS selector (document.querySelector); -1 if none. dom_query is the same; dom_query_in searches under 'parent'. |
| dom_query | |
| dom_query_in | |
| dom_get_by_id | document.getElementById. |
| dom_query_all | Every match (querySelectorAll), appended to 'out' as handles; returns the count. |
| dom_query_all_in | |
| dom_body | document.body / document.head. |
| dom_head | |
| dom_create_element | document.createElement / createTextNode. |
| dom_create_text | |
| dom_append | parent.appendChild(child); parent.insertBefore(child, ref) (ref -1: append); node.remove(). |
| dom_insert_before | |
| dom_remove | |
| dom_clear | Removes every child (textContent = ""). |
| dom_release | Drops the handle (the element itself is untouched). |
| dom_parent | parentElement / firstElementChild / nextElementSibling / previousElementSibling / lastElementChild, or -1. |
| dom_first_child | |
| dom_next_sibling | |
| dom_prev_sibling | |
| dom_last_child | |
| dom_children | The element children (handles appended to 'out'); returns the count. |
| dom_set_text | textContent |
| dom_get_text | |
| dom_set_html | innerHTML (trusted markup only) |
| dom_get_html | |
| dom_set_attr | |
| dom_get_attr | The attribute's value; empty if absent (dom_has_attr tells the two apart). |
| dom_has_attr | |
| dom_remove_attr | |
| dom_class_add | classList.add / remove / toggle (returns the new state) / contains. |
| dom_class_remove | |
| dom_class_toggle | |
| dom_class_contains | |
| dom_set_style | style.setProperty(prop, value) ("" removes it); CSS property names ("background-color"), not camelCase. |
| dom_set_visible | Shows/hides via style.display ("" / "none"). |
| dom_get_value | Form controls: .value, .checked, .disabled. |
| dom_set_value | |
| dom_get_checked | |
| dom_set_checked | |
| dom_set_disabled | |
| dom_get_prop | Any property: String(el[name]) (empty for undefined/null), el[name] = value; the numeric forms convert with Number() / assign a number (0/1 works for boolean properties). DOM_DOCUMENT and DOM_WINDOW work too: dom_set_prop(DOM_DOCUMENT, "title", "..."). |
| dom_get_prop_buf | The same into a caller buffer: copies up to buf.len - 1 bytes, NUL- terminates, returns the full length (-1 if undefined/null). |
| dom_set_prop | |
| dom_get_prop_num | |
| dom_set_prop_num | |
| dom_call | elmethod with no arguments: "focus", "blur", "click", "select", "scrollIntoView", "reset", "submit", ... |
| dom_focus | |
| dom_console_log | |
| dom_console | console.debug/info/log/warn/error by DOM_LOG_* level. |
| dom_unregister_callback | Frees a callback slot (dom_off / dom_clear_timer / ... do this). |
| dom_register_callback | |
| dom_on_click | |
| dom_add_listener | |
| dom_on | |
| dom_on_selector | |
| dom_off | removeEventListener + frees the callback slot. |
| dom_set_timeout | |
| dom_set_interval | |
| dom_clear_timer | |
| dom_request_animation_frame | |
| dom_cancel_animation_frame | |
| dom_now | performance.now(), in milliseconds. |
| dom_fetch | |
| dom_fetch_get | |
| dom_storage_get | localStorage (DOM_STORAGE_LOCAL) / sessionStorage (DOM_STORAGE_SESSION). get: 1 and the value appended to 'out', or 0 if the key is absent. |
| dom_storage_set | 1 on success, 0 if the browser refused (quota, storage disabled). |
| dom_storage_remove | |
| dom_storage_clear | |
| dom_storage_length | |
| dom_storage_key | The i-th key (empty past the end). |
| dom_location | A location field (DOM_LOC_*): href, pathname, search ("?a=1"), hash ("#x"), host, origin. |
| dom_location_path | |
| dom_history_push | history.pushState(null, "", url) / replaceState / back() / forward(). Listen for DOM_WINDOW "popstate" to react to back/forward. |
| dom_history_replace | |
| dom_history_back | |
| dom_history_forward | |
| dom_navigate | location.assign(url): a real navigation. |
| dom_dispatch_event | Exported for the JS glue, never called by SafeC code. Names are fixed (std_dom_dispatch_event is the original export existing glue uses). |
| dom_dispatch_frame | |
| dom_dispatch_fetch |
Macros
| DOM_NONE | |
| DOM_DOCUMENT | handle of 'document' |
| DOM_WINDOW | handle of 'window' (popstate, resize, keydown, ...) |
| DOM_LOG_DEBUG | |
| DOM_LOG_INFO | |
| DOM_LOG_LOG | |
| DOM_LOG_WARN | |
| DOM_LOG_ERROR | |
| DOM_STORAGE_LOCAL | |
| DOM_STORAGE_SESSION | |
| DOM_LOC_HREF | dom_location() fields. |
| DOM_LOC_PATHNAME | |
| DOM_LOC_SEARCH | |
| DOM_LOC_HASH | |
| DOM_LOC_HOST | |
| DOM_LOC_ORIGIN | |
| DOM_LISTEN_CAPTURE | dom_add_listener flags. |
| DOM_LISTEN_ONCE | |
| DOM_LISTEN_PASSIVE | |
| DOM_EV_TARGET | DomEvent fields (js_event_num / js_event_str). |
| DOM_EV_CLIENT_X | |
| DOM_EV_CLIENT_Y | |
| DOM_EV_BUTTON | |
| DOM_EV_BUTTONS | |
| DOM_EV_MODIFIERS | bit 0 alt, 1 ctrl, 2 shift, 3 meta |
| DOM_EV_DELTA_X | |
| DOM_EV_DELTA_Y | |
| DOM_EV_REPEAT | |
| DOM_EV_TIMESTAMP | |
| DOM_EV_OFFSET_X | |
| DOM_EV_OFFSET_Y | |
| DOM_EV_TARGET_CHECKED | |
| DOM_EV_CURRENT_TARGET | |
| DOM_EV_STR_TYPE | |
| DOM_EV_STR_KEY | |
| DOM_EV_STR_CODE | |
| DOM_EV_STR_VALUE | event.target.value |
| DOM_EV_STR_DATA | InputEvent.data / MessageEvent.data |
| DOM_EV_STR_STATE | popstate: JSON.stringify(event.state) |
| DOM_MOD_ALT | |
| DOM_MOD_CTRL | |
| DOM_MOD_SHIFT | |
| DOM_MOD_META | |
| DOM_MAX_CALLBACKS |