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/// Non-generic core of [`new_instance`].
258///
259/// Constructing and wrapping the JavaScript object does not depend on the
260/// concrete Rust class. Accept the type-dependent finalizer and class name as
261/// values so addons with many classes only emit the N-API and reference-map
262/// setup once.
263#[inline(never)]
264unsafe fn new_instance_raw(
265  env: sys::napi_env,
266  wrapped_value: *mut std::ffi::c_void,
267  ctor_ref: sys::napi_ref,
268  finalize_cb: sys::napi_finalize,
269  class_name: &str,
270) -> Result<sys::napi_value> {
271  let mut ctor = std::ptr::null_mut();
272  check_status!(
273    sys::napi_get_reference_value(env, ctor_ref, &mut ctor),
274    "Failed to get constructor reference of class `{}`",
275    class_name,
276  )?;
277
278  let mut result = std::ptr::null_mut();
279  crate::__private::___CALL_FROM_FACTORY.with(|inner| inner.set(true));
280  check_status!(
281    sys::napi_new_instance(env, ctor, 0, std::ptr::null_mut(), &mut result),
282    "Failed to construct class `{}`",
283    class_name,
284  )?;
285  crate::__private::___CALL_FROM_FACTORY.with(|inner| inner.set(false));
286  let mut object_ref = std::ptr::null_mut();
287  let initial_finalize: Box<dyn FnOnce()> = Box::new(|| {});
288  // `Arc` (not `Rc`) is required so it matches `Reference`'s `Arc<Cell<..>>` field and its
289  // `Sync` impl; the `Cell` is only accessed on the JS thread.
290  #[allow(clippy::arc_with_non_send_sync)]
291  let finalize_callbacks_ptr = std::sync::Arc::into_raw(std::sync::Arc::new(std::cell::Cell::new(
292    Box::into_raw(initial_finalize),
293  )));
294  check_status!(
295    sys::napi_wrap(
296      env,
297      result,
298      wrapped_value,
299      finalize_cb,
300      std::ptr::null_mut(),
301      &mut object_ref,
302    ),
303    "Failed to wrap native object of class `{}`",
304    class_name,
305  )?;
306
307  super::value_ref::add_ref(
308    env,
309    wrapped_value,
310    (wrapped_value, object_ref, finalize_callbacks_ptr),
311  );
312
313  Ok(result)
314}
315
316/// # Safety
317///
318/// create instance of class
319#[doc(hidden)]
320pub unsafe fn new_instance<T: 'static + ObjectFinalize + MaybeTypeTag>(
321  env: sys::napi_env,
322  wrapped_value: *mut std::ffi::c_void,
323  ctor_ref: sys::napi_ref,
324) -> Result<sys::napi_value> {
325  let result = unsafe {
326    new_instance_raw(
327      env,
328      wrapped_value,
329      ctor_ref,
330      Some(raw_finalize_unchecked::<T>),
331      type_name::<T>(),
332    )
333  }?;
334
335  // Stamp the type tag AFTER `add_ref` so a tag failure cannot leak the Arc +
336  // napi_ref (see `CallbackInfo::_construct`). Compiled only on napi8 NATIVE
337  // targets (the `T: MaybeTypeTag` bound provides `T::type_tag()` only there).
338  #[cfg(all(feature = "napi8", not(target_family = "wasm")))]
339  unsafe {
340    crate::bindgen_runtime::tag_object(env, result, &T::type_tag())?;
341  }
342
343  Ok(result)
344}