use anyhow::Result;
#[cfg(feature = "wit-bindings")]
use tairitsu_vdom::{ElementHandle, EventHandle};
#[cfg(feature = "wit-bindings")]
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct WitElement(u64);
#[cfg(feature = "wit-bindings")]
impl WitElement {
pub const fn from_raw(id: u64) -> Self {
Self(id)
}
pub const fn as_raw(&self) -> u64 {
self.0
}
}
#[cfg(feature = "wit-bindings")]
impl ElementHandle for WitElement {
fn as_any(&self) -> &dyn std::any::Any {
self
}
}
#[cfg(feature = "wit-bindings")]
#[derive(Clone)]
pub struct WitEvent(u64);
#[cfg(feature = "wit-bindings")]
impl WitEvent {
pub const fn from_raw(id: u64) -> Self {
Self(id)
}
pub const fn as_raw(&self) -> u64 {
self.0
}
}
#[cfg(feature = "wit-bindings")]
impl EventHandle for WitEvent {
fn as_any(&self) -> &dyn std::any::Any {
self
}
}
#[derive(Debug, Clone)]
#[cfg(feature = "wit-bindings")]
pub struct WitPlatform;
#[cfg(feature = "wit-bindings")]
impl WitPlatform {
pub fn new() -> Result<Self> {
#[cfg(not(target_family = "wasm"))]
anyhow::bail!(
"WitPlatform is only available on wasm32 targets (wasm32-wasip2). \
Native hosts should execute through tairitsu-runtime/wasmtime host flow."
);
#[allow(unreachable_code)]
Ok(Self)
}
pub fn set_style_static(_element: &WitElement, _name: &str, _value: &str) -> Result<()> {
#[cfg(target_family = "wasm")]
{
wasm_impl::set_style_static_impl(_element.as_raw(), _name, _value)
}
#[cfg(not(target_family = "wasm"))]
{
Ok(())
}
}
pub fn mount_vnode_to_app(&self, _vnode: tairitsu_vdom::VNode) -> Result<()> {
#[cfg(not(target_family = "wasm"))]
anyhow::bail!("mount_vnode_to_app is only available on wasm32 targets (wasm32-wasip2)");
#[cfg(target_family = "wasm")]
{
wasm_impl::mount_vnode_to_app(self, _vnode)
}
}
pub fn apply_patches(
&self,
_parent: &WitElement,
_patches: &[tairitsu_vdom::Patch],
) -> Result<()> {
#[cfg(not(target_family = "wasm"))]
anyhow::bail!("apply_patches is only available on wasm32 targets (wasm32-wasip2)");
#[cfg(target_family = "wasm")]
{
wasm_impl::apply_patches(self, _parent, _patches)
}
}
pub fn clear_cache() {
#[cfg(target_family = "wasm")]
{
crate::handle_cache::HandleCache::with(|cache| cache.clear());
}
}
pub fn cache_stats() -> crate::handle_cache::CacheStats {
#[cfg(target_family = "wasm")]
{
crate::handle_cache::HandleCache::with(|cache| cache.stats())
}
#[cfg(not(target_family = "wasm"))]
{
crate::handle_cache::CacheStats {
hits: 0,
misses: 0,
total: 0,
hit_rate: 0.0,
size: 0,
}
}
}
pub fn invalidate_element_cache(_element: &WitElement) {
#[cfg(target_family = "wasm")]
{
crate::handle_cache::HandleCache::with(|cache| {
cache.invalidate_style_handle(_element.as_raw());
});
}
}
}
#[cfg(all(feature = "wit-bindings", target_family = "wasm"))]
pub mod wasm_impl {
use anyhow::Result;
use std::cell::RefCell;
use std::collections::HashMap;
use std::sync::atomic::{AtomicU64, Ordering};
use tairitsu_vdom::{
CanvasContext, CanvasOps, ClipboardOps, ContentEditableOps, DomOps, DomRect, EventData,
EventWitHandle, FileOps, FocusEvent, GenericEvent, GeoOps, IdbOps, InputEvent,
KeyboardEvent, LayoutOps, MediaOps, MediaQueryOps, ObserverOps, Platform, QueryOps,
ScrollOps, TimerOps, VNode,
};
use super::{WitElement, WitEvent, WitPlatform};
thread_local! {
static CURRENT_RENDER_COMPONENT: RefCell<Option<tairitsu_vdom::ComponentId>> = RefCell::new(None);
}
fn with_render_component<T>(id: tairitsu_vdom::ComponentId, f: impl FnOnce() -> T) -> T {
CURRENT_RENDER_COMPONENT.with(|c| {
*c.borrow_mut() = Some(id);
});
let result = f();
CURRENT_RENDER_COMPONENT.with(|c| {
*c.borrow_mut() = None;
});
result
}
fn create_tracked_effect<F>(f: F) -> tairitsu_vdom::EffectHandle
where
F: FnMut() + 'static,
{
let handle = tairitsu_vdom::create_effect(f);
CURRENT_RENDER_COMPONENT.with(|c| {
if let Some(id) = *c.borrow() {
tairitsu_vdom::register_effect_handle(id, handle.clone());
}
});
handle
}
unsafe extern "C" {
fn tairitsu_component_bootstrap();
}
type EventCallback = Box<dyn FnMut(Box<dyn EventData>)>;
type EventCallbackMap = HashMap<u64, EventCallback>;
type TimeoutCallback = Option<Box<dyn FnOnce()>>;
type AnimationCallback = Option<Box<dyn FnOnce(f64)>>;
type ResizeObserverCallback = Box<dyn FnMut(Vec<tairitsu_vdom::ResizeObserverEntry>)>;
type MutationObserverCallback = Box<dyn FnMut(Vec<tairitsu_vdom::MutationRecord>)>;
type MediaQueryListCallback = Box<dyn FnMut(bool)>;
type ScrollCallback = Box<dyn FnMut(f64, f64)>;
type WindowResizeCallback = Box<dyn FnMut(i32, i32)>;
type VideoFrameCallback = Box<dyn FnMut(String)>;
static NEXT_CALLBACK_ID: AtomicU64 = AtomicU64::new(1);
pub fn next_callback_id() -> u64 {
NEXT_CALLBACK_ID.fetch_add(1, Ordering::SeqCst)
}
#[cfg(target_family = "wasm")]
fn log_error(message: &str) {
let _ = message;
}
#[cfg(target_family = "wasm")]
fn log_warning(message: &str) {
let _ = message;
}
#[cfg(target_family = "wasm")]
fn log_info(message: &str) {
let _ = message;
}
#[cfg(not(target_family = "wasm"))]
fn log_error(message: &str) {
eprintln!("[WitPlatform ERROR] {}", message);
}
#[cfg(not(target_family = "wasm"))]
fn log_warning(message: &str) {
eprintln!("[WitPlatform WARNING] {}", message);
}
#[cfg(not(target_family = "wasm"))]
fn log_info(message: &str) {
eprintln!("[WitPlatform] {}", message);
}
type GeoCallback =
Box<dyn FnOnce(Result<tairitsu_vdom::GeoPosition, tairitsu_vdom::GeoPositionError>)>;
type IntervalCallback = Box<dyn FnMut()>;
thread_local! {
static EVENT_CALLBACKS: RefCell<EventCallbackMap> = RefCell::new(HashMap::new());
static ELEMENT_LISTENERS: RefCell<HashMap<(u64, String), u64>>
= RefCell::new(HashMap::new());
static TIMEOUT_CALLBACKS: RefCell<HashMap<u64, TimeoutCallback>> = RefCell::new(HashMap::new());
static INTERVAL_CALLBACKS: RefCell<HashMap<u64, IntervalCallback>> = RefCell::new(HashMap::new());
pub static ANIMATION_CALLBACKS: RefCell<HashMap<u64, AnimationCallback>> = RefCell::new(HashMap::new());
static RESIZE_OBSERVER_CALLBACKS: RefCell<HashMap<u64, ResizeObserverCallback>> = RefCell::new(HashMap::new());
static MUTATION_OBSERVER_CALLBACKS: RefCell<HashMap<u64, MutationObserverCallback>> = RefCell::new(HashMap::new());
static MEDIA_QUERY_LIST_CALLBACKS: RefCell<HashMap<u64, MediaQueryListCallback>> = RefCell::new(HashMap::new());
static SCROLL_CALLBACKS: RefCell<HashMap<u64, ScrollCallback>> = RefCell::new(HashMap::new());
static WINDOW_RESIZE_CALLBACKS: RefCell<HashMap<u64, WindowResizeCallback>> = RefCell::new(HashMap::new());
static VIDEO_FRAME_CALLBACKS: RefCell<HashMap<u64, VideoFrameCallback>> = RefCell::new(HashMap::new());
static PROMISE_CALLBACKS: RefCell<HashMap<u64, Box<dyn FnOnce(Result<String, String>)>>> = RefCell::new(HashMap::new());
static GEO_CALLBACKS: RefCell<HashMap<u64, GeoCallback>> = RefCell::new(HashMap::new());
static FILE_READER_CALLBACKS: RefCell<HashMap<u64, Box<dyn FnOnce(Result<String, String>)>>> = RefCell::new(HashMap::new());
static FILE_READER_BIN_CALLBACKS: RefCell<HashMap<u64, Box<dyn FnOnce(Result<Vec<u8>, String>)>>> = RefCell::new(HashMap::new());
static IDB_CALLBACKS: RefCell<HashMap<u64, Box<dyn FnOnce(Result<String, String>)>>> = RefCell::new(HashMap::new());
}
pub mod bindings {
use super::BrowserComponent;
include!(concat!(env!("OUT_DIR"), "/wit_bindings_generated.rs"));
export!(BrowserComponent);
}
pub(super) fn set_style_static_impl(element: u64, name: &str, value: &str) -> Result<()> {
let style_handle = crate::handle_cache::HandleCache::with(|cache| {
if let Some(cached_handle) = cache.get_style_handle(element) {
return cached_handle;
}
let style_handle =
bindings::tairitsu_browser::full::element_css_inline_style::get_style(element);
cache.set_style_handle(element, style_handle);
style_handle
});
bindings::tairitsu_browser::full::css_style_declaration::set_property(
style_handle,
name,
value,
None, );
Ok(())
}
pub fn get_tag_name(_platform: &WitPlatform, element: &WitElement) -> String {
bindings::tairitsu_browser::full::element::get_tag_name(element.as_raw())
}
pub fn get_attribute(
_platform: &WitPlatform,
element: &WitElement,
name: &str,
) -> Option<String> {
bindings::tairitsu_browser::full::element::get_attribute(element.as_raw(), name)
}
pub fn get_parent_element(_platform: &WitPlatform, element: &WitElement) -> Option<WitElement> {
bindings::tairitsu_browser::full::node::get_parent_element(element.as_raw())
.map(WitElement::from_raw)
}
pub fn prevent_event_default(event_handle: u64) {
bindings::tairitsu_browser::full::event_target::prevent_default(event_handle);
}
pub fn wasm_get_pathname() -> String {
bindings::tairitsu_browser::full::location::get_pathname()
}
pub fn wasm_push_state(url: &str) {
bindings::tairitsu_browser::full::history::push_state("", "", Some(url));
}
pub fn wasm_replace_state(url: &str) {
bindings::tairitsu_browser::full::history::replace_state("", "", Some(url));
}
pub(super) struct BrowserComponent;
impl bindings::exports::tairitsu_browser::full::event_callbacks::Guest for BrowserComponent {
fn on_mouse_event(
listener_id: u64,
event_handle: u64,
data: bindings::exports::tairitsu_browser::full::event_callbacks::MouseEventData,
) {
let wit_handle = EventWitHandle::from_wit(event_handle);
let target = bindings::tairitsu_browser::full::event::get_target(event_handle);
let current_target =
bindings::tairitsu_browser::full::event::get_current_target(event_handle);
let event: Box<dyn EventData> = Box::new(
MouseEvent::new()
.client_x(data.client_x as i32)
.client_y(data.client_y as i32)
.screen_x(0)
.screen_y(0)
.offset_x(data.offset_x as i32)
.offset_y(data.offset_y as i32)
.page_x(0)
.page_y(0)
.movement_x(0)
.movement_y(0)
.button(data.button as i16)
.buttons(data.buttons as u16)
.ctrl_key(data.ctrl_key)
.shift_key(data.shift_key)
.alt_key(data.alt_key)
.meta_key(data.meta_key)
.event_handle(wit_handle)
.target(target.unwrap_or(0))
.current_target(current_target.unwrap_or(0)),
);
dispatch_event(listener_id, "mouse", event);
}
fn on_keyboard_event(
listener_id: u64,
event_handle: u64,
data: bindings::exports::tairitsu_browser::full::event_callbacks::KeyboardEventData,
) {
let wit_handle = EventWitHandle::from_wit(event_handle);
let target = bindings::tairitsu_browser::full::event::get_target(event_handle);
let current_target =
bindings::tairitsu_browser::full::event::get_current_target(event_handle);
let event: Box<dyn EventData> = Box::new(
KeyboardEvent::new()
.key(data.key)
.code(data.code)
.key_code(data.key_code)
.ctrl_key(data.ctrl_key)
.shift_key(data.shift_key)
.alt_key(data.alt_key)
.meta_key(data.meta_key)
.repeat(data.repeat)
.event_handle(wit_handle)
.target(target.unwrap_or(0))
.current_target(current_target.unwrap_or(0)),
);
dispatch_event(listener_id, "keyboard", event);
}
fn on_focus_event(
listener_id: u64,
event_handle: u64,
_data: bindings::exports::tairitsu_browser::full::event_callbacks::FocusEventData,
) {
let wit_handle = EventWitHandle::from_wit(event_handle);
let target = bindings::tairitsu_browser::full::event::get_target(event_handle);
let current_target =
bindings::tairitsu_browser::full::event::get_current_target(event_handle);
let event: Box<dyn EventData> = Box::new(
FocusEvent::new()
.event_handle(wit_handle)
.target(target.unwrap_or(0))
.current_target(current_target.unwrap_or(0)),
);
dispatch_event(listener_id, "focus", event);
}
fn on_input_event(
listener_id: u64,
event_handle: u64,
data: bindings::exports::tairitsu_browser::full::event_callbacks::InputEventData,
) {
let wit_handle = EventWitHandle::from_wit(event_handle);
let target = bindings::tairitsu_browser::full::event::get_target(event_handle);
let current_target =
bindings::tairitsu_browser::full::event::get_current_target(event_handle);
let event: Box<dyn EventData> = Box::new(
InputEvent::new()
.data(data.data.unwrap_or_default())
.event_handle(wit_handle)
.target(target.unwrap_or(0))
.current_target(current_target.unwrap_or(0)),
);
dispatch_event(listener_id, "input", event);
}
fn on_generic_event(listener_id: u64, event_handle: u64, event_type: String) {
let wit_handle = EventWitHandle::from_wit(event_handle);
let target = bindings::tairitsu_browser::full::event::get_target(event_handle);
let event: Box<dyn EventData> = match event_type.as_str() {
"submit" => Box::new(tairitsu_vdom::SubmitEvent {
target,
form_data: Vec::new(),
event_handle: wit_handle,
}),
"change" => Box::new(tairitsu_vdom::ChangeEvent {
value: String::new(),
target,
event_handle: wit_handle,
}),
_ => {
let current_target =
bindings::tairitsu_browser::full::event::get_current_target(event_handle);
Box::new(
GenericEvent::new()
.event_type(&event_type)
.event_handle(wit_handle)
.target(target.unwrap_or(0))
.current_target(current_target.unwrap_or(0)),
)
}
};
dispatch_event(listener_id, &event_type, event);
}
}
fn dispatch_event(listener_id: u64, _event_type: &str, event: Box<dyn EventData>) {
EVENT_CALLBACKS.with(|m| {
let mut handler_opt = m.borrow_mut().remove(&listener_id);
if let Some(handler) = &mut handler_opt {
handler(event);
}
if let Some(handler) = handler_opt {
m.borrow_mut().insert(listener_id, handler);
}
});
}
impl bindings::exports::tairitsu_browser::full::timer_callbacks::Guest for BrowserComponent {
fn on_timeout(callback_id: u64) {
TIMEOUT_CALLBACKS.with(|m| {
if let Some(callback) = m.borrow_mut().remove(&callback_id) {
if let Some(cb) = callback {
cb();
}
}
});
}
fn on_interval(callback_id: u64) {
INTERVAL_CALLBACKS.with(|m| {
let mut callbacks = m.borrow_mut();
if let Some(callback) = callbacks.get_mut(&callback_id) {
callback();
}
});
}
}
impl bindings::exports::tairitsu_browser::full::animation_callbacks::Guest for BrowserComponent {
fn on_frame(callback_id: u64, timestamp: f64) {
let callback =
ANIMATION_CALLBACKS.with(|m| m.borrow_mut().remove(&callback_id).flatten());
if let Some(cb) = callback {
cb(timestamp);
}
}
}
impl bindings::exports::tairitsu_browser::full::resize_observer_callbacks::Guest
for BrowserComponent
{
fn on_resize(
callback_id: u64,
entries: Vec<
bindings::exports::tairitsu_browser::full::resize_observer_callbacks::ResizeEntry,
>,
) {
RESIZE_OBSERVER_CALLBACKS.with(|m| {
let mut callbacks = m.borrow_mut();
if let Some(handler) = callbacks.get_mut(&callback_id) {
let converted_entries: Vec<tairitsu_vdom::ResizeObserverEntry> = entries
.into_iter()
.map(|entry| tairitsu_vdom::ResizeObserverEntry {
target: entry.target,
content_rect: tairitsu_vdom::DomRect {
x: entry.content_rect.x,
y: entry.content_rect.y,
width: entry.content_rect.width,
height: entry.content_rect.height,
},
border_box_size: vec![],
content_box_size: vec![],
})
.collect();
handler(converted_entries);
}
});
}
}
impl bindings::exports::tairitsu_browser::full::mutation_observer_callbacks::Guest
for BrowserComponent
{
fn on_mutate(
callback_id: u64,
entries: Vec<bindings::exports::tairitsu_browser::full::mutation_observer_callbacks::MutationEntry>,
) {
MUTATION_OBSERVER_CALLBACKS.with(|m| {
let mut callbacks = m.borrow_mut();
if let Some(handler) = callbacks.get_mut(&callback_id) {
let converted_records: Vec<tairitsu_vdom::MutationRecord> = entries
.into_iter()
.map(|entry| tairitsu_vdom::MutationRecord {
record_type: entry.mutation_type,
target: entry.target,
added_nodes: vec![],
removed_nodes: vec![],
previous_sibling: entry.previous_sibling,
next_sibling: entry.next_sibling,
attribute_name: entry.attribute_name,
attribute_namespace: entry.attribute_namespace,
old_value: entry.old_value,
})
.collect();
handler(converted_records);
}
});
}
}
impl bindings::exports::tairitsu_browser::full::media_query_list_callbacks::Guest
for BrowserComponent
{
fn on_change(callback_id: u64, matches: bool) {
MEDIA_QUERY_LIST_CALLBACKS.with(|m| {
let mut callbacks = m.borrow_mut();
if let Some(handler) = callbacks.get_mut(&callback_id) {
handler(matches);
}
});
}
}
impl bindings::exports::tairitsu_browser::full::scroll_callbacks::Guest for BrowserComponent {
fn on_scroll_event(callback_id: u64, scroll_x: f64, scroll_y: f64) {
SCROLL_CALLBACKS.with(|m| {
let mut callbacks = m.borrow_mut();
if let Some(handler) = callbacks.get_mut(&callback_id) {
handler(scroll_x, scroll_y);
}
});
}
}
impl bindings::exports::tairitsu_browser::full::window_resize_callbacks::Guest
for BrowserComponent
{
fn on_window_resize(callback_id: u64, width: i32, height: i32) {
WINDOW_RESIZE_CALLBACKS.with(|m| {
let mut callbacks = m.borrow_mut();
if let Some(handler) = callbacks.get_mut(&callback_id) {
handler(width, height);
}
});
}
}
impl bindings::exports::tairitsu_browser::full::video_frame_callbacks::Guest for BrowserComponent {
fn on_video_frame(callback_id: u64, event: String) {
VIDEO_FRAME_CALLBACKS.with(|m| {
let mut callbacks = m.borrow_mut();
if let Some(handler) = callbacks.get_mut(&callback_id) {
handler(event);
}
});
}
}
impl bindings::exports::tairitsu_browser::full::promise_callbacks::Guest for BrowserComponent {
fn on_promise_resolved(promise_id: u64, value: String) {
PROMISE_CALLBACKS.with(|m| {
if let Some(callback) = m.borrow_mut().remove(&promise_id) {
callback(Ok(value));
}
});
}
fn on_promise_rejected(promise_id: u64, error: String) {
PROMISE_CALLBACKS.with(|m| {
if let Some(callback) = m.borrow_mut().remove(&promise_id) {
callback(Err(error));
}
});
}
}
impl bindings::exports::tairitsu_browser::full::geolocation_callbacks::Guest for BrowserComponent {
fn on_position_success(
callback_id: u64,
position: bindings::exports::tairitsu_browser::full::geolocation_callbacks::GeoPosition,
) {
GEO_CALLBACKS.with(|m| {
if let Some(callback) = m.borrow_mut().remove(&callback_id) {
callback(Ok(tairitsu_vdom::GeoPosition {
latitude: position.latitude,
longitude: position.longitude,
altitude: position.altitude,
accuracy: position.accuracy,
altitude_accuracy: position.altitude_accuracy,
heading: position.heading,
speed: position.speed,
}));
}
});
}
fn on_position_error(
callback_id: u64,
error: bindings::exports::tairitsu_browser::full::geolocation_callbacks::GeoPositionError,
) {
GEO_CALLBACKS.with(|m| {
if let Some(callback) = m.borrow_mut().remove(&callback_id) {
callback(Err(tairitsu_vdom::GeoPositionError {
code: error.code,
message: error.message,
}));
}
});
}
}
impl bindings::exports::tairitsu_browser::full::idb_callbacks::Guest for BrowserComponent {
fn on_idb_request_success(callback_id: u64, result: Option<String>) {
IDB_CALLBACKS.with(|m| {
if let Some(callback) = m.borrow_mut().remove(&callback_id) {
callback(Ok(result.unwrap_or_default()));
}
});
}
fn on_idb_request_error(callback_id: u64, error: String) {
IDB_CALLBACKS.with(|m| {
if let Some(callback) = m.borrow_mut().remove(&callback_id) {
callback(Err(error));
}
});
}
}
impl bindings::exports::tairitsu_browser::full::file_reader_callbacks::Guest for BrowserComponent {
fn on_file_reader_load(callback_id: u64, result: String) {
FILE_READER_CALLBACKS.with(|m| {
if let Some(callback) = m.borrow_mut().remove(&callback_id) {
callback(Ok(result));
}
});
}
fn on_file_reader_error(callback_id: u64, error: String) {
FILE_READER_CALLBACKS.with(|m| {
if let Some(callback) = m.borrow_mut().remove(&callback_id) {
callback(Err(error));
}
});
}
}
impl bindings::exports::tairitsu_browser::full::lifecycle::Guest for BrowserComponent {
fn start() -> Result<(), String> {
if let Err(e) = validate_wit_environment() {
let msg = format!("WIT environment validation failed: {}", e);
log_error(&msg);
return Err(msg);
}
#[cfg(target_family = "wasm")]
register_dom_ops_functions();
unsafe {
tairitsu_component_bootstrap();
}
Ok(())
}
}
#[cfg(target_family = "wasm")]
fn register_dom_ops_functions() {
use tairitsu_vdom::register_wit_functions;
unsafe {
register_wit_functions(
|element, property, value| {
let style_handle =
bindings::tairitsu_browser::full::element_css_inline_style::get_style(
element,
);
bindings::tairitsu_browser::full::css_style_declaration::set_property(
style_handle,
property,
value,
None,
);
Ok(())
},
|element| {
let rect = bindings::tairitsu_browser::full::element::get_bounding_client_rect(
element,
);
tairitsu_vdom::DomRect {
x: rect.x,
y: rect.y,
width: rect.width,
height: rect.height,
}
},
|element, name, value| {
bindings::tairitsu_browser::full::element::set_attribute(element, name, value);
},
);
}
use tairitsu_vdom::DomFuncs;
unsafe {
tairitsu_vdom::register_dom_functions(DomFuncs {
get_scroll_top: |el| bindings::tairitsu_browser::full::element::get_scroll_top(el),
set_scroll_top: |el, v| {
bindings::tairitsu_browser::full::element::set_scroll_top(el, v);
},
get_scroll_height: |el| {
bindings::tairitsu_browser::full::element::get_scroll_height(el)
},
get_client_height: |el| {
bindings::tairitsu_browser::full::element::get_client_height(el)
},
get_class_list: |el| bindings::tairitsu_browser::full::element::get_class_list(el),
class_list_add: |list, tokens| {
bindings::tairitsu_browser::full::dom_token_list::add(list, tokens);
},
class_list_remove: |list, tokens| {
bindings::tairitsu_browser::full::dom_token_list::remove(list, tokens);
},
class_list_contains: |list, token| {
bindings::tairitsu_browser::full::dom_token_list::contains(list, token)
},
first_child: |el| bindings::tairitsu_browser::full::node::get_first_child(el),
query_selector_on: |el, sel| {
bindings::tairitsu_browser::full::parent_node::query_selector(el, sel)
},
create_element: |tag| {
bindings::tairitsu_browser::full::document::create_element(tag, None)
},
append_child: |parent, child| {
bindings::tairitsu_browser::full::node::append_child(parent, child)
},
remove_child: |parent, child| {
bindings::tairitsu_browser::full::node::remove_child(parent, child)
},
get_computed_style_value: |el, prop| {
let style =
bindings::tairitsu_browser::full::window::get_computed_style(el, None);
bindings::tairitsu_browser::full::css_style_declaration::get_property_value(
style, prop,
)
},
set_timeout_fn: |callback: Box<dyn FnOnce()>, ms: i32| -> i32 {
let callback_id = next_callback_id() as i32;
TIMEOUT_CALLBACKS.with(|m| {
m.borrow_mut().insert(callback_id as u64, Some(callback));
});
bindings::tairitsu_browser::full::platform_helpers::set_timeout(
callback_id as u64,
ms,
) as i32
},
clear_timeout_fn: |id: i32| {
TIMEOUT_CALLBACKS.with(|m| {
m.borrow_mut().remove(&(id as u64));
});
bindings::tairitsu_browser::full::platform_helpers::clear_timeout(id);
},
request_animation_frame_fn: |callback: Box<dyn FnMut(f64)>| -> u32 {
let callback_id = next_callback_id();
ANIMATION_CALLBACKS.with(|m| {
m.borrow_mut().insert(callback_id, Some(callback));
});
bindings::tairitsu_browser::full::platform_helpers::request_animation_frame(
callback_id,
) as u32
},
cancel_animation_frame_fn: |id: u32| {
ANIMATION_CALLBACKS.with(|m| {
m.borrow_mut().remove(&(id as u64));
});
bindings::tairitsu_browser::full::platform_helpers::cancel_animation_frame(id);
},
});
}
tairitsu_vdom::register_ref_resolver(|any| {
any.downcast_ref::<super::WitElement>().map(|w| w.as_raw())
});
log_info("DOM operations functions registered for event handlers");
}
fn validate_wit_environment() -> Result<(), String> {
let result = std::panic::catch_unwind(|| {
let _body = bindings::tairitsu_browser::full::document::get_body();
let _width = bindings::tairitsu_browser::full::window::get_inner_width();
log_info("Environment validation passed");
});
match result {
Ok(_) => Ok(()),
Err(_) => Err("WIT host call failed during validation".to_string()),
}
}
impl DomOps for WitPlatform {
type Element = WitElement;
type Event = WitEvent;
fn create_element(&self, tag: &str) -> Self::Element {
let handle = bindings::tairitsu_browser::full::document::create_element(tag, None);
WitElement::from_raw(handle)
}
fn create_text_node(&self, text: &str) -> Self::Element {
let handle = bindings::tairitsu_browser::full::document::create_text_node(text);
WitElement::from_raw(handle)
}
fn append_child(&self, parent: &Self::Element, child: &Self::Element) {
let _ = bindings::tairitsu_browser::full::node::append_child(
parent.as_raw(),
child.as_raw(),
);
}
fn remove_child(&self, parent: &Self::Element, child: &Self::Element) {
crate::handle_cache::HandleCache::with(|cache| {
cache.invalidate_style_handle(child.as_raw());
});
let _ = bindings::tairitsu_browser::full::node::remove_child(
parent.as_raw(),
child.as_raw(),
);
}
fn set_attribute(&self, element: &Self::Element, name: &str, value: &str) {
bindings::tairitsu_browser::full::element::set_attribute(element.as_raw(), name, value);
}
fn remove_attribute(&self, element: &Self::Element, name: &str) {
bindings::tairitsu_browser::full::element::remove_attribute(element.as_raw(), name);
}
fn set_style(&self, element: &Self::Element, name: &str, value: &str) {
let style_handle = crate::handle_cache::HandleCache::with(|cache| {
if let Some(cached_handle) = cache.get_style_handle(element.as_raw()) {
return cached_handle;
}
let style_handle =
bindings::tairitsu_browser::full::element_css_inline_style::get_style(
element.as_raw(),
);
cache.set_style_handle(element.as_raw(), style_handle);
style_handle
});
bindings::tairitsu_browser::full::css_style_declaration::set_property(
style_handle,
name,
value,
None,
);
}
fn set_class(&self, element: &Self::Element, class: &str) {
bindings::tairitsu_browser::full::element::set_attribute(
element.as_raw(),
"class",
class,
);
}
fn add_event_listener(
&self,
element: &Self::Element,
event: &str,
handler: Box<dyn FnMut(Box<dyn EventData>)>,
) {
let listener_id = bindings::tairitsu_browser::full::event_target::add_event_listener(
element.as_raw(),
event,
false,
)
.unwrap_or_else(|e| {
log_error(&format!("add_event_listener failed: {}", e));
0
});
EVENT_CALLBACKS.with(|m| m.borrow_mut().insert(listener_id, handler));
ELEMENT_LISTENERS.with(|m| {
m.borrow_mut()
.insert((element.as_raw(), event.to_string()), listener_id);
});
log_info(&format!(
"Added event listener: event={}, listener_id={}",
event, listener_id
));
}
fn remove_event_listener(&self, element: &Self::Element, event: &str) {
let listener_id = ELEMENT_LISTENERS.with(|m| {
m.borrow_mut()
.remove(&(element.as_raw(), event.to_string()))
});
if let Some(listener_id) = listener_id {
EVENT_CALLBACKS.with(|m| m.borrow_mut().remove(&listener_id));
let _ = bindings::tairitsu_browser::full::event_target::remove_event_listener(
element.as_raw(),
listener_id,
);
log_info(&format!(
"Removed event listener: event={}, listener_id={}",
event, listener_id
));
} else {
log_warning(&format!(
"remove_event_listener: no listener found for event '{}' on element {}",
event,
element.as_raw()
));
}
}
fn add_event_listener_with_options(
&self,
element: &Self::Element,
event: &str,
handler: Box<dyn FnMut(Box<dyn EventData>)>,
options: tairitsu_vdom::ListenerOptions,
) {
let listener_id = bindings::tairitsu_browser::full::event_target::add_event_listener(
element.as_raw(),
event,
options.capture,
)
.unwrap_or_else(|e| {
log_error(&format!("add_event_listener failed: {}", e));
0
});
EVENT_CALLBACKS.with(|m| m.borrow_mut().insert(listener_id, handler));
ELEMENT_LISTENERS.with(|m| {
m.borrow_mut()
.insert((element.as_raw(), event.to_string()), listener_id);
});
log_info(&format!(
"Added event listener with options: event={}, listener_id={}",
event, listener_id
));
}
fn get_inner_html(&self, element: &Self::Element) -> String {
bindings::tairitsu_browser::full::element::get_inner_html(element.as_raw())
}
fn set_inner_html(&self, element: &Self::Element, html: String) {
bindings::tairitsu_browser::full::element::set_inner_html(element.as_raw(), &html);
}
fn get_attribute(&self, element: &Self::Element, name: &str) -> Option<String> {
bindings::tairitsu_browser::full::element::get_attribute(element.as_raw(), name)
}
fn class_list_add(&self, element: &Self::Element, tokens: &[&str]) {
let list = bindings::tairitsu_browser::full::element::get_class_list(element.as_raw());
let wit_tokens: Vec<String> = tokens.iter().map(|s| s.to_string()).collect();
bindings::tairitsu_browser::full::dom_token_list::add(list, &wit_tokens);
}
fn class_list_remove(&self, element: &Self::Element, tokens: &[&str]) {
let list = bindings::tairitsu_browser::full::element::get_class_list(element.as_raw());
let wit_tokens: Vec<String> = tokens.iter().map(|s| s.to_string()).collect();
bindings::tairitsu_browser::full::dom_token_list::remove(list, &wit_tokens);
}
fn class_list_contains(&self, element: &Self::Element, token: &str) -> bool {
let list = bindings::tairitsu_browser::full::element::get_class_list(element.as_raw());
bindings::tairitsu_browser::full::dom_token_list::contains(list, token)
}
fn first_child(&self, element: &Self::Element) -> Option<Self::Element> {
bindings::tairitsu_browser::full::node::get_first_child(element.as_raw())
.map(WitElement::from_raw)
}
fn insert_before(
&self,
parent: &Self::Element,
new_node: &Self::Element,
reference_node: Option<&Self::Element>,
) {
let ref_handle = reference_node.map(|r| r.as_raw());
let _ = bindings::tairitsu_browser::full::node::insert_before(
parent.as_raw(),
new_node.as_raw(),
ref_handle,
);
}
fn query_selector_on(
&self,
element: &Self::Element,
selector: &str,
) -> Option<Self::Element> {
bindings::tairitsu_browser::full::parent_node::query_selector(
element.as_raw(),
selector,
)
.map(WitElement::from_raw)
}
}
impl TimerOps for WitPlatform {
fn set_timeout(&self, callback: Box<dyn FnOnce()>, ms: i32) -> i32 {
let callback_id = next_callback_id();
TIMEOUT_CALLBACKS.with(|m| m.borrow_mut().insert(callback_id, Some(callback)));
bindings::tairitsu_browser::full::platform_helpers::set_timeout(callback_id, ms)
}
fn clear_timeout(&self, id: i32) {
bindings::tairitsu_browser::full::platform_helpers::clear_timeout(id)
}
fn set_interval(&self, callback: Box<dyn FnMut()>, ms: i32) -> i32 {
let callback_id = next_callback_id();
INTERVAL_CALLBACKS.with(|m| m.borrow_mut().insert(callback_id, callback));
bindings::tairitsu_browser::full::platform_helpers::set_interval(callback_id, ms)
}
fn clear_interval(&self, id: i32) {
bindings::tairitsu_browser::full::platform_helpers::clear_interval(id)
}
fn request_animation_frame(&self, callback: Box<dyn FnOnce(f64)>) -> u32 {
let callback_id = next_callback_id();
ANIMATION_CALLBACKS.with(|m| m.borrow_mut().insert(callback_id, Some(callback)));
bindings::tairitsu_browser::full::platform_helpers::request_animation_frame(callback_id)
}
fn cancel_animation_frame(&self, id: u32) {
bindings::tairitsu_browser::full::platform_helpers::cancel_animation_frame(id)
}
}
impl LayoutOps for WitPlatform {
fn get_bounding_client_rect(&self, element: &Self::Element) -> DomRect {
let rect = bindings::tairitsu_browser::full::element::get_bounding_client_rect(
element.as_raw(),
);
DomRect {
x: rect.x,
y: rect.y,
width: rect.width,
height: rect.height,
}
}
fn inner_width(&self) -> i32 {
bindings::tairitsu_browser::full::window::get_inner_width()
}
fn inner_height(&self) -> i32 {
bindings::tairitsu_browser::full::window::get_inner_height()
}
fn get_element_scroll_top(&self, element: &Self::Element) -> f64 {
bindings::tairitsu_browser::full::element::get_scroll_top(element.as_raw())
}
fn set_element_scroll_top(&self, element: &Self::Element, value: f64) {
bindings::tairitsu_browser::full::element::set_scroll_top(element.as_raw(), value);
}
fn get_element_scroll_height(&self, element: &Self::Element) -> i32 {
bindings::tairitsu_browser::full::element::get_scroll_height(element.as_raw())
}
fn get_element_client_height(&self, element: &Self::Element) -> i32 {
bindings::tairitsu_browser::full::element::get_client_height(element.as_raw())
}
fn get_element_client_width(&self, element: &Self::Element) -> i32 {
bindings::tairitsu_browser::full::element::get_client_width(element.as_raw())
}
}
impl ObserverOps for WitPlatform {
fn create_resize_observer(
&self,
callback: Box<dyn FnMut(Vec<tairitsu_vdom::ResizeObserverEntry>)>,
) -> u64 {
let callback_id = next_callback_id();
RESIZE_OBSERVER_CALLBACKS.with(|m| m.borrow_mut().insert(callback_id, callback));
bindings::tairitsu_browser::full::platform_helpers::create_resize_observer(callback_id)
}
fn observe_resize(&self, observer: u64, element: &Self::Element) {
bindings::tairitsu_browser::full::resize_observer::observe(
observer,
element.as_raw(),
None,
);
}
fn unobserve_resize(&self, observer: u64, element: &Self::Element) {
bindings::tairitsu_browser::full::resize_observer::unobserve(
observer,
element.as_raw(),
);
}
fn disconnect_resize(&self, observer: u64) {
bindings::tairitsu_browser::full::resize_observer::disconnect(observer);
}
fn create_mutation_observer(
&self,
callback: Box<dyn FnMut(Vec<tairitsu_vdom::MutationRecord>)>,
) -> u64 {
let callback_id = next_callback_id();
MUTATION_OBSERVER_CALLBACKS.with(|m| m.borrow_mut().insert(callback_id, callback));
bindings::tairitsu_browser::full::platform_helpers::create_mutation_observer(
callback_id,
)
}
fn observe_mutations(
&self,
observer: u64,
element: &Self::Element,
_options: Option<tairitsu_vdom::MutationObserverInit>,
) {
bindings::tairitsu_browser::full::mutation_observer::observe(
observer,
element.as_raw(),
None,
);
}
fn disconnect_mutation(&self, observer: u64) {
bindings::tairitsu_browser::full::mutation_observer::disconnect(observer);
}
}
impl MediaQueryOps for WitPlatform {
fn match_media(&self, query: &str) -> u64 {
bindings::tairitsu_browser::full::window::match_media(query)
}
fn media_query_list_get_media(&self, list: u64) -> String {
bindings::tairitsu_browser::full::media_query_list::get_media(list)
}
fn media_query_list_get_matches(&self, list: u64) -> bool {
bindings::tairitsu_browser::full::media_query_list::get_matches(list)
}
fn media_query_list_add_listener(&self, list: u64, callback: Box<dyn FnMut(bool)>) -> u64 {
let callback_id = next_callback_id();
MEDIA_QUERY_LIST_CALLBACKS.with(|m| m.borrow_mut().insert(callback_id, callback));
bindings::tairitsu_browser::full::media_query_list::add_listener(
list,
Some(callback_id),
);
callback_id
}
fn media_query_list_remove_listener(&self, list: u64, listener_id: u64) {
MEDIA_QUERY_LIST_CALLBACKS.with(|m| m.borrow_mut().remove(&listener_id));
bindings::tairitsu_browser::full::media_query_list::remove_listener(
list,
Some(listener_id),
);
}
}
impl ClipboardOps for WitPlatform {
fn copy_to_clipboard(&self, text: &str) -> bool {
bindings::tairitsu_browser::full::platform_helpers::copy_to_clipboard(text)
}
fn read_clipboard(&self) -> Option<String> {
bindings::tairitsu_browser::full::platform_helpers::read_clipboard()
}
fn clipboard_write_text_async(
&self,
text: &str,
on_complete: Box<dyn FnOnce(Result<(), String>)>,
) {
let promise_id =
bindings::tairitsu_browser::full::platform_helpers::clipboard_write_text_promise(
text,
);
PROMISE_CALLBACKS.with(|m| {
m.borrow_mut().insert(
promise_id,
Box::new(|res| match res {
Ok(_) => on_complete(Ok(())),
Err(e) => on_complete(Err(e)),
}),
);
});
}
fn clipboard_read_text_async(&self, on_complete: Box<dyn FnOnce(Result<String, String>)>) {
let promise_id =
bindings::tairitsu_browser::full::platform_helpers::clipboard_read_text_promise();
PROMISE_CALLBACKS.with(|m| {
m.borrow_mut().insert(promise_id, on_complete);
});
}
}
impl ContentEditableOps for WitPlatform {
fn get_contenteditable_state(
&self,
element: &Self::Element,
) -> Option<tairitsu_vdom::ContentEditableState> {
bindings::tairitsu_browser::full::platform_helpers::get_contenteditable_state(
element.as_raw(),
)
.map(|state| tairitsu_vdom::ContentEditableState {
editable: state.editable,
focused: state.focused,
})
}
fn exec_command(&self, command: &str, value: Option<&str>) -> bool {
bindings::tairitsu_browser::full::document::exec_command(command, None, value)
}
fn get_selection_start(&self, element: &Self::Element) -> Option<u32> {
bindings::tairitsu_browser::full::platform_helpers::get_selection_start(
element.as_raw(),
)
}
fn get_selection_end(&self, element: &Self::Element) -> Option<u32> {
bindings::tairitsu_browser::full::platform_helpers::get_selection_end(element.as_raw())
}
fn set_content_editable(&self, element: &Self::Element, editable: bool) {
bindings::tairitsu_browser::full::platform_helpers::set_content_editable(
element.as_raw(),
editable,
);
}
}
impl ScrollOps for WitPlatform {
fn get_scroll_y(&self) -> f64 {
bindings::tairitsu_browser::full::window::get_scroll_y()
}
fn scroll_to(&self, top: f64, behavior: &str) {
bindings::tairitsu_browser::full::platform_helpers::scroll_to(top, behavior)
}
fn on_scroll(&self, callback: Box<dyn FnMut(f64, f64)>) {
let callback_id = next_callback_id();
SCROLL_CALLBACKS.with(|m| m.borrow_mut().insert(callback_id, callback));
bindings::tairitsu_browser::full::platform_helpers::on_scroll(callback_id);
}
fn on_resize(&self, callback: Box<dyn FnMut(i32, i32)>) {
let callback_id = next_callback_id();
WINDOW_RESIZE_CALLBACKS.with(|m| m.borrow_mut().insert(callback_id, callback));
bindings::tairitsu_browser::full::platform_helpers::on_resize_callback(callback_id);
}
fn prefers_dark_mode(&self) -> bool {
bindings::tairitsu_browser::full::platform_helpers::prefers_dark_mode()
}
fn request_fullscreen(&self, element: &Self::Element) {
let _ = bindings::tairitsu_browser::full::element::request_fullscreen(
element.as_raw(),
None,
);
}
fn get_scroll_top_from_point(&self, x: i32, y: i32) -> f64 {
bindings::tairitsu_browser::full::platform_helpers::get_scroll_top_from_point(x, y)
}
fn get_scroll_top_by_selector(&self, selector: &str) -> f64 {
bindings::tairitsu_browser::full::platform_helpers::get_scroll_top_by_selector(selector)
}
fn get_target_element_from_event(
&self,
client_x: i32,
client_y: i32,
) -> Option<Self::Element> {
bindings::tairitsu_browser::full::document::element_from_point(
client_x as f64,
client_y as f64,
)
.map(WitElement::from_raw)
}
}
impl QueryOps for WitPlatform {
fn get_element_by_id(&self, id: &str) -> Option<Self::Element> {
bindings::tairitsu_browser::full::platform_helpers::get_element_by_id(id)
.map(WitElement::from_raw)
}
fn query_selector(&self, selector: &str) -> Option<Self::Element> {
bindings::tairitsu_browser::full::platform_helpers::query_selector(selector)
.map(WitElement::from_raw)
}
fn query_selector_all(&self, selector: &str) -> Vec<Self::Element> {
bindings::tairitsu_browser::full::platform_helpers::query_selector_all(selector)
.into_iter()
.map(WitElement::from_raw)
.collect()
}
fn element_from_point(&self, x: i32, y: i32) -> Option<Self::Element> {
bindings::tairitsu_browser::full::document::element_from_point(x as f64, y as f64)
.map(WitElement::from_raw)
}
fn element_closest(
&self,
element: &Self::Element,
selector: &str,
) -> Option<Self::Element> {
bindings::tairitsu_browser::full::element::closest(element.as_raw(), selector)
.map(WitElement::from_raw)
}
fn get_element_rect_by_id(&self, id: &str) -> Option<DomRect> {
bindings::tairitsu_browser::full::platform_helpers::get_element_rect_by_id(id).map(
|rect| DomRect {
x: rect.x,
y: rect.y,
width: rect.width,
height: rect.height,
},
)
}
fn get_bounding_rect_by_class(
&self,
class_name: &str,
element: &Self::Element,
) -> Option<DomRect> {
bindings::tairitsu_browser::full::platform_helpers::get_bounding_rect_by_class(
class_name,
element.as_raw(),
)
.map(|rect| DomRect {
x: rect.x,
y: rect.y,
width: rect.width,
height: rect.height,
})
}
}
impl CanvasOps for WitPlatform {
fn get_canvas_context(
&self,
element: &Self::Element,
context_type: &str,
) -> Option<CanvasContext> {
bindings::tairitsu_browser::full::html_canvas_element::get_context(
element.as_raw(),
context_type,
None,
)
}
fn canvas_set_fill_style(&self, ctx: CanvasContext, color: &str) {
bindings::tairitsu_browser::full::canvas_fill_stroke_styles::set_fill_style(ctx, color)
}
fn canvas_fill_rect(&self, ctx: CanvasContext, x: f64, y: f64, w: f64, h: f64) {
bindings::tairitsu_browser::full::canvas_rect::fill_rect(ctx, x, y, w, h)
}
fn canvas_clear_rect(&self, ctx: CanvasContext, x: f64, y: f64, w: f64, h: f64) {
bindings::tairitsu_browser::full::canvas_rect::clear_rect(ctx, x, y, w, h)
}
fn draw_qrcode_on_canvas_by_id(
&self,
canvas_id: &str,
matrix: &[Vec<bool>],
modules: u64,
color: &str,
background: &str,
) -> bool {
let wit_matrix: Vec<Vec<bool>> = matrix.to_vec();
bindings::tairitsu_browser::full::platform_helpers::draw_qrcode_on_canvas_by_id(
canvas_id,
&wit_matrix,
modules,
color,
background,
)
}
}
impl MediaOps for WitPlatform {
fn video_play(&self, element: &Self::Element) {
let _ = bindings::tairitsu_browser::full::html_media_element::play(element.as_raw());
}
fn video_pause(&self, element: &Self::Element) {
bindings::tairitsu_browser::full::html_media_element::pause(element.as_raw());
}
fn video_get_current_time(&self, element: &Self::Element) -> f64 {
bindings::tairitsu_browser::full::html_media_element::get_current_time(element.as_raw())
}
fn video_get_duration(&self, element: &Self::Element) -> f64 {
bindings::tairitsu_browser::full::html_media_element::get_duration(element.as_raw())
}
fn video_seek(&self, element: &Self::Element, time: f64) {
bindings::tairitsu_browser::full::html_media_element::set_current_time(
element.as_raw(),
time,
);
}
fn video_set_muted(&self, element: &Self::Element, muted: bool) {
bindings::tairitsu_browser::full::html_media_element::set_muted(
element.as_raw(),
muted,
);
}
fn video_set_volume(&self, element: &Self::Element, volume: f64) {
bindings::tairitsu_browser::full::html_media_element::set_volume(
element.as_raw(),
volume,
);
}
fn create_audio_context(&self) -> u64 {
bindings::tairitsu_browser::full::platform_helpers::create_audio_context()
}
fn create_analyser_node(&self, audio_context: u64) -> u64 {
bindings::tairitsu_browser::full::base_audio_context::create_analyser(audio_context)
}
fn create_media_element_source(&self, audio_context: u64, element: u64) -> u64 {
bindings::tairitsu_browser::full::audio_context::create_media_element_source(
audio_context,
element,
)
}
fn analyser_node_get_frequency_data(&self, analyser: u64) -> Vec<f32> {
bindings::tairitsu_browser::full::platform_helpers::analyser_get_frequency_data(
analyser,
)
}
fn analyser_node_get_time_domain_data(&self, analyser: u64) -> Vec<f32> {
bindings::tairitsu_browser::full::platform_helpers::analyser_get_time_domain_data(
analyser,
)
}
}
impl GeoOps for WitPlatform {
fn get_current_position(
&self,
on_success: Box<dyn FnOnce(tairitsu_vdom::GeoPosition)>,
on_error: Box<dyn FnOnce(tairitsu_vdom::GeoPositionError)>,
enable_high_accuracy: bool,
timeout: u32,
maximum_age: u32,
) {
let callback_id = next_callback_id();
GEO_CALLBACKS.with(|m| {
m.borrow_mut().insert(
callback_id,
Box::new(move |result| match result {
Ok(pos) => on_success(pos),
Err(err) => on_error(err),
}),
);
});
let geo_handle =
bindings::tairitsu_browser::full::platform_helpers::get_geolocation_handle();
bindings::tairitsu_browser::full::platform_helpers::get_current_position(
geo_handle,
callback_id,
callback_id,
enable_high_accuracy,
timeout,
maximum_age,
);
}
}
impl FileOps for WitPlatform {
fn file_reader_sync_read_as_text(
&self,
blob: u64,
encoding: Option<&str>,
) -> Result<String, String> {
bindings::tairitsu_browser::full::platform_helpers::file_reader_sync_read_as_text(
blob, encoding,
)
}
fn file_reader_sync_read_as_array_buffer(&self, blob: u64) -> Result<Vec<u8>, String> {
bindings::tairitsu_browser::full::platform_helpers::file_reader_sync_read_as_array_buffer(blob)
}
fn file_reader_read_as_text(
&self,
blob: u64,
encoding: Option<&str>,
on_complete: Box<dyn FnOnce(Result<String, String>)>,
) {
let callback_id = next_callback_id();
FILE_READER_CALLBACKS.with(|m| {
m.borrow_mut().insert(callback_id, on_complete);
});
bindings::tairitsu_browser::full::platform_helpers::file_reader_read_as_text(
blob,
encoding,
callback_id,
);
}
fn file_reader_read_as_array_buffer(
&self,
blob: u64,
on_complete: Box<dyn FnOnce(Result<Vec<u8>, String>)>,
) {
let callback_id = next_callback_id();
FILE_READER_BIN_CALLBACKS.with(|m| {
m.borrow_mut().insert(
callback_id,
Box::new(move |result| {
on_complete(result.map_err(|e| e));
}),
);
});
bindings::tairitsu_browser::full::platform_helpers::file_reader_read_as_array_buffer(
blob,
callback_id,
);
}
}
impl IdbOps for WitPlatform {
fn idb_open(
&self,
name: &str,
version: Option<u64>,
on_complete: Box<dyn FnOnce(Result<u64, String>)>,
) -> u64 {
let callback_id = next_callback_id();
IDB_CALLBACKS.with(|m| {
m.borrow_mut().insert(
callback_id,
Box::new(move |result| {
let _ = on_complete(result.map(|s| s.parse::<u64>().unwrap_or(0)));
}),
);
});
bindings::tairitsu_browser::full::platform_helpers::idb_open(name, version, callback_id)
}
fn idb_put(
&self,
db: u64,
store_name: &str,
value: &str,
key: Option<&str>,
on_complete: Box<dyn FnOnce(Result<(), String>)>,
) {
let callback_id = next_callback_id();
IDB_CALLBACKS.with(|m| {
m.borrow_mut().insert(
callback_id,
Box::new(move |result| {
let _ = on_complete(result.map(|_| ()));
}),
);
});
bindings::tairitsu_browser::full::platform_helpers::idb_put(
db,
store_name,
value,
key,
callback_id,
);
}
fn idb_get(
&self,
db: u64,
store_name: &str,
key: &str,
on_complete: Box<dyn FnOnce(Result<Option<String>, String>)>,
) {
let callback_id = next_callback_id();
IDB_CALLBACKS.with(|m| {
m.borrow_mut().insert(
callback_id,
Box::new(move |result| {
let _ =
on_complete(result.map(|s| if s.is_empty() { None } else { Some(s) }));
}),
);
});
bindings::tairitsu_browser::full::platform_helpers::idb_get(
db,
store_name,
key,
callback_id,
);
}
fn idb_delete(
&self,
db: u64,
store_name: &str,
key: &str,
on_complete: Box<dyn FnOnce(Result<(), String>)>,
) {
let callback_id = next_callback_id();
IDB_CALLBACKS.with(|m| {
m.borrow_mut().insert(
callback_id,
Box::new(move |result| {
let _ = on_complete(result.map(|_| ()));
}),
);
});
bindings::tairitsu_browser::full::platform_helpers::idb_delete(
db,
store_name,
key,
callback_id,
);
}
fn idb_get_all(
&self,
db: u64,
store_name: &str,
on_complete: Box<dyn FnOnce(Result<Vec<String>, String>)>,
) {
let callback_id = next_callback_id();
IDB_CALLBACKS.with(|m| {
m.borrow_mut().insert(
callback_id,
Box::new(move |result| {
let _ = on_complete(result.map(|s| {
if s.is_empty() {
Vec::new()
} else {
s.split('\n').map(String::from).collect()
}
}));
}),
);
});
bindings::tairitsu_browser::full::platform_helpers::idb_get_all(
db,
store_name,
callback_id,
);
}
fn idb_clear(
&self,
db: u64,
store_name: &str,
on_complete: Box<dyn FnOnce(Result<(), String>)>,
) {
let callback_id = next_callback_id();
IDB_CALLBACKS.with(|m| {
m.borrow_mut().insert(
callback_id,
Box::new(move |result| {
let _ = on_complete(result.map(|_| ()));
}),
);
});
bindings::tairitsu_browser::full::platform_helpers::idb_clear(
db,
store_name,
callback_id,
);
}
}
pub(super) fn mount_vnode_to_app(platform: &WitPlatform, vnode: VNode) -> Result<()> {
let doc_handle: u64 = 0;
let app = if let Some(handle) =
bindings::tairitsu_browser::full::non_element_parent_node::get_element_by_id(
doc_handle, "app",
) {
WitElement::from_raw(handle)
} else {
let body = bindings::tairitsu_browser::full::document::get_body()
.ok_or_else(|| anyhow::anyhow!("document.body is not available"))?;
let div = bindings::tairitsu_browser::full::document::create_element("div", None);
bindings::tairitsu_browser::full::element::set_attribute(div, "id", "app");
let _ = bindings::tairitsu_browser::full::node::append_child(body, div);
WitElement::from_raw(div)
};
bindings::tairitsu_browser::full::node::set_text_content(app.as_raw(), Some(""));
render_vnode(platform, &vnode, &app)
}
fn render_vnode(platform: &WitPlatform, vnode: &VNode, parent: &WitElement) -> Result<()> {
match vnode {
VNode::Element(velement) => {
let element = platform.create_element(&velement.tag);
if let Some(ref element_ref) = velement.element_ref {
use std::any::Any;
let mut ref_mut = element_ref.borrow_mut();
*ref_mut = Some(Box::new(element.clone()) as Box<dyn Any>);
}
for (name, value) in &velement.attributes {
platform.set_attribute(&element, name, value);
}
if !velement.class.static_classes.is_empty() {
platform.set_class(&element, &velement.class.static_classes);
}
if !velement.style.static_styles.is_empty() {
for part in velement.style.static_styles.split(';') {
let part = part.trim();
if part.is_empty() {
continue;
}
if let Some((name, value)) = part.split_once(':') {
platform.set_style(&element, name.trim(), value.trim());
}
}
}
for (name, value) in &velement.style.css_variables {
platform.set_style(&element, name, value);
}
for (event_name, handler) in &velement.event_handlers {
let handler = handler.clone();
platform.add_event_listener(
&element,
event_name,
Box::new(move |event| {
(handler.borrow_mut())(event);
}),
);
}
if let Some(ref inner) = velement.inner_html {
platform.set_inner_html(&element, inner.clone());
} else {
for child in &velement.children {
render_vnode(platform, child, &element)?;
}
}
for (name, compute) in &velement.dynamic_attributes {
let raw = element.as_raw();
let name = name.clone();
let compute = compute.clone();
create_tracked_effect(move || {
let value = (compute.borrow_mut())();
bindings::tairitsu_browser::full::element::set_attribute(
raw, &name, &value,
);
});
}
for (name, compute) in &velement.dynamic_styles {
let raw = element.as_raw();
let name = name.clone();
let compute = compute.clone();
let platform = platform.clone();
create_tracked_effect(move || {
let value = (compute.borrow_mut())();
let el = WitElement::from_raw(raw);
platform.set_style(&el, &name, &value);
});
}
for compute in &velement.dynamic_classes {
let raw = element.as_raw();
let compute = compute.clone();
create_tracked_effect(move || {
let value = (compute.borrow_mut())();
bindings::tairitsu_browser::full::element::set_attribute(
raw, "class", &value,
);
});
}
platform.append_child(parent, &element);
}
VNode::Text(vtext) => {
let text_node = platform.create_text_node(&vtext.text);
platform.append_child(parent, &text_node);
}
VNode::DynamicText(dt) => {
let text_node = platform.create_text_node(&dt.initial);
platform.append_child(parent, &text_node);
let raw = text_node.as_raw();
let compute = dt.compute.clone();
create_tracked_effect(move || {
let new_text = (compute.borrow_mut())();
bindings::tairitsu_browser::full::node::set_text_content(raw, Some(&new_text));
});
}
VNode::Fragment(children) => {
for child in children {
render_vnode(platform, child, parent)?;
}
}
}
Ok(())
}
pub fn apply_patches(
platform: &WitPlatform,
parent: &WitElement,
patches: &[tairitsu_vdom::Patch],
) -> Result<()> {
for patch in patches {
apply_patch(platform, parent, patch)?;
}
Ok(())
}
fn apply_patch(
platform: &WitPlatform,
element: &WitElement,
patch: &tairitsu_vdom::Patch,
) -> Result<()> {
use tairitsu_vdom::Patch;
match patch {
Patch::CreateNode { node } => {
render_vnode(platform, node, element)?;
}
Patch::RemoveNode => {
}
Patch::ReplaceNode { node } => {
replace_element_content(platform, element, node)?;
}
Patch::UpdateText { text } => {
bindings::tairitsu_browser::full::node::set_text_content(
element.as_raw(),
Some(text),
);
}
Patch::UpdateAttribute { name, value } => {
platform.set_attribute(element, name, value);
}
Patch::AddAttribute { name, value } => {
platform.set_attribute(element, name, value);
}
Patch::RemoveAttribute { name } => {
platform.remove_attribute(element, name);
}
Patch::UpdateStyle { style } => {
apply_style(platform, element, style)?;
}
Patch::UpdateClass { class } => {
if !class.static_classes.is_empty() {
platform.set_class(element, &class.static_classes);
}
}
Patch::InsertChild { index, node } => {
insert_child_at(platform, element, *index, node)?;
}
Patch::RemoveChild { index } => {
remove_child_at(platform, element, *index)?;
}
Patch::UpdateChild {
index,
patches: child_patches,
} => {
let child = get_child_at(element, *index)?;
if let Some(child_element) = child {
for child_patch in child_patches {
apply_patch(platform, &child_element, child_patch)?;
}
}
}
Patch::AddEvent { name, handler } => {
let handler = handler.clone();
platform.add_event_listener(
element,
name,
Box::new(move |event| {
(handler.borrow_mut())(event);
}),
);
}
Patch::UpdateEvent { name, handler } => {
platform.remove_event_listener(element, name);
let handler = handler.clone();
platform.add_event_listener(
element,
name,
Box::new(move |event| {
(handler.borrow_mut())(event);
}),
);
}
Patch::RemoveEvent { name } => {
platform.remove_event_listener(element, name);
}
}
Ok(())
}
fn create_vnode_element(platform: &WitPlatform, vnode: &VNode) -> Result<WitElement> {
match vnode {
VNode::Element(velement) => {
let element = platform.create_element(&velement.tag);
if let Some(ref element_ref) = velement.element_ref {
use std::any::Any;
let mut ref_mut = element_ref.borrow_mut();
*ref_mut = Some(Box::new(element.clone()) as Box<dyn Any>);
}
for (name, value) in &velement.attributes {
platform.set_attribute(&element, name, value);
}
if !velement.class.static_classes.is_empty() {
platform.set_class(&element, &velement.class.static_classes);
}
apply_style(platform, &element, &velement.style)?;
for (event_name, handler) in &velement.event_handlers {
let handler = handler.clone();
platform.add_event_listener(
&element,
event_name,
Box::new(move |event| {
(handler.borrow_mut())(event);
}),
);
}
for (name, compute) in &velement.dynamic_attributes {
let raw = element.as_raw();
let name = name.clone();
let compute = compute.clone();
create_tracked_effect(move || {
let value = (compute.borrow_mut())();
bindings::tairitsu_browser::full::element::set_attribute(
raw, &name, &value,
);
});
}
for (name, compute) in &velement.dynamic_styles {
let raw = element.as_raw();
let name = name.clone();
let compute = compute.clone();
let p = *platform;
create_tracked_effect(move || {
let value = (compute.borrow_mut())();
let el = WitElement::from_raw(raw);
p.set_style(&el, &name, &value);
});
}
for compute in &velement.dynamic_classes {
let raw = element.as_raw();
let compute = compute.clone();
create_tracked_effect(move || {
let value = (compute.borrow_mut())();
bindings::tairitsu_browser::full::element::set_attribute(
raw, "class", &value,
);
});
}
Ok(element)
}
VNode::Text(vtext) => Ok(platform.create_text_node(&vtext.text)),
VNode::DynamicText(dt) => {
let text_node = platform.create_text_node(&dt.initial);
let raw = text_node.as_raw();
let compute = dt.compute.clone();
create_tracked_effect(move || {
let new_text = (compute.borrow_mut())();
bindings::tairitsu_browser::full::node::set_text_content(raw, Some(&new_text));
});
Ok(text_node)
}
VNode::Fragment(_) => {
Ok(platform.create_element("div"))
}
}
}
fn replace_element_content(
platform: &WitPlatform,
element: &WitElement,
vnode: &VNode,
) -> Result<()> {
bindings::tairitsu_browser::full::node::set_text_content(element.as_raw(), Some(""));
match vnode {
VNode::Element(velement) => {
for (name, value) in &velement.attributes {
platform.set_attribute(element, name, value);
}
if !velement.class.static_classes.is_empty() {
platform.set_class(element, &velement.class.static_classes);
}
apply_style(platform, element, &velement.style)?;
for event_name in velement.event_handlers.keys() {
platform.remove_event_listener(element, event_name);
}
for (event_name, handler) in &velement.event_handlers {
let handler = handler.clone();
platform.add_event_listener(
&element,
event_name,
Box::new(move |event| {
(handler.borrow_mut())(event);
}),
);
}
for child in &velement.children {
render_vnode(platform, child, element)?;
}
}
VNode::Text(vtext) => {
bindings::tairitsu_browser::full::node::set_text_content(
element.as_raw(),
Some(&vtext.text),
);
}
VNode::Fragment(children) => {
for child in children {
render_vnode(platform, child, element)?;
}
}
VNode::DynamicText(dt) => {
bindings::tairitsu_browser::full::node::set_text_content(
element.as_raw(),
Some(&dt.initial),
);
let raw = element.as_raw();
let compute = dt.compute.clone();
create_tracked_effect(move || {
let new_text = (compute.borrow_mut())();
bindings::tairitsu_browser::full::node::set_text_content(raw, Some(&new_text));
});
}
}
Ok(())
}
fn apply_style(
platform: &WitPlatform,
element: &WitElement,
style: &tairitsu_vdom::Style,
) -> Result<()> {
if !style.static_styles.is_empty() {
for part in style.static_styles.split(';') {
let part = part.trim();
if part.is_empty() {
continue;
}
if let Some((name, value)) = part.split_once(':') {
platform.set_style(element, name.trim(), value.trim());
}
}
}
for (name, value) in &style.css_variables {
platform.set_style(element, name, value);
}
Ok(())
}
fn insert_child_at(
platform: &WitPlatform,
parent: &WitElement,
index: usize,
node: &VNode,
) -> Result<()> {
let new_element = create_vnode_element(platform, node)?;
let child_nodes_handle =
bindings::tairitsu_browser::full::node::get_child_nodes(parent.as_raw());
let child_count =
bindings::tairitsu_browser::full::node_list::get_length(child_nodes_handle) as usize;
if index >= child_count {
platform.append_child(parent, &new_element);
} else {
let next_sibling =
bindings::tairitsu_browser::full::node_list::item(child_nodes_handle, index as u32);
if let Some(sibling) = next_sibling {
let _ = bindings::tairitsu_browser::full::node::insert_before(
parent.as_raw(),
new_element.as_raw(),
Some(sibling),
);
} else {
platform.append_child(parent, &new_element);
}
}
if let VNode::Element(velement) = node {
for child in &velement.children {
render_vnode(platform, child, &new_element)?;
}
}
Ok(())
}
fn remove_child_at(platform: &WitPlatform, parent: &WitElement, index: usize) -> Result<()> {
let child = get_child_at(parent, index)?;
if let Some(child_element) = child {
platform.remove_child(parent, &child_element);
}
Ok(())
}
fn get_child_at(parent: &WitElement, index: usize) -> Result<Option<WitElement>> {
let child_nodes_handle =
bindings::tairitsu_browser::full::node::get_child_nodes(parent.as_raw());
let child_handle =
bindings::tairitsu_browser::full::node_list::item(child_nodes_handle, index as u32);
Ok(child_handle.map(WitElement::from_raw))
}
}
pub fn get_pathname() -> String {
#[cfg(not(target_family = "wasm"))]
{
"/".to_string()
}
#[cfg(all(feature = "wit-bindings", target_family = "wasm"))]
{
wasm_impl::wasm_get_pathname()
}
}
pub fn push_state(url: &str) {
#[cfg(all(feature = "wit-bindings", target_family = "wasm"))]
{
wasm_impl::wasm_push_state(url);
}
#[allow(unused_variables)]
let _ = url;
}
pub fn replace_state(url: &str) {
#[cfg(all(feature = "wit-bindings", target_family = "wasm"))]
{
wasm_impl::wasm_replace_state(url);
}
#[allow(unused_variables)]
let _ = url;
}
#[cfg(all(feature = "wit-bindings", target_family = "wasm"))]
pub fn get_tag_name(platform: &WitPlatform, element: &WitElement) -> String {
wasm_impl::get_tag_name(platform, element)
}
#[cfg(all(feature = "wit-bindings", target_family = "wasm"))]
pub fn get_attribute(platform: &WitPlatform, element: &WitElement, name: &str) -> Option<String> {
wasm_impl::get_attribute(platform, element, name)
}
#[cfg(all(feature = "wit-bindings", target_family = "wasm"))]
pub fn get_parent_element(platform: &WitPlatform, element: &WitElement) -> Option<WitElement> {
wasm_impl::get_parent_element(platform, element)
}
#[cfg(all(feature = "wit-bindings", target_family = "wasm"))]
pub fn prevent_event_default(event_handle: u64) {
wasm_impl::prevent_event_default(event_handle)
}
#[cfg(test)]
mod tests {
use tairitsu_vdom::{Classes, Patch, Style, VElement, VNode, VText};
#[test]
fn test_apply_empty_patches() {
let patches: Vec<Patch> = vec![];
assert!(patches.is_empty(), "Empty patches should be empty");
}
#[test]
fn test_patch_variants() {
let node = VNode::Text(VText::new("Hello"));
let create_patch = Patch::CreateNode { node: node.clone() };
match create_patch {
Patch::CreateNode { node } => {
assert_eq!(node, VNode::Text(VText::new("Hello")));
}
_ => panic!("Expected CreateNode patch"),
}
let update_text_patch = Patch::UpdateText {
text: "World".to_string(),
};
match update_text_patch {
Patch::UpdateText { text } => {
assert_eq!(text, "World");
}
_ => panic!("Expected UpdateText patch"),
}
let update_attr_patch = Patch::UpdateAttribute {
name: "class".to_string(),
value: "active".to_string(),
};
match update_attr_patch {
Patch::UpdateAttribute { name, value } => {
assert_eq!(name, "class");
assert_eq!(value, "active");
}
_ => panic!("Expected UpdateAttribute patch"),
}
let add_attr_patch = Patch::AddAttribute {
name: "id".to_string(),
value: "test".to_string(),
};
match add_attr_patch {
Patch::AddAttribute { name, value } => {
assert_eq!(name, "id");
assert_eq!(value, "test");
}
_ => panic!("Expected AddAttribute patch"),
}
let remove_attr_patch = Patch::RemoveAttribute {
name: "disabled".to_string(),
};
match remove_attr_patch {
Patch::RemoveAttribute { name } => {
assert_eq!(name, "disabled");
}
_ => panic!("Expected RemoveAttribute patch"),
}
let child = VNode::Text(VText::new("Child"));
let insert_child_patch = Patch::InsertChild {
index: 0,
node: child,
};
match insert_child_patch {
Patch::InsertChild { index, node } => {
assert_eq!(index, 0);
assert_eq!(node, VNode::Text(VText::new("Child")));
}
_ => panic!("Expected InsertChild patch"),
}
let remove_child_patch = Patch::RemoveChild { index: 0 };
match remove_child_patch {
Patch::RemoveChild { index } => {
assert_eq!(index, 0);
}
_ => panic!("Expected RemoveChild patch"),
}
let child_patches = vec![Patch::UpdateText {
text: "Updated".to_string(),
}];
let update_child_patch = Patch::UpdateChild {
index: 1,
patches: child_patches,
};
match update_child_patch {
Patch::UpdateChild { index, patches } => {
assert_eq!(index, 1);
assert_eq!(patches.len(), 1);
}
_ => panic!("Expected UpdateChild patch"),
}
let new_node = VNode::Element(VElement::new("div"));
let replace_patch = Patch::ReplaceNode {
node: new_node.clone(),
};
match replace_patch {
Patch::ReplaceNode { node } => {
assert_eq!(node, new_node);
}
_ => panic!("Expected ReplaceNode patch"),
}
#[allow(clippy::type_complexity)]
let dummy_handler: std::rc::Rc<
std::cell::RefCell<dyn FnMut(Box<dyn tairitsu_vdom::events::EventData>)>,
> = std::rc::Rc::new(std::cell::RefCell::new(|_| {}));
let add_event_patch = Patch::AddEvent {
name: "click".to_string(),
handler: dummy_handler.clone(),
};
match add_event_patch {
Patch::AddEvent { name, .. } => {
assert_eq!(name, "click");
}
_ => panic!("Expected AddEvent patch"),
}
let update_event_patch = Patch::UpdateEvent {
name: "click".to_string(),
handler: dummy_handler.clone(),
};
match update_event_patch {
Patch::UpdateEvent { name, .. } => {
assert_eq!(name, "click");
}
_ => panic!("Expected UpdateEvent patch"),
}
let remove_event_patch = Patch::RemoveEvent {
name: "click".to_string(),
};
match remove_event_patch {
Patch::RemoveEvent { name } => {
assert_eq!(name, "click");
}
_ => panic!("Expected RemoveEvent patch"),
}
}
#[test]
fn test_nested_patches() {
let nested_patches = vec![
Patch::UpdateAttribute {
name: "href".to_string(),
value: "#".to_string(),
},
Patch::UpdateClass {
class: Classes::new().add("active"),
},
Patch::UpdateStyle {
style: Style::new().add("color", "red"),
},
];
let update_child_patch = Patch::UpdateChild {
index: 0,
patches: nested_patches,
};
match update_child_patch {
Patch::UpdateChild { index, patches } => {
assert_eq!(index, 0);
assert_eq!(patches.len(), 3);
assert!(matches!(patches[0], Patch::UpdateAttribute { .. }));
assert!(matches!(patches[1], Patch::UpdateClass { .. }));
assert!(matches!(patches[2], Patch::UpdateStyle { .. }));
}
_ => panic!("Expected UpdateChild patch"),
}
}
#[test]
fn test_patch_is_empty() {
let empty_patch = Patch::UpdateChild {
index: 0,
patches: vec![],
};
assert!(empty_patch.is_empty());
let non_empty_patch = Patch::UpdateChild {
index: 0,
patches: vec![Patch::UpdateText {
text: "test".to_string(),
}],
};
assert!(!non_empty_patch.is_empty());
let text_patch = Patch::UpdateText {
text: "test".to_string(),
};
assert!(!text_patch.is_empty());
}
#[test]
fn test_style_and_class_patches() {
let style = Style::new()
.add("color", "red")
.add("background-color", "blue");
let update_style_patch = Patch::UpdateStyle { style };
match update_style_patch {
Patch::UpdateStyle { style } => {
assert_eq!(style.static_styles, "color:red;background-color:blue");
}
_ => panic!("Expected UpdateStyle patch"),
}
let class = Classes::new().add("btn").add("active");
let update_class_patch = Patch::UpdateClass { class };
match update_class_patch {
Patch::UpdateClass { class } => {
assert_eq!(class.static_classes, "btn active");
}
_ => panic!("Expected UpdateClass patch"),
}
}
#[test]
fn test_remove_node_patch() {
let remove_node_patch = Patch::RemoveNode;
match remove_node_patch {
Patch::RemoveNode => {
}
_ => panic!("Expected RemoveNode patch"),
}
}
#[cfg(feature = "wit-bindings")]
#[test]
fn test_wit_element_clone() {
let element1 = super::WitElement::from_raw(42);
let element2 = element1;
let element3 = element2;
assert_eq!(element3.as_raw(), 42);
}
#[cfg(feature = "wit-bindings")]
#[test]
fn test_wit_element_equality() {
let elem1 = super::WitElement::from_raw(100);
let elem2 = super::WitElement::from_raw(100);
let elem3 = super::WitElement::from_raw(200);
assert_eq!(elem1, elem2);
assert_ne!(elem1, elem3);
}
#[cfg(feature = "wit-bindings")]
#[test]
fn test_wit_event_clone() {
let event1 = super::WitEvent::from_raw(123);
let event2 = event1.clone();
assert_eq!(event1.as_raw(), 123);
assert_eq!(event2.as_raw(), 123);
}
#[cfg(feature = "wit-bindings")]
#[test]
fn test_wit_element_element_handle() {
use tairitsu_vdom::ElementHandle;
let element = super::WitElement::from_raw(42);
let any_ref = element.as_any();
assert!(any_ref.is::<super::WitElement>());
if let Some(downcasted) = any_ref.downcast_ref::<super::WitElement>() {
assert_eq!(downcasted.as_raw(), 42);
} else {
panic!("Failed to downcast to WitElement");
}
}
#[cfg(feature = "wit-bindings")]
#[test]
fn test_wit_event_event_handle() {
use tairitsu_vdom::EventHandle;
let event = super::WitEvent::from_raw(999);
let any_ref = event.as_any();
assert!(any_ref.is::<super::WitEvent>());
if let Some(downcasted) = any_ref.downcast_ref::<super::WitEvent>() {
assert_eq!(downcasted.as_raw(), 999);
} else {
panic!("Failed to downcast to WitEvent");
}
}
#[cfg(all(feature = "wit-bindings", not(target_family = "wasm")))]
#[test]
fn test_wit_platform_new_native() {
let result = super::WitPlatform::new();
assert!(
result.is_err(),
"WitPlatform::new should fail on native targets"
);
let err_msg = result.unwrap_err().to_string();
assert!(err_msg.contains("wasm32"), "Error should mention wasm32");
}
#[cfg(all(feature = "wit-bindings", target_family = "wasm"))]
#[test]
fn test_wit_platform_new_wasm() {
let result = super::WitPlatform::new();
assert!(
result.is_ok(),
"WitPlatform::new should succeed on wasm targets"
);
}
#[cfg(all(feature = "wit-bindings", not(target_family = "wasm")))]
#[test]
fn test_set_style_static_native() {
let element = super::WitElement::from_raw(42);
let result = super::WitPlatform::set_style_static(&element, "color", "red");
assert!(
result.is_ok(),
"set_style_static should return Ok on native targets"
);
}
#[cfg(feature = "wit-bindings")]
#[test]
fn test_set_style_static_inputs() {
let element = super::WitElement::from_raw(1);
let result = super::WitPlatform::set_style_static(&element, "", "");
#[cfg(target_family = "wasm")]
{
}
#[cfg(not(target_family = "wasm"))]
{
assert!(result.is_ok(), "Empty style values should be handled");
}
let result = super::WitPlatform::set_style_static(&element, "--my-var", "blue");
#[cfg(not(target_family = "wasm"))]
{
assert!(result.is_ok(), "CSS variables should be handled");
}
}
#[cfg(all(feature = "wit-bindings", target_family = "wasm"))]
#[test]
fn test_next_callback_id() {
use super::wasm_impl;
let id1 = wasm_impl::next_callback_id();
let id2 = wasm_impl::next_callback_id();
let id3 = wasm_impl::next_callback_id();
assert!(id2 > id1, "Callback IDs should increment");
assert!(id3 > id2, "Callback IDs should increment");
assert_eq!(id2, id1 + 1, "IDs should increment by 1");
assert_eq!(id3, id2 + 1, "IDs should increment by 1");
}
#[cfg(all(feature = "wit-bindings", target_family = "wasm"))]
#[test]
fn test_log_functions_no_crash() {
use super::wasm_impl::{log_error, log_info, log_warning};
log_error("test error message");
log_warning("test warning message");
log_info("test info message");
}
#[cfg(feature = "wit-bindings")]
#[test]
fn test_wit_element_debug() {
let element = super::WitElement::from_raw(42);
let debug_str = format!("{:?}", element);
assert!(
debug_str.contains("42"),
"Debug output should contain the handle value"
);
}
#[cfg(feature = "wit-bindings")]
#[test]
fn test_wit_event_multiple_clones() {
let event1 = super::WitEvent::from_raw(100);
let event2 = event1.clone();
let event3 = event2.clone();
assert_eq!(event1.as_raw(), 100);
assert_eq!(event2.as_raw(), 100);
assert_eq!(event3.as_raw(), 100);
}
#[cfg(feature = "wit-bindings")]
#[test]
fn test_wit_element_copy() {
let elem1 = super::WitElement::from_raw(55);
let elem2 = elem1;
assert_eq!(elem1.as_raw(), 55);
assert_eq!(elem2.as_raw(), 55);
}
#[cfg(feature = "wit-bindings")]
#[test]
fn test_wit_element_any_operations() {
use std::any::TypeId;
use tairitsu_vdom::ElementHandle;
let element = super::WitElement::from_raw(777);
let any_ref = element.as_any();
assert_eq!(any_ref.type_id(), TypeId::of::<super::WitElement>());
assert!(any_ref.is::<super::WitElement>());
assert!(!any_ref.is::<String>());
assert!(!any_ref.is::<i32>());
}
}