Menu
Public repository / master
AkurAI-Framework / crates/ui/src/host/wasm.rs
Latest change fee5b6c2cde89fa6bf8dc51068af7336b56a88b8 - akurai-ui: platform::open_url (hand a URL, e.g. a native shell's scheme, to the window) by Ólafur Búi Ólafsson
//! Browser host: raw imports from `host.js` (module `"akurai"`).
//!
//! Strings go Rust→JS as `(ptr, len)` into linear memory. Strings coming back
//! synchronously use a two-step read: the import returns the byte length (or
//! `u32::MAX` for "none") and parks the bytes in JS, then [`take`] copies them
//! out. This module is the crate's only `unsafe`; every call is a plain FFI
//! call with pointers into memory we own for the duration of the call.
use super::{EventRef, Node};
#[link(wasm_import_module = "akurai")]
extern "C" {
fn ak_create_element(p: *const u8, l: usize) -> u32;
fn ak_create_element_ns(np: *const u8, nl: usize, p: *const u8, l: usize) -> u32;
fn ak_create_text(p: *const u8, l: usize) -> u32;
fn ak_create_comment(p: *const u8, l: usize) -> u32;
fn ak_set_attr(n: u32, np: *const u8, nl: usize, vp: *const u8, vl: usize);
fn ak_remove_attr(n: u32, np: *const u8, nl: usize);
fn ak_set_prop_str(n: u32, np: *const u8, nl: usize, vp: *const u8, vl: usize);
fn ak_set_prop_bool(n: u32, np: *const u8, nl: usize, v: u32);
fn ak_get_prop_str(n: u32, np: *const u8, nl: usize) -> u32;
fn ak_insert_before(parent: u32, child: u32, before: u32);
fn ak_remove(n: u32);
fn ak_clear_between(start: u32, end: u32);
fn ak_move_range(start: u32, end: u32, before: u32);
fn ak_parent(n: u32) -> u32;
fn ak_next_sibling(n: u32) -> u32;
fn ak_listen(n: u32, ep: *const u8, el: usize, cb: u32);
fn ak_get_by_id(p: *const u8, l: usize) -> u32;
fn ak_focus(n: u32);
fn ak_focus_first(n: u32) -> u32;
fn ak_active_element() -> u32;
fn ak_contains(a: u32, n: u32) -> u32;
fn ak_event_value(ev: u32) -> u32;
fn ak_event_checked(ev: u32) -> u32;
fn ak_event_key(ev: u32) -> u32;
fn ak_event_target(ev: u32) -> u32;
fn ak_event_shift(ev: u32) -> u32;
fn ak_prevent_default(ev: u32);
fn ak_stop_propagation(ev: u32);
fn ak_fetch(
mp: *const u8,
ml: usize,
up: *const u8,
ul: usize,
bp: *const u8,
bl: usize,
has_body: u32,
cb: u32,
);
fn ak_set_timeout(ms: u32, cb: u32);
fn ak_record_start() -> u32;
fn ak_record_stop(cb: u32);
fn ak_play_audio(p: *const u8, l: usize);
fn ak_stream_audio(p: *const u8, l: usize, volume: f64);
fn ak_stream_video(p: *const u8, l: usize, volume: f64);
fn ak_open_url(p: *const u8, l: usize);
fn ak_event_source(up: *const u8, ul: usize, np: *const u8, nl: usize, cb: u32) -> u32;
fn ak_event_source_close(id: u32);
fn ak_location_hash() -> u32;
fn ak_set_location_hash(p: *const u8, l: usize);
fn ak_now_ms() -> f64;
fn ak_tz_offset_minutes() -> i32;
fn ak_storage_get(p: *const u8, l: usize) -> u32;
fn ak_storage_set(kp: *const u8, kl: usize, vp: *const u8, vl: usize);
fn ak_set_title(p: *const u8, l: usize);
fn ak_set_root_attr(np: *const u8, nl: usize, vp: *const u8, vl: usize);
fn ak_log(p: *const u8, l: usize);
fn ak_take(dst: *mut u8);
}
const NONE: u32 = u32::MAX;
fn take(len: u32) -> Option<String> {
if len == NONE {
return None;
}
let mut buf = vec![0u8; len as usize];
// SAFETY: `buf` holds exactly `len` writable bytes; JS copies the parked
// string of that length into it.
unsafe { ak_take(buf.as_mut_ptr()) };
Some(String::from_utf8(buf).unwrap_or_default())
}
fn node(id: u32) -> Option<Node> {
(id != 0).then_some(Node(id))
}
// SAFETY (all fns below): FFI calls passing pointers/lengths of live `&str`s.
pub(crate) fn create_element(tag: &str) -> Node {
Node(unsafe { ak_create_element(tag.as_ptr(), tag.len()) })
}
pub(crate) fn create_element_ns(ns: &str, tag: &str) -> Node {
Node(unsafe { ak_create_element_ns(ns.as_ptr(), ns.len(), tag.as_ptr(), tag.len()) })
}
pub(crate) fn create_text(text: &str) -> Node {
Node(unsafe { ak_create_text(text.as_ptr(), text.len()) })
}
pub(crate) fn create_comment(text: &str) -> Node {
Node(unsafe { ak_create_comment(text.as_ptr(), text.len()) })
}
pub(crate) fn set_attr(n: Node, name: &str, value: &str) {
unsafe { ak_set_attr(n.0, name.as_ptr(), name.len(), value.as_ptr(), value.len()) }
}
pub(crate) fn remove_attr(n: Node, name: &str) {
unsafe { ak_remove_attr(n.0, name.as_ptr(), name.len()) }
}
pub(crate) fn set_prop_str(n: Node, name: &str, value: &str) {
unsafe { ak_set_prop_str(n.0, name.as_ptr(), name.len(), value.as_ptr(), value.len()) }
}
pub(crate) fn set_prop_bool(n: Node, name: &str, value: bool) {
unsafe { ak_set_prop_bool(n.0, name.as_ptr(), name.len(), value as u32) }
}
pub(crate) fn get_prop_str(n: Node, name: &str) -> String {
take(unsafe { ak_get_prop_str(n.0, name.as_ptr(), name.len()) }).unwrap_or_default()
}
pub(crate) fn insert_before(parent: Node, child: Node, before: Option<Node>) {
unsafe { ak_insert_before(parent.0, child.0, before.map_or(0, |b| b.0)) }
}
pub(crate) fn remove(n: Node) {
unsafe { ak_remove(n.0) }
}
pub(crate) fn clear_between(start: Node, end: Node) {
unsafe { ak_clear_between(start.0, end.0) }
}
pub(crate) fn move_range(start: Node, end: Node, before: Node) {
unsafe { ak_move_range(start.0, end.0, before.0) }
}
pub(crate) fn parent(n: Node) -> Option<Node> {
node(unsafe { ak_parent(n.0) })
}
pub(crate) fn next_sibling(n: Node) -> Option<Node> {
node(unsafe { ak_next_sibling(n.0) })
}
pub(crate) fn listen(n: Node, event: &str, cb: u32) {
unsafe { ak_listen(n.0, event.as_ptr(), event.len(), cb) }
}
pub(crate) fn get_by_id(id: &str) -> Option<Node> {
node(unsafe { ak_get_by_id(id.as_ptr(), id.len()) })
}
pub(crate) fn focus(n: Node) {
unsafe { ak_focus(n.0) }
}
pub(crate) fn focus_first(n: Node) -> bool {
unsafe { ak_focus_first(n.0) != 0 }
}
pub(crate) fn active_element() -> Option<Node> {
node(unsafe { ak_active_element() })
}
pub(crate) fn contains(a: Node, n: Node) -> bool {
unsafe { ak_contains(a.0, n.0) != 0 }
}
pub(crate) fn event_value(ev: EventRef) -> String {
take(unsafe { ak_event_value(ev.0) }).unwrap_or_default()
}
pub(crate) fn event_checked(ev: EventRef) -> bool {
unsafe { ak_event_checked(ev.0) != 0 }
}
pub(crate) fn event_key(ev: EventRef) -> String {
take(unsafe { ak_event_key(ev.0) }).unwrap_or_default()
}
pub(crate) fn event_target(ev: EventRef) -> Option<Node> {
node(unsafe { ak_event_target(ev.0) })
}
pub(crate) fn event_shift(ev: EventRef) -> bool {
unsafe { ak_event_shift(ev.0) != 0 }
}
pub(crate) fn prevent_default(ev: EventRef) {
unsafe { ak_prevent_default(ev.0) }
}
pub(crate) fn stop_propagation(ev: EventRef) {
unsafe { ak_stop_propagation(ev.0) }
}
pub(crate) fn fetch(method: &str, url: &str, body: Option<&str>, cb: u32) {
let b = body.unwrap_or("");
unsafe {
ak_fetch(
method.as_ptr(),
method.len(),
url.as_ptr(),
url.len(),
b.as_ptr(),
b.len(),
body.is_some() as u32,
cb,
)
}
}
pub(crate) fn set_timeout(ms: u32, cb: u32) {
unsafe { ak_set_timeout(ms, cb) }
}
pub(crate) fn record_start() -> bool {
unsafe { ak_record_start() == 1 }
}
pub(crate) fn record_stop(cb: u32) {
unsafe { ak_record_stop(cb) }
}
pub(crate) fn play_audio(src: &str) {
unsafe { ak_play_audio(src.as_ptr(), src.len()) }
}
pub(crate) fn stream_audio(src: &str, volume: f64) {
unsafe { ak_stream_audio(src.as_ptr(), src.len(), volume) }
}
pub(crate) fn open_url(url: &str) {
unsafe { ak_open_url(url.as_ptr(), url.len()) }
}
pub(crate) fn stream_video(src: &str, volume: f64) {
unsafe { ak_stream_video(src.as_ptr(), src.len(), volume) }
}
pub(crate) fn event_source(url: &str, names: &str, cb: u32) -> u32 {
unsafe { ak_event_source(url.as_ptr(), url.len(), names.as_ptr(), names.len(), cb) }
}
pub(crate) fn event_source_close(id: u32) {
unsafe { ak_event_source_close(id) }
}
pub(crate) fn location_hash() -> String {
take(unsafe { ak_location_hash() }).unwrap_or_default()
}
pub(crate) fn set_location_hash(hash: &str) {
unsafe { ak_set_location_hash(hash.as_ptr(), hash.len()) }
}
pub(crate) fn now_ms() -> f64 {
unsafe { ak_now_ms() }
}
pub(crate) fn tz_offset_minutes() -> i32 {
unsafe { ak_tz_offset_minutes() }
}
pub(crate) fn storage_get(key: &str) -> Option<String> {
take(unsafe { ak_storage_get(key.as_ptr(), key.len()) })
}
pub(crate) fn storage_set(key: &str, value: &str) {
unsafe { ak_storage_set(key.as_ptr(), key.len(), value.as_ptr(), value.len()) }
}
pub(crate) fn set_title(title: &str) {
unsafe { ak_set_title(title.as_ptr(), title.len()) }
}
pub(crate) fn set_root_attr(name: &str, value: &str) {
unsafe { ak_set_root_attr(name.as_ptr(), name.len(), value.as_ptr(), value.len()) }
}
pub(crate) fn log(message: &str) {
unsafe { ak_log(message.as_ptr(), message.len()) }
}