boa_engine/native_function/
mod.rs1use std::cell::RefCell;
7
8use boa_gc::{Finalize, Gc, Trace, custom_trace};
9use boa_string::JsString;
10
11use crate::job::NativeAsyncJob;
12use crate::object::internal_methods::InternalMethodCallContext;
13use crate::value::JsVariant;
14use crate::{
15 Context, JsNativeError, JsObject, JsResult, JsValue,
16 builtins::{OrdinaryObject, function::ConstructorKind},
17 context::intrinsics::StandardConstructors,
18 object::{
19 FunctionObjectBuilder, JsData, JsFunction, JsPromise,
20 internal_methods::{
21 CallValue, InternalObjectMethods, ORDINARY_INTERNAL_METHODS,
22 get_prototype_from_constructor,
23 },
24 },
25 realm::Realm,
26};
27
28mod continuation;
29
30pub(crate) use continuation::{CoroutineBranch, CoroutineState, NativeCoroutine};
31
32pub type NativeFunctionPointer = fn(&JsValue, &[JsValue], &mut Context) -> JsResult<JsValue>;
42
43trait TraceableClosure: Trace {
44 fn call(&self, this: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue>;
45}
46
47#[derive(Trace, Finalize)]
48struct Closure<F, T>
49where
50 F: Fn(&JsValue, &[JsValue], &T, &mut Context) -> JsResult<JsValue>,
51 T: Trace,
52{
53 #[unsafe_ignore_trace]
57 f: F,
58 captures: T,
59}
60
61impl<F, T> TraceableClosure for Closure<F, T>
62where
63 F: Fn(&JsValue, &[JsValue], &T, &mut Context) -> JsResult<JsValue>,
64 T: Trace,
65{
66 fn call(&self, this: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
67 (self.f)(this, args, &self.captures, context)
68 }
69}
70
71#[derive(Clone, Debug, Finalize)]
72pub struct NativeFunctionObject {
74 pub(crate) f: NativeFunction,
76
77 pub(crate) name: JsString,
79
80 pub(crate) constructor: Option<ConstructorKind>,
82
83 pub(crate) realm: Option<Realm>,
85}
86
87unsafe impl Trace for NativeFunctionObject {
89 custom_trace!(this, mark, {
90 mark(&this.f);
91 mark(&this.realm);
92 });
93}
94
95impl JsData for NativeFunctionObject {
96 fn internal_methods(&self) -> &'static InternalObjectMethods {
97 static FUNCTION: InternalObjectMethods = InternalObjectMethods {
98 __call__: native_function_call,
99 ..ORDINARY_INTERNAL_METHODS
100 };
101
102 static CONSTRUCTOR: InternalObjectMethods = InternalObjectMethods {
103 __call__: native_function_call,
104 __construct__: native_function_construct,
105 ..ORDINARY_INTERNAL_METHODS
106 };
107
108 if self.constructor.is_some() {
109 &CONSTRUCTOR
110 } else {
111 &FUNCTION
112 }
113 }
114}
115
116#[derive(Clone, Finalize)]
130pub struct NativeFunction {
131 inner: Inner,
132}
133
134#[derive(Clone)]
135enum Inner {
136 PointerFn(NativeFunctionPointer),
137 Closure(Gc<dyn TraceableClosure>),
138}
139
140unsafe impl Trace for NativeFunction {
143 custom_trace!(this, mark, {
144 if let Inner::Closure(c) = &this.inner {
145 mark(c);
146 }
147 });
148}
149
150impl std::fmt::Debug for NativeFunction {
151 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
152 f.debug_struct("NativeFunction").finish_non_exhaustive()
153 }
154}
155
156impl NativeFunction {
157 #[inline]
159 pub fn from_fn_ptr(function: NativeFunctionPointer) -> Self {
160 Self {
161 inner: Inner::PointerFn(function),
162 }
163 }
164
165 pub fn from_async_fn<F>(f: F) -> Self
197 where
198 F: AsyncFn(&JsValue, &[JsValue], &RefCell<&mut Context>) -> JsResult<JsValue> + 'static,
199 F: Copy,
200 {
201 Self::from_copy_closure(move |this, args, context| {
202 let (promise, resolvers) = JsPromise::new_pending(context);
203 let this = this.clone();
204 let args = args.to_vec();
205
206 context.enqueue_job(
207 NativeAsyncJob::new(async move |context| {
208 let result = f(&this, &args, context).await;
209
210 let context = &mut context.borrow_mut();
211 match result {
212 Ok(v) => resolvers.resolve.call(&JsValue::undefined(), &[v], context),
213 Err(e) => {
214 let e = e.into_opaque(context)?;
215 resolvers.reject.call(&JsValue::undefined(), &[e], context)
216 }
217 }
218 })
219 .into(),
220 );
221
222 Ok(promise.into())
223 })
224 }
225
226 pub fn from_copy_closure<F>(closure: F) -> Self
228 where
229 F: Fn(&JsValue, &[JsValue], &mut Context) -> JsResult<JsValue> + Copy + 'static,
230 {
231 unsafe { Self::from_closure(closure) }
233 }
234
235 pub fn from_copy_closure_with_captures<F, T>(closure: F, captures: T) -> Self
237 where
238 F: Fn(&JsValue, &[JsValue], &T, &mut Context) -> JsResult<JsValue> + Copy + 'static,
239 T: Trace + 'static,
240 {
241 unsafe { Self::from_closure_with_captures(closure, captures) }
243 }
244
245 pub unsafe fn from_closure<F>(closure: F) -> Self
254 where
255 F: Fn(&JsValue, &[JsValue], &mut Context) -> JsResult<JsValue> + 'static,
256 {
257 unsafe {
259 Self::from_closure_with_captures(
260 move |this, args, (), context| closure(this, args, context),
261 (),
262 )
263 }
264 }
265
266 pub unsafe fn from_closure_with_captures<F, T>(closure: F, captures: T) -> Self
275 where
276 F: Fn(&JsValue, &[JsValue], &T, &mut Context) -> JsResult<JsValue> + 'static,
277 T: Trace + 'static,
278 {
279 let ptr = Gc::into_raw(Gc::new(Closure {
282 f: closure,
283 captures,
284 }));
285 unsafe {
288 Self {
289 inner: Inner::Closure(Gc::from_raw(ptr)),
290 }
291 }
292 }
293
294 #[inline]
296 pub fn call(
297 &self,
298 this: &JsValue,
299 args: &[JsValue],
300 context: &mut Context,
301 ) -> JsResult<JsValue> {
302 match self.inner {
303 Inner::PointerFn(f) => f(this, args, context),
304 Inner::Closure(ref c) => c.call(this, args, context),
305 }
306 }
307
308 #[must_use]
312 pub fn to_js_function(self, realm: &Realm) -> JsFunction {
313 FunctionObjectBuilder::new(realm, self).build()
314 }
315}
316
317pub(crate) fn native_function_call(
324 obj: &JsObject,
325 argument_count: usize,
326 context: &mut InternalMethodCallContext<'_>,
327) -> JsResult<CallValue> {
328 let args = context
329 .vm
330 .stack
331 .calling_convention_pop_arguments(argument_count);
332 let _func = context.vm.stack.pop();
333 let this = context.vm.stack.pop();
334
335 context.check_runtime_limits()?;
338 let this_function_object = obj.clone();
339
340 let NativeFunctionObject {
341 f: function,
342 name,
343 constructor,
344 realm,
345 } = obj
346 .downcast_ref::<NativeFunctionObject>()
347 .expect("the object should be a native function object")
348 .clone();
349
350 let pc = context.vm.frame().pc;
351 let native_source_info = context.native_source_info();
352 context
353 .vm
354 .shadow_stack
355 .push_native(pc, name, native_source_info);
356
357 let mut realm = realm.unwrap_or_else(|| context.realm().clone());
358
359 context.swap_realm(&mut realm);
360 context.vm.native_active_function = Some(this_function_object);
361
362 let result = if constructor.is_some() {
363 function.call(&JsValue::undefined(), &args, context)
364 } else {
365 function.call(&this, &args, context)
366 }
367 .map_err(|err| err.inject_realm(context.realm().clone()));
368
369 context.vm.native_active_function = None;
370 context.swap_realm(&mut realm);
371
372 context.vm.shadow_stack.pop();
373
374 context.vm.stack.push(result?);
375
376 Ok(CallValue::Complete)
377}
378
379fn native_function_construct(
386 obj: &JsObject,
387 argument_count: usize,
388 context: &mut InternalMethodCallContext<'_>,
389) -> JsResult<CallValue> {
390 context.check_runtime_limits()?;
393 let this_function_object = obj.clone();
394
395 let NativeFunctionObject {
396 f: function,
397 name,
398 constructor,
399 realm,
400 } = obj
401 .downcast_ref::<NativeFunctionObject>()
402 .expect("the object should be a native function object")
403 .clone();
404
405 let pc = context.vm.frame().pc;
406 let native_source_info = context.native_source_info();
407 context
408 .vm
409 .shadow_stack
410 .push_native(pc, name, native_source_info);
411
412 let mut realm = realm.unwrap_or_else(|| context.realm().clone());
413
414 context.swap_realm(&mut realm);
415 context.vm.native_active_function = Some(this_function_object);
416
417 let new_target = context.vm.stack.pop();
418 let args = context
419 .vm
420 .stack
421 .calling_convention_pop_arguments(argument_count);
422 let _func = context.vm.stack.pop();
423 let _this = context.vm.stack.pop();
424
425 let result = function
426 .call(&new_target, &args, context)
427 .map_err(|err| err.inject_realm(context.realm().clone()))
428 .and_then(|v| match v.variant() {
429 JsVariant::Object(o) => Ok(o.clone()),
430 val => {
431 if constructor.expect("must be a constructor").is_base() || val.is_undefined() {
432 let prototype = get_prototype_from_constructor(
433 &new_target,
434 StandardConstructors::object,
435 context,
436 )?;
437 Ok(JsObject::from_proto_and_data_with_shared_shape(
438 context.root_shape(),
439 prototype,
440 OrdinaryObject,
441 )
442 .upcast())
443 } else {
444 Err(JsNativeError::typ()
445 .with_message("derived constructor can only return an Object or undefined")
446 .into())
447 }
448 }
449 });
450
451 context.vm.native_active_function = None;
452 context.swap_realm(&mut realm);
453
454 context.vm.shadow_stack.pop();
455
456 context.vm.stack.push(result?);
457
458 Ok(CallValue::Complete)
459}