quick_js/
bindings.rs

1use std::{
2    collections::HashMap,
3    ffi::CString,
4    os::raw::{c_char, c_int, c_void},
5    sync::Mutex,
6};
7
8use libquickjs_sys as q;
9
10#[cfg(feature = "bigint")]
11use crate::value::{bigint::BigIntOrI64, BigInt};
12use crate::{
13    callback::{Arguments, Callback},
14    console::ConsoleBackend,
15    droppable_value::DroppableValue,
16    ContextError, ExecutionError, JsValue, ValueError,
17};
18
19// JS_TAG_* constants from quickjs.
20// For some reason bindgen does not pick them up.
21#[cfg(feature = "bigint")]
22const TAG_BIG_INT: i64 = -10;
23const TAG_STRING: i64 = -7;
24const TAG_OBJECT: i64 = -1;
25const TAG_INT: i64 = 0;
26const TAG_BOOL: i64 = 1;
27const TAG_NULL: i64 = 2;
28const TAG_UNDEFINED: i64 = 3;
29const TAG_EXCEPTION: i64 = 6;
30const TAG_FLOAT64: i64 = 7;
31
32/// Free a JSValue.
33/// This function is the equivalent of JS_FreeValue from quickjs, which can not
34/// be used due to being `static inline`.
35unsafe fn free_value(context: *mut q::JSContext, value: q::JSValue) {
36    // All tags < 0 are garbage collected and need to be freed.
37    if value.tag < 0 {
38        // This transmute is OK since if tag < 0, the union will be a refcount
39        // pointer.
40        let ptr = value.u.ptr as *mut q::JSRefCountHeader;
41        let pref: &mut q::JSRefCountHeader = &mut *ptr;
42        pref.ref_count -= 1;
43        if pref.ref_count <= 0 {
44            q::__JS_FreeValue(context, value);
45        }
46    }
47}
48
49#[cfg(feature = "chrono")]
50fn js_date_constructor(context: *mut q::JSContext) -> q::JSValue {
51    let global = unsafe { q::JS_GetGlobalObject(context) };
52    assert_eq!(global.tag, TAG_OBJECT);
53
54    let date_constructor = unsafe {
55        q::JS_GetPropertyStr(
56            context,
57            global,
58            std::ffi::CStr::from_bytes_with_nul(b"Date\0")
59                .unwrap()
60                .as_ptr(),
61        )
62    };
63    assert_eq!(date_constructor.tag, TAG_OBJECT);
64    unsafe { free_value(context, global) };
65    date_constructor
66}
67
68#[cfg(feature = "bigint")]
69fn js_create_bigint_function(context: *mut q::JSContext) -> q::JSValue {
70    let global = unsafe { q::JS_GetGlobalObject(context) };
71    assert_eq!(global.tag, TAG_OBJECT);
72
73    let bigint_function = unsafe {
74        q::JS_GetPropertyStr(
75            context,
76            global,
77            std::ffi::CStr::from_bytes_with_nul(b"BigInt\0")
78                .unwrap()
79                .as_ptr(),
80        )
81    };
82    assert_eq!(bigint_function.tag, TAG_OBJECT);
83    unsafe { free_value(context, global) };
84    bigint_function
85}
86
87/// Serialize a Rust value into a quickjs runtime value.
88fn serialize_value(context: *mut q::JSContext, value: JsValue) -> Result<q::JSValue, ValueError> {
89    let v = match value {
90        JsValue::Undefined => q::JSValue {
91            u: q::JSValueUnion { int32: 0 },
92            tag: TAG_UNDEFINED,
93        },
94        JsValue::Null => q::JSValue {
95            u: q::JSValueUnion { int32: 0 },
96            tag: TAG_NULL,
97        },
98        JsValue::Bool(flag) => q::JSValue {
99            u: q::JSValueUnion {
100                int32: if flag { 1 } else { 0 },
101            },
102            tag: TAG_BOOL,
103        },
104        JsValue::Int(val) => q::JSValue {
105            u: q::JSValueUnion { int32: val },
106            tag: TAG_INT,
107        },
108        JsValue::Float(val) => q::JSValue {
109            u: q::JSValueUnion { float64: val },
110            tag: TAG_FLOAT64,
111        },
112        JsValue::String(val) => {
113            let qval = unsafe {
114                q::JS_NewStringLen(context, val.as_ptr() as *const c_char, val.len() as _)
115            };
116
117            if qval.tag == TAG_EXCEPTION {
118                return Err(ValueError::Internal(
119                    "Could not create string in runtime".into(),
120                ));
121            }
122
123            qval
124        }
125        JsValue::Array(values) => {
126            // Allocate a new array in the runtime.
127            let arr = unsafe { q::JS_NewArray(context) };
128            if arr.tag == TAG_EXCEPTION {
129                return Err(ValueError::Internal(
130                    "Could not create array in runtime".into(),
131                ));
132            }
133
134            for (index, value) in values.into_iter().enumerate() {
135                let qvalue = match serialize_value(context, value) {
136                    Ok(qval) => qval,
137                    Err(e) => {
138                        // Make sure to free the array if a individual element
139                        // fails.
140                        unsafe {
141                            free_value(context, arr);
142                        }
143                        return Err(e);
144                    }
145                };
146
147                let ret = unsafe {
148                    q::JS_DefinePropertyValueUint32(
149                        context,
150                        arr,
151                        index as u32,
152                        qvalue,
153                        q::JS_PROP_C_W_E as i32,
154                    )
155                };
156                if ret < 0 {
157                    // Make sure to free the array if a individual
158                    // element fails.
159                    unsafe {
160                        free_value(context, arr);
161                    }
162                    return Err(ValueError::Internal(
163                        "Could not append element to array".into(),
164                    ));
165                }
166            }
167            arr
168        }
169        JsValue::Object(map) => {
170            let obj = unsafe { q::JS_NewObject(context) };
171            if obj.tag == TAG_EXCEPTION {
172                return Err(ValueError::Internal("Could not create object".into()));
173            }
174
175            for (key, value) in map {
176                let ckey = make_cstring(key)?;
177
178                let qvalue = serialize_value(context, value).map_err(|e| {
179                    // Free the object if a property failed.
180                    unsafe {
181                        free_value(context, obj);
182                    }
183                    e
184                })?;
185
186                let ret = unsafe {
187                    q::JS_DefinePropertyValueStr(
188                        context,
189                        obj,
190                        ckey.as_ptr(),
191                        qvalue,
192                        q::JS_PROP_C_W_E as i32,
193                    )
194                };
195                if ret < 0 {
196                    // Free the object if a property failed.
197                    unsafe {
198                        free_value(context, obj);
199                    }
200                    return Err(ValueError::Internal(
201                        "Could not add add property to object".into(),
202                    ));
203                }
204            }
205
206            obj
207        }
208        #[cfg(feature = "chrono")]
209        JsValue::Date(datetime) => {
210            let date_constructor = js_date_constructor(context);
211
212            let f = datetime.timestamp_millis() as f64;
213
214            let timestamp = q::JSValue {
215                u: q::JSValueUnion { float64: f },
216                tag: TAG_FLOAT64,
217            };
218
219            let mut args = vec![timestamp];
220
221            let value = unsafe {
222                q::JS_CallConstructor(
223                    context,
224                    date_constructor,
225                    args.len() as i32,
226                    args.as_mut_ptr(),
227                )
228            };
229            unsafe {
230                free_value(context, date_constructor);
231            }
232
233            if value.tag != TAG_OBJECT {
234                return Err(ValueError::Internal(
235                    "Could not construct Date object".into(),
236                ));
237            }
238            value
239        }
240        #[cfg(feature = "bigint")]
241        JsValue::BigInt(int) => match int.inner {
242            BigIntOrI64::Int(int) => unsafe { q::JS_NewBigInt64(context, int) },
243            BigIntOrI64::BigInt(bigint) => {
244                let bigint_string = bigint.to_str_radix(10);
245                let s = unsafe {
246                    q::JS_NewStringLen(
247                        context,
248                        bigint_string.as_ptr() as *const c_char,
249                        bigint_string.len() as q::size_t,
250                    )
251                };
252                let s = DroppableValue::new(s, |&mut s| unsafe {
253                    free_value(context, s);
254                });
255                if (*s).tag != TAG_STRING {
256                    return Err(ValueError::Internal(
257                        "Could not construct String object needed to create BigInt object".into(),
258                    ));
259                }
260
261                let mut args = vec![*s];
262
263                let bigint_function = js_create_bigint_function(context);
264                let bigint_function =
265                    DroppableValue::new(bigint_function, |&mut bigint_function| unsafe {
266                        free_value(context, bigint_function);
267                    });
268                let js_bigint = unsafe {
269                    q::JS_Call(
270                        context,
271                        *bigint_function,
272                        js_null_value(),
273                        1,
274                        args.as_mut_ptr(),
275                    )
276                };
277
278                if js_bigint.tag != TAG_BIG_INT {
279                    return Err(ValueError::Internal(
280                        "Could not construct BigInt object".into(),
281                    ));
282                }
283
284                js_bigint
285            }
286        },
287        JsValue::__NonExhaustive => unreachable!(),
288    };
289    Ok(v)
290}
291
292fn deserialize_array(
293    context: *mut q::JSContext,
294    raw_value: &q::JSValue,
295) -> Result<JsValue, ValueError> {
296    assert_eq!(raw_value.tag, TAG_OBJECT);
297
298    let length_name = make_cstring("length")?;
299
300    let len_raw = unsafe { q::JS_GetPropertyStr(context, *raw_value, length_name.as_ptr()) };
301
302    let len_res = deserialize_value(context, &len_raw);
303    unsafe { free_value(context, len_raw) };
304    let len = match len_res? {
305        JsValue::Int(x) => x,
306        _ => {
307            return Err(ValueError::Internal(
308                "Could not determine array length".into(),
309            ));
310        }
311    };
312
313    let mut values = Vec::new();
314    for index in 0..(len as usize) {
315        let value_raw = unsafe { q::JS_GetPropertyUint32(context, *raw_value, index as u32) };
316        if value_raw.tag == TAG_EXCEPTION {
317            return Err(ValueError::Internal("Could not build array".into()));
318        }
319        let value_res = deserialize_value(context, &value_raw);
320        unsafe { free_value(context, value_raw) };
321
322        let value = value_res?;
323        values.push(value);
324    }
325
326    Ok(JsValue::Array(values))
327}
328
329fn deserialize_object(context: *mut q::JSContext, obj: &q::JSValue) -> Result<JsValue, ValueError> {
330    assert_eq!(obj.tag, TAG_OBJECT);
331
332    let mut properties: *mut q::JSPropertyEnum = std::ptr::null_mut();
333    let mut count: u32 = 0;
334
335    let flags = (q::JS_GPN_STRING_MASK | q::JS_GPN_SYMBOL_MASK | q::JS_GPN_ENUM_ONLY) as i32;
336    let ret =
337        unsafe { q::JS_GetOwnPropertyNames(context, &mut properties, &mut count, *obj, flags) };
338    if ret != 0 {
339        return Err(ValueError::Internal(
340            "Could not get object properties".into(),
341        ));
342    }
343
344    // TODO: refactor into a more Rust-idiomatic iterator wrapper.
345    let properties = DroppableValue::new(properties, |&mut properties| {
346        for index in 0..count {
347            let prop = unsafe { properties.offset(index as isize) };
348            unsafe {
349                q::JS_FreeAtom(context, (*prop).atom);
350            }
351        }
352        unsafe {
353            q::js_free(context, properties as *mut std::ffi::c_void);
354        }
355    });
356
357    let mut map = HashMap::new();
358    for index in 0..count {
359        let prop = unsafe { (*properties).offset(index as isize) };
360        let raw_value = unsafe { q::JS_GetPropertyInternal(context, *obj, (*prop).atom, *obj, 0) };
361        if raw_value.tag == TAG_EXCEPTION {
362            return Err(ValueError::Internal("Could not get object property".into()));
363        }
364
365        let value_res = deserialize_value(context, &raw_value);
366        unsafe {
367            free_value(context, raw_value);
368        }
369        let value = value_res?;
370
371        let key_value = unsafe { q::JS_AtomToString(context, (*prop).atom) };
372        if key_value.tag == TAG_EXCEPTION {
373            return Err(ValueError::Internal(
374                "Could not get object property name".into(),
375            ));
376        }
377
378        let key_res = deserialize_value(context, &key_value);
379        unsafe {
380            free_value(context, key_value);
381        }
382        let key = match key_res? {
383            JsValue::String(s) => s,
384            _ => {
385                return Err(ValueError::Internal("Could not get property name".into()));
386            }
387        };
388        map.insert(key, value);
389    }
390
391    Ok(JsValue::Object(map))
392}
393
394fn deserialize_value(
395    context: *mut q::JSContext,
396    value: &q::JSValue,
397) -> Result<JsValue, ValueError> {
398    let r = value;
399
400    match r.tag {
401        // Int.
402        TAG_INT => {
403            let val = unsafe { r.u.int32 };
404            Ok(JsValue::Int(val))
405        }
406        // Bool.
407        TAG_BOOL => {
408            let raw = unsafe { r.u.int32 };
409            let val = raw > 0;
410            Ok(JsValue::Bool(val))
411        }
412        // Null.
413        TAG_NULL => Ok(JsValue::Null),
414        // Undefined.
415        TAG_UNDEFINED => Ok(JsValue::Undefined),
416        // Float.
417        TAG_FLOAT64 => {
418            let val = unsafe { r.u.float64 };
419            Ok(JsValue::Float(val))
420        }
421        // String.
422        TAG_STRING => {
423            let ptr = unsafe { q::JS_ToCStringLen2(context, std::ptr::null_mut(), *r, 0) };
424
425            if ptr.is_null() {
426                return Err(ValueError::Internal(
427                    "Could not convert string: got a null pointer".into(),
428                ));
429            }
430
431            let cstr = unsafe { std::ffi::CStr::from_ptr(ptr) };
432
433            let s = cstr
434                .to_str()
435                .map_err(ValueError::InvalidString)?
436                .to_string();
437
438            // Free the c string.
439            unsafe { q::JS_FreeCString(context, ptr) };
440
441            Ok(JsValue::String(s))
442        }
443        // Object.
444        TAG_OBJECT => {
445            let is_array = unsafe { q::JS_IsArray(context, *r) } > 0;
446            if is_array {
447                deserialize_array(context, r)
448            } else {
449                #[cfg(feature = "chrono")]
450                {
451                    use chrono::offset::TimeZone;
452
453                    let date_constructor = js_date_constructor(context);
454                    let is_date = unsafe { q::JS_IsInstanceOf(context, *r, date_constructor) > 0 };
455
456                    if is_date {
457                        let getter = unsafe {
458                            q::JS_GetPropertyStr(
459                                context,
460                                *r,
461                                std::ffi::CStr::from_bytes_with_nul(b"getTime\0")
462                                    .unwrap()
463                                    .as_ptr(),
464                            )
465                        };
466                        assert_eq!(getter.tag, TAG_OBJECT);
467
468                        let timestamp_raw =
469                            unsafe { q::JS_Call(context, getter, *r, 0, std::ptr::null_mut()) };
470
471                        unsafe {
472                            free_value(context, getter);
473                            free_value(context, date_constructor);
474                        };
475
476                        let res = if timestamp_raw.tag == TAG_FLOAT64 {
477                            let f = unsafe { timestamp_raw.u.float64 } as i64;
478                            let datetime = chrono::Utc.timestamp_millis(f);
479                            Ok(JsValue::Date(datetime))
480                        } else if timestamp_raw.tag == TAG_INT {
481                            let f = unsafe { timestamp_raw.u.int32 } as i64;
482                            let datetime = chrono::Utc.timestamp_millis(f);
483                            Ok(JsValue::Date(datetime))
484                        } else {
485                            Err(ValueError::Internal(
486                                "Could not convert 'Date' instance to timestamp".into(),
487                            ))
488                        };
489                        return res;
490                    } else {
491                        unsafe { free_value(context, date_constructor) };
492                    }
493                }
494
495                deserialize_object(context, r)
496            }
497        }
498        // BigInt
499        #[cfg(feature = "bigint")]
500        TAG_BIG_INT => {
501            let mut int: i64 = 0;
502            let ret = unsafe { q::JS_ToBigInt64(context, &mut int, *r) };
503            if ret == 0 {
504                Ok(JsValue::BigInt(BigInt {
505                    inner: BigIntOrI64::Int(int),
506                }))
507            } else {
508                let ptr = unsafe { q::JS_ToCStringLen2(context, std::ptr::null_mut(), *r, 0) };
509
510                if ptr.is_null() {
511                    return Err(ValueError::Internal(
512                        "Could not convert BigInt to string: got a null pointer".into(),
513                    ));
514                }
515
516                let cstr = unsafe { std::ffi::CStr::from_ptr(ptr) };
517                let bigint = num_bigint::BigInt::parse_bytes(cstr.to_bytes(), 10).unwrap();
518
519                // Free the c string.
520                unsafe { q::JS_FreeCString(context, ptr) };
521
522                Ok(JsValue::BigInt(BigInt {
523                    inner: BigIntOrI64::BigInt(bigint),
524                }))
525            }
526        }
527        x => Err(ValueError::Internal(format!(
528            "Unhandled JS_TAG value: {}",
529            x
530        ))),
531    }
532}
533
534/// Helper for creating CStrings.
535fn make_cstring(value: impl Into<Vec<u8>>) -> Result<CString, ValueError> {
536    CString::new(value).map_err(ValueError::StringWithZeroBytes)
537}
538
539/// Helper to construct null JsValue
540fn js_null_value() -> q::JSValue {
541    q::JSValue {
542        u: q::JSValueUnion { int32: 0 },
543        tag: TAG_NULL,
544    }
545}
546
547type WrappedCallback = dyn Fn(c_int, *mut q::JSValue) -> q::JSValue;
548
549/// Taken from: https://s3.amazonaws.com/temp.michaelfbryan.com/callbacks/index.html
550///
551/// Create a C wrapper function for a Rust closure to enable using it as a
552/// callback function in the Quickjs runtime.
553///
554/// Both the boxed closure and the boxed data are returned and must be stored
555/// by the caller to guarantee they stay alive.
556unsafe fn build_closure_trampoline<F>(
557    closure: F,
558) -> ((Box<WrappedCallback>, Box<q::JSValue>), q::JSCFunctionData)
559where
560    F: Fn(c_int, *mut q::JSValue) -> q::JSValue + 'static,
561{
562    unsafe extern "C" fn trampoline<F>(
563        _ctx: *mut q::JSContext,
564        _this: q::JSValue,
565        argc: c_int,
566        argv: *mut q::JSValue,
567        _magic: c_int,
568        data: *mut q::JSValue,
569    ) -> q::JSValue
570    where
571        F: Fn(c_int, *mut q::JSValue) -> q::JSValue,
572    {
573        let closure_ptr = (*data).u.ptr;
574        let closure: &mut F = &mut *(closure_ptr as *mut F);
575        (*closure)(argc, argv)
576    }
577
578    let boxed_f = Box::new(closure);
579
580    let data = Box::new(q::JSValue {
581        u: q::JSValueUnion {
582            ptr: (&*boxed_f) as *const F as *mut c_void,
583        },
584        tag: TAG_NULL,
585    });
586
587    ((boxed_f, data), Some(trampoline::<F>))
588}
589
590/// OwnedValueRef wraps a Javascript value from the quickjs runtime.
591/// It prevents leaks by ensuring that the inner value is deallocated on drop.
592pub struct OwnedValueRef<'a> {
593    context: &'a ContextWrapper,
594    value: q::JSValue,
595}
596
597impl<'a> Drop for OwnedValueRef<'a> {
598    fn drop(&mut self) {
599        unsafe {
600            free_value(self.context.context, self.value);
601        }
602    }
603}
604
605impl<'a> std::fmt::Debug for OwnedValueRef<'a> {
606    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
607        match self.value.tag {
608            TAG_EXCEPTION => write!(f, "Exception(?)"),
609            TAG_NULL => write!(f, "NULL"),
610            TAG_UNDEFINED => write!(f, "UNDEFINED"),
611            TAG_BOOL => write!(f, "Bool(?)",),
612            TAG_INT => write!(f, "Int(?)"),
613            TAG_FLOAT64 => write!(f, "Float(?)"),
614            TAG_STRING => write!(f, "String(?)"),
615            TAG_OBJECT => write!(f, "Object(?)"),
616            _ => write!(f, "?"),
617        }
618    }
619}
620
621impl<'a> OwnedValueRef<'a> {
622    pub fn new(context: &'a ContextWrapper, value: q::JSValue) -> Self {
623        Self { context, value }
624    }
625
626    /// Get the inner JSValue without freeing in drop.
627    ///
628    /// Unsafe because the caller is responsible for freeing the value.
629    //unsafe fn into_inner(mut self) -> q::JSValue {
630    //let v = self.value;
631    //self.value = q::JSValue {
632    //u: q::JSValueUnion { int32: 0 },
633    //tag: TAG_NULL,
634    //};
635    //v
636    //}
637
638    pub fn is_null(&self) -> bool {
639        self.value.tag == TAG_NULL
640    }
641
642    pub fn is_bool(&self) -> bool {
643        self.value.tag == TAG_BOOL
644    }
645
646    pub fn is_exception(&self) -> bool {
647        self.value.tag == TAG_EXCEPTION
648    }
649
650    pub fn is_object(&self) -> bool {
651        self.value.tag == TAG_OBJECT
652    }
653
654    pub fn is_string(&self) -> bool {
655        self.value.tag == TAG_STRING
656    }
657
658    pub fn to_string(&self) -> Result<String, ExecutionError> {
659        let value = if self.is_string() {
660            self.to_value()?
661        } else {
662            let raw = unsafe { q::JS_ToString(self.context.context, self.value) };
663            let value = OwnedValueRef::new(self.context, raw);
664
665            if value.value.tag != TAG_STRING {
666                return Err(ExecutionError::Exception(
667                    "Could not convert value to string".into(),
668                ));
669            }
670            value.to_value()?
671        };
672
673        Ok(value.as_str().unwrap().to_string())
674    }
675
676    pub fn to_value(&self) -> Result<JsValue, ValueError> {
677        self.context.to_value(&self.value)
678    }
679
680    pub fn to_bool(&self) -> Result<bool, ValueError> {
681        match self.to_value()? {
682            JsValue::Bool(b) => Ok(b),
683            _ => Err(ValueError::UnexpectedType),
684        }
685    }
686}
687
688/// Wraps an object from the quickjs runtime.
689/// Provides convenience property accessors.
690pub struct OwnedObjectRef<'a> {
691    value: OwnedValueRef<'a>,
692}
693
694impl<'a> OwnedObjectRef<'a> {
695    pub fn new(value: OwnedValueRef<'a>) -> Result<Self, ValueError> {
696        if value.value.tag != TAG_OBJECT {
697            Err(ValueError::Internal("Expected an object".into()))
698        } else {
699            Ok(Self { value })
700        }
701    }
702
703    fn into_value(self) -> OwnedValueRef<'a> {
704        self.value
705    }
706
707    /// Get the tag of a property.
708    fn property_tag(&self, name: &str) -> Result<i64, ValueError> {
709        let cname = make_cstring(name)?;
710        let raw = unsafe {
711            q::JS_GetPropertyStr(self.value.context.context, self.value.value, cname.as_ptr())
712        };
713        let t = raw.tag;
714        unsafe {
715            free_value(self.value.context.context, raw);
716        }
717        Ok(t)
718    }
719
720    /// Determine if the object is a promise by checking the presence of
721    /// a 'then' and a 'catch' property.
722    fn is_promise(&self) -> Result<bool, ValueError> {
723        if self.property_tag("then")? == TAG_OBJECT && self.property_tag("catch")? == TAG_OBJECT {
724            Ok(true)
725        } else {
726            Ok(false)
727        }
728    }
729
730    pub fn property(&self, name: &str) -> Result<OwnedValueRef<'a>, ExecutionError> {
731        let cname = make_cstring(name)?;
732        let raw = unsafe {
733            q::JS_GetPropertyStr(self.value.context.context, self.value.value, cname.as_ptr())
734        };
735
736        if raw.tag == TAG_EXCEPTION {
737            Err(ExecutionError::Internal(format!(
738                "Exception while getting property '{}'",
739                name
740            )))
741        } else if raw.tag == TAG_UNDEFINED {
742            Err(ExecutionError::Internal(format!(
743                "Property '{}' not found",
744                name
745            )))
746        } else {
747            Ok(OwnedValueRef::new(self.value.context, raw))
748        }
749    }
750
751    // Set a property on an object.
752    // NOTE: this method takes ownership of the `JSValue`, so it must not be
753    // freed later.
754    unsafe fn set_property_raw(&self, name: &str, value: q::JSValue) -> Result<(), ExecutionError> {
755        let cname = make_cstring(name)?;
756        let ret = q::JS_SetPropertyStr(
757            self.value.context.context,
758            self.value.value,
759            cname.as_ptr(),
760            value,
761        );
762        if ret < 0 {
763            Err(ExecutionError::Exception("Could not set property".into()))
764        } else {
765            Ok(())
766        }
767    }
768
769    pub fn set_property(&self, name: &str, value: JsValue) -> Result<(), ExecutionError> {
770        let qval = self.value.context.serialize_value(value)?;
771        unsafe {
772            self.set_property_raw(name, qval.value)?;
773            // set_property_raw takes ownership, so we must prevent a free.
774            std::mem::forget(qval);
775        }
776        Ok(())
777    }
778}
779
780/*
781type ModuleInit = dyn Fn(*mut q::JSContext, *mut q::JSModuleDef);
782
783thread_local! {
784    static NATIVE_MODULE_INIT: RefCell<Option<Box<ModuleInit>>> = RefCell::new(None);
785}
786
787unsafe extern "C" fn native_module_init(
788    ctx: *mut q::JSContext,
789    m: *mut q::JSModuleDef,
790) -> ::std::os::raw::c_int {
791    NATIVE_MODULE_INIT.with(|init| {
792        let init = init.replace(None).unwrap();
793        init(ctx, m);
794    });
795    0
796}
797*/
798
799/// Wraps a quickjs context.
800///
801/// Cleanup of the context happens in drop.
802pub struct ContextWrapper {
803    runtime: *mut q::JSRuntime,
804    context: *mut q::JSContext,
805    /// Stores callback closures and quickjs data pointers.
806    /// This array is write-only and only exists to ensure the lifetime of
807    /// the closure.
808    // A Mutex is used over a RefCell because it needs to be unwind-safe.
809    callbacks: Mutex<Vec<(Box<WrappedCallback>, Box<q::JSValue>)>>,
810}
811
812impl Drop for ContextWrapper {
813    fn drop(&mut self) {
814        unsafe {
815            q::JS_FreeContext(self.context);
816            q::JS_FreeRuntime(self.runtime);
817        }
818    }
819}
820
821impl ContextWrapper {
822    /// Initialize a wrapper by creating a JSRuntime and JSContext.
823    pub fn new(memory_limit: Option<usize>) -> Result<Self, ContextError> {
824        let runtime = unsafe { q::JS_NewRuntime() };
825        if runtime.is_null() {
826            return Err(ContextError::RuntimeCreationFailed);
827        }
828
829        // Configure memory limit if specified.
830        if let Some(limit) = memory_limit {
831            unsafe {
832                q::JS_SetMemoryLimit(runtime, limit as _);
833            }
834        }
835
836        let context = unsafe { q::JS_NewContext(runtime) };
837        if context.is_null() {
838            unsafe {
839                q::JS_FreeRuntime(runtime);
840            }
841            return Err(ContextError::ContextCreationFailed);
842        }
843
844        // Initialize the promise resolver helper code.
845        // This code is needed by Self::resolve_value
846        let wrapper = Self {
847            runtime,
848            context,
849            callbacks: Mutex::new(Vec::new()),
850        };
851
852        Ok(wrapper)
853    }
854
855    // See console standard: https://console.spec.whatwg.org
856    pub fn set_console(&self, backend: Box<dyn ConsoleBackend>) -> Result<(), ExecutionError> {
857        use crate::console::Level;
858
859        self.add_callback("__console_write", move |args: Arguments| {
860            let mut args = args.into_vec();
861
862            if args.len() > 1 {
863                let level_raw = args.remove(0);
864
865                let level_opt = level_raw.as_str().and_then(|v| match v {
866                    "trace" => Some(Level::Trace),
867                    "debug" => Some(Level::Debug),
868                    "log" => Some(Level::Log),
869                    "info" => Some(Level::Info),
870                    "warn" => Some(Level::Warn),
871                    "error" => Some(Level::Error),
872                    _ => None,
873                });
874
875                if let Some(level) = level_opt {
876                    backend.log(level, args);
877                }
878            }
879        })?;
880
881        self.eval(
882            r#"
883            globalThis.console = {
884                trace: (...args) => {
885                    globalThis.__console_write("trace", ...args);
886                },
887                debug: (...args) => {
888                    globalThis.__console_write("debug", ...args);
889                },
890                log: (...args) => {
891                    globalThis.__console_write("log", ...args);
892                },
893                info: (...args) => {
894                    globalThis.__console_write("info", ...args);
895                },
896                warn: (...args) => {
897                    globalThis.__console_write("warn", ...args);
898                },
899                error: (...args) => {
900                    globalThis.__console_write("error", ...args);
901                },
902            };
903        "#,
904        )?;
905
906        Ok(())
907    }
908
909    /// Reset the wrapper by creating a new context.
910    pub fn reset(self) -> Result<Self, ContextError> {
911        unsafe {
912            q::JS_FreeContext(self.context);
913        };
914        self.callbacks.lock().unwrap().clear();
915        let context = unsafe { q::JS_NewContext(self.runtime) };
916        if context.is_null() {
917            return Err(ContextError::ContextCreationFailed);
918        }
919
920        let mut s = self;
921        s.context = context;
922        Ok(s)
923    }
924
925    pub fn serialize_value(&self, value: JsValue) -> Result<OwnedValueRef<'_>, ExecutionError> {
926        let serialized = serialize_value(self.context, value)?;
927        Ok(OwnedValueRef::new(self, serialized))
928    }
929
930    // Deserialize a quickjs runtime value into a Rust value.
931    fn to_value(&self, value: &q::JSValue) -> Result<JsValue, ValueError> {
932        deserialize_value(self.context, value)
933    }
934
935    /// Get the global object.
936    pub fn global(&self) -> Result<OwnedObjectRef<'_>, ExecutionError> {
937        let global_raw = unsafe { q::JS_GetGlobalObject(self.context) };
938        let global_ref = OwnedValueRef::new(self, global_raw);
939        let global = OwnedObjectRef::new(global_ref)?;
940        Ok(global)
941    }
942
943    /// Get the last exception from the runtime, and if present, convert it to a ExceptionError.
944    fn get_exception(&self) -> Option<ExecutionError> {
945        let raw = unsafe { q::JS_GetException(self.context) };
946        let value = OwnedValueRef::new(self, raw);
947
948        if value.is_null() {
949            None
950        } else {
951            let err = if value.is_exception() {
952                ExecutionError::Internal("Could get exception from runtime".into())
953            } else {
954                match value.to_string() {
955                    Ok(strval) => {
956                        if strval.contains("out of memory") {
957                            ExecutionError::OutOfMemory
958                        } else {
959                            ExecutionError::Exception(JsValue::String(strval))
960                        }
961                    }
962                    Err(_) => ExecutionError::Internal("Unknown exception".into()),
963                }
964            };
965            Some(err)
966        }
967    }
968
969    /// If the given value is a promise, run the event loop until it is
970    /// resolved, and return the final value.
971    fn resolve_value<'a>(
972        &'a self,
973        value: OwnedValueRef<'a>,
974    ) -> Result<OwnedValueRef<'a>, ExecutionError> {
975        if value.is_exception() {
976            let err = self
977                .get_exception()
978                .unwrap_or_else(|| ExecutionError::Exception("Unknown exception".into()));
979            Err(err)
980        } else if value.is_object() {
981            let obj = OwnedObjectRef::new(value)?;
982            if obj.is_promise()? {
983                self.eval(
984                    r#"
985                    // Values:
986                    //   - undefined: promise not finished
987                    //   - false: error ocurred, __promiseError is set.
988                    //   - true: finished, __promiseSuccess is set.
989                    var __promiseResult = 0;
990                    var __promiseValue = 0;
991
992                    var __resolvePromise = function(p) {
993                        p
994                            .then(value => {
995                                __promiseResult = true;
996                                __promiseValue = value;
997                            })
998                            .catch(e => {
999                                __promiseResult = false;
1000                                __promiseValue = e;
1001                            });
1002                    }
1003                "#,
1004                )?;
1005
1006                let global = self.global()?;
1007                let resolver = global.property("__resolvePromise")?;
1008
1009                // Call the resolver code that sets the result values once
1010                // the promise resolves.
1011                self.call_function(resolver, vec![obj.into_value()])?;
1012
1013                loop {
1014                    let flag = unsafe {
1015                        let wrapper_mut = self as *const Self as *mut Self;
1016                        let ctx_mut = &mut (*wrapper_mut).context;
1017                        q::JS_ExecutePendingJob(self.runtime, ctx_mut)
1018                    };
1019                    if flag < 0 {
1020                        let e = self.get_exception().unwrap_or_else(|| {
1021                            ExecutionError::Exception("Unknown exception".into())
1022                        });
1023                        return Err(e);
1024                    }
1025
1026                    // Check if promise is finished.
1027                    let res_val = global.property("__promiseResult")?;
1028                    if res_val.is_bool() {
1029                        let ok = res_val.to_bool()?;
1030                        let value = global.property("__promiseValue")?;
1031
1032                        if ok {
1033                            return self.resolve_value(value);
1034                        } else {
1035                            let err_msg = value.to_string()?;
1036                            return Err(ExecutionError::Exception(JsValue::String(err_msg)));
1037                        }
1038                    }
1039                }
1040            } else {
1041                Ok(obj.into_value())
1042            }
1043        } else {
1044            Ok(value)
1045        }
1046    }
1047
1048    /// Evaluate javascript code.
1049    pub fn eval<'a>(&'a self, code: &str) -> Result<OwnedValueRef<'a>, ExecutionError> {
1050        let filename = "script.js";
1051        let filename_c = make_cstring(filename)?;
1052        let code_c = make_cstring(code)?;
1053
1054        let value_raw = unsafe {
1055            q::JS_Eval(
1056                self.context,
1057                code_c.as_ptr(),
1058                code.len() as _,
1059                filename_c.as_ptr(),
1060                q::JS_EVAL_TYPE_GLOBAL as i32,
1061            )
1062        };
1063        let value = OwnedValueRef::new(self, value_raw);
1064        self.resolve_value(value)
1065    }
1066
1067    /*
1068    /// Call a constructor function.
1069    fn call_constructor<'a>(
1070        &'a self,
1071        function: OwnedValueRef<'a>,
1072        args: Vec<OwnedValueRef<'a>>,
1073    ) -> Result<OwnedValueRef<'a>, ExecutionError> {
1074        let mut qargs = args.iter().map(|arg| arg.value).collect::<Vec<_>>();
1075
1076        let value_raw = unsafe {
1077            q::JS_CallConstructor(
1078                self.context,
1079                function.value,
1080                qargs.len() as i32,
1081                qargs.as_mut_ptr(),
1082            )
1083        };
1084        let value = OwnedValueRef::new(self, value_raw);
1085        if value.is_exception() {
1086            let err = self
1087                .get_exception()
1088                .unwrap_or_else(|| ExecutionError::Exception("Unknown exception".into()));
1089            Err(err)
1090        } else {
1091            Ok(value)
1092        }
1093    }
1094    */
1095
1096    /// Call a JS function with the given arguments.
1097    pub fn call_function<'a>(
1098        &'a self,
1099        function: OwnedValueRef<'a>,
1100        args: Vec<OwnedValueRef<'a>>,
1101    ) -> Result<OwnedValueRef<'a>, ExecutionError> {
1102        let mut qargs = args.iter().map(|arg| arg.value).collect::<Vec<_>>();
1103
1104        let qres_raw = unsafe {
1105            q::JS_Call(
1106                self.context,
1107                function.value,
1108                js_null_value(),
1109                qargs.len() as i32,
1110                qargs.as_mut_ptr(),
1111            )
1112        };
1113        let qres = OwnedValueRef::new(self, qres_raw);
1114        self.resolve_value(qres)
1115    }
1116
1117    /// Helper for executing a callback closure.
1118    fn exec_callback<F>(
1119        context: *mut q::JSContext,
1120        argc: c_int,
1121        argv: *mut q::JSValue,
1122        callback: &impl Callback<F>,
1123    ) -> Result<q::JSValue, ExecutionError> {
1124        let result = std::panic::catch_unwind(|| {
1125            let arg_slice = unsafe { std::slice::from_raw_parts(argv, argc as usize) };
1126
1127            let args = arg_slice
1128                .iter()
1129                .map(|raw| deserialize_value(context, raw))
1130                .collect::<Result<Vec<_>, _>>()?;
1131
1132            match callback.call(args) {
1133                Ok(Ok(result)) => {
1134                    let serialized = serialize_value(context, result)?;
1135                    Ok(serialized)
1136                }
1137                // TODO: better error reporting.
1138                Ok(Err(e)) => Err(ExecutionError::Exception(JsValue::String(e))),
1139                Err(e) => Err(e.into()),
1140            }
1141        });
1142
1143        match result {
1144            Ok(r) => r,
1145            Err(_e) => Err(ExecutionError::Internal("Callback panicked!".to_string())),
1146        }
1147    }
1148
1149    /// Add a global JS function that is backed by a Rust function or closure.
1150    pub fn create_callback<'a, F>(
1151        &'a self,
1152        callback: impl Callback<F> + 'static,
1153    ) -> Result<q::JSValue, ExecutionError> {
1154        let argcount = callback.argument_count() as i32;
1155
1156        let context = self.context;
1157        let wrapper = move |argc: c_int, argv: *mut q::JSValue| -> q::JSValue {
1158            match Self::exec_callback(context, argc, argv, &callback) {
1159                Ok(value) => value,
1160                // TODO: better error reporting.
1161                Err(e) => {
1162                    let js_exception_value = match e {
1163                        ExecutionError::Exception(e) => e,
1164                        other => other.to_string().into(),
1165                    };
1166                    let js_exception = serialize_value(context, js_exception_value).unwrap();
1167                    unsafe {
1168                        q::JS_Throw(context, js_exception);
1169                    }
1170
1171                    q::JSValue {
1172                        u: q::JSValueUnion { int32: 0 },
1173                        tag: TAG_EXCEPTION,
1174                    }
1175                }
1176            }
1177        };
1178
1179        let (pair, trampoline) = unsafe { build_closure_trampoline(wrapper) };
1180        let data = (&*pair.1) as *const q::JSValue as *mut q::JSValue;
1181        self.callbacks.lock().unwrap().push(pair);
1182
1183        let cfunc =
1184            unsafe { q::JS_NewCFunctionData(self.context, trampoline, argcount, 0, 1, data) };
1185        if cfunc.tag != TAG_OBJECT {
1186            return Err(ExecutionError::Internal("Could not create callback".into()));
1187        }
1188
1189        Ok(cfunc)
1190    }
1191
1192    pub fn add_callback<'a, F>(
1193        &'a self,
1194        name: &str,
1195        callback: impl Callback<F> + 'static,
1196    ) -> Result<(), ExecutionError> {
1197        let cfunc = self.create_callback(callback)?;
1198        let global = self.global()?;
1199        unsafe {
1200            global.set_property_raw(name, cfunc)?;
1201        }
1202        Ok(())
1203    }
1204}