ion/function/
function.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::ffi::{CStr, CString};
8use std::ops::Deref;
9use std::ptr::NonNull;
10
11use mozjs::conversions::jsstr_to_string;
12use mozjs::gc::{RootableVec, RootedVec};
13use mozjs::jsapi::{
14	HandleValueArray, JS_CallFunction, JS_DecompileFunction, JS_GetFunctionArity, JS_GetFunctionDisplayId,
15	JS_GetFunctionId, JS_GetFunctionLength, JS_GetFunctionObject, JS_GetObjectFunction, JS_IsBuiltinEvalFunction,
16	JS_IsBuiltinFunctionConstructor, JS_IsConstructor, JS_NewFunction, JS_ObjectIsFunction, JSContext, JSFunction,
17	JSFunctionSpec, JSObject, NewFunctionFromSpec1, NewFunctionWithReserved, SetFunctionNativeReserved,
18};
19use mozjs::jsval::{JSVal, ObjectValue};
20
21use crate::flags::PropertyFlags;
22use crate::function::closure::{
23	Closure, ClosureOnce, call_closure, call_closure_once, create_closure_object, create_closure_once_object,
24};
25use crate::{Context, Error, ErrorReport, Local, Object, Value};
26
27/// Native Function that can be used from JavaScript.
28pub type NativeFunction = unsafe extern "C" fn(*mut JSContext, u32, *mut JSVal) -> bool;
29
30/// Represents a [Function] within the JavaScript Runtime.
31/// Refer to [MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions) for more details.
32#[derive(Debug)]
33pub struct Function<'f> {
34	function: Local<'f, *mut JSFunction>,
35}
36
37impl<'f> Function<'f> {
38	/// Creates a new [Function] with the given name, native function, number of arguments and flags.
39	pub fn new(
40		cx: &'f Context, name: &str, func: Option<NativeFunction>, nargs: u32, flags: PropertyFlags,
41	) -> Function<'f> {
42		let name = CString::new(name).unwrap();
43		Function {
44			function: cx
45				.root(unsafe { JS_NewFunction(cx.as_ptr(), func, nargs, u32::from(flags.bits()), name.as_ptr()) }),
46		}
47	}
48
49	/// Creates a new [Function] with the given [`spec`](JSFunctionSpec).
50	pub fn from_spec(cx: &'f Context, spec: &JSFunctionSpec) -> Function<'f> {
51		Function {
52			function: cx.root(unsafe { NewFunctionFromSpec1(cx.as_ptr(), spec) }),
53		}
54	}
55
56	/// Creates a new [Function] with a [ClosureOnce].
57	///
58	/// Throws a JS Exception if called more than once.
59	pub fn from_closure_once(
60		cx: &'f Context, name: &CStr, closure: Box<ClosureOnce>, nargs: u32, flags: PropertyFlags,
61	) -> Function<'f> {
62		let closure = create_closure_once_object(cx, closure);
63		Function::create_with_closure(cx, call_closure_once, name, &closure, nargs, flags)
64	}
65
66	/// Creates a new [Function] with a [Closure].
67	pub fn from_closure(
68		cx: &'f Context, name: &CStr, closure: Box<Closure>, nargs: u32, flags: PropertyFlags,
69	) -> Function<'f> {
70		let closure = create_closure_object(cx, closure);
71		Function::create_with_closure(cx, call_closure, name, &closure, nargs, flags)
72	}
73
74	fn create_with_closure(
75		cx: &'f Context, call: NativeFunction, name: &CStr, closure: &Object, nargs: u32, flags: PropertyFlags,
76	) -> Function<'f> {
77		unsafe {
78			let function = Function {
79				function: cx.root(NewFunctionWithReserved(
80					cx.as_ptr(),
81					Some(call),
82					nargs,
83					u32::from(flags.bits()),
84					name.as_ptr().cast(),
85				)),
86			};
87			SetFunctionNativeReserved(
88				JS_GetFunctionObject(function.get()),
89				0,
90				&ObjectValue(closure.handle().get()),
91			);
92			function
93		}
94	}
95
96	/// Creates a new [Function] from an object.
97	/// Returns [None] if the object is not a function.
98	pub fn from_object(cx: &'f Context, obj: &Local<'_, *mut JSObject>) -> Option<Function<'f>> {
99		(unsafe { Function::is_function_raw(obj.get()) }).then(|| Function {
100			function: cx.root(unsafe { JS_GetObjectFunction(obj.get()) }),
101		})
102	}
103
104	/// Converts the [Function] into an [Object].
105	pub fn to_object(&self, cx: &'f Context) -> Object<'f> {
106		cx.root(unsafe { JS_GetFunctionObject(self.get()) }).into()
107	}
108
109	/// Converts the [Function] into a [String] in the form of its definition/source.
110	pub fn to_string(&self, cx: &Context) -> Option<String> {
111		unsafe {
112			NonNull::new(JS_DecompileFunction(cx.as_ptr(), self.handle().into()))
113				.map(|str| jsstr_to_string(cx.as_ptr(), str))
114		}
115	}
116
117	/// Returns the name of the function.
118	pub fn name(&self, cx: &Context) -> crate::Result<String> {
119		let mut name = crate::String::new(cx);
120		if unsafe { JS_GetFunctionId(cx.as_ptr(), self.handle().into(), name.handle_mut().into()) } {
121			name.to_owned(cx)
122		} else {
123			Err(Error::none())
124		}
125	}
126
127	/// Returns the display name of the function.
128	/// Function display names are a non-standard feature.
129	/// Refer to [MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/displayName) for more details.
130	pub fn display_name(&self, cx: &Context) -> crate::Result<String> {
131		let mut name = crate::String::new(cx);
132		if unsafe { JS_GetFunctionDisplayId(cx.as_ptr(), self.handle().into(), name.handle_mut().into()) } {
133			name.to_owned(cx)
134		} else {
135			Err(Error::none())
136		}
137	}
138
139	/// Returns the number of arguments of the function.
140	pub fn nargs(&self) -> u16 {
141		unsafe { JS_GetFunctionArity(self.get()) }
142	}
143
144	/// Returns the length of the source of the function.
145	pub fn length(&self, cx: &Context) -> Option<u16> {
146		let mut length = 0;
147		unsafe { JS_GetFunctionLength(cx.as_ptr(), self.handle().into(), &raw mut length) }.then_some(length)
148	}
149
150	/// Calls the [Function] with the given `this` [Object] and arguments.
151	/// Returns the result of the [Function] as a [Value].
152	/// Returns [Err] if the function call fails or an exception occurs.
153	pub fn call<'cx>(
154		&self, cx: &'cx Context, this: &Object, args: &[Value],
155	) -> Result<Value<'cx>, Option<ErrorReport>> {
156		let mut root = RootableVec::new_unrooted();
157		let args = RootedVec::from_iter(&mut root, args.iter().map(|a| a.get()));
158		self.call_with_handle(cx, this, HandleValueArray::from(&args))
159	}
160
161	/// Calls the [Function] with the given `this` [Object] and arguments as a [HandleValueArray].
162	/// Returns the result of the [Function] as a [Value].
163	/// Returns [Err] if the function call fails or an exception occurs.
164	pub fn call_with_handle<'cx>(
165		&self, cx: &'cx Context, this: &Object, args: HandleValueArray,
166	) -> Result<Value<'cx>, Option<ErrorReport>> {
167		let mut rval = Value::undefined(cx);
168		if unsafe {
169			JS_CallFunction(
170				cx.as_ptr(),
171				this.handle().into(),
172				self.handle().into(),
173				&raw const args,
174				rval.handle_mut().into(),
175			)
176		} {
177			Ok(rval)
178		} else {
179			Err(ErrorReport::new_with_exception_stack(cx).unwrap())
180		}
181	}
182
183	/// Checks if the [Function] is the built-in eval function.
184	pub fn is_eval(&self) -> bool {
185		unsafe { JS_IsBuiltinEvalFunction(self.get()) }
186	}
187
188	/// Checks if the [Function] is a constructor.
189	pub fn is_constructor(&self) -> bool {
190		unsafe { JS_IsConstructor(self.get()) }
191	}
192
193	/// Checks if the [Function] is the built-in function constructor.
194	pub fn is_function_constructor(&self) -> bool {
195		unsafe { JS_IsBuiltinFunctionConstructor(self.get()) }
196	}
197
198	/// Checks if [a raw object](*mut JSObject) is a function.
199	pub unsafe fn is_function_raw(obj: *mut JSObject) -> bool {
200		unsafe { JS_ObjectIsFunction(obj) }
201	}
202}
203
204impl<'f> From<Local<'f, *mut JSFunction>> for Function<'f> {
205	fn from(function: Local<'f, *mut JSFunction>) -> Function<'f> {
206		Function { function }
207	}
208}
209
210impl<'f> Deref for Function<'f> {
211	type Target = Local<'f, *mut JSFunction>;
212
213	fn deref(&self) -> &Self::Target {
214		&self.function
215	}
216}