diff --git a/src/web_executor/js.rs b/src/web_executor/js.rs index 1d4bc469a..c5dd798de 100644 --- a/src/web_executor/js.rs +++ b/src/web_executor/js.rs @@ -6,6 +6,7 @@ use crate::web_executor::js::v8::V8Engine; pub use compile::*; pub use context::*; pub use function::*; +pub use object::*; pub use runtime::*; pub use value::*; pub use value_conversion::*; @@ -15,6 +16,7 @@ use crate::types::Result; mod compile; mod context; mod function; +mod object; mod runtime; pub mod v8; mod value; @@ -45,22 +47,6 @@ lazy_static! { pub static ref RUNTIME: Mutex> = Mutex::new(V8Engine::new()); } -pub trait JSObject { - type Value: JSValue; - type Function: JSFunction; - type FunctionVariadic: JSFunctionVariadic; - - fn set_property(&self, name: &str, value: &Self::Value) -> Result<()>; - - fn get_property(&self, name: &str) -> Result; - - fn call_method(&self, name: &str, args: &[&Self::Value]) -> Result; - - fn set_method(&self, name: &str, func: &Self::Function) -> Result<()>; - - fn set_method_variadic(&self, name: &str, func: &Self::FunctionVariadic) -> Result<()>; -} - pub trait JSArray { type Value: JSValue; diff --git a/src/web_executor/js/function.rs b/src/web_executor/js/function.rs index 4976df9b9..c8ee8e8e6 100644 --- a/src/web_executor/js/function.rs +++ b/src/web_executor/js/function.rs @@ -1,5 +1,6 @@ use crate::types::Result; use crate::web_executor::js::{JSContext, JSError, JSObject, JSRuntime, JSValue}; +use core::fmt::Display; struct Function(pub T); @@ -31,6 +32,8 @@ pub trait JSFunctionCallBack { self.len() == 0 } + fn error(&mut self, error: impl Display); + fn ret(&mut self, value: Self::Value); } @@ -77,6 +80,8 @@ pub trait JSFunctionCallBackVariadic { self.len() == 0 } + fn error(&mut self, error: impl Display); + fn ret(&mut self, value: Self::Value); } diff --git a/src/web_executor/js/object.rs b/src/web_executor/js/object.rs new file mode 100644 index 000000000..e99f2cb42 --- /dev/null +++ b/src/web_executor/js/object.rs @@ -0,0 +1,54 @@ +use crate::web_executor::js::{JSContext, JSFunction, JSFunctionVariadic, JSValue}; +use core::fmt::Display; + +pub trait JSObject { + type Value: JSValue; + type Function: JSFunction; + type FunctionVariadic: JSFunctionVariadic; + type GetterCB: JSGetterCallback; + type SetterCB: JSSetterCallback; + + fn set_property(&self, name: &str, value: &Self::Value) -> crate::types::Result<()>; + + fn get_property(&self, name: &str) -> crate::types::Result; + + fn call_method(&self, name: &str, args: &[&Self::Value]) -> crate::types::Result; + + fn set_method(&self, name: &str, func: &Self::Function) -> crate::types::Result<()>; + + fn set_method_variadic( + &self, + name: &str, + func: &Self::FunctionVariadic, + ) -> crate::types::Result<()>; + + #[allow(clippy::type_complexity)] + fn set_property_accessor( + &self, + name: &str, + getter: Box, + setter: Box, + ) -> crate::types::Result<()>; +} + +pub trait JSGetterCallback { + type Value: JSValue; + type Context: JSContext; + + fn context(&mut self) -> &mut Self::Context; + + fn error(&mut self, error: impl Display); + + fn ret(&mut self, value: Self::Value); +} + +pub trait JSSetterCallback { + type Value: JSValue; + type Context: JSContext; + + fn context(&mut self) -> &mut Self::Context; + + fn error(&mut self, error: impl Display); + + fn value(&mut self) -> &Self::Value; +} diff --git a/src/web_executor/js/v8.rs b/src/web_executor/js/v8.rs index 54cc68d69..91ae7dc25 100644 --- a/src/web_executor/js/v8.rs +++ b/src/web_executor/js/v8.rs @@ -21,7 +21,6 @@ mod compile; mod context; mod function; mod object; -mod utils; mod value; // status of the V8 engine @@ -43,15 +42,26 @@ impl Default for V8Engine<'_> { } } +const MAX_V8_INIT_SECONDS: u64 = 10; + impl V8Engine<'_> { pub fn initialize() { if PLATFORM_INITIALIZED.load(Ordering::SeqCst) { return; } + let mut wait_time = MAX_V8_INIT_SECONDS * 1000; + if PLATFORM_INITIALIZING.load(Ordering::SeqCst) { while !PLATFORM_INITIALIZED.load(Ordering::SeqCst) { std::thread::sleep(std::time::Duration::from_millis(10)); + wait_time -= 10; + if wait_time <= 9 { + panic!( + "V8 initialization timed out after {} seconds", + MAX_V8_INIT_SECONDS + ); + } } return; } @@ -108,24 +118,6 @@ mod tests { assert!(PLATFORM_INITIALIZED.load(Ordering::SeqCst)); } - // #[test] - // fn v8_bindings_test() { - // let platform = v8::new_default_platform(0, false).make_shared(); - // v8::V8::initialize_platform(platform); - // v8::V8::initialize(); - // - // let isolate = &mut v8::Isolate::new(Default::default()); - // let hs = &mut v8::HandleScope::new(isolate); - // let c = v8::Context::new(hs); - // let s = &mut v8::ContextScope::new(hs, c); - // - // let code = v8::String::new(s, "console.log(\"Hello World!\"); 1234").unwrap(); - // - // let value = v8::Script::compile(s, code, None).unwrap().run(s).unwrap(); - // - // println!("{}", value.to_rust_string_lossy(s)); - // } - #[test] fn v8_js_execution() { let mut engine = crate::web_executor::js::v8::V8Engine::new(); diff --git a/src/web_executor/js/v8/context.rs b/src/web_executor/js/v8/context.rs index b7f37ff0f..6a733d66d 100644 --- a/src/web_executor/js/v8/context.rs +++ b/src/web_executor/js/v8/context.rs @@ -16,10 +16,10 @@ use crate::web_executor::js::{JSContext, JSError, JSRuntime}; /// SAFETY: This is NOT thread safe, as the rest of the engine is not thread safe. /// This struct uses `NonNull` internally to store pointers to the V8Context "values" in one struct. pub struct V8Ctx<'a> { - isolate: NonNull, - handle_scope: NonNull>, - ctx: NonNull>, - context_scope: NonNull>>, + pub(crate) isolate: NonNull, + pub(crate) handle_scope: NonNull>, + pub(crate) ctx: NonNull>, + pub(crate) context_scope: NonNull>>, copied: Copied, } @@ -41,8 +41,9 @@ impl Copied { } } -enum HandleScopeType<'a> { +pub(crate) enum HandleScopeType<'a> { WithContext(HandleScope<'a>), + WithContextRef(&'a mut HandleScope<'a>), WithoutContext(HandleScope<'a, ()>), CallbackScope(CallbackScope<'a>), } @@ -52,11 +53,12 @@ impl<'a> HandleScopeType<'a> { Self::WithoutContext(HandleScope::new(isolate)) } - fn get(&mut self) -> &mut HandleScope<'a, ()> { + pub(crate) fn get(&mut self) -> &mut HandleScope<'a, ()> { match self { Self::WithContext(scope) => scope, Self::WithoutContext(scope) => scope, Self::CallbackScope(scope) => scope, + Self::WithContextRef(scope) => scope, } } } @@ -149,9 +151,13 @@ impl<'a> V8Ctx<'a> { } } -pub(crate) fn ctx_from_function_callback_info(mut scope: CallbackScope) -> Result { +pub(crate) fn ctx_from_scope_isolate<'a>( + scope: HandleScopeType<'a>, + ctx: Local<'a, v8::Context>, + isolate: NonNull, +) -> std::result::Result, (HandleScopeType<'a>, Error)> { let mut v8_ctx = V8Ctx { - isolate: NonNull::dangling(), + isolate, handle_scope: NonNull::dangling(), ctx: NonNull::dangling(), context_scope: NonNull::dangling(), @@ -160,25 +166,30 @@ pub(crate) fn ctx_from_function_callback_info(mut scope: CallbackScope) -> Resul handle_scope: false, ctx: false, context_scope: false, - }, //TODO: figure out what to deallocate + }, }; - let ctx = Box::new(scope.get_current_context()); + let ctx = Box::new(ctx); let Some(ctx) = NonNull::new(Box::into_raw(ctx)) else { - return Err(Error::JS(JSError::Compile( - "Failed to create context".to_owned(), - ))); + return Err(( + scope, + Error::JS(JSError::Compile("Failed to create context".to_owned())), + )); }; v8_ctx.ctx = ctx; - let scope = Box::new(HandleScopeType::CallbackScope(scope)); + let scope = Box::new(scope); - let Some(scope) = NonNull::new(Box::into_raw(scope)) else { - return Err(Error::JS(JSError::Compile( - "Failed to create handle scope".to_owned(), - ))); + let raw_scope = Box::into_raw(scope); + let Some(scope) = NonNull::new(raw_scope) else { + //SAFETY: we just created this, so it is safe to convert it back to a Box + let scope = unsafe { Box::from_raw(raw_scope) }; + return Err(( + *scope, + Error::JS(JSError::Compile("Failed to create handle scope".to_owned())), + )); }; v8_ctx.handle_scope = scope; @@ -189,9 +200,15 @@ pub(crate) fn ctx_from_function_callback_info(mut scope: CallbackScope) -> Resul )); let Some(ctx_scope) = NonNull::new(Box::into_raw(ctx_scope)) else { - return Err(Error::JS(JSError::Compile( - "Failed to create context scope".to_owned(), - ))); + //SAFETY: we just created this, so it is safe to convert it back to a Box + let scope = unsafe { Box::from_raw(v8_ctx.handle_scope.as_ptr()) }; + + return Err(( + *scope, + Error::JS(JSError::Compile( + "Failed to create context scope".to_owned(), + )), + )); }; v8_ctx.context_scope = ctx_scope; @@ -200,6 +217,30 @@ pub(crate) fn ctx_from_function_callback_info(mut scope: CallbackScope) -> Resul // Ok(v8_ctx) } +pub(crate) fn ctx_from_function_callback_info( + mut scope: CallbackScope, + isolate: NonNull, +) -> std::result::Result { + let ctx = scope.get_current_context(); + let scope = HandleScopeType::CallbackScope(scope); + + ctx_from_scope_isolate(scope, ctx, isolate) +} + +pub(crate) fn ctx_from<'a>( + scope: &'a mut HandleScope, + isolate: NonNull, +) -> std::result::Result, (HandleScopeType<'a>, Error)> { + let ctx = scope.get_current_context(); + + //SAFETY: This can only shorten the lifetime of the scope, which is fine. (we borrow it for 'a and it is '2, which will always be longer than 'a) + let scope = unsafe { std::mem::transmute(scope) }; + + let scope = HandleScopeType::WithContextRef(scope); + + ctx_from_scope_isolate(scope, ctx, isolate) +} + impl Drop for V8Ctx<'_> { fn drop(&mut self) { // order is important here: context scope, then handle scope (and ctx), then isolate diff --git a/src/web_executor/js/v8/function.rs b/src/web_executor/js/v8/function.rs index 518aca631..fbbc86a1e 100644 --- a/src/web_executor/js/v8/function.rs +++ b/src/web_executor/js/v8/function.rs @@ -1,4 +1,5 @@ use alloc::rc::Rc; +use core::fmt::Display; use v8::{ CallbackScope, External, Function, FunctionBuilder, FunctionCallbackArguments, @@ -105,6 +106,16 @@ impl<'a> JSFunctionCallBack for V8FunctionCallBack<'a> { fn ret(&mut self, value: Self::Value) { self.ret = Ok(value.value); } + + fn error(&mut self, error: impl Display) { + let scope = self.ctx.borrow_mut().scope(); + let err = error.to_string(); + let Some(e) = v8::String::new(scope, &err) else { + eprintln!("failed to create exception string\nexception was: {}", err); + return; + }; + scope.throw_exception(Local::from(e)); + } } impl<'a> V8Function<'a> { @@ -142,7 +153,7 @@ impl<'a> V8Function<'a> { { exception.into() } else { - eprintln!("failed to create exception string\nexception was: {e}"); //TODO: replace with our own logger + eprintln!("failed to create exception string\nexception was: {e}"); v8::undefined(ctx.borrow_mut().scope()).into() }; @@ -161,7 +172,7 @@ extern "C" fn callback(info: *const FunctionCallbackInfo) { Ok(external) => external, Err(e) => { let Some(e) = v8::String::new(scope, &e.to_string()) else { - eprintln!("failed to create exception string\nexception was: {e}"); //TODO: replace with our own logger + eprintln!("failed to create exception string\nexception was: {e}"); return; }; scope.throw_exception(Local::from(e)); @@ -171,11 +182,15 @@ extern "C" fn callback(info: *const FunctionCallbackInfo) { let data = unsafe { &mut *(external.value() as *mut CallbackWrapper) }; - let ctx = match ctx_from_function_callback_info(unsafe { CallbackScope::new(info) }) { + let ctx = match ctx_from_function_callback_info( + unsafe { CallbackScope::new(info) }, + data.ctx.borrow().isolate, + ) { Ok(scope) => scope, - Err(e) => { + Err((mut st, e)) => { + let scope = st.get(); let Some(e) = v8::String::new(scope, &e.to_string()) else { - eprintln!("failed to create exception string\nexception was: {e}"); //TODO: replace with our own logger + eprintln!("failed to create exception string\nexception was: {e}"); return; }; scope.throw_exception(Local::from(e)); @@ -248,8 +263,6 @@ impl<'a> JSFunction for V8Function<'a> { } } -//TODO: maybe move both implementations into a macro, so we have less code duplication - pub struct V8FunctionVariadic<'a> { pub(super) ctx: V8Context<'a>, pub(super) function: Local<'a, Function>, @@ -369,6 +382,16 @@ impl<'a> JSFunctionCallBackVariadic for V8FunctionCallBackVariadic<'a> { self.args.len() } + fn error(&mut self, error: impl Display) { + let scope = self.ctx.borrow_mut().scope(); + let err = error.to_string(); + let Some(e) = v8::String::new(scope, &err) else { + eprintln!("failed to create exception string\nexception was: {}", err); + return; + }; + scope.throw_exception(Local::from(e)); + } + fn ret(&mut self, value: Self::Value) { self.ret = Ok(value.value); } @@ -409,7 +432,7 @@ impl<'a> V8FunctionVariadic<'a> { { exception.into() } else { - eprintln!("failed to create exception string\nexception was: {e}"); //TODO: replace with our own logger + eprintln!("failed to create exception string\nexception was: {e}"); v8::undefined(ctx.borrow_mut().scope()).into() }; @@ -421,14 +444,14 @@ impl<'a> V8FunctionVariadic<'a> { extern "C" fn callback_variadic(info: *const FunctionCallbackInfo) { let info = unsafe { &*info }; - let scope = &mut unsafe { CallbackScope::new(info) }; + let mut scope = unsafe { CallbackScope::new(info) }; let args = FunctionCallbackArguments::from_function_callback_info(info); let rv = ReturnValue::from_function_callback_info(info); let external = match >::try_from(args.data()) { Ok(external) => external, Err(e) => { - let Some(e) = v8::String::new(scope, &e.to_string()) else { - eprintln!("failed to create exception string\nexception was: {e}"); //TODO: replace with our own logger + let Some(e) = v8::String::new(&mut scope, &e.to_string()) else { + eprintln!("failed to create exception string\nexception was: {e}"); return; }; scope.throw_exception(Local::from(e)); @@ -438,11 +461,12 @@ extern "C" fn callback_variadic(info: *const FunctionCallbackInfo) { let data = unsafe { &mut *(external.value() as *mut CallbackWrapperVariadic) }; - let ctx = match ctx_from_function_callback_info(unsafe { CallbackScope::new(info) }) { + let ctx = match ctx_from_function_callback_info(scope, data.ctx.borrow().isolate) { Ok(scope) => scope, - Err(e) => { + Err((mut st, e)) => { + let scope = st.get(); let Some(e) = v8::String::new(scope, &e.to_string()) else { - eprintln!("failed to create exception string\nexception was: {e}"); //TODO: replace with our own logger + eprintln!("failed to create exception string\nexception was: {e}"); return; }; scope.throw_exception(Local::from(e)); diff --git a/src/web_executor/js/v8/object.rs b/src/web_executor/js/v8/object.rs index decdda7c9..6d7e03a16 100644 --- a/src/web_executor/js/v8/object.rs +++ b/src/web_executor/js/v8/object.rs @@ -1,20 +1,93 @@ -use v8::{Local, Object}; +use core::fmt::Display; +use std::ffi::c_void; + +use v8::{ + AccessorConfiguration, External, HandleScope, Local, Name, Object, PropertyCallbackArguments, + ReturnValue, Value, +}; use crate::types::{Error, Result}; use crate::web_executor::js::v8::{ - FromContext, V8Context, V8Ctx, V8Function, V8FunctionVariadic, V8Value, + ctx_from, FromContext, V8Context, V8Ctx, V8Function, V8FunctionVariadic, V8Value, +}; +use crate::web_executor::js::{ + JSArray, JSError, JSGetterCallback, JSObject, JSSetterCallback, JSValue, }; -use crate::web_executor::js::{JSArray, JSError, JSObject, JSValue}; pub struct V8Object<'a> { ctx: V8Context<'a>, pub(crate) value: Local<'a, Object>, } +pub struct GetterCallback<'a, 'r> { + ctx: V8Context<'a>, + ret: &'r mut V8Value<'a>, +} + +impl<'a> JSGetterCallback for GetterCallback<'a, '_> { + type Value = V8Value<'a>; + type Context = V8Context<'a>; + + fn context(&mut self) -> &mut Self::Context { + &mut self.ctx + } + + fn error(&mut self, error: impl Display) { + let scope = self.ctx.borrow_mut().scope(); + let err = error.to_string(); + let Some(e) = v8::String::new(scope, &err) else { + eprintln!("failed to create exception string\nexception was: {}", err); + return; + }; + scope.throw_exception(Local::from(e)); + } + + fn ret(&mut self, value: Self::Value) { + *self.ret = value; + } +} + +pub struct SetterCallback<'a, 'v> { + ctx: V8Context<'a>, + value: &'v V8Value<'a>, +} + +impl<'a, 'v> JSSetterCallback for SetterCallback<'a, 'v> { + type Value = V8Value<'a>; + type Context = V8Context<'a>; + + fn context(&mut self) -> &mut Self::Context { + &mut self.ctx + } + + fn error(&mut self, error: impl Display) { + let scope = self.ctx.borrow_mut().scope(); + let err = error.to_string(); + let Some(e) = v8::String::new(scope, &err) else { + eprintln!("failed to create exception string\nexception was: {}", err); + return; + }; + scope.throw_exception(Local::from(e)); + } + + fn value(&mut self) -> &'v Self::Value { + self.value + } +} + +struct GetterSetter<'a, 'r> { + ctx: V8Context<'a>, + getter: Box)>, + setter: Box)>, +} + impl<'a> JSObject for V8Object<'a> { type Value = V8Value<'a>; type Function = V8Function<'a>; type FunctionVariadic = V8FunctionVariadic<'a>; + + type GetterCB = GetterCallback<'a, 'a>; + type SetterCB = SetterCallback<'a, 'a>; fn set_property(&self, name: &str, value: &Self::Value) -> Result<()> { let Some(name) = v8::String::new(self.ctx.borrow_mut().scope(), name) else { return Err(Error::JS(JSError::Generic( @@ -63,7 +136,7 @@ impl<'a> JSObject for V8Object<'a> { let function = Local::::try_from(func).unwrap(); - let args: Vec> = args.iter().map(|v| v.value).collect(); + let args: Vec> = args.iter().map(|v| v.value).collect(); let try_catch = &mut v8::TryCatch::new(self.ctx.borrow_mut().scope()); @@ -136,6 +209,132 @@ impl<'a> JSObject for V8Object<'a> { Ok(()) } } + + fn set_property_accessor( + &self, + name: &str, + getter: Box, + setter: Box, + ) -> Result<()> { + let name = v8::String::new(self.ctx.borrow_mut().scope(), name) + .ok_or_else(|| Error::JS(JSError::Generic("failed to create a string".to_owned())))?; + + let scope = self.ctx.borrow_mut().scope(); + + let gs = Box::new(GetterSetter { + ctx: self.ctx.clone(), + getter, + setter, + }); + + let data = External::new(scope, Box::into_raw(gs) as *mut c_void); + + let config = AccessorConfiguration::new( + |scope: &mut HandleScope, + name: Local, + args: PropertyCallbackArguments, + mut rv: ReturnValue| { + let external = match Local::::try_from(args.data()) { + Ok(external) => external, + Err(e) => { + let Some(e) = v8::String::new(scope, &e.to_string()) else { + eprintln!("failed to create exception string\nexception was: {e}"); + return; + }; + scope.throw_exception(Local::from(e)); + return; + } + }; + + let gs = unsafe { &*(external.value() as *const GetterSetter) }; + + let ctx = scope.get_current_context(); + + let ctx = match ctx_from(scope, gs.ctx.borrow().isolate) { + Ok(ctx) => ctx, + Err((mut st, e)) => { + let scope = st.get(); + let Some(e) = v8::String::new(scope, &e.to_string()) else { + eprintln!("failed to create exception string\nexception was: {e}"); + return; + }; + scope.throw_exception(Local::from(e)); + return; + } + }; + + let mut ret = match V8Value::new_undefined(ctx.clone()) { + Ok(ret) => ret, + Err(e) => { + let scope = ctx.borrow_mut().scope(); + let Some(e) = v8::String::new(scope, &e.to_string()) else { + eprintln!("failed to create exception string\nexception was: {e}"); + return; + }; + scope.throw_exception(Local::from(e)); + return; + } + }; + + let mut gc = GetterCallback { ctx, ret: &mut ret }; + + (gs.getter)(&mut gc); + + rv.set(ret.value); + }, + ) + .setter( + |scope: &mut HandleScope, + name: Local, + value: Local, + args: PropertyCallbackArguments, + rv: ReturnValue| { + let external = match Local::::try_from(args.data()) { + Ok(external) => external, + Err(e) => { + let Some(e) = v8::String::new(scope, &e.to_string()) else { + eprintln!("failed to create exception string\nexception was: {e}"); + return; + }; + scope.throw_exception(Local::from(e)); + return; + } + }; + + let gs = unsafe { &*(external.value() as *const GetterSetter) }; + + let mut ctx = scope.get_current_context(); + + let ctx = match ctx_from(scope, gs.ctx.borrow().isolate) { + Ok(ctx) => ctx, + Err((mut st, e)) => { + let scope = st.get(); + let Some(e) = v8::String::new(scope, &e.to_string()) else { + eprintln!("failed to create exception string\nexception was: {e}"); + return; + }; + scope.throw_exception(Local::from(e)); + return; + } + }; + + let mut val = V8Value::from_value(ctx.clone(), value); + + let mut sc = SetterCallback { + ctx, + value: &mut val, + }; + + (gs.setter)(&mut sc); + }, + ) + .data(Local::from(data)); + + self.value + .set_accessor_with_configuration(scope, name.into(), config); + + Ok(()) + } } impl<'a> FromContext<'a, Local<'a, Object>> for V8Object<'a> { diff --git a/src/web_executor/js/v8/utils.rs b/src/web_executor/js/v8/utils.rs deleted file mode 100644 index 926eddc02..000000000 --- a/src/web_executor/js/v8/utils.rs +++ /dev/null @@ -1,191 +0,0 @@ -use crate::web_executor::js::v8::{V8Context, V8Function, V8FunctionCallBack}; -use std::marker::PhantomData; -use std::mem::size_of; -use v8::{ - CallbackScope, FunctionCallback, FunctionCallbackArguments, FunctionCallbackInfo, ReturnValue, -}; - -// --- https://github.com/denoland/rusty_v8/blob/ff2a50ccdf7d5f7091e2bfbdedf0927101e2844c/src/support.rs#L562 --- -pub trait UnitType -where - Self: Copy + Sized, -{ - #[inline(always)] - fn get() -> Self { - UnitValue::::get() - } -} - -impl UnitType for T where T: Copy + Sized {} - -#[derive(Copy, Clone, Debug)] -struct UnitValue(PhantomData) -where - Self: Sized; - -impl UnitValue -where - Self: Copy + Sized, -{ - const SELF: Self = Self::new_checked(); - - const fn new_checked() -> Self { - // Statically assert that T is indeed a unit type. - let size_must_be_0 = size_of::(); - let s = Self(PhantomData::); - [s][size_must_be_0] - } - - #[inline(always)] - fn get_checked(self) -> T { - // This run-time check serves just as a backup for the compile-time - // check when Self::SELF is initialized. - assert_eq!(size_of::(), 0); - unsafe { std::mem::MaybeUninit::::zeroed().assume_init() } - } - - #[inline(always)] - pub fn get() -> T { - // Accessing the Self::SELF is necessary to make the compile-time type check - // work. - Self::SELF.get_checked() - } -} - -#[derive(Debug)] -pub struct DefaultTag; - -#[derive(Debug)] -pub struct IdenticalConversionTag; - -pub trait MapFnFrom<'a, F, Tag = DefaultTag> -where - F: UnitType, - Self: Sized, -{ - fn mapping(ctx: &V8Context<'a>) -> Self; - - #[inline(always)] - fn map_fn_from(ctx: &V8Context<'a>, _: F) -> Self { - Self::mapping(ctx) - } -} - -impl<'a, F> MapFnFrom<'a, F, IdenticalConversionTag> for F -where - Self: UnitType, -{ - #[inline(always)] - fn mapping(ctx: &V8Context<'a>) -> Self { - Self::get() - } -} - -pub trait MapFnTo<'a, T, Tag = DefaultTag> -where - Self: UnitType, - T: Sized, -{ - fn mapping(ctx: &V8Context<'a>) -> T; - - #[inline(always)] - fn map_fn_to(self, ctx: &V8Context<'a>) -> T { - Self::mapping(ctx) - } -} - -impl<'a, F, T, Tag> MapFnTo<'a, T, Tag> for F -where - Self: UnitType, - T: MapFnFrom<'a, F, Tag>, -{ - #[inline(always)] - fn mapping(ctx: &V8Context<'a>) -> T { - T::map_fn_from(ctx, F::get()) - } -} - -pub trait CFnFrom -where - Self: Sized, - F: UnitType, -{ - fn mapping() -> Self; - - #[inline(always)] - fn c_fn_from(_: F) -> Self { - Self::mapping() - } -} - -macro_rules! impl_c_fn_from { - ($($arg:ident: $ty:ident),*) => { - impl CFnFrom for extern "C" fn($($ty),*) -> R - where - F: UnitType + Fn($($ty),*) -> R, - { - #[inline(always)] - fn mapping() -> Self { - extern "C" fn c_fn($($arg: $ty),*) -> R - where - F: UnitType + Fn($($ty),*) -> R, - { - (F::get())($($arg),*) - } - c_fn:: - } - } - }; -} - -impl_c_fn_from!(); -impl_c_fn_from!(a0: A0); -impl_c_fn_from!(a0: A0, a1: A1); -impl_c_fn_from!(a0: A0, a1: A1, a2: A2); -impl_c_fn_from!(a0: A0, a1: A1, a2: A2, a3: A3); -impl_c_fn_from!(a0: A0, a1: A1, a2: A2, a3: A3, a4: A4); -impl_c_fn_from!(a0: A0, a1: A1, a2: A2, a3: A3, a4: A4, a5: A5); -impl_c_fn_from!(a0: A0, a1: A1, a2: A2, a3: A3, a4: A4, a5: A5, a6: A6); - -pub trait ToCFn -where - Self: UnitType, - T: Sized, -{ - fn mapping() -> T; - - #[inline(always)] - fn to_c_fn(self) -> T { - Self::mapping() - } -} - -impl ToCFn for F -where - Self: UnitType, - T: CFnFrom, -{ - #[inline(always)] - fn mapping() -> T { - T::c_fn_from(F::get()) - } -} - -// --- copy end --- - -impl<'a, F> MapFnFrom<'a, F> for FunctionCallback -where - F: UnitType + Fn(&mut V8FunctionCallBack<'a>), -{ - fn mapping(ctx: &V8Context<'a>) -> Self { - let f = |info: *const FunctionCallbackInfo| { - let info = unsafe { &*info }; - let scope = &mut unsafe { CallbackScope::new(info) }; - let args = FunctionCallbackArguments::from_function_callback_info(info); - let rv = ReturnValue::from_function_callback_info(info); - - // V8Function::callback(ctx, scope, args, rv, F::get()); - }; - f.to_c_fn() - } -} diff --git a/src/web_executor/test.rs b/src/web_executor/test.rs index 0e38eb4d6..3e1f73992 100644 --- a/src/web_executor/test.rs +++ b/src/web_executor/test.rs @@ -6,12 +6,12 @@ use std::ops::Add; //use webinterop::{web_fns, web_interop}; use crate::types::Result; use crate::web_executor::js::v8::{ - V8Context, V8Function, V8FunctionVariadic, V8Value, V8VariadicArgsInternal, + GetterCallback, SetterCallback, V8Context, V8Function, V8FunctionVariadic, V8Value, }; use crate::web_executor::js::{ Args, JSContext, JSFunction, JSFunctionCallBack, JSFunctionCallBackVariadic, - JSFunctionVariadic, JSObject, JSRuntime, JSValue, ValueConversion, VariadicArgs, - VariadicArgsInternal, + JSFunctionVariadic, JSGetterCallback, JSObject, JSRuntime, JSSetterCallback, JSValue, + ValueConversion, VariadicArgs, VariadicArgsInternal, }; //#[web_interop] @@ -90,6 +90,7 @@ fn ref_size_slice(slice: &[i32; 3]) {} fn mut_size_slice(slice: &mut [i32; 3]) {} +#[derive(Debug)] struct Test2 { field: i32, other_field: String, @@ -124,21 +125,105 @@ impl Test2 { fn implement(s: Rc>, mut ctx: V8Context) -> Result<()> { let obj = ctx.new_global_object("test2")?; //#name + { + //field getter and setter + let getter = { + let s = Rc::clone(&s); + Box::new(move |cb: &mut GetterCallback| { + let ctx = cb.context(); + let value = s.borrow().field; + println!("got a call to getter: {}", value); + let value = match value.to_js_value(ctx.clone()) { + Ok(value) => value, + Err(e) => { + cb.error(e); + return; + } + }; + cb.ret(value); + }) + }; + + let setter = { + let s = Rc::clone(&s); + Box::new(move |cb: &mut SetterCallback| { + let ctx = cb.context(); + let value = cb.value(); + let value = match value.as_number() { + Ok(value) => value, + Err(e) => { + cb.error(e); + return; + } + }; + + println!("got a call to setter: {}", value); + + s.borrow_mut().field = value as i32; + }) + }; + + obj.set_property_accessor("field", getter, setter)?; + } + + { + //other_field getter and setter + let getter = { + let s = Rc::clone(&s); + Box::new(move |cb: &mut GetterCallback| { + let ctx = cb.context(); + let value = s.borrow().other_field.clone(); + println!("got a call to getter: {}", value); + let value = match value.to_js_value(ctx.clone()) { + Ok(value) => value, + Err(e) => { + cb.error(e); + return; + } + }; + cb.ret(value); + }) + }; + + let setter = { + let s = Rc::clone(&s); + Box::new(move |cb: &mut SetterCallback| { + let ctx = cb.context(); + let value = cb.value(); + let value = match value.as_string() { + Ok(value) => value, + Err(e) => { + cb.error(e); + return; + } + }; + + println!("got a call to setter: {}", value); + + s.borrow_mut().other_field = value; + }) + }; + + obj.set_property_accessor("other_field", getter, setter)?; + } + let cool_fn = { let s = Rc::clone(&s); V8Function::new(ctx.clone(), move |cb| { - //TODO: add R::Function::new let num_args = 0; //function.arguments.len(); if num_args != cb.len() { - // cb.error("wrong number of arguments"); //TODO + cb.error("wrong number of arguments"); return; } let ctx = cb.context(); - let Ok(ret) = s.borrow().cool_fn().to_js_value(ctx.clone()) else { - // cb.error(e); //TODO - return; + let ret = match s.borrow().cool_fn().to_js_value(ctx.clone()) { + Ok(ret) => ret, + Err(e) => { + cb.error(e); + return; + } }; cb.ret(ret); @@ -152,7 +237,7 @@ impl Test2 { V8Function::new(ctx.clone(), move |cb| { let num_args = 1; //function.arguments.len(); if num_args != cb.len() { - // cb.error("wrong number of arguments"); //TODO + cb.error("wrong number of arguments"); return; } @@ -161,12 +246,12 @@ impl Test2 { let args = cb.args(); let Some(arg0) = cb.args().get(0, ctx.clone()) else { - // cb.error("failed to get argument"); //TODO + cb.error("failed to get argument"); return; }; let Ok(arg0) = arg0.as_number() else { - // cb.error("failed to convert argument"); //TODO + cb.error("failed to convert argument"); return; }; @@ -187,7 +272,7 @@ impl Test2 { V8Function::new(ctx.clone(), move |cb| { let num_args = 1; //function.arguments.len(); if num_args != cb.len() { - // cb.error("wrong number of arguments"); //TODO + cb.error("wrong number of arguments"); return; } @@ -196,12 +281,12 @@ impl Test2 { let args = cb.args(); let Some(arg0) = cb.args().get(0, ctx.clone()) else { - // cb.error("failed to get argument"); //TODO + cb.error("failed to get argument"); return; }; let Ok(arg0) = arg0.as_string() else { - // cb.error("failed to convert argument"); //TODO + cb.error("failed to convert argument"); return; }; @@ -217,7 +302,7 @@ impl Test2 { V8Function::new(ctx.clone(), move |cb| { let num_args = 1; //function.arguments.len(); if num_args != cb.len() { - // cb.error("wrong number of arguments"); //TODO + cb.error("wrong number of arguments"); return; } @@ -226,12 +311,12 @@ impl Test2 { let args = cb.args(); let Some(arg0) = cb.args().get(0, ctx.clone()) else { - // cb.error("failed to get argument"); //TODO + cb.error("failed to get argument"); return; }; let Ok(arg0) = arg0.as_string() else { - // cb.error("failed to convert argument"); //TODO + cb.error("failed to convert argument"); return; }; @@ -277,7 +362,7 @@ fn manual_js_inop() { other_field: "Hello, ".to_string(), })); - Test2::implement(t2, context.clone()).unwrap(); + Test2::implement(t2.clone(), context.clone()).unwrap(); let out = context .run( @@ -286,6 +371,13 @@ fn manual_js_inop() { test2.cool_fn() // \ test2.add(3) // |-> functions defined in rust test2.cool_fn() // / + test2.variadic(test2, test2.cool_fn, test2.cool_fn(), test2.field, test2.other_field) + + test2.field += 5 + test2.field = 33 + test2.field + test2.other_field += "World!" + test2.other_field "#, ) .expect("no value") @@ -293,4 +385,5 @@ fn manual_js_inop() { .unwrap(); println!("JS: {}", out); + println!("Rust: {:?}", t2.borrow()) }