Skip to main content

napi/bindgen_runtime/js_values/
class.rs

1use std::any::type_name;
2use std::ffi::CString;
3use std::marker::PhantomData;
4use std::ops::{Deref, DerefMut};
5use std::ptr;
6
7use crate::{
8  bindgen_runtime::{
9    raw_finalize_unchecked, FromNapiValue, JsObjectValue, MaybeTypeTag, Object, ObjectFinalize,
10    Reference, Result, TypeName, ValidateNapiValue,
11  },
12  check_status, sys, Env, JsValue, Property, PropertyAttributes, Value, ValueType,
13};
14
15#[derive(Clone, Copy)]
16pub struct This<'env, T = Object<'env>> {
17  pub object: T,
18  _phantom: &'env PhantomData<()>,
19}
20
21impl<T> From<T> for This<'_, T> {
22  fn from(value: T) -> Self {
23    Self {
24      object: value,
25      _phantom: &PhantomData,
26    }
27  }
28}
29
30impl<T> Deref for This<'_, T> {
31  type Target = T;
32
33  fn deref(&self) -> &Self::Target {
34    &self.object
35  }
36}
37
38impl<T> DerefMut for This<'_, T> {
39  fn deref_mut(&mut self) -> &mut Self::Target {
40    &mut self.object
41  }
42}
43
44impl<'env, T: JsValue<'env>> JsValue<'env> for This<'_, T> {
45  fn value(&self) -> Value {
46    self.object.value()
47  }
48}
49
50impl<T: FromNapiValue> FromNapiValue for This<'_, T> {
51  unsafe fn from_napi_value(env: sys::napi_env, napi_val: sys::napi_value) -> Result<Self> {
52    Ok(Self {
53      object: T::from_napi_value(env, napi_val)?,
54      _phantom: &PhantomData,
55    })
56  }
57}
58
59#[derive(Clone, Copy)]
60pub struct ClassInstance<'env, T: 'env> {
61  pub value: sys::napi_value,
62  env: sys::napi_env,
63  inner: *mut T,
64  _phantom: &'env PhantomData<()>,
65}
66
67// NOTE: the `MaybeTypeTag` bound here is transitively required, not collateral:
68// `JsValue: FromNapiValue` (see `js_values/value.rs`), and
69// `FromNapiValue for ClassInstance<T>` (below) needs `T: MaybeTypeTag` to name
70// `T::type_tag()` under napi8-native. Without `napi8` and on all wasm targets
71// `MaybeTypeTag` is vacuous, so the default and wasm public API are unchanged.
72impl<'env, T: 'env + MaybeTypeTag> JsValue<'env> for ClassInstance<'env, T> {
73  fn value(&self) -> Value {
74    Value {
75      env: self.env,
76      value: self.value,
77      value_type: ValueType::Object,
78    }
79  }
80}
81
82impl<'env, T: 'env + MaybeTypeTag> JsObjectValue<'env> for ClassInstance<'env, T> {}
83
84impl<'env, T: 'env> ClassInstance<'env, T> {
85  #[doc(hidden)]
86  pub unsafe fn new(value: sys::napi_value, env: sys::napi_env, inner: *mut T) -> Self {
87    Self {
88      value,
89      env,
90      inner: unsafe { &mut *inner },
91      _phantom: &PhantomData,
92    }
93  }
94
95  pub fn as_object<'a>(&self, env: &'a Env) -> Object<'a> {
96    Object(
97      Value {
98        env: env.raw(),
99        value: self.value,
100        value_type: ValueType::Object,
101      },
102      PhantomData,
103    )
104  }
105
106  /// Assign this `ClassInstance` to another `This` object
107  ///
108  /// Extends the lifetime of `ClassInstance` to `This`.
109  pub fn assign_to_this<'a, 'this, U>(
110    &'a self,
111    name: &'a str,
112    this: &'a mut This<U>,
113  ) -> Result<ClassInstance<'this, T>>
114  where
115    'this: 'env,
116    U: FromNapiValue + JsValue<'this>,
117  {
118    let name = CString::new(name)?;
119    check_status!(
120      unsafe {
121        sys::napi_set_named_property(self.env, this.object.raw(), name.as_ptr(), self.value)
122      },
123      "Failed to assign ClassInstance<{}> to this",
124      std::any::type_name::<T>()
125    )?;
126    let val: ClassInstance<'this, T> = ClassInstance {
127      value: self.value,
128      env: self.env,
129      inner: self.inner,
130      _phantom: &PhantomData,
131    };
132    Ok(val)
133  }
134
135  /// Assign this `ClassInstance` to another `This` object with `PropertyAttributes`.
136  ///
137  /// Extends the lifetime of `ClassInsatnce` to `This`.
138  pub fn assign_to_this_with_attributes<'a, 'this, U>(
139    &'a self,
140    name: &'a str,
141    attributes: PropertyAttributes,
142    this: &'a mut This<U>,
143  ) -> Result<ClassInstance<'this, T>>
144  where
145    'this: 'env,
146    // Transitively required (not collateral): `.with_value(self)` below needs
147    // `ClassInstance<T>: JsValue`, which needs `T: MaybeTypeTag`. Vacuous without
148    // `napi8` and on all wasm targets, so the default and wasm API are unchanged.
149    T: MaybeTypeTag,
150    U: FromNapiValue + JsValue<'this>,
151  {
152    let property = Property::new()
153      .with_utf8_name(name)?
154      .with_value(self)
155      .with_property_attributes(attributes);
156
157    check_status!(
158      unsafe {
159        sys::napi_define_properties(
160          self.env,
161          this.object.value().value,
162          1,
163          [property.raw()].as_ptr(),
164        )
165      },
166      "Failed to define properties on This in `assign_to_this_with_attributes`"
167    )?;
168
169    let val: ClassInstance<'this, T> = ClassInstance {
170      value: self.value,
171      env: self.env,
172      inner: self.inner,
173      _phantom: &PhantomData,
174    };
175    Ok(val)
176  }
177}
178
179impl<'env, T: 'env> TypeName for ClassInstance<'env, T>
180where
181  &'env T: TypeName,
182{
183  fn type_name() -> &'static str {
184    type_name::<&T>()
185  }
186
187  fn value_type() -> ValueType {
188    <&T>::value_type()
189  }
190}
191
192impl<'env, T: 'env> ValidateNapiValue for ClassInstance<'env, T>
193where
194  &'env T: ValidateNapiValue,
195{
196  unsafe fn validate(
197    env: sys::napi_env,
198    napi_val: sys::napi_value,
199  ) -> crate::Result<sys::napi_value> {
200    unsafe { <&'env T>::validate(env, napi_val) }
201  }
202}
203
204impl<'env, T: 'env + MaybeTypeTag> FromNapiValue for ClassInstance<'env, T> {
205  unsafe fn from_napi_value(env: sys::napi_env, napi_val: sys::napi_value) -> crate::Result<Self> {
206    let mut value = ptr::null_mut();
207    check_status!(
208      unsafe { sys::napi_unwrap(env, napi_val, &mut value) },
209      "Unwrap value [{}] from class failed",
210      type_name::<T>(),
211    )?;
212
213    // Reject a wrong-class / prototype-spoofed object before the blind cast.
214    // Compiled only on napi8 NATIVE targets (the `T: MaybeTypeTag` bound provides
215    // `T::type_tag()` only there; elsewhere this is the pre-tag unchecked cast).
216    #[cfg(all(feature = "napi8", not(target_family = "wasm")))]
217    unsafe {
218      crate::bindgen_runtime::validate_type_tag(env, napi_val, &T::type_tag(), type_name::<T>())?;
219    }
220
221    let value = unsafe { Box::from_raw(value as *mut T) };
222    Ok(Self {
223      value: napi_val,
224      inner: Box::leak(value),
225      env,
226      _phantom: &PhantomData,
227    })
228  }
229}
230
231impl<'env, T: 'env> Deref for ClassInstance<'env, T> {
232  type Target = T;
233
234  fn deref(&self) -> &Self::Target {
235    unsafe { &*self.inner }
236  }
237}
238
239impl<'env, T: 'env> DerefMut for ClassInstance<'env, T> {
240  fn deref_mut(&mut self) -> &mut Self::Target {
241    unsafe { &mut *self.inner }
242  }
243}
244
245impl<'env, T: 'env> AsRef<T> for ClassInstance<'env, T> {
246  fn as_ref(&self) -> &T {
247    unsafe { &*self.inner }
248  }
249}
250
251pub trait JavaScriptClassExt: Sized {
252  fn into_instance(self, env: &Env) -> Result<ClassInstance<'_, Self>>;
253  fn into_reference(self, env: Env) -> Result<Reference<Self>>;
254  fn instance_of<'env, V: JsValue<'env>>(env: &Env, value: &V) -> Result<bool>;
255}
256
257/// # Safety
258///
259/// create instance of class
260#[doc(hidden)]
261pub unsafe fn new_instance<T: 'static + ObjectFinalize + MaybeTypeTag>(
262  env: sys::napi_env,
263  wrapped_value: *mut std::ffi::c_void,
264  ctor_ref: sys::napi_ref,
265) -> Result<sys::napi_value> {
266  let mut ctor = std::ptr::null_mut();
267  check_status!(
268    sys::napi_get_reference_value(env, ctor_ref, &mut ctor),
269    "Failed to get constructor reference of class `{}`",
270    type_name::<T>(),
271  )?;
272
273  let mut result = std::ptr::null_mut();
274  crate::__private::___CALL_FROM_FACTORY.with(|inner| inner.set(true));
275  check_status!(
276    sys::napi_new_instance(env, ctor, 0, std::ptr::null_mut(), &mut result),
277    "Failed to construct class `{}`",
278    type_name::<T>(),
279  )?;
280  crate::__private::___CALL_FROM_FACTORY.with(|inner| inner.set(false));
281  let mut object_ref = std::ptr::null_mut();
282  let initial_finalize: Box<dyn FnOnce()> = Box::new(|| {});
283  // `Arc` (not `Rc`) is required so it matches `Reference`'s `Arc<Cell<..>>` field and its
284  // `Sync` impl; the `Cell` is only accessed on the JS thread.
285  #[allow(clippy::arc_with_non_send_sync)]
286  let finalize_callbacks_ptr = std::sync::Arc::into_raw(std::sync::Arc::new(std::cell::Cell::new(
287    Box::into_raw(initial_finalize),
288  )));
289  check_status!(
290    sys::napi_wrap(
291      env,
292      result,
293      wrapped_value,
294      Some(raw_finalize_unchecked::<T>),
295      std::ptr::null_mut(),
296      &mut object_ref,
297    ),
298    "Failed to wrap native object of class `{}`",
299    type_name::<T>(),
300  )?;
301
302  Reference::<T>::add_ref(
303    env,
304    wrapped_value,
305    (wrapped_value, object_ref, finalize_callbacks_ptr),
306  );
307
308  // Stamp the type tag AFTER `add_ref` so a tag failure cannot leak the Arc +
309  // napi_ref (see `CallbackInfo::_construct`). Compiled only on napi8 NATIVE
310  // targets (the `T: MaybeTypeTag` bound provides `T::type_tag()` only there).
311  #[cfg(all(feature = "napi8", not(target_family = "wasm")))]
312  unsafe {
313    crate::bindgen_runtime::tag_object(env, result, &T::type_tag())?;
314  }
315
316  Ok(result)
317}