Skip to main content

script_bindings/
reflector.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5use std::cell::Cell;
6use std::rc::Rc;
7
8use js::context::JSContext;
9use js::jsapi::{AddAssociatedMemory, Heap, JSObject, MemoryUse, RemoveAssociatedMemory};
10use js::rust::HandleObject;
11use malloc_size_of_derive::MallocSizeOf;
12
13use crate::conversions::DerivedFrom;
14use crate::interfaces::GlobalScopeHelpers;
15use crate::iterable::{Iterable, IterableIterator};
16use crate::realms::enter_auto_realm;
17use crate::root::{Dom, DomRoot, Root};
18use crate::{DomTypes, JSTraceable};
19
20pub trait AssociatedMemorySize: Default {
21    fn size(&self) -> usize;
22}
23
24impl AssociatedMemorySize for () {
25    fn size(&self) -> usize {
26        0
27    }
28}
29
30#[derive(Default, MallocSizeOf)]
31pub struct AssociatedMemory(Cell<usize>);
32
33impl AssociatedMemorySize for AssociatedMemory {
34    fn size(&self) -> usize {
35        self.0.get()
36    }
37}
38
39/// A struct to store a reference to the reflector of a DOM object.
40#[derive(MallocSizeOf)]
41#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
42// If you're renaming or moving this field, update the path in plugins::reflector as well
43pub struct Reflector<T = ()> {
44    #[ignore_malloc_size_of = "defined and measured in rust-mozjs"]
45    object: Heap<*mut JSObject>,
46    /// Associated memory size (of rust side). Used for memory reporting to SM.
47    size: T,
48    /// Cached prototype ID for fast type checks.
49    proto_id: Cell<u16>,
50}
51
52unsafe impl<T> js::gc::Traceable for Reflector<T> {
53    unsafe fn trace(&self, tracer: *mut js::jsapi::JSTracer) {
54        unsafe {
55            self.object.trace(tracer);
56        }
57    }
58}
59
60impl<T> PartialEq for Reflector<T> {
61    fn eq(&self, other: &Reflector<T>) -> bool {
62        self.object.get() == other.object.get()
63    }
64}
65
66impl<T> Reflector<T> {
67    /// Get the reflector.
68    #[inline]
69    pub fn get_jsobject(&self) -> HandleObject<'_> {
70        // We're rooted, so it's safe to hand out a handle to object in Heap
71        unsafe { HandleObject::from_raw(self.object.handle()) }
72    }
73
74    /// Get the cached prototype ID.
75    #[inline]
76    pub fn proto_id(&self) -> u16 {
77        self.proto_id.get()
78    }
79
80    /// Set the cached prototype ID.
81    #[inline]
82    pub fn set_proto_id(&self, id: u16) {
83        self.proto_id.set(id);
84    }
85
86    /// Initialize the reflector. (May be called only once.)
87    ///
88    /// # Safety
89    ///
90    /// The provided [`JSObject`] pointer must point to a valid [`JSObject`].
91    unsafe fn set_jsobject(&self, object: *mut JSObject) {
92        assert!(self.object.get().is_null());
93        assert!(!object.is_null());
94        self.object.set(object);
95    }
96
97    /// Return a pointer to the memory location at which the JS reflector
98    /// object is stored. Used to root the reflector, as
99    /// required by the JSAPI rooting APIs.
100    pub fn rootable(&self) -> &Heap<*mut JSObject> {
101        &self.object
102    }
103}
104
105impl<T: AssociatedMemorySize> Reflector<T> {
106    /// Create an uninitialized `Reflector`.
107    // These are used by the bindings and do not need `default()` functions.
108    #[expect(clippy::new_without_default)]
109    pub fn new() -> Reflector<T> {
110        Reflector {
111            object: Heap::default(),
112            proto_id: Cell::new(u16::MAX),
113            size: T::default(),
114        }
115    }
116
117    pub fn rust_size<D>(&self, _: &D) -> usize {
118        size_of::<D>() + size_of::<Box<D>>() + self.size.size()
119    }
120
121    /// This function should be called from finalize of the DOM objects
122    pub fn drop_memory<D>(&self, d: &D) {
123        unsafe {
124            RemoveAssociatedMemory(self.object.get(), self.rust_size(d), MemoryUse::DOMBinding);
125        }
126    }
127}
128
129impl Reflector<AssociatedMemory> {
130    /// Update the associated memory size.
131    pub fn update_memory_size<D>(&self, d: &D, new_size: usize) {
132        if self.size.size() == new_size {
133            return;
134        }
135        unsafe {
136            RemoveAssociatedMemory(self.object.get(), self.rust_size(d), MemoryUse::DOMBinding);
137            self.size.0.set(new_size);
138            AddAssociatedMemory(self.object.get(), self.rust_size(d), MemoryUse::DOMBinding);
139        }
140    }
141}
142
143/// A trait to provide access to the `Reflector` for a DOM object.
144pub trait DomObject: js::gc::Traceable + 'static {
145    type ReflectorType: AssociatedMemorySize;
146    /// Returns the receiver's reflector.
147    fn reflector(&self) -> &Reflector<Self::ReflectorType>;
148}
149
150impl DomObject for Reflector<()> {
151    type ReflectorType = ();
152
153    fn reflector(&self) -> &Reflector<Self::ReflectorType> {
154        self
155    }
156}
157
158impl DomObject for Reflector<AssociatedMemory> {
159    type ReflectorType = AssociatedMemory;
160
161    fn reflector(&self) -> &Reflector<Self::ReflectorType> {
162        self
163    }
164}
165
166/// A trait to initialize the `Reflector` for a DOM object.
167pub trait MutDomObject: DomObject {
168    /// Initializes the Reflector
169    ///
170    /// # Safety
171    ///
172    /// The provided [`JSObject`] pointer must point to a valid [`JSObject`].
173    /// The provided [`JSObject`] pointer must not be allocated in the nursery.
174    unsafe fn init_reflector<D>(&self, obj: *mut JSObject);
175
176    /// Initializes the Reflector without recording any associated memory usage.
177    ///
178    /// # Safety
179    ///
180    /// The provided [`JSObject`] pointer must point to a valid [`JSObject`].
181    unsafe fn init_reflector_without_associated_memory(&self, obj: *mut JSObject);
182}
183
184impl MutDomObject for Reflector<()> {
185    unsafe fn init_reflector<D>(&self, obj: *mut JSObject) {
186        unsafe {
187            js::jsapi::AddAssociatedMemory(
188                obj,
189                size_of::<D>() + size_of::<Box<D>>(),
190                MemoryUse::DOMBinding,
191            );
192            self.init_reflector_without_associated_memory(obj);
193        }
194    }
195
196    unsafe fn init_reflector_without_associated_memory(&self, obj: *mut JSObject) {
197        unsafe {
198            self.set_jsobject(obj);
199        }
200    }
201}
202
203impl MutDomObject for Reflector<AssociatedMemory> {
204    unsafe fn init_reflector<D>(&self, obj: *mut JSObject) {
205        unsafe {
206            js::jsapi::AddAssociatedMemory(
207                obj,
208                size_of::<D>() + size_of::<Box<D>>(),
209                MemoryUse::DOMBinding,
210            );
211            self.init_reflector_without_associated_memory(obj);
212        }
213    }
214
215    unsafe fn init_reflector_without_associated_memory(&self, obj: *mut JSObject) {
216        unsafe {
217            self.set_jsobject(obj);
218        }
219    }
220}
221
222pub trait DomGlobalGeneric<D: DomTypes>: DomObject {
223    /// Returns the [`GlobalScope`] of the realm that the [`DomObject`] was created in.  If this
224    /// object is a `Node`, this will be different from it's owning `Document` if adopted by. For
225    /// `Node`s it's almost always better to use `NodeTraits::owning_global`.
226    fn global_from_reflector(&self) -> DomRoot<D::GlobalScope>
227    where
228        Self: Sized,
229    {
230        // SAFETY: We only use this `cx` to enter a realm. That does not
231        // incur a GC and hence is safe to perform. We do not want to
232        // pass a `cx` as parameter to this function, as this used in
233        // loads of places. At the same time, it also isn't necessary in
234        // nearly all cases to enter realm, since we are already in the
235        // correct realm.
236        //
237        // However, there are cases where it is difficult to ensure that
238        // we are in the correct realm. Hence we always enter a realm here
239        // even if that is unnecessary at times.
240        let cx = unsafe { JSContext::get_from_thread() };
241        let cx = &mut cx.expect("JS runtime has shut down");
242        let _realm = enter_auto_realm::<D>(cx, self);
243        D::GlobalScope::from_reflector(self)
244    }
245}
246
247impl<D: DomTypes, T: DomObject> DomGlobalGeneric<D> for T {}
248
249/// A trait to provide a function pointer to wrap function for DOM objects.
250pub trait DomObjectWrap<D: DomTypes>: Sized + DomObject + DomGlobalGeneric<D> {
251    /// Function pointer to the general wrap function type
252    #[expect(clippy::type_complexity)]
253    const WRAP: unsafe fn(
254        &mut JSContext,
255        &D::GlobalScope,
256        Option<HandleObject>,
257        Box<Self>,
258    ) -> Root<Dom<Self>>;
259}
260
261/// A trait to provide a function pointer to wrap function for DOM objects.
262pub trait WeakReferenceableDomObjectWrap<D: DomTypes>:
263    Sized + DomObject + DomGlobalGeneric<D>
264{
265    /// Function pointer to the general wrap function type
266    #[expect(clippy::type_complexity)]
267    const WRAP: unsafe fn(
268        &mut js::context::JSContext,
269        &D::GlobalScope,
270        Option<HandleObject>,
271        Rc<Self>,
272    ) -> Root<Dom<Self>>;
273}
274
275/// A trait to provide a function pointer to wrap function for
276/// DOM iterator interfaces.
277pub trait DomObjectIteratorWrap<D: DomTypes>: DomObjectWrap<D> + JSTraceable + Iterable {
278    /// Function pointer to the wrap function for `IterableIterator<T>`
279    #[expect(clippy::type_complexity)]
280    const ITER_WRAP: unsafe fn(
281        &mut JSContext,
282        &D::GlobalScope,
283        Option<HandleObject>,
284        Box<IterableIterator<D, Self>>,
285    ) -> Root<Dom<IterableIterator<D, Self>>>;
286}
287
288/// Create the reflector for a new DOM object and yield ownership to the
289/// reflector.
290pub fn reflect_dom_object<D, T, U>(cx: &mut JSContext, obj: Box<T>, global: &U) -> DomRoot<T>
291where
292    D: DomTypes,
293    T: DomObject + DomObjectWrap<D>,
294    U: DerivedFrom<D::GlobalScope>,
295{
296    let global_scope = global.upcast();
297    unsafe { T::WRAP(cx, global_scope, None, obj) }
298}
299
300pub fn reflect_dom_object_with_proto<D, T, U>(
301    cx: &mut JSContext,
302    obj: Box<T>,
303    global: &U,
304    proto: Option<HandleObject>,
305) -> DomRoot<T>
306where
307    D: DomTypes,
308    T: DomObject + DomObjectWrap<D>,
309    U: DerivedFrom<D::GlobalScope>,
310{
311    let global_scope = global.upcast();
312    unsafe { T::WRAP(cx, global_scope, proto, obj) }
313}
314
315/// Create the reflector for a new DOM object and yield ownership to the
316/// reflector.
317/// Deprecated, use `reflect_dom_object` instead.
318pub fn reflect_dom_object_with_cx<D, T, U>(
319    obj: Box<T>,
320    global: &U,
321    cx: &mut JSContext,
322) -> DomRoot<T>
323where
324    D: DomTypes,
325    T: DomObject + DomObjectWrap<D>,
326    U: DerivedFrom<D::GlobalScope>,
327{
328    let global_scope = global.upcast();
329    unsafe { T::WRAP(cx, global_scope, None, obj) }
330}
331
332/// Create the reflector for a new DOM object and yield ownership to the
333/// reflector.
334pub fn reflect_weak_referenceable_dom_object<D, T, U>(
335    cx: &mut JSContext,
336    obj: Rc<T>,
337    global: &U,
338) -> DomRoot<T>
339where
340    D: DomTypes,
341    T: DomObject + WeakReferenceableDomObjectWrap<D>,
342    U: DerivedFrom<D::GlobalScope>,
343{
344    let global_scope = global.upcast();
345    unsafe { T::WRAP(cx, global_scope, None, obj) }
346}
347
348pub fn reflect_weak_referenceable_dom_object_with_proto<D, T, U>(
349    cx: &mut JSContext,
350    obj: Rc<T>,
351    global: &U,
352    proto: Option<HandleObject>,
353) -> DomRoot<T>
354where
355    D: DomTypes,
356    T: DomObject + WeakReferenceableDomObjectWrap<D>,
357    U: DerivedFrom<D::GlobalScope>,
358{
359    let global_scope = global.upcast();
360    unsafe { T::WRAP(cx, global_scope, proto, obj) }
361}
362
363type WrapFn<D, AbstractType> = unsafe fn(
364    &mut js::context::JSContext,
365    &<D as DomTypes>::GlobalScope,
366    Option<HandleObject>,
367    Box<AbstractType>,
368) -> DomRoot<AbstractType>;
369
370/// Create the reflector for a new DOM object and yield ownership to the
371/// reflector.
372pub fn reflect_dom_object_with_proto_and_wrap<D, AbstractType, GlobalType>(
373    obj: Box<AbstractType>,
374    global: &GlobalType,
375    proto: Option<HandleObject>,
376    cx: &mut js::context::JSContext,
377    wrap: WrapFn<D, AbstractType>,
378) -> DomRoot<AbstractType>
379where
380    D: DomTypes,
381    AbstractType: DomObject,
382    GlobalType: DerivedFrom<D::GlobalScope>,
383    Box<AbstractType>: From<Box<AbstractType>>,
384    DomRoot<AbstractType>: From<DomRoot<AbstractType>>,
385{
386    let global_scope = global.upcast();
387    unsafe { wrap(cx, global_scope, proto, obj) }
388}
389
390/// Create the reflector for a new DOM object and yield ownership to the
391/// reflector.
392pub fn reflect_dom_object_with_wrap<D, AbstractType, GlobalType>(
393    obj: Box<AbstractType>,
394    global: &GlobalType,
395    cx: &mut js::context::JSContext,
396    wrap: WrapFn<D, AbstractType>,
397) -> DomRoot<AbstractType>
398where
399    D: DomTypes,
400    AbstractType: DomObject,
401    GlobalType: DerivedFrom<D::GlobalScope>,
402    Box<AbstractType>: From<Box<AbstractType>>,
403    DomRoot<AbstractType>: From<DomRoot<AbstractType>>,
404{
405    let global_scope = global.upcast();
406    unsafe { wrap(cx, global_scope, None, obj) }
407}
408
409type WrapFnRc<D, AbstractType> = unsafe fn(
410    &mut js::context::JSContext,
411    &<D as DomTypes>::GlobalScope,
412    Option<HandleObject>,
413    Rc<AbstractType>,
414) -> DomRoot<AbstractType>;
415
416/// Create the reflector for a new DOM object and yield ownership to the
417/// reflector.
418pub fn reflect_weak_referenceable_dom_object_with_cx_and_wrap<D, AbstractType, GlobalType>(
419    cx: &mut JSContext,
420    obj: Rc<AbstractType>,
421    global: &GlobalType,
422    wrap: WrapFnRc<D, AbstractType>,
423) -> DomRoot<AbstractType>
424where
425    D: DomTypes,
426    AbstractType: DomObject,
427    GlobalType: DerivedFrom<D::GlobalScope>,
428    Rc<AbstractType>: From<Rc<AbstractType>>,
429    DomRoot<AbstractType>: From<DomRoot<AbstractType>>,
430{
431    let global_scope = global.upcast();
432    unsafe { wrap(cx, global_scope, None, obj) }
433}