use std::cell::RefCell;
use boa_gc::{Finalize, Gc, Trace, custom_trace};
use boa_string::JsString;
use crate::job::NativeAsyncJob;
use crate::object::internal_methods::InternalMethodCallContext;
use crate::value::JsVariant;
use crate::{
Context, JsNativeError, JsObject, JsResult, JsValue,
builtins::{OrdinaryObject, function::ConstructorKind},
context::intrinsics::StandardConstructors,
object::{
FunctionObjectBuilder, JsData, JsFunction, JsPromise,
internal_methods::{
CallValue, InternalObjectMethods, ORDINARY_INTERNAL_METHODS,
get_prototype_from_constructor,
},
},
realm::Realm,
};
#[cfg(feature = "experimental")]
mod continuation;
#[cfg(feature = "experimental")]
pub(crate) use continuation::{CoroutineState, NativeCoroutine};
pub type NativeFunctionPointer = fn(&JsValue, &[JsValue], &mut Context) -> JsResult<JsValue>;
trait TraceableClosure: Trace {
fn call(&self, this: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue>;
}
#[derive(Trace, Finalize)]
struct Closure<F, T>
where
F: Fn(&JsValue, &[JsValue], &T, &mut Context) -> JsResult<JsValue>,
T: Trace,
{
#[unsafe_ignore_trace]
f: F,
captures: T,
}
impl<F, T> TraceableClosure for Closure<F, T>
where
F: Fn(&JsValue, &[JsValue], &T, &mut Context) -> JsResult<JsValue>,
T: Trace,
{
fn call(&self, this: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
(self.f)(this, args, &self.captures, context)
}
}
#[derive(Clone, Debug, Finalize)]
pub struct NativeFunctionObject {
pub(crate) f: NativeFunction,
pub(crate) name: JsString,
pub(crate) constructor: Option<ConstructorKind>,
pub(crate) realm: Option<Realm>,
}
unsafe impl Trace for NativeFunctionObject {
custom_trace!(this, mark, {
mark(&this.f);
mark(&this.realm);
});
}
impl JsData for NativeFunctionObject {
fn internal_methods(&self) -> &'static InternalObjectMethods {
static FUNCTION: InternalObjectMethods = InternalObjectMethods {
__call__: native_function_call,
..ORDINARY_INTERNAL_METHODS
};
static CONSTRUCTOR: InternalObjectMethods = InternalObjectMethods {
__call__: native_function_call,
__construct__: native_function_construct,
..ORDINARY_INTERNAL_METHODS
};
if self.constructor.is_some() {
&CONSTRUCTOR
} else {
&FUNCTION
}
}
}
#[derive(Clone, Finalize)]
pub struct NativeFunction {
inner: Inner,
}
#[derive(Clone)]
enum Inner {
PointerFn(NativeFunctionPointer),
Closure(Gc<dyn TraceableClosure>),
}
unsafe impl Trace for NativeFunction {
custom_trace!(this, mark, {
if let Inner::Closure(c) = &this.inner {
mark(c);
}
});
}
impl std::fmt::Debug for NativeFunction {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("NativeFunction").finish_non_exhaustive()
}
}
impl NativeFunction {
#[inline]
pub fn from_fn_ptr(function: NativeFunctionPointer) -> Self {
Self {
inner: Inner::PointerFn(function),
}
}
pub fn from_async_fn<F>(f: F) -> Self
where
F: AsyncFn(&JsValue, &[JsValue], &RefCell<&mut Context>) -> JsResult<JsValue> + 'static,
F: Copy,
{
Self::from_copy_closure(move |this, args, context| {
let (promise, resolvers) = JsPromise::new_pending(context);
let this = this.clone();
let args = args.to_vec();
context.enqueue_job(
NativeAsyncJob::new(async move |context| {
let result = f(&this, &args, context).await;
let context = &mut context.borrow_mut();
match result {
Ok(v) => resolvers.resolve.call(&JsValue::undefined(), &[v], context),
Err(e) => {
let e = e.to_opaque(context);
resolvers.reject.call(&JsValue::undefined(), &[e], context)
}
}
})
.into(),
);
Ok(promise.into())
})
}
pub fn from_copy_closure<F>(closure: F) -> Self
where
F: Fn(&JsValue, &[JsValue], &mut Context) -> JsResult<JsValue> + Copy + 'static,
{
unsafe { Self::from_closure(closure) }
}
pub fn from_copy_closure_with_captures<F, T>(closure: F, captures: T) -> Self
where
F: Fn(&JsValue, &[JsValue], &T, &mut Context) -> JsResult<JsValue> + Copy + 'static,
T: Trace + 'static,
{
unsafe { Self::from_closure_with_captures(closure, captures) }
}
pub unsafe fn from_closure<F>(closure: F) -> Self
where
F: Fn(&JsValue, &[JsValue], &mut Context) -> JsResult<JsValue> + 'static,
{
unsafe {
Self::from_closure_with_captures(
move |this, args, (), context| closure(this, args, context),
(),
)
}
}
pub unsafe fn from_closure_with_captures<F, T>(closure: F, captures: T) -> Self
where
F: Fn(&JsValue, &[JsValue], &T, &mut Context) -> JsResult<JsValue> + 'static,
T: Trace + 'static,
{
let ptr = Gc::into_raw(Gc::new(Closure {
f: closure,
captures,
}));
unsafe {
Self {
inner: Inner::Closure(Gc::from_raw(ptr)),
}
}
}
#[inline]
pub fn call(
&self,
this: &JsValue,
args: &[JsValue],
context: &mut Context,
) -> JsResult<JsValue> {
match self.inner {
Inner::PointerFn(f) => f(this, args, context),
Inner::Closure(ref c) => c.call(this, args, context),
}
}
#[must_use]
pub fn to_js_function(self, realm: &Realm) -> JsFunction {
FunctionObjectBuilder::new(realm, self).build()
}
}
pub(crate) fn native_function_call(
obj: &JsObject,
argument_count: usize,
context: &mut InternalMethodCallContext<'_>,
) -> JsResult<CallValue> {
let args = context
.vm
.stack
.calling_convention_pop_arguments(argument_count);
let _func = context.vm.stack.pop();
let this = context.vm.stack.pop();
context.check_runtime_limits()?;
let this_function_object = obj.clone();
let NativeFunctionObject {
f: function,
name,
constructor,
realm,
} = obj
.downcast_ref::<NativeFunctionObject>()
.expect("the object should be a native function object")
.clone();
let pc = context.vm.frame.pc;
let native_source_info = context.native_source_info();
context
.vm
.shadow_stack
.push_native(pc, name, native_source_info);
let mut realm = realm.unwrap_or_else(|| context.realm().clone());
context.swap_realm(&mut realm);
context.vm.native_active_function = Some(this_function_object);
let result = if constructor.is_some() {
function.call(&JsValue::undefined(), &args, context)
} else {
function.call(&this, &args, context)
}
.map_err(|err| err.inject_realm(context.realm().clone()));
context.vm.native_active_function = None;
context.swap_realm(&mut realm);
context.vm.shadow_stack.pop();
context.vm.stack.push(result?);
Ok(CallValue::Complete)
}
fn native_function_construct(
obj: &JsObject,
argument_count: usize,
context: &mut InternalMethodCallContext<'_>,
) -> JsResult<CallValue> {
context.check_runtime_limits()?;
let this_function_object = obj.clone();
let NativeFunctionObject {
f: function,
name,
constructor,
realm,
} = obj
.downcast_ref::<NativeFunctionObject>()
.expect("the object should be a native function object")
.clone();
let pc = context.vm.frame.pc;
let native_source_info = context.native_source_info();
context
.vm
.shadow_stack
.push_native(pc, name, native_source_info);
let mut realm = realm.unwrap_or_else(|| context.realm().clone());
context.swap_realm(&mut realm);
context.vm.native_active_function = Some(this_function_object);
let new_target = context.vm.stack.pop();
let args = context
.vm
.stack
.calling_convention_pop_arguments(argument_count);
let _func = context.vm.stack.pop();
let _this = context.vm.stack.pop();
let result = function
.call(&new_target, &args, context)
.map_err(|err| err.inject_realm(context.realm().clone()))
.and_then(|v| match v.variant() {
JsVariant::Object(o) => Ok(o.clone()),
val => {
if constructor.expect("must be a constructor").is_base() || val.is_undefined() {
let prototype = get_prototype_from_constructor(
&new_target,
StandardConstructors::object,
context,
)?;
Ok(JsObject::from_proto_and_data_with_shared_shape(
context.root_shape(),
prototype,
OrdinaryObject,
))
} else {
Err(JsNativeError::typ()
.with_message("derived constructor can only return an Object or undefined")
.into())
}
}
});
context.vm.native_active_function = None;
context.swap_realm(&mut realm);
context.vm.shadow_stack.pop();
context.vm.stack.push(result?);
Ok(CallValue::Complete)
}