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