emscripten_val/
val.rs

1use emscripten_val_sys::val;
2use std::ffi::{CStr, CString};
3
4use crate::externs::*;
5use crate::id::JsType;
6
7/// Emscripten's EM_VAL type
8#[allow(non_camel_case_types)]
9pub type EM_VAL = val::EM_VAL;
10
11/// A helper macro which transforms every argument into a Val object.
12/// This helps reduce boilerplate for `Val::call`.
13#[macro_export]
14macro_rules! argv {
15    ($($rest:expr),*) => {{
16        &[$(&Val::from($rest)),*]
17    }};
18}
19
20/// Val is a wrapper around emscripten's EM_VAL type, which itself represents javascript objects
21#[repr(C)]
22#[derive(Eq)]
23pub struct Val {
24    handle: EM_VAL,
25}
26
27impl Val {
28    fn id() -> val::TYPEID {
29        extern "C" {
30            fn EmvalType() -> val::TYPEID;
31        }
32        unsafe { EmvalType() }
33    }
34    /// Looks up a global value represented by `name`
35    pub fn global(name: &str) -> Self {
36        let name = CString::new(name).unwrap();
37        Self {
38            handle: unsafe { val::_emval_get_global(name.as_ptr()) },
39        }
40    }
41
42    /// Creates a Val from a raw handle. This can be used for retrieving values from JavaScript, where the JavaScript side should wrap a value with Emval.toHandle, pass it to Rust, and then Rust can use take_ownership to convert it to a Val instance
43    pub fn take_ownership(v: val::EM_VAL) -> Self {
44        Self { handle: v }
45    }
46
47    /// Create a Val from another Val instance
48    pub fn from_val(v: &Val) -> Self {
49        let handle = v.as_handle();
50        if v.uses_ref_count() {
51            unsafe {
52                val::_emval_incref(handle);
53            }
54        }
55        Self { handle }
56    }
57
58    /// Create a Val that represents undefined
59    pub fn undefined() -> Self {
60        Self {
61            handle: val::_EMVAL_UNDEFINED as EM_VAL,
62        }
63    }
64
65    /// Creates a new Object
66    pub fn object() -> Self {
67        Self {
68            handle: unsafe { val::_emval_new_object() },
69        }
70    }
71
72    /// Create a Val that represents null
73    pub fn null() -> Self {
74        Self {
75            handle: val::_EMVAL_NULL as EM_VAL,
76        }
77    }
78
79    /// Creates and returns a new Array
80    pub fn array() -> Self {
81        Self {
82            handle: unsafe { val::_emval_new_array() },
83        }
84    }
85
86    /// Creates a Val from a string slice
87    #[allow(clippy::should_implement_trait)]
88    pub fn from_str(s: &str) -> Self {
89        let s = CString::new(s).unwrap();
90        Self {
91            handle: unsafe { val::_emval_new_cstring(s.as_ptr() as _) },
92        }
93    }
94
95    /// Looks up a value by the provided name on the Emscripten Module object.
96    pub fn module_property(s: &str) -> Self {
97        let s = CString::new(s).unwrap();
98        Self {
99            handle: unsafe { val::_emval_get_module_property(s.as_ptr() as _) },
100        }
101    }
102
103    /// Creates a Val from an array
104    pub fn from_array<T: Clone + Into<Val>>(arr: &[T]) -> Self {
105        let v = Val::array();
106        for elem in arr {
107            v.call("push", argv![elem.clone().into()]);
108        }
109        v
110    }
111
112    /// Get the EM_VAL handle of a Val object
113    pub fn as_handle(&self) -> EM_VAL {
114        self.handle
115    }
116
117    /// Call a method associated with the JS object represented by the Val object
118    pub fn call(&self, f: &str, args: &[&Val]) -> Val {
119        unsafe {
120            let typeids = vec![Val::id(); args.len() + 1];
121            let f = CString::new(f).unwrap();
122            let caller = val::_emval_create_invoker(
123                typeids.len() as u32, 
124                typeids.as_ptr() as _, 
125                val::EM_INVOKER_KIND_METHOD
126            );
127            
128            for arg in args {
129                val::_emval_incref(arg.handle);
130            }
131
132            let ret = val::_emval_invoke(
133                caller,
134                self.handle,
135                f.as_ptr() as _,
136                std::ptr::null_mut(),
137                *(args.as_ptr() as *const *const ()) as _,
138            );
139            
140            // For Val return types, the wire type contains the handle encoded as double
141            let ret_wire = crate::id::GenericWireType(ret);
142            let ret_handle = ret_wire.0 as usize as EM_VAL;
143            
144            // Check for reserved handles - these don't need reference counting
145            let handle_value = ret_handle as usize;
146            if handle_value <= val::_EMVAL_LAST_RESERVED_HANDLE as usize {
147                // Reserved handle - use directly without take_ownership
148                Val { handle: ret_handle }
149            } else {
150                // Regular handle - use take_ownership
151                Val::take_ownership(ret_handle)
152            }
153        }
154    }
155
156    /// Get a property
157    pub fn get<T: Clone + Into<Val>>(&self, prop: &T) -> Val {
158        let prop: Val = prop.clone().into();
159        Val {
160            handle: unsafe { val::_emval_get_property(self.handle, prop.handle) },
161        }
162    }
163
164    /// Set a property
165    pub fn set<T: Clone + Into<Val>, U: Clone + Into<Val>>(&self, prop: &T, val: &U) {
166        let prop: Val = prop.clone().into();
167        let val: Val = val.clone().into();
168        unsafe { val::_emval_set_property(self.handle, prop.handle, val.handle) };
169    }
170
171    /// Generate a Val object from a type implementing JsType
172    pub fn from_<T: JsType>(v: T) -> Self {
173        unsafe {
174            // For pointer-like/user types (default signature 'p'), embind expects a
175            // pointer value read from memory (i.e., argv points to a location that
176            // contains the raw pointer). To ensure correct lifetime, allocate on the heap
177            // and pass a pointer to that pointer. For primitive types ('i', 'd'), pass
178            // a pointer to the value directly.
179            let handle = match T::signature() {
180                'p' => {
181                    let boxed = Box::new(v);
182                    let mut ptr: *mut T = Box::into_raw(boxed);
183                    val::_emval_take_value(T::id(), (&mut ptr as *mut *mut T) as _)
184                }
185                _ => val::_emval_take_value(T::id(), (&v as *const T) as _),
186            };
187            Self { handle }
188        }
189    }
190
191    /// Generate a Val object from a type implementing JsType
192    pub fn as_<T: JsType>(&self) -> T {
193        unsafe {
194            T::from_generic_wire_type(crate::id::GenericWireType(val::_emval_as(
195                self.handle,
196                T::id(),
197                std::ptr::null_mut(),
198            )))
199        }
200    }
201
202    /// Generate a Val object from a type implementing JsType
203    pub fn as_i32(&self) -> i32 {
204        unsafe { val::_emval_as(self.handle, i32::id(), std::ptr::null_mut()) as i32 }
205    }
206
207    /// Checks whether the underlying type uses ref counting
208    fn uses_ref_count(&self) -> bool {
209        self.handle > val::_EMVAL_LAST_RESERVED_HANDLE as EM_VAL
210    }
211
212    /// Get and release ownership of the internal handle
213    pub fn release_ownership(&mut self) -> EM_VAL {
214        let h = self.handle;
215        self.handle = std::ptr::null_mut();
216        h
217    }
218
219    /// Checks if the JavaScript object has own (non-inherited) property with the specified name.
220    pub fn has_own_property(&self, key: &str) -> bool {
221        Val::global("Object")
222            .get(&"prototype")
223            .get(&"hasOwnProperty")
224            .call("call", argv![self.clone(), key])
225            .as_::<bool>()
226    }
227
228    /// Checks whether a value is null
229    pub fn is_null(&self) -> bool {
230        self.handle == val::_EMVAL_NULL as EM_VAL
231    }
232
233    /// Checks whether a value is undefined
234    pub fn is_undefined(&self) -> bool {
235        self.handle == val::_EMVAL_UNDEFINED as EM_VAL
236    }
237
238    /// Checks whether a value is true
239    pub fn is_true(&self) -> bool {
240        self.handle == val::_EMVAL_TRUE as EM_VAL
241    }
242
243    /// Checks whether a value is false
244    pub fn is_false(&self) -> bool {
245        self.handle == val::_EMVAL_FALSE as EM_VAL
246    }
247
248    /// Checks whether a value is a number
249    pub fn is_number(&self) -> bool {
250        unsafe { val::_emval_is_number(self.handle) }
251    }
252
253    /// Checks whether a value is a string
254    pub fn is_string(&self) -> bool {
255        unsafe { val::_emval_is_string(self.handle) }
256    }
257
258    /// Checks whether the object is an instanceof another object
259    pub fn instanceof(&self, v: &Val) -> bool {
260        unsafe { val::_emval_instanceof(self.as_handle(), v.as_handle()) }
261    }
262
263    /// Checks whether a value is an Array
264    pub fn is_array(&self) -> bool {
265        self.instanceof(&Val::global("Array"))
266    }
267
268    /// Checks if the specified property is in the specified object
269    pub fn is_in(&self, v: &Val) -> bool {
270        unsafe { val::_emval_in(self.as_handle(), v.as_handle()) }
271    }
272
273    /// Returns the typeof the object
274    pub fn type_of(&self) -> Val {
275        Val {
276            handle: unsafe { val::_emval_typeof(self.handle) },
277        }
278    }
279
280    /// Throw the object as a JS exception
281    pub fn throw(&self) -> bool {
282        unsafe { val::_emval_throw(self.as_handle()) }
283    }
284
285    /// Pauses the Rust code to await the Promise / thenable. This requires [ASYNCIFY](https://emscripten.org/docs/tools_reference/settings_reference.html#asyncify) to be enabled
286    pub fn await_(&self) -> Val {
287        Val {
288            handle: unsafe { val::_emval_await(self.handle) },
289        }
290    }
291
292    /// Removes a property from an object
293    pub fn delete<T: Clone + Into<Val>>(&self, prop: &T) -> bool {
294        unsafe { val::_emval_delete(self.as_handle(), prop.clone().into().as_handle()) }
295    }
296
297    /// Instantiate a new object, passes the `args` to the object's contructor
298    pub fn new(&self, args: &[&Val]) -> Val {
299        unsafe {
300            let typeids = vec![Val::id(); args.len() + 1];
301            let caller = val::_emval_create_invoker(
302                typeids.len() as u32, 
303                typeids.as_ptr() as _, 
304                val::EM_INVOKER_KIND_CONSTRUCTOR
305            );
306            for arg in args {
307                val::_emval_incref(arg.handle);
308            }
309            
310            let ret = val::_emval_invoke(
311                caller,
312                self.handle,
313                std::ptr::null_mut(),
314                std::ptr::null_mut(),
315                *(args.as_ptr() as *const *const ()) as _,
316            );
317            
318            // For Val return types, the wire type contains the handle encoded as double
319            let ret_wire = crate::id::GenericWireType(ret);
320            let ret_handle = ret_wire.0 as usize as EM_VAL;
321            
322            // Check for reserved handles - these don't need reference counting
323            let handle_value = ret_handle as usize;
324            if handle_value <= val::_EMVAL_LAST_RESERVED_HANDLE as usize {
325                // Reserved handle - use directly without take_ownership
326                Val { handle: ret_handle }
327            } else {
328                // Regular handle - use take_ownership
329                Val::take_ownership(ret_handle)
330            }
331        }
332    }
333
334    fn gt<T: Clone + Into<Val>>(&self, v: &T) -> bool {
335        unsafe { val::_emval_greater_than(self.handle, v.clone().into().handle) }
336    }
337
338    fn lt<T: Clone + Into<Val>>(&self, v: &T) -> bool {
339        unsafe { val::_emval_less_than(self.handle, v.clone().into().handle) }
340    }
341
342    fn equals<T: Clone + Into<Val>>(&self, v: &T) -> bool {
343        unsafe { val::_emval_equals(self.handle, v.clone().into().handle) }
344    }
345
346    /// Check if the current object is strictly equals to another object `===`
347    pub fn strictly_equals<T: Clone + Into<Val>>(&self, v: &T) -> bool {
348        unsafe { val::_emval_strictly_equals(self.handle, v.clone().into().handle) }
349    }
350
351    /// Checks the validity of an object
352    pub fn not(&self) -> bool {
353        unsafe { val::_emval_not(self.handle) }
354    }
355
356    /// Convenience method.
357    /// Adds a callback to an EventTarget object
358    pub fn add_event_listener<F: (FnMut(&Val) -> Val) + 'static>(&self, ev: &str, f: F) {
359        unsafe {
360            let a: *mut Box<dyn FnMut(&Val) -> Val> = Box::into_raw(Box::new(Box::new(f)));
361            let data: *mut std::os::raw::c_void = a as *mut std::os::raw::c_void;
362            let ev = CString::new(ev).unwrap();
363            _emval_add_event_listener(self.handle, ev.as_ptr() as _, data as _);
364        }
365    }
366
367    /// Generates a Val object from a function object which takes 0 args
368    pub fn from_fn0<F: (FnMut() -> Val) + 'static>(f: F) -> Val {
369        unsafe {
370            let a: *mut Box<dyn FnMut() -> Val> = Box::into_raw(Box::new(Box::new(f)));
371            let data: *mut std::os::raw::c_void = a as *mut std::os::raw::c_void;
372            Self {
373                handle: _emval_take_fn(0, data as _),
374            }
375        }
376    }
377
378    /// Generates a Val object from a function object which takes 1 arg
379    pub fn from_fn1<F: (FnMut(&Val) -> Val) + 'static>(f: F) -> Val {
380        unsafe {
381            #[allow(clippy::type_complexity)]
382            let a: *mut Box<dyn FnMut(&Val) -> Val> = Box::into_raw(Box::new(Box::new(f)));
383            let data: *mut std::os::raw::c_void = a as *mut std::os::raw::c_void;
384            Self {
385                handle: _emval_take_fn(1, data as _),
386            }
387        }
388    }
389
390    /// Generates a Val object from a function object which takes 2 args
391    pub fn from_fn2<F: (FnMut(&Val, &Val) -> Val) + 'static>(f: F) -> Val {
392        unsafe {
393            #[allow(clippy::type_complexity)]
394            let a: *mut Box<dyn FnMut(&Val, &Val) -> Val> = Box::into_raw(Box::new(Box::new(f)));
395            let data: *mut std::os::raw::c_void = a as *mut std::os::raw::c_void;
396            Self {
397                handle: _emval_take_fn(2, data as _),
398            }
399        }
400    }
401
402    /// Generates a Val object from a function object which takes 3 args
403    pub fn from_fn3<F: (FnMut(&Val, &Val, &Val) -> Val) + 'static>(f: F) -> Val {
404        unsafe {
405            #[allow(clippy::type_complexity)]
406            let a: *mut Box<dyn FnMut(&Val, &Val, &Val) -> Val> =
407                Box::into_raw(Box::new(Box::new(f)));
408            let data: *mut std::os::raw::c_void = a as *mut std::os::raw::c_void;
409            Self {
410                handle: _emval_take_fn(3, data as _),
411            }
412        }
413    }
414
415    /// Generates a Val object from a function object which takes 4 args
416    pub fn from_fn4<F: (FnMut(&Val, &Val, &Val, &Val) -> Val) + 'static>(f: F) -> Val {
417        unsafe {
418            #[allow(clippy::type_complexity)]
419            let a: *mut Box<dyn FnMut(&Val, &Val, &Val, &Val) -> Val> =
420                Box::into_raw(Box::new(Box::new(f)));
421            let data: *mut std::os::raw::c_void = a as *mut std::os::raw::c_void;
422            Self {
423                handle: _emval_take_fn(4, data as _),
424            }
425        }
426    }
427
428    /// Converts current value to a string
429    pub fn as_string(&self) -> String {
430        unsafe {
431            let ptr = _emval_as_str(self.handle);
432            let ret = CStr::from_ptr(ptr).to_string_lossy().to_string();
433            free(ptr as _);
434            ret
435        }
436    }
437
438    /// Convert a javascript Array to a Rust Vec
439    pub fn to_vec<T: JsType>(&self) -> Vec<T> {
440        let len = self.get(&"length").as_::<u32>();
441        let mut v: Vec<T> = vec![];
442        for i in 0..len {
443            v.push(self.get(&i).as_::<T>());
444        }
445        v
446    }
447}
448
449use std::cmp::Ordering;
450
451impl Default for Val {
452    fn default() -> Val {
453        Val::null()
454    }
455}
456
457impl Drop for Val {
458    fn drop(&mut self) {
459        if self.uses_ref_count() {
460            unsafe {
461                val::_emval_decref(self.as_handle());
462            }
463            self.handle = std::ptr::null_mut();
464        }
465    }
466}
467
468impl Clone for Val {
469    fn clone(&self) -> Self {
470        if self.uses_ref_count() {
471            unsafe {
472                val::_emval_incref(self.handle);
473            }
474        }
475        Self {
476            handle: self.handle,
477        }
478    }
479}
480
481impl<T: JsType> From<T> for Val {
482    fn from(v: T) -> Self {
483        Val::from_(v)
484    }
485}
486
487impl From<()> for Val {
488    fn from(_: ()) -> Self {
489        Val::null()
490    }
491}
492
493impl From<&Val> for Val {
494    fn from(item: &Val) -> Self {
495        Val::from_val(item)
496    }
497}
498
499impl From<&str> for Val {
500    fn from(item: &str) -> Self {
501        Val::from_str(item)
502    }
503}
504
505impl From<String> for Val {
506    fn from(item: String) -> Self {
507        Val::from_str(&item)
508    }
509}
510
511impl PartialEq for Val {
512    fn eq(&self, other: &Val) -> bool {
513        self.equals(other)
514    }
515}
516
517impl PartialOrd for Val {
518    fn partial_cmp(&self, other: &Val) -> Option<Ordering> {
519        if self.equals(other) {
520            Some(Ordering::Equal)
521        } else if self.gt(other) {
522            Some(Ordering::Greater)
523        } else if self.lt(other) {
524            Some(Ordering::Less)
525        } else {
526            None
527        }
528    }
529}