1use std::panic::{AssertUnwindSafe, catch_unwind};
8use std::ptr;
9
10use mozjs::glue::JS_GetReservedSlot;
11use mozjs::jsapi::{
12 GCContext, GetFunctionNativeReserved, JS_NewObject, JS_SetReservedSlot, JSCLASS_BACKGROUND_FINALIZE, JSClass,
13 JSClassOps, JSContext, JSObject,
14};
15use mozjs::jsval::{JSVal, PrivateValue, UndefinedValue};
16
17use crate::conversions::ToValue as _;
18use crate::function::__handle_native_function_result;
19use crate::object::class_reserved_slots;
20use crate::{Arguments, Context, Error, ErrorKind, Object, ResultExc, ThrowException as _, Value};
21
22const CLOSURE_SLOT: u32 = 0;
23
24pub type ClosureOnce = dyn for<'cx> FnOnce(&mut Arguments<'cx>) -> ResultExc<Value<'cx>> + 'static;
25pub type Closure = dyn for<'cx> FnMut(&mut Arguments<'cx>) -> ResultExc<Value<'cx>> + 'static;
26
27type ClosurePrivate = Box<Closure>;
28type ClosureOncePrivate = Option<Box<ClosureOnce>>;
29
30pub(crate) fn create_closure_once_object(cx: &Context, closure: Box<ClosureOnce>) -> Object<'_> {
31 unsafe {
32 let object = Object::from(cx.root(JS_NewObject(cx.as_ptr(), &raw const CLOSURE_ONCE_CLASS)));
33 JS_SetReservedSlot(
34 object.handle().get(),
35 CLOSURE_SLOT,
36 &PrivateValue(Box::into_raw(Box::new(Some(closure))).cast_const().cast()),
37 );
38 object
39 }
40}
41
42pub(crate) fn create_closure_object(cx: &Context, closure: Box<Closure>) -> Object<'_> {
43 unsafe {
44 let object = Object::from(cx.root(JS_NewObject(cx.as_ptr(), &raw const CLOSURE_CLASS)));
45 JS_SetReservedSlot(
46 object.handle().get(),
47 CLOSURE_SLOT,
48 &PrivateValue(Box::into_raw(Box::new(closure)).cast_const().cast()),
49 );
50 object
51 }
52}
53
54fn get_function_reserved<'cx>(cx: &'cx Context, args: &Arguments) -> Value<'cx> {
55 let callee = args.callee();
56 Value::from(cx.root(unsafe { *GetFunctionNativeReserved(callee.handle().get(), 0) }))
57}
58
59fn get_reserved(object: *mut JSObject, slot: u32) -> JSVal {
60 let mut value = UndefinedValue();
61 unsafe { JS_GetReservedSlot(object, slot, &raw mut value) };
62 value
63}
64
65pub(crate) unsafe extern "C" fn call_closure_once(cx: *mut JSContext, argc: u32, vp: *mut JSVal) -> bool {
66 let cx = &unsafe { Context::new_unchecked(cx) };
67 let args = &mut unsafe { Arguments::new(cx, argc, vp) };
68
69 let reserved = get_function_reserved(cx, args);
70 let value = get_reserved(reserved.handle().to_object(), CLOSURE_SLOT);
71 let closure = unsafe { &mut *(value.to_private().cast::<ClosureOncePrivate>().cast_mut()) };
72
73 if let Some(closure) = closure.take() {
74 let result = catch_unwind(AssertUnwindSafe(|| {
75 closure(args).map(|result| result.to_value(cx, &mut args.rval()))
76 }));
77 __handle_native_function_result(cx, result)
78 } else {
79 Error::new("ClosureOnce was called more than once.", ErrorKind::Type).throw(cx);
80 false
81 }
82}
83
84pub(crate) unsafe extern "C" fn call_closure(cx: *mut JSContext, argc: u32, vp: *mut JSVal) -> bool {
85 let cx = &unsafe { Context::new_unchecked(cx) };
86 let args = &mut unsafe { Arguments::new(cx, argc, vp) };
87
88 let reserved = get_function_reserved(cx, args);
89 let value = get_reserved(reserved.handle().to_object(), CLOSURE_SLOT);
90 let closure = unsafe { &mut *(value.to_private().cast::<ClosurePrivate>().cast_mut()) };
91
92 let result = catch_unwind(AssertUnwindSafe(|| {
93 closure(args).map(|result| result.to_value(cx, &mut args.rval()))
94 }));
95 __handle_native_function_result(cx, result)
96}
97
98unsafe extern "C" fn finalise_closure<T>(_: *mut GCContext, object: *mut JSObject) {
99 let mut value = UndefinedValue();
100 unsafe {
101 JS_GetReservedSlot(object, CLOSURE_SLOT, &raw mut value);
102 let _ = Box::from_raw(value.to_private().cast::<T>().cast_mut());
103 }
104}
105
106static CLOSURE_ONCE_OPS: JSClassOps = JSClassOps {
107 addProperty: None,
108 delProperty: None,
109 enumerate: None,
110 newEnumerate: None,
111 resolve: None,
112 mayResolve: None,
113 finalize: Some(finalise_closure::<ClosureOncePrivate>),
114 call: None,
115 construct: None,
116 trace: None,
117};
118
119static CLOSURE_ONCE_CLASS: JSClass = JSClass {
120 name: c"ClosureOnce".as_ptr(),
121 flags: JSCLASS_BACKGROUND_FINALIZE | class_reserved_slots(1),
122 cOps: &raw const CLOSURE_ONCE_OPS,
123 spec: ptr::null_mut(),
124 ext: ptr::null_mut(),
125 oOps: ptr::null_mut(),
126};
127
128static CLOSURE_OPS: JSClassOps = JSClassOps {
129 addProperty: None,
130 delProperty: None,
131 enumerate: None,
132 newEnumerate: None,
133 resolve: None,
134 mayResolve: None,
135 finalize: Some(finalise_closure::<ClosurePrivate>),
136 call: None,
137 construct: None,
138 trace: None,
139};
140
141static CLOSURE_CLASS: JSClass = JSClass {
142 name: c"Closure".as_ptr(),
143 flags: JSCLASS_BACKGROUND_FINALIZE | class_reserved_slots(1),
144 cOps: &raw const CLOSURE_OPS,
145 spec: ptr::null_mut(),
146 ext: ptr::null_mut(),
147 oOps: ptr::null_mut(),
148};