ion/function/
closure.rs

1/*
2 * This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
5 */
6
7use 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};