use crate::{JSCContext, JSCValue, jsc};
use rong_core::{JSClass, JSClassExt, JSContext, JSContextImpl, JSTypeOf, JSValueImpl, Source};
use std::collections::HashMap;
use std::ffi::{CString, c_char};
use std::ptr;
use std::sync::{LazyLock, RwLock};
static CLASS: LazyLock<RwLock<HashMap<usize, usize>>> =
LazyLock::new(|| RwLock::new(HashMap::new()));
#[derive(Clone)]
struct PublicConstructorFactory(JSCValue);
const PUBLIC_CONSTRUCTOR_FACTORY: &[u8] = br#"
(function(nativeCtor, callWithoutNew) {
function HostClass(...args) {
if (new.target) {
return Reflect.construct(nativeCtor, args, new.target);
}
return callWithoutNew(...args);
}
HostClass.prototype = nativeCtor.prototype;
return HostClass;
})
"#;
pub(crate) fn get_classref_by_constructor(constructor: JSCValue) -> jsc::JSClassRef {
let constructor_ptr = constructor.as_value() as usize;
if let Ok(map) = CLASS.read()
&& let Some(&class_ref) = map.get(&constructor_ptr)
{
return class_ref as jsc::JSClassRef;
}
ptr::null_mut()
}
unsafe extern "C" fn generic_constructor<JC>(
ctx: jsc::JSContextRef,
constructor: jsc::JSObjectRef,
argument_count: usize,
arguments: *const jsc::JSValueRef,
exception: *mut jsc::JSValueRef,
) -> jsc::JSObjectRef
where
JC: JSClass<JSCValue>,
{
let raw: *mut jsc::OpaqueJSContext = ctx as _;
let this = JSCValue::from_borrowed_obj(raw, constructor);
let args: Vec<_> = (0..argument_count)
.map(move |i| unsafe { JSCValue::from_borrowed_raw(raw, *arguments.add(i)) })
.collect();
let ctx = JSCContext::from_borrowed_raw(raw);
let value = <JC as JSClassExt<JSCValue>>::constructor(&ctx, this, args);
if value.is_exception() {
if !exception.is_null() {
unsafe {
*exception = value.into_raw_value();
}
}
unsafe {
return jsc::JSValueMakeUndefined(ctx.to_raw()) as _;
}
}
value.into_raw_value() as jsc::JSObjectRef
}
unsafe extern "C" fn call_without_new<JC>(
ctx: jsc::JSContextRef,
_function: jsc::JSObjectRef,
_this_object: jsc::JSObjectRef,
argument_count: usize,
arguments: *const jsc::JSValueRef,
exception: *mut jsc::JSValueRef,
) -> jsc::JSValueRef
where
JC: JSClass<JSCValue>,
{
let ctx = JSCContext::from_borrowed_raw(ctx as _);
let args: Vec<JSCValue> = if !arguments.is_null() {
(0..argument_count)
.map(|i| unsafe { JSCValue::from_borrowed_raw(ctx.to_raw(), *arguments.add(i)) })
.collect()
} else {
vec![]
};
let value =
<JC as JSClassExt<JSCValue>>::constructor(&ctx, JSCValue::create_undefined(&ctx), args);
if value.is_exception() {
if !exception.is_null() {
unsafe {
*exception = value.into_raw_value();
}
}
unsafe {
return jsc::JSValueMakeUndefined(ctx.to_raw());
}
}
value.into_raw_value()
}
unsafe extern "C" fn finalizer<JC>(object: jsc::JSObjectRef)
where
JC: JSClass<JSCValue>,
{
let classid = unsafe { jsc::JSObjectGetPrivate(object) } as usize;
if classid & 0x1 == 1 {
let class_ref = classid & !0x1;
unsafe {
jsc::JSClassRelease(class_ref as _);
}
return;
}
let ctx: *mut jsc::OpaqueJSContext = std::ptr::null_mut();
let value = JSCValue::from_borrowed_obj(ctx, object);
<JC as JSClassExt<JSCValue>>::free(value);
}
unsafe extern "C" fn call_as_function<JC>(
ctx: jsc::JSContextRef,
function: jsc::JSObjectRef,
this_object: jsc::JSObjectRef,
argument_count: usize,
arguments: *const jsc::JSValueRef,
exception: *mut jsc::JSValueRef,
) -> jsc::JSValueRef
where
JC: JSClass<JSCValue>,
{
let ctx = JSCContext::from_borrowed_raw(ctx as _);
let function = JSCValue::from_borrowed_obj(ctx.to_raw(), function);
let this = JSCValue::from_borrowed_obj(ctx.to_raw(), this_object);
let args: Vec<JSCValue> = if !arguments.is_null() {
(0..argument_count)
.map(|i| unsafe { JSCValue::from_borrowed_raw(ctx.to_raw(), *arguments.add(i)) })
.collect()
} else {
vec![]
};
let value = <JC as JSClassExt<JSCValue>>::call(&ctx, function, this, args);
if value.is_exception() {
if !exception.is_null() {
unsafe {
*exception = value.into_raw_value();
}
}
unsafe {
return jsc::JSValueMakeUndefined(ctx.to_raw());
}
}
value.into_raw_value()
}
unsafe extern "C" fn has_instance(
ctx: jsc::JSContextRef,
_constructor: jsc::JSObjectRef,
possible_instance: jsc::JSValueRef,
_exception: *mut jsc::JSValueRef,
) -> bool {
if unsafe { jsc::JSValueIsObject(ctx, possible_instance) } {
let instance =
unsafe { jsc::JSValueToObject(ctx, possible_instance, std::ptr::null_mut()) };
let private = unsafe { jsc::JSObjectGetPrivate(instance) };
if !private.is_null() {
return true;
}
}
false
}
unsafe fn set_function_name(
ctx: *mut jsc::OpaqueJSContext,
function: jsc::JSObjectRef,
class_name_cstr: &CString,
) {
unsafe {
let name_key = jsc::JSStringCreateWithUTF8CString(c"name".as_ptr());
let class_name = jsc::JSStringCreateWithUTF8CString(class_name_cstr.as_ptr());
let name_value = jsc::JSValueMakeString(ctx, class_name);
jsc::JSObjectSetProperty(
ctx,
function,
name_key,
name_value,
jsc::kJSPropertyAttributeReadOnly | jsc::kJSPropertyAttributeDontEnum,
ptr::null_mut(),
);
jsc::JSStringRelease(name_key);
jsc::JSStringRelease(class_name);
}
}
unsafe fn set_prototype_constructor(
ctx: *mut jsc::OpaqueJSContext,
function: jsc::JSObjectRef,
constructor: jsc::JSObjectRef,
) {
unsafe {
let prototype_key = jsc::JSStringCreateWithUTF8CString(c"prototype".as_ptr());
let prototype = jsc::JSObjectGetProperty(ctx, function, prototype_key, ptr::null_mut());
let prototype = jsc::JSValueToObject(ctx, prototype, ptr::null_mut());
let constructor_key = jsc::JSStringCreateWithUTF8CString(c"constructor".as_ptr());
jsc::JSObjectSetProperty(
ctx,
prototype,
constructor_key,
constructor,
jsc::kJSPropertyAttributeDontEnum,
ptr::null_mut(),
);
jsc::JSStringRelease(constructor_key);
jsc::JSStringRelease(prototype_key);
}
}
fn build_public_constructor<JC>(
ctx: &JSCContext,
native_ctor: jsc::JSObjectRef,
class_name_cstr: &CString,
) -> JSCValue
where
JC: JSClass<JSCValue>,
{
let call_without_new = unsafe {
jsc::JSObjectMakeFunctionWithCallback(
ctx.to_raw(),
ptr::null_mut(),
Some(call_without_new::<JC>),
)
};
let factory = get_public_constructor_factory(ctx);
if factory.is_exception() {
return JSCValue::from_owned_obj(ctx.to_raw(), native_ctor);
}
let public_ctor = ctx.call(
&factory,
JSCValue::create_undefined(ctx),
&[
JSCValue::from_borrowed_obj(ctx.to_raw(), native_ctor),
JSCValue::from_owned_obj(ctx.to_raw(), call_without_new),
],
);
if public_ctor.is_exception() || !public_ctor.is_object() {
return JSCValue::from_owned_obj(ctx.to_raw(), native_ctor);
}
let public_ctor_obj = public_ctor.as_obj();
unsafe {
set_function_name(ctx.to_raw(), public_ctor_obj, class_name_cstr);
set_prototype_constructor(ctx.to_raw(), public_ctor_obj, public_ctor_obj);
}
JSCValue::from_owned_obj(ctx.to_raw(), public_ctor_obj)
}
fn get_public_constructor_factory(ctx: &JSCContext) -> JSCValue {
let host_ctx = JSContext::<JSCContext>::from_borrowed_raw_ptr(ctx.as_raw());
if let Some(factory) = host_ctx.get_state::<PublicConstructorFactory>() {
return factory.0.clone();
}
let factory = ctx.eval(Source::from_bytes(PUBLIC_CONSTRUCTOR_FACTORY));
if !factory.is_exception() {
host_ctx.set_state(PublicConstructorFactory(factory.clone()));
}
factory
}
pub(crate) fn register_class_internal<JC>(ctx: &JSCContext, class_name: &str) -> JSCValue
where
JC: JSClass<JSCValue>,
{
let class_name_cstr = CString::new(class_name).unwrap();
let ctx_raw = ctx.to_raw();
unsafe {
let class_def = jsc::JSClassDefinition {
version: 0,
attributes: jsc::kJSClassAttributeNone,
className: class_name_cstr.as_ptr(),
parentClass: ptr::null_mut(),
staticValues: ptr::null(),
staticFunctions: ptr::null(),
initialize: None,
finalize: Some(finalizer::<JC>),
hasProperty: None,
getProperty: None,
setProperty: None,
deleteProperty: None,
getPropertyNames: None,
callAsFunction: Some(call_as_function::<JC>),
callAsConstructor: None,
hasInstance: Some(has_instance),
convertToType: None,
};
let js_export = jsc::JSClassCreate(&class_def);
let native_ctor =
jsc::JSObjectMakeConstructor(ctx_raw, js_export, Some(generic_constructor::<JC>));
let constructor = build_public_constructor::<JC>(ctx, native_ctor, &class_name_cstr);
CLASS
.write()
.unwrap()
.insert(constructor.as_value() as usize, js_export as usize);
constructor
}
}
pub(crate) fn get_constructor(
ctx: *mut jsc::OpaqueJSContext,
name: *const c_char,
) -> jsc::JSObjectRef {
unsafe {
let global = jsc::JSContextGetGlobalObject(ctx);
let js_name = jsc::JSStringCreateWithUTF8CString(name);
let value = jsc::JSObjectGetProperty(ctx, global, js_name, ptr::null_mut());
jsc::JSStringRelease(js_name);
jsc::JSValueToObject(ctx, value, ptr::null_mut())
}
}