// SafeC Standard Library — wasm32 DOM interop implementation (see dom.h).
#pragma once
#include <std/wasm/dom.h>
#include <std/collections/string.h>
#include <std/collections/vec.h>
#include <std/mem.h>
namespace std {
#define DOM_KIND_TIMER_ 16 // plain callback used by a timer (slot.extra = JS timer id)
#define DOM_KIND_ONESHOT_ 32 // freed after its first dispatch
struct DomCallbackSlot {
fn void() handler; // erased: the fn type 'kind' names (DomEventFn_, ...)
?&Erased userdata;
int kind; // DOM_CB_* | DOM_KIND_* bits
int used;
int gen; // bumped every time the slot is freed
int extra; // JS-side id (listener / timer / frame / request)
};
static struct DomCallbackSlot gDomCallbacks[DOM_MAX_CALLBACKS];
// ── Strings ────────────────────────────────────────────────────────────────
unsigned long wasm_strlen(const &char s) {
unsigned long n = 0UL;
unsafe {
while (s[n] != (char)0) {
n = n + 1UL;
}
}
return n;
}
static int dom_len_(const &char s) { return (int)wasm_strlen(s); }
void wasm_itoa(long long v, []char buf_s) {
unsigned long cap = buf_s.len;
if (cap == 0UL) { return; }
&char buf;
unsafe { buf = (&char)buf_s.ptr; }
unsafe {
if (cap == 0UL) {
return;
}
if (v == 0LL) {
buf[0] = '0';
if (cap > 1UL) { buf[1] = (char)0; } else { buf[0] = (char)0; }
return;
}
int neg = 0;
unsigned long long uv;
if (v < 0LL) {
neg = 1;
uv = (unsigned long long)(-v);
} else {
uv = (unsigned long long)v;
}
char tmp[24];
unsigned long n = 0UL;
while (uv > 0ULL && n < 23UL) {
tmp[n] = (char)('0' + (int)(uv % 10ULL));
uv = uv / 10ULL;
n = n + 1UL;
}
unsigned long outLen = n + (unsigned long)neg;
if (outLen >= cap) {
outLen = cap - 1UL;
}
unsigned long w = 0UL;
if (neg && w < outLen) {
buf[w] = '-';
w = w + 1UL;
}
while (w < outLen) {
n = n - 1UL;
buf[w] = tmp[n];
w = w + 1UL;
}
buf[w] = (char)0;
}
}
// One string-returning import, described so dom_str_ can call it twice
// (a stack buffer first, an exact-size heap one if that was too small).
#define DOM_STR_TEXT_ 0
#define DOM_STR_HTML_ 1
#define DOM_STR_ATTR_ 2
#define DOM_STR_PROP_ 3
#define DOM_STR_EVENT_ 4
#define DOM_STR_FETCH_ 5
#define DOM_STR_FHEADER_ 6
#define DOM_STR_STORAGE_ 7
#define DOM_STR_SKEY_ 8
#define DOM_STR_LOCATION_ 9
struct DomStrReq_ {
int op;
int a;
int b;
const char* s;
int slen;
};
static int dom_str_call_(&DomStrReq_ r, char* buf, int cap) {
unsafe {
if (r.op == DOM_STR_TEXT_) { return js_get_text(r.a, buf, cap); }
if (r.op == DOM_STR_HTML_) { return js_get_html(r.a, buf, cap); }
if (r.op == DOM_STR_ATTR_) { return js_get_attr(r.a, r.s, r.slen, buf, cap); }
if (r.op == DOM_STR_PROP_) { return js_get_prop(r.a, r.s, r.slen, buf, cap); }
if (r.op == DOM_STR_EVENT_) { return js_event_str(r.a, buf, cap); }
if (r.op == DOM_STR_FETCH_) { return js_fetch_str(r.a, r.b, buf, cap); }
if (r.op == DOM_STR_FHEADER_) { return js_fetch_header(r.a, r.s, r.slen, buf, cap); }
if (r.op == DOM_STR_STORAGE_) { return js_storage_get(r.a, r.s, r.slen, buf, cap); }
if (r.op == DOM_STR_SKEY_) { return js_storage_key(r.a, r.b, buf, cap); }
if (r.op == DOM_STR_LOCATION_) { return js_location(r.a, buf, cap); }
}
return -1;
}
// Appends the result to 'out'; returns 0 when JS reported "absent" (-1).
static int dom_str_into_(&DomStrReq_ r, &String out) {
char small[256];
int n;
unsafe { n = dom_str_call_(r, &small[0], 256); }
if (n < 0) { return 0; }
if (n <= 256) {
unsafe { out.push_n_((const char*)&small[0], (unsigned long)n); }
return 1;
}
unsafe {
char* big = (char*)alloc_raw_((unsigned long)n);
if (big == (char*)0) { return 1; }
int m = dom_str_call_(r, big, n);
if (m > n) { m = n; }
if (m > 0) { out.push_n_((const char*)big, (unsigned long)m); }
dealloc(big);
}
return 1;
}
static struct String dom_str_(int op, int a, int b, const char* s, int slen) {
struct DomStrReq_ r;
r.op = op;
r.a = a;
r.b = b;
r.s = s;
r.slen = slen;
struct String out = String::new();
dom_str_into_(&r, &out);
return out;
}
// ── Elements ───────────────────────────────────────────────────────────────
int dom_get_element(const &char selector) {
unsigned long len = wasm_strlen(selector);
int handle;
unsafe { handle = js_get_element(selector, len); }
return handle;
}
int dom_query(const &char selector) {
unsafe { return js_query(DOM_DOCUMENT, selector, dom_len_(selector)); }
}
int dom_query_in(int parent, const &char selector) {
unsafe { return js_query(parent, selector, dom_len_(selector)); }
}
int dom_get_by_id(const &char id) {
struct String sel = String::from("#");
// CSS-escape is not needed for the usual [A-Za-z0-9_-] ids; anything
// else goes through an attribute selector.
int plain = 1;
unsafe {
unsigned long i = 0UL;
while (id[i] != (char)0) {
char c = id[i];
if (!((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '_' || c == '-')) {
plain = 0;
}
i = i + 1UL;
}
if (id[0] >= '0' && id[0] <= '9') { plain = 0; }
}
if (plain) {
sel.push(id);
} else {
sel.clear();
sel.push("[id=\"");
unsafe {
unsigned long i = 0UL;
while (id[i] != (char)0) {
if (id[i] == '"' || id[i] == '\\') { sel.push_char('\\'); }
sel.push_char(id[i]);
i = i + 1UL;
}
}
sel.push("\"]");
}
int h = dom_query(sel.as_ptr());
sel.free();
return h;
}
static unsigned long dom_collect_(int parent, const char* sel, int selLen, int children, &Vec<int> out) {
int small[64];
int n;
unsafe {
if (children) { n = js_children(parent, &small[0], 64); }
else { n = js_query_all(parent, sel, selLen, &small[0], 64); }
}
if (n <= 0) { return 0UL; }
if (n <= 64) {
int i = 0;
while (i < n) { unsafe { out.push(small[i]); } i = i + 1; }
return (unsigned long)n;
}
// Too many for the stack buffer: the first call already created
// handles for all of them, so release those and ask again.
int i = 0;
while (i < 64) { unsafe { js_release(small[i]); } i = i + 1; }
unsafe {
int* big = (int*)alloc_raw_((unsigned long)n * sizeof(int));
if (big == (int*)0) { return 0UL; }
int m;
if (children) { m = js_children(parent, big, n); }
else { m = js_query_all(parent, sel, selLen, big, n); }
if (m > n) { m = n; }
int k = 0;
while (k < m) { out.push(big[k]); k = k + 1; }
dealloc(big);
return (unsigned long)m;
}
}
unsigned long dom_query_all(const &char selector, &Vec<int> out) {
unsafe { return dom_collect_(DOM_DOCUMENT, selector, dom_len_(selector), 0, out); }
}
unsigned long dom_query_all_in(int parent, const &char selector, &Vec<int> out) {
unsafe { return dom_collect_(parent, selector, dom_len_(selector), 0, out); }
}
unsigned long dom_children(int handle, &Vec<int> out) {
unsafe { return dom_collect_(handle, (const char*)0, 0, 1, out); }
}
int dom_body() { unsafe { return js_relative(DOM_DOCUMENT, 10); } }
int dom_head() { unsafe { return js_relative(DOM_DOCUMENT, 11); } }
int dom_create_element(const &char tag) {
unsafe { return js_create_element(tag, dom_len_(tag)); }
}
int dom_create_text(const &char text) {
unsafe { return js_create_text(text, dom_len_(text)); }
}
void dom_append(int parent, int child) { unsafe { js_append_child(parent, child); } }
void dom_insert_before(int parent, int child, int ref) { unsafe { js_insert_before(parent, child, ref); } }
void dom_remove(int handle) { unsafe { js_remove(handle); } }
void dom_clear(int handle) { unsafe { js_set_text(handle, "", 0UL); } }
void dom_release(int handle) { unsafe { js_release(handle); } }
int dom_parent(int handle) { unsafe { return js_relative(handle, 0); } }
int dom_first_child(int handle) { unsafe { return js_relative(handle, 1); } }
int dom_next_sibling(int handle) { unsafe { return js_relative(handle, 2); } }
int dom_prev_sibling(int handle) { unsafe { return js_relative(handle, 3); } }
int dom_last_child(int handle) { unsafe { return js_relative(handle, 4); } }
// ── Content ────────────────────────────────────────────────────────────────
void dom_set_text(int handle, const &char text) {
unsigned long len = wasm_strlen(text);
unsafe { js_set_text(handle, text, len); }
}
struct String dom_get_text(int handle) {
return dom_str_(DOM_STR_TEXT_, handle, 0, (const char*)0, 0);
}
void dom_set_html(int handle, const &char html) {
unsafe { js_set_html(handle, html, dom_len_(html)); }
}
struct String dom_get_html(int handle) {
return dom_str_(DOM_STR_HTML_, handle, 0, (const char*)0, 0);
}
void dom_set_attr(int handle, const &char name, const &char value) {
unsigned long nameLen = wasm_strlen(name);
unsigned long valLen = wasm_strlen(value);
unsafe { js_set_attr(handle, name, nameLen, value, valLen); }
}
struct String dom_get_attr(int handle, const &char name) {
unsafe { return dom_str_(DOM_STR_ATTR_, handle, 0, name, dom_len_(name)); }
}
int dom_has_attr(int handle, const &char name) {
char one[1];
int n;
unsafe { n = js_get_attr(handle, name, dom_len_(name), &one[0], 0); }
return n >= 0;
}
void dom_remove_attr(int handle, const &char name) {
unsafe { js_remove_attr(handle, name, dom_len_(name)); }
}
void dom_class_add(int handle, const &char cls) { unsafe { js_class_op(handle, 0, cls, dom_len_(cls)); } }
void dom_class_remove(int handle, const &char cls) { unsafe { js_class_op(handle, 1, cls, dom_len_(cls)); } }
int dom_class_toggle(int handle, const &char cls) { unsafe { return js_class_op(handle, 2, cls, dom_len_(cls)); } }
int dom_class_contains(int handle, const &char cls) { unsafe { return js_class_op(handle, 3, cls, dom_len_(cls)); } }
void dom_set_style(int handle, const &char prop, const &char value) {
unsafe { js_set_style(handle, prop, dom_len_(prop), value, dom_len_(value)); }
}
void dom_set_visible(int handle, int visible) {
if (visible) { dom_set_style(handle, "display", ""); } else { dom_set_style(handle, "display", "none"); }
}
struct String dom_get_prop(int handle, const &char name) {
unsafe { return dom_str_(DOM_STR_PROP_, handle, 0, name, dom_len_(name)); }
}
int dom_get_prop_buf(int handle, const &char name, []char buf_s) {
int cap = buf_s.len > 2147483647UL ? 2147483647 : (int)buf_s.len;
if (cap == 0) { return 0; }
&char buf;
unsafe { buf = (&char)buf_s.ptr; }
if (cap <= 0) { return 0; }
int n;
unsafe {
n = js_get_prop(handle, name, dom_len_(name), buf, cap - 1);
int w = n < 0 ? 0 : (n < cap - 1 ? n : cap - 1);
buf[w] = (char)0;
}
return n;
}
void dom_set_prop(int handle, const &char name, const &char value) {
unsafe { js_set_prop(handle, name, dom_len_(name), value, dom_len_(value)); }
}
double dom_get_prop_num(int handle, const &char name) {
unsafe { return js_get_prop_num(handle, name, dom_len_(name)); }
}
void dom_set_prop_num(int handle, const &char name, double value) {
unsafe { js_set_prop_num(handle, name, dom_len_(name), value); }
}
void dom_call(int handle, const &char method) {
unsafe { js_call(handle, method, dom_len_(method)); }
}
void dom_focus(int handle) { dom_call(handle, "focus"); }
struct String dom_get_value(int handle) { return dom_get_prop(handle, "value"); }
void dom_set_value(int handle, const &char value) { dom_set_prop(handle, "value", value); }
int dom_get_checked(int handle) { return dom_get_prop_num(handle, "checked") != 0.0; }
void dom_set_checked(int handle, int checked) { dom_set_prop_num(handle, "checked", checked ? 1.0 : 0.0); }
void dom_set_disabled(int handle, int disabled) { dom_set_prop_num(handle, "disabled", disabled ? 1.0 : 0.0); }
// ── Console ────────────────────────────────────────────────────────────────
void dom_console_log(const &char text) {
unsigned long len = wasm_strlen(text);
unsafe { js_console_log(text, len); }
}
void dom_console(int level, const &char text) {
unsafe { js_console(level, text, dom_len_(text)); }
}
// ── Callback table ─────────────────────────────────────────────────────────
static int dom_register_callback_kind_raw_(int kind, fn void() handler, ?&Erased userdata) {
int i = 0;
while (i < DOM_MAX_CALLBACKS) {
int used;
unsafe { used = gDomCallbacks[i].used; }
if (!used) {
int gen;
unsafe {
gDomCallbacks[i].handler = handler;
gDomCallbacks[i].userdata = userdata;
gDomCallbacks[i].kind = kind;
gDomCallbacks[i].used = 1;
gDomCallbacks[i].extra = -1;
if (gDomCallbacks[i].gen <= 0) { gDomCallbacks[i].gen = 1; }
gen = gDomCallbacks[i].gen;
}
return i + DOM_MAX_CALLBACKS * gen;
}
i = i + 1;
}
return -1;
}
// The slot of a live callback id, or -1.
static int dom_slot_(int id) {
if (id < 0) { return -1; }
int slot = id % DOM_MAX_CALLBACKS;
int gen = id / DOM_MAX_CALLBACKS;
int ok;
unsafe { ok = gDomCallbacks[slot].used && gDomCallbacks[slot].gen == gen; }
return ok ? slot : -1;
}
void dom_unregister_callback(int callbackId) {
int slot = dom_slot_(callbackId);
if (slot < 0) { return; }
unsafe {
gDomCallbacks[slot].used = 0;
gDomCallbacks[slot].gen = gDomCallbacks[slot].gen + 1;
if (gDomCallbacks[slot].gen > 1000000) { gDomCallbacks[slot].gen = 1; }
}
}
int dom_register_callback_raw_(fn void(?&Erased) fh, ?&Erased fu) {
fn void() handler;
unsafe { handler = (fn void())fh; }
?&Erased userdata = fu;
return dom_register_callback_kind_raw_(DOM_CB_PLAIN_, handler, userdata);
}
int dom_on_click_raw_(const &char selector, fn void(?&Erased) fh, ?&Erased fu) {
int handle = dom_get_element(selector);
if (handle < 0) {
return -1;
}
int id = dom_register_callback_raw_(fh, fu);
if (id < 0) { return -1; }
unsafe { js_add_click_listener(handle, id); }
return handle;
}
// ── Events ─────────────────────────────────────────────────────────────────
struct String DomEvent::type() const { return dom_str_(DOM_STR_EVENT_, DOM_EV_STR_TYPE, 0, (const char*)0, 0); }
struct String DomEvent::key() const { return dom_str_(DOM_STR_EVENT_, DOM_EV_STR_KEY, 0, (const char*)0, 0); }
struct String DomEvent::code() const { return dom_str_(DOM_STR_EVENT_, DOM_EV_STR_CODE, 0, (const char*)0, 0); }
struct String DomEvent::target_value() const { return dom_str_(DOM_STR_EVENT_, DOM_EV_STR_VALUE, 0, (const char*)0, 0); }
struct String DomEvent::data() const { return dom_str_(DOM_STR_EVENT_, DOM_EV_STR_DATA, 0, (const char*)0, 0); }
struct String DomEvent::state_json() const { return dom_str_(DOM_STR_EVENT_, DOM_EV_STR_STATE, 0, (const char*)0, 0); }
static double dom_ev_num_(int f) { unsafe { return js_event_num(f); } }
int DomEvent::target_checked() const { return dom_ev_num_(DOM_EV_TARGET_CHECKED) != 0.0; }
int DomEvent::target() const { return (int)dom_ev_num_(DOM_EV_TARGET); }
int DomEvent::current_target() const { return (int)dom_ev_num_(DOM_EV_CURRENT_TARGET); }
double DomEvent::client_x() const { return dom_ev_num_(DOM_EV_CLIENT_X); }
double DomEvent::client_y() const { return dom_ev_num_(DOM_EV_CLIENT_Y); }
double DomEvent::offset_x() const { return dom_ev_num_(DOM_EV_OFFSET_X); }
double DomEvent::offset_y() const { return dom_ev_num_(DOM_EV_OFFSET_Y); }
int DomEvent::button() const { return (int)dom_ev_num_(DOM_EV_BUTTON); }
int DomEvent::buttons() const { return (int)dom_ev_num_(DOM_EV_BUTTONS); }
double DomEvent::delta_x() const { return dom_ev_num_(DOM_EV_DELTA_X); }
double DomEvent::delta_y() const { return dom_ev_num_(DOM_EV_DELTA_Y); }
int DomEvent::modifiers() const { return (int)dom_ev_num_(DOM_EV_MODIFIERS); }
int DomEvent::alt_key() const { return (self.modifiers() & DOM_MOD_ALT) != 0; }
int DomEvent::ctrl_key() const { return (self.modifiers() & DOM_MOD_CTRL) != 0; }
int DomEvent::shift_key() const { return (self.modifiers() & DOM_MOD_SHIFT) != 0; }
int DomEvent::meta_key() const { return (self.modifiers() & DOM_MOD_META) != 0; }
int DomEvent::repeat() const { return dom_ev_num_(DOM_EV_REPEAT) != 0.0; }
double DomEvent::timestamp() const { return dom_ev_num_(DOM_EV_TIMESTAMP); }
void DomEvent::prevent_default() const { unsafe { js_event_control(0); } }
void DomEvent::stop_propagation() const { unsafe { js_event_control(1); } }
int dom_add_listener_raw_(int handle, const &char type, fn void(?&Erased, &DomEvent) fh, ?&Erased fu, int flags) {
fn void() handler;
unsafe { handler = (fn void())fh; }
?&Erased userdata = fu;
int id = dom_register_callback_kind_raw_(DOM_CB_EVENT_ | ((flags & DOM_LISTEN_ONCE) != 0 ? DOM_KIND_ONESHOT_ : 0),
handler, userdata);
if (id < 0) { return -1; }
int jsId;
unsafe { jsId = js_add_listener(handle, type, dom_len_(type), id, flags); }
if (jsId < 0) {
dom_unregister_callback(id);
return -1;
}
unsafe { gDomCallbacks[id % DOM_MAX_CALLBACKS].extra = jsId; }
return id;
}
int dom_on_selector_raw_(const &char selector, const &char type, fn void(?&Erased, &DomEvent) handler, ?&Erased userdata) {
int h = dom_query(selector);
if (h < 0) { return -1; }
return dom_add_listener_raw_(h, type, handler, userdata, 0);
}
void dom_off(int listenerId) {
int slot = dom_slot_(listenerId);
if (slot < 0) { return; }
int jsId;
unsafe { jsId = gDomCallbacks[slot].extra; }
if (jsId >= 0) { unsafe { js_remove_listener(jsId); } }
dom_unregister_callback(listenerId);
}
// ── Timers ─────────────────────────────────────────────────────────────────
int dom_set_timer_raw_(fn void(?&Erased) fh, ?&Erased fu, int ms, int repeat) {
fn void() handler;
unsafe { handler = (fn void())fh; }
?&Erased userdata = fu;
int kind = DOM_CB_PLAIN_ | DOM_KIND_TIMER_ | (repeat ? 0 : DOM_KIND_ONESHOT_);
int id = dom_register_callback_kind_raw_(kind, handler, userdata);
if (id < 0) { return -1; }
int jsId;
unsafe { jsId = js_set_timer(id, ms, repeat); }
unsafe { gDomCallbacks[id % DOM_MAX_CALLBACKS].extra = jsId; }
return id;
}
void dom_clear_timer(int timerId) {
int slot = dom_slot_(timerId);
if (slot < 0) { return; }
int jsId;
unsafe { jsId = gDomCallbacks[slot].extra; }
unsafe { js_clear_timer(jsId); }
dom_unregister_callback(timerId);
}
int dom_request_animation_frame_raw_(fn void(?&Erased, double) fh, ?&Erased fu) {
fn void() handler;
unsafe { handler = (fn void())fh; }
?&Erased userdata = fu;
int id = dom_register_callback_kind_raw_(DOM_CB_FRAME_ | DOM_KIND_ONESHOT_, handler, userdata);
if (id < 0) { return -1; }
int jsId;
unsafe { jsId = js_request_frame(id); }
unsafe { gDomCallbacks[id % DOM_MAX_CALLBACKS].extra = jsId; }
return id;
}
void dom_cancel_animation_frame(int frameId) {
int slot = dom_slot_(frameId);
if (slot < 0) { return; }
int jsId;
unsafe { jsId = gDomCallbacks[slot].extra; }
unsafe { js_cancel_frame(jsId); }
dom_unregister_callback(frameId);
}
double dom_now() { unsafe { return js_now(); } }
// ── fetch() ────────────────────────────────────────────────────────────────
struct String DomFetchResponse::text() const { return dom_str_(DOM_STR_FETCH_, self.request, 0, (const char*)0, 0); }
struct String DomFetchResponse::status_text() const { return dom_str_(DOM_STR_FETCH_, self.request, 1, (const char*)0, 0); }
struct String DomFetchResponse::url() const { return dom_str_(DOM_STR_FETCH_, self.request, 2, (const char*)0, 0); }
struct String DomFetchResponse::header(const &char name) const {
unsafe { return dom_str_(DOM_STR_FHEADER_, self.request, 0, name, dom_len_(name)); }
}
int dom_fetch_raw_(const &char method, const &char url, const ?&char headers, const ?&char body,
fn void(?&Erased, &DomFetchResponse) fh, ?&Erased fu) {
fn void() handler;
unsafe { handler = (fn void())fh; }
?&Erased userdata = fu;
int id = dom_register_callback_kind_raw_(DOM_CB_FETCH_ | DOM_KIND_ONESHOT_, handler, userdata);
if (id < 0) { return -1; }
const char* hp = (const char*)0;
int hl = -1;
const char* bp = (const char*)0;
int bl = -1;
match (headers) { case some(h): { hp = h; hl = dom_len_(h); } case null: { } }
match (body) { case some(b): { bp = b; bl = dom_len_(b); } case null: { } }
int req;
unsafe { req = js_fetch(id, method, dom_len_(method), url, dom_len_(url), hp, hl, bp, bl); }
if (req < 0) {
dom_unregister_callback(id);
return -1;
}
unsafe { gDomCallbacks[id % DOM_MAX_CALLBACKS].extra = req; }
return req;
}
// ── Storage ────────────────────────────────────────────────────────────────
int dom_storage_get(int kind, const &char key, &String out) {
struct DomStrReq_ r;
r.op = DOM_STR_STORAGE_;
r.a = kind;
r.b = 0;
unsafe { r.s = key; }
r.slen = dom_len_(key);
return dom_str_into_(&r, out);
}
int dom_storage_set(int kind, const &char key, const &char value) {
int rc;
unsafe { rc = js_storage_set(kind, key, dom_len_(key), value, dom_len_(value)); }
return rc == 0;
}
void dom_storage_remove(int kind, const &char key) { unsafe { js_storage_remove(kind, key, dom_len_(key)); } }
void dom_storage_clear(int kind) { unsafe { js_storage_clear(kind); } }
int dom_storage_length(int kind) { unsafe { return js_storage_length(kind); } }
struct String dom_storage_key(int kind, int index) { return dom_str_(DOM_STR_SKEY_, kind, index, (const char*)0, 0); }
// ── Location / history ─────────────────────────────────────────────────────
struct String dom_location(int field) { return dom_str_(DOM_STR_LOCATION_, field, 0, (const char*)0, 0); }
struct String dom_location_path() { return dom_location(DOM_LOC_PATHNAME); }
void dom_history_push(const &char url) { unsafe { js_history(0, url, dom_len_(url)); } }
void dom_history_replace(const &char url) { unsafe { js_history(1, url, dom_len_(url)); } }
void dom_history_back() { unsafe { js_history(2, "", 0); } }
void dom_history_forward() { unsafe { js_history(3, "", 0); } }
void dom_navigate(const &char url) { unsafe { js_history(4, url, dom_len_(url)); } }
// ── Dispatch (called by the host) ──────────────────────────────────────────
typedef fn void(?&Erased ud, &DomEvent ev) DomEventFn_;
typedef fn void(?&Erased ud, double ts) DomFrameFn_;
typedef fn void(?&Erased ud, &DomFetchResponse r) DomFetchFn_;
void dom_dispatch_event(int callbackId) {
int slot = dom_slot_(callbackId);
if (slot < 0) {
// Ids below DOM_MAX_CALLBACKS come from glue written before
// generations existed; they name slot+generation 1.
if (callbackId >= 0 && callbackId < DOM_MAX_CALLBACKS) { slot = dom_slot_(callbackId + DOM_MAX_CALLBACKS); }
if (slot < 0) { return; }
}
fn void() h;
?&Erased ud;
int kind;
unsafe {
h = gDomCallbacks[slot].handler;
ud = gDomCallbacks[slot].userdata;
kind = gDomCallbacks[slot].kind;
}
if ((kind & DOM_KIND_ONESHOT_) != 0) { dom_unregister_callback(callbackId); }
int base = kind & 15;
if (base == DOM_CB_EVENT_) {
struct DomEvent ev;
ev.listener = callbackId;
unsafe { ((DomEventFn_)h)(ud, &ev); }
} else if (base == DOM_CB_PLAIN_) {
unsafe { ((DomEventHandler)h)(ud); }
}
}
void dom_dispatch_frame(int callbackId, double timestamp) {
int slot = dom_slot_(callbackId);
if (slot < 0) { return; }
fn void() h;
?&Erased ud;
unsafe {
h = gDomCallbacks[slot].handler;
ud = gDomCallbacks[slot].userdata;
}
dom_unregister_callback(callbackId);
unsafe { ((DomFrameFn_)h)(ud, timestamp); }
}
void dom_dispatch_fetch(int callbackId, int requestId, int status) {
int slot = dom_slot_(callbackId);
if (slot < 0) {
unsafe { js_fetch_done(requestId); }
return;
}
fn void() h;
?&Erased ud;
unsafe {
h = gDomCallbacks[slot].handler;
ud = gDomCallbacks[slot].userdata;
}
dom_unregister_callback(callbackId);
struct DomFetchResponse r;
r.request = requestId;
r.status = status;
r.ok = status >= 200 && status < 300;
unsafe { ((DomFetchFn_)h)(ud, &r); }
unsafe { js_fetch_done(requestId); }
}
} // namespace std