Skip to content

Commit ae8fb11

Browse files
committed
Implement JsNativeObject wrapper
1 parent 4707868 commit ae8fb11

File tree

2 files changed

+271
-0
lines changed

2 files changed

+271
-0
lines changed
Lines changed: 269 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,269 @@
1+
//! A Rust API wrapper for [`NativeObject`]s stored in Boa's builtin [`JsObject`].
2+
3+
use std::{marker::PhantomData, ops::Deref};
4+
5+
use boa_gc::{GcRef, GcRefMut};
6+
use boa_macros::{Finalize, Trace};
7+
8+
use crate::{
9+
class::Class,
10+
object::{JsObjectType, NativeObject, Object, ObjectData},
11+
value::TryFromJs,
12+
Context, JsNativeError, JsObject, JsResult, JsValue,
13+
};
14+
15+
/// [`JsNativeObject<T>`] provides a wrapper for a Rust type `T` stored as a
16+
/// [`NativeObject`] in Boa's [`JsObject`].
17+
#[derive(Debug, Trace, Finalize)]
18+
pub struct JsNativeObject<T: NativeObject> {
19+
// INVARIANT: `inner.is::<T>() == true`
20+
inner: JsObject,
21+
marker: PhantomData<T>,
22+
}
23+
24+
impl<T: NativeObject> Clone for JsNativeObject<T> {
25+
fn clone(&self) -> Self {
26+
Self {
27+
inner: self.inner.clone(),
28+
marker: self.marker,
29+
}
30+
}
31+
}
32+
33+
impl<T: NativeObject> JsNativeObject<T> {
34+
/// Creates a [`JsNativeObject<T>`] from a valid [`JsObject`], or returns a `TypeError`
35+
/// if the provided object is not a [`NativeObject`] with type `T`.
36+
///
37+
/// # Examples
38+
///
39+
/// ### Valid Example - Matching types
40+
/// ```
41+
/// # use boa_engine::{
42+
/// # object::{ObjectInitializer, builtins::JsNativeObject},
43+
/// # Context, JsResult
44+
/// # };
45+
/// #
46+
/// # fn main() -> JsResult<()> {
47+
/// # let context = &mut Context::default();
48+
/// // Create a native object represented as a `JsObject`
49+
/// let buffer: Vec<u8> = vec![42; 6];
50+
/// let js_buffer = ObjectInitializer::with_native(buffer, context).build();
51+
///
52+
/// // Create `JsNativeObject` from `JsObject`
53+
/// let js_buffer = JsNativeObject::<Vec<u8>>::from_object(js_buffer)?;
54+
/// # Ok(())
55+
/// # }
56+
/// ```
57+
///
58+
/// ### Invalid Example - Mismatching types
59+
/// ```
60+
/// # use boa_engine::{
61+
/// # object::{ObjectInitializer, builtins::JsNativeObject},
62+
/// # Context, JsObject, JsResult
63+
/// # };
64+
/// #
65+
/// # let context = &mut Context::default();
66+
/// let js_int = ObjectInitializer::with_native(42, context).build();
67+
///
68+
/// // js_int is an int, not unit
69+
/// assert!(JsNativeObject::<()>::from_object(js_int).is_err());
70+
/// ```
71+
#[inline]
72+
pub fn from_object(object: JsObject) -> JsResult<Self> {
73+
if object.is::<T>() {
74+
Ok(Self {
75+
inner: object,
76+
marker: PhantomData,
77+
})
78+
} else {
79+
Err(JsNativeError::typ()
80+
.with_message("object is not a native object of the expected type")
81+
.into())
82+
}
83+
}
84+
85+
/// Creates a [`JsNativeObject<T>`] with the provided prototype and native object
86+
/// data of type `T`
87+
pub fn new_with_proto<P>(prototype: P, native_object: T, context: &mut Context) -> Self
88+
where
89+
P: Into<Option<JsObject>>,
90+
{
91+
let instance = JsObject::from_proto_and_data_with_shared_shape(
92+
context.root_shape(),
93+
prototype,
94+
ObjectData::native_object(native_object),
95+
);
96+
97+
Self {
98+
inner: instance,
99+
marker: PhantomData,
100+
}
101+
}
102+
103+
/// Creates a [`JsNativeObject<T>`] with the native object data of type `T`
104+
/// and prototype of the native class implemented by `T`, or returns a
105+
/// `TypeError` if the native class is not registered in the context.
106+
///
107+
/// # Example
108+
///
109+
/// Create a [`JsNativeObject<Animal>`] using the example native class from
110+
/// [`Class`][./trait.Class.html].
111+
/// ```
112+
/// # use boa_engine::{
113+
/// # js_string,
114+
/// # class::{Class, ClassBuilder},
115+
/// # object::{ObjectInitializer, builtins::JsNativeObject},
116+
/// # Context, JsArgs, JsNativeError, JsObject, JsResult, JsValue, NativeFunction,
117+
/// # };
118+
/// # use boa_gc::{Finalize, Trace};
119+
///
120+
/// #[derive(Debug, Trace, Finalize)]
121+
/// enum Animal {
122+
/// Cat,
123+
/// Dog,
124+
/// Other,
125+
/// }
126+
///
127+
/// // Implement native class for `Animal` through the `Class` trait.
128+
/// impl Class for Animal {
129+
/// // See `Class` documentation for implementation.
130+
/// // ...
131+
/// # // We set the binging name of this function to be `"Animal"`.
132+
/// # const NAME: &'static str = "Animal";
133+
/// #
134+
/// # // We set the length to `1` since we accept 1 arguments in the constructor.
135+
/// # const LENGTH: usize = 1;
136+
/// #
137+
/// # // This is what is called when we do `new Animal()` to construct the inner data of the class.
138+
/// # fn make_data(_new_target: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<Self> {
139+
/// # // This is equivalent to `String(arg)`.
140+
/// # let kind = args.get_or_undefined(0).to_string(context)?;
141+
/// #
142+
/// # let animal = match kind.to_std_string_escaped().as_str() {
143+
/// # "cat" => Self::Cat,
144+
/// # "dog" => Self::Dog,
145+
/// # _ => Self::Other,
146+
/// # };
147+
/// #
148+
/// # Ok(animal)
149+
/// # }
150+
/// #
151+
/// # /// This is where the object is initialized.
152+
/// # fn init(class: &mut ClassBuilder) -> JsResult<()> {
153+
/// # class.method(
154+
/// # js_string!("speak"),
155+
/// # 0,
156+
/// # NativeFunction::from_fn_ptr(|this, _args, _ctx| {
157+
/// # if let Some(object) = this.as_object() {
158+
/// # if let Some(animal) = object.downcast_ref::<Animal>() {
159+
/// # match &*animal {
160+
/// # Self::Cat => println!("meow"),
161+
/// # Self::Dog => println!("woof"),
162+
/// # Self::Other => println!(r"¯\_(ツ)_/¯"),
163+
/// # }
164+
/// # }
165+
/// # }
166+
/// # Ok(JsValue::undefined())
167+
/// # }),
168+
/// # );
169+
/// # Ok(())
170+
/// # }
171+
/// }
172+
///
173+
/// # let context = &mut Context::default();
174+
///
175+
/// // Create a `JsNativeObject<Animal>`
176+
/// let js_dog = JsNativeObject::new(Animal::Dog, context);
177+
/// ```
178+
pub fn new(native_object: T, context: &mut Context) -> JsResult<Self>
179+
where
180+
T: Class,
181+
{
182+
let class = context.get_global_class::<T>().ok_or_else(|| {
183+
JsNativeError::typ().with_message(format!(
184+
"could not find native class `{}` in the map of registered classes",
185+
T::NAME
186+
))
187+
})?;
188+
189+
Ok(Self::new_with_proto(
190+
class.prototype(),
191+
native_object,
192+
context,
193+
))
194+
}
195+
196+
/// Returns a reference to the native object data of type `T`.
197+
///
198+
/// # Panics
199+
///
200+
/// Panics if the object is currently mutably borrowed.
201+
#[must_use]
202+
pub fn as_ref(&self) -> GcRef<'_, T> {
203+
// SAFETY: The invariant of `JsNativeObject<T>` ensures that
204+
// `inner.is::<T>() == true`.
205+
self.inner
206+
.downcast_ref::<T>()
207+
.expect("Type mismatch in `JsNativeObject`")
208+
}
209+
210+
/// Returns a mutable reference to the native object of type `T`.
211+
///
212+
/// # Panic
213+
///
214+
/// Panics if the object is currently borrowed.
215+
#[must_use]
216+
pub fn as_mut(&self) -> GcRefMut<'_, Object, T> {
217+
self.inner
218+
.downcast_mut::<T>()
219+
.expect("Type mismatch in `JsNativeObject`")
220+
}
221+
}
222+
223+
impl<T> From<JsNativeObject<T>> for JsObject
224+
where
225+
T: NativeObject,
226+
{
227+
#[inline]
228+
fn from(o: JsNativeObject<T>) -> Self {
229+
o.inner.clone()
230+
}
231+
}
232+
233+
impl<T> From<JsNativeObject<T>> for JsValue
234+
where
235+
T: NativeObject,
236+
{
237+
#[inline]
238+
fn from(o: JsNativeObject<T>) -> Self {
239+
o.inner.clone().into()
240+
}
241+
}
242+
243+
impl<T> Deref for JsNativeObject<T>
244+
where
245+
T: NativeObject,
246+
{
247+
type Target = JsObject;
248+
249+
#[inline]
250+
fn deref(&self) -> &Self::Target {
251+
&self.inner
252+
}
253+
}
254+
255+
impl<T> JsObjectType for JsNativeObject<T> where T: NativeObject {}
256+
257+
impl<T> TryFromJs for JsNativeObject<T>
258+
where
259+
T: NativeObject,
260+
{
261+
fn try_from_js(value: &JsValue, _context: &mut Context) -> JsResult<Self> {
262+
match value {
263+
JsValue::Object(o) => Self::from_object(o.clone()),
264+
_ => Err(JsNativeError::typ()
265+
.with_message("value is not a native object")
266+
.into()),
267+
}
268+
}
269+
}

boa_engine/src/object/builtins/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ mod jsfunction;
1010
mod jsgenerator;
1111
mod jsmap;
1212
mod jsmap_iterator;
13+
mod jsnative_object;
1314
mod jspromise;
1415
mod jsproxy;
1516
mod jsregexp;
@@ -26,6 +27,7 @@ pub use jsfunction::*;
2627
pub use jsgenerator::*;
2728
pub use jsmap::*;
2829
pub use jsmap_iterator::*;
30+
pub use jsnative_object::*;
2931
pub use jspromise::*;
3032
pub use jsproxy::{JsProxy, JsProxyBuilder, JsRevocableProxy};
3133
pub use jsregexp::JsRegExp;

0 commit comments

Comments
 (0)