diff --git a/core/src/lib.rs b/core/src/lib.rs index 6f417008..f9c4ef40 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -39,9 +39,9 @@ pub use persistent::Persistent; pub use result::{CatchResultExt, CaughtError, CaughtResult, Error, Result, ThrowResultExt}; pub use value::{ array, atom, convert, function, module, object, promise, proxy, Array, Atom, BigInt, CString, - Coerced, Exception, Filter, FromAtom, FromIteratorJs, FromJs, Function, IntoAtom, IntoJs, - IteratorJs, Module, Null, Object, Promise, Proxy, String, Symbol, Type, Undefined, Value, - WriteOptions, WriteOptionsEndianness, + Coerced, Constructor, Exception, Filter, FromAtom, FromIteratorJs, FromJs, Function, IntoAtom, + IntoJs, IteratorJs, Module, Null, Object, Promise, Proxy, String, Symbol, Type, Undefined, + Value, WriteOptions, WriteOptionsEndianness, }; pub mod allocator; diff --git a/core/src/value.rs b/core/src/value.rs index af662b71..38165642 100644 --- a/core/src/value.rs +++ b/core/src/value.rs @@ -353,25 +353,37 @@ impl<'js> Value<'js> { /// Check if the value is an array #[inline] pub fn is_array(&self) -> bool { - unsafe { qjs::JS_IsArray(self.value) } + if unsafe { qjs::JS_IsArray(self.value) } { + return true; + } + self.is_proxy_of(Type::Array) } /// Check if the value is a function #[inline] pub fn is_function(&self) -> bool { - (unsafe { qjs::JS_IsFunction(self.ctx.as_ptr(), self.value) } as i32) != 0 + if unsafe { qjs::JS_IsFunction(self.ctx.as_ptr(), self.value) } { + return true; + } + self.is_proxy_of(Type::Function) } /// Check if the value is a constructor function #[inline] pub fn is_constructor(&self) -> bool { - (unsafe { qjs::JS_IsConstructor(self.ctx.as_ptr(), self.value) } as i32) != 0 + if unsafe { qjs::JS_IsConstructor(self.ctx.as_ptr(), self.value) } { + return true; + } + self.is_proxy_of(Type::Constructor) } /// Check if the value is a promise. #[inline] pub fn is_promise(&self) -> bool { - (unsafe { qjs::JS_PromiseState(self.ctx.as_ptr(), self.value) } as core::ffi::c_int) >= 0 + if unsafe { qjs::JS_PromiseState(self.ctx.as_ptr(), self.value) } as core::ffi::c_int >= 0 { + return true; + } + self.is_proxy_of(Type::Promise) } /// Check if the value is an exception @@ -383,7 +395,11 @@ impl<'js> Value<'js> { /// Check if the value is an error #[inline] pub fn is_error(&self) -> bool { - (unsafe { qjs::JS_IsError(self.value) } as i32) != 0 + if unsafe { qjs::JS_IsError(self.value) } { + return true; + } + // This feels wrong, but Type::Exception means Javascript Error + self.is_proxy_of(Type::Exception) } /// Check if the value is a BigInt @@ -516,6 +532,30 @@ macro_rules! type_impls { pub fn type_name(&self) -> &'static str { self.type_of().as_str() } + + /// Checks recursively if the value is a proxy of a + /// certain type + pub fn is_proxy_of(&self, r#type: Type) -> bool { + if !self.is_proxy() { + return false; + } + let mut value = self.clone(); + loop { + // Must use ref here otherwise we will enter an infinite loop + // if using other methods. + let proxy = unsafe { value.ref_proxy() }; + let Ok(target) = proxy.target() else { + return false; + }; + if target.type_of() == r#type { + return true; + } + if !target.is_proxy() { + return false; + } + value = target.into_value(); + } + } } }; @@ -542,8 +582,8 @@ type_impls! { Function: function => JS_TAG_OBJECT, Promise: promise => JS_TAG_OBJECT, Exception: exception => JS_TAG_OBJECT, - Proxy: proxy => JS_TAG_OBJECT, Object: object => JS_TAG_OBJECT, + Proxy: proxy => JS_TAG_OBJECT, // MUST be the last of tag object Module: module => JS_TAG_MODULE, BigInt: big_int => JS_TAG_BIG_INT | JS_TAG_SHORT_BIG_INT, } diff --git a/core/src/value/array.rs b/core/src/value/array.rs index f9e11b67..730ffc71 100644 --- a/core/src/value/array.rs +++ b/core/src/value/array.rs @@ -201,6 +201,65 @@ mod test { }); } + #[test] + fn from_javascript_proxy() { + test_with(|ctx| { + let val: Array = ctx + .eval( + r#"new Proxy( + [1, 2, 3], + { + get: (target, property, receiver) => { + if (property === "length") { + return target.length; + } + return target[property]; + }, + } + )"#, + ) + .unwrap(); + assert_eq!(val.get::(0).unwrap(), 1); + assert_eq!(val.get::(1).unwrap(), 2); + assert_eq!(val.get::(2).unwrap(), 3); + assert_eq!(val.len(), 3); + }); + } + + #[test] + fn from_javascript_proxy_nested() { + test_with(|ctx| { + let val: Array = ctx + .eval( + r#" + new Proxy( + new Proxy( + [1, 2, 3], + { + get: (target, property, receiver) => { + if (property === "length") { + return target.length; + } + return target[property]; + }, + } + ), + { + get: (target, property, receiver) => { + if (property === "length") { + return target.length; + } + return target[property]; + }, + } + ) + "#, + ) + .unwrap(); + assert_eq!(val.get::(0).unwrap(), 1); + }); + } + #[test] fn into_object() { test_with(|ctx| { diff --git a/core/src/value/exception.rs b/core/src/value/exception.rs index d208ea32..a27b88f2 100644 --- a/core/src/value/exception.rs +++ b/core/src/value/exception.rs @@ -228,3 +228,24 @@ impl fmt::Display for Exception<'_> { Ok(()) } } + +#[cfg(test)] +mod test { + use crate::*; + + #[test] + fn from_javascript() { + test_with(|ctx| { + let val: Exception = ctx.eval(r#"new Error("test")"#).unwrap(); + assert_eq!(val.message().unwrap(), "test"); + }); + } + + #[test] + fn from_javascript_proxy() { + test_with(|ctx| { + let val: Exception = ctx.eval(r#"new Proxy(new Error("test"), { get: (target, property) => target[property] })"#).unwrap(); + assert_eq!(val.message().unwrap(), "test"); + }); + } +} diff --git a/core/src/value/function.rs b/core/src/value/function.rs index 8ffc164d..037d8367 100644 --- a/core/src/value/function.rs +++ b/core/src/value/function.rs @@ -817,4 +817,46 @@ mod test { assert_eq!(n, 3); }); } + + #[test] + fn js_fn_from_proxy() { + test_with(|ctx| { + let val: Function = ctx + .eval( + r#"new Proxy( + () => 1, + { + get: (target) => { + return target(); + }, + } + )"#, + ) + .unwrap(); + assert_eq!(val.call::<_, i32>(()).unwrap(), 1); + }); + } + + #[test] + fn js_constructor_from_proxy() { + test_with(|ctx| { + let val: Constructor = ctx + .eval( + r#"new Proxy( + function MyConstructor(input) { + this.a = input; + }, + { + construct: (target, args) => { + return new target(...args); + }, + } + )"#, + ) + .unwrap(); + let obj: Object = val.construct((42_i32,)).unwrap(); + let a: i32 = obj.get("a").unwrap(); + assert_eq!(a, 42); + }); + } } diff --git a/core/src/value/object.rs b/core/src/value/object.rs index a7232c73..1689b6da 100644 --- a/core/src/value/object.rs +++ b/core/src/value/object.rs @@ -801,4 +801,14 @@ mod test { ); }) } + + #[test] + fn from_javascript_proxy() { + test_with(|ctx| { + let val: Object = ctx + .eval(r#"new Proxy({ a: 1 }, { get: (target, property) => target[property] })"#) + .unwrap(); + assert_eq!(val.get::<_, i32>("a").unwrap(), 1); + }); + } } diff --git a/core/src/value/promise.rs b/core/src/value/promise.rs index d2c017c9..a952f34b 100644 --- a/core/src/value/promise.rs +++ b/core/src/value/promise.rs @@ -516,4 +516,27 @@ mod test { assert!(DID_EXECUTE.load(Ordering::SeqCst)); }) } + + #[cfg(feature = "futures")] + #[tokio::test] + async fn promise_from_javascript_proxy() { + let rt = AsyncRuntime::new().unwrap(); + let ctx = AsyncContext::full(&rt).await.unwrap(); + + async_with!(ctx => |ctx| { + let val: Promise = ctx + .eval( + r#"new Proxy( + new Promise((resolve) => { resolve(42) }), + { + get: (target, property) => { + var value = target[property]; + return typeof value == 'function' ? value.bind(target) : value; + } + })"#, + ) + .unwrap(); + assert_eq!(val.into_future::().await.unwrap(), 42); + }); + } }