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#[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
32unsafe fn free_value(context: *mut q::JSContext, value: q::JSValue) {
36 if value.tag < 0 {
38 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
87fn 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 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 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 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 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 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 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 TAG_INT => {
403 let val = unsafe { r.u.int32 };
404 Ok(JsValue::Int(val))
405 }
406 TAG_BOOL => {
408 let raw = unsafe { r.u.int32 };
409 let val = raw > 0;
410 Ok(JsValue::Bool(val))
411 }
412 TAG_NULL => Ok(JsValue::Null),
414 TAG_UNDEFINED => Ok(JsValue::Undefined),
416 TAG_FLOAT64 => {
418 let val = unsafe { r.u.float64 };
419 Ok(JsValue::Float(val))
420 }
421 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 unsafe { q::JS_FreeCString(context, ptr) };
440
441 Ok(JsValue::String(s))
442 }
443 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 #[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 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
534fn make_cstring(value: impl Into<Vec<u8>>) -> Result<CString, ValueError> {
536 CString::new(value).map_err(ValueError::StringWithZeroBytes)
537}
538
539fn 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
549unsafe 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
590pub 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 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
688pub 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 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 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 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 std::mem::forget(qval);
775 }
776 Ok(())
777 }
778}
779
780pub struct ContextWrapper {
803 runtime: *mut q::JSRuntime,
804 context: *mut q::JSContext,
805 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 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 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 let wrapper = Self {
847 runtime,
848 context,
849 callbacks: Mutex::new(Vec::new()),
850 };
851
852 Ok(wrapper)
853 }
854
855 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 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 fn to_value(&self, value: &q::JSValue) -> Result<JsValue, ValueError> {
932 deserialize_value(self.context, value)
933 }
934
935 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 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 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 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 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 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 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 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 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 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 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}