Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
52 changes: 46 additions & 6 deletions core/src/value.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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();
}
}
}
};

Expand All @@ -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,
}
Expand Down
59 changes: 59 additions & 0 deletions core/src/value/array.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::<i32>(0).unwrap(), 1);
assert_eq!(val.get::<i32>(1).unwrap(), 2);
assert_eq!(val.get::<i32>(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::<i32>(0).unwrap(), 1);
});
}

#[test]
fn into_object() {
test_with(|ctx| {
Expand Down
21 changes: 21 additions & 0 deletions core/src/value/exception.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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");
});
}
}
42 changes: 42 additions & 0 deletions core/src/value/function.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
});
}
}
10 changes: 10 additions & 0 deletions core/src/value/object.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
});
}
}
23 changes: 23 additions & 0 deletions core/src/value/promise.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::<i32>().await.unwrap(), 42);
});
}
}
Loading