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 js::context::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.
319/// Deprecated, use `reflect_dom_object` instead.
320pub fn reflect_dom_object_with_cx<D, T, U>(
321    obj: Box<T>,
322    global: &U,
323    cx: &mut JSContext,
324) -> DomRoot<T>
325where
326    D: DomTypes,
327    T: DomObject + DomObjectWrap<D>,
328    U: DerivedFrom<D::GlobalScope>,
329{
330    let global_scope = global.upcast();
331    unsafe { T::WRAP(cx, global_scope, None, obj) }
332}
333
334/// Create the reflector for a new DOM object and yield ownership to the
335/// reflector.
336pub fn reflect_weak_referenceable_dom_object<D, T, U>(
337    cx: &mut JSContext,
338    obj: Rc<T>,
339    global: &U,
340) -> DomRoot<T>
341where
342    D: DomTypes,
343    T: DomObject + WeakReferenceableDomObjectWrap<D>,
344    U: DerivedFrom<D::GlobalScope>,
345{
346    let global_scope = global.upcast();
347    unsafe { T::WRAP(cx, global_scope, None, obj) }
348}
349
350pub fn reflect_weak_referenceable_dom_object_with_proto<D, T, U>(
351    cx: &mut JSContext,
352    obj: Rc<T>,
353    global: &U,
354    proto: Option<HandleObject>,
355) -> DomRoot<T>
356where
357    D: DomTypes,
358    T: DomObject + WeakReferenceableDomObjectWrap<D>,
359    U: DerivedFrom<D::GlobalScope>,
360{
361    let global_scope = global.upcast();
362    unsafe { T::WRAP(cx, global_scope, proto, obj) }
363}
364
365type WrapFn<D, AbstractType> = unsafe fn(
366    &mut js::context::JSContext,
367    &<D as DomTypes>::GlobalScope,
368    Option<HandleObject>,
369    Box<AbstractType>,
370) -> DomRoot<AbstractType>;
371
372/// Create the reflector for a new DOM object and yield ownership to the
373/// reflector.
374pub fn reflect_dom_object_with_proto_and_wrap<D, AbstractType, GlobalType>(
375    obj: Box<AbstractType>,
376    global: &GlobalType,
377    proto: Option<HandleObject>,
378    cx: &mut js::context::JSContext,
379    wrap: WrapFn<D, AbstractType>,
380) -> DomRoot<AbstractType>
381where
382    D: DomTypes,
383    AbstractType: DomObject,
384    GlobalType: DerivedFrom<D::GlobalScope>,
385    Box<AbstractType>: From<Box<AbstractType>>,
386    DomRoot<AbstractType>: From<DomRoot<AbstractType>>,
387{
388    let global_scope = global.upcast();
389    unsafe { wrap(cx, global_scope, proto, obj) }
390}
391
392/// Create the reflector for a new DOM object and yield ownership to the
393/// reflector.
394pub fn reflect_dom_object_with_wrap<D, AbstractType, GlobalType>(
395    obj: Box<AbstractType>,
396    global: &GlobalType,
397    cx: &mut js::context::JSContext,
398    wrap: WrapFn<D, AbstractType>,
399) -> DomRoot<AbstractType>
400where
401    D: DomTypes,
402    AbstractType: DomObject,
403    GlobalType: DerivedFrom<D::GlobalScope>,
404    Box<AbstractType>: From<Box<AbstractType>>,
405    DomRoot<AbstractType>: From<DomRoot<AbstractType>>,
406{
407    let global_scope = global.upcast();
408    unsafe { wrap(cx, global_scope, None, obj) }
409}
410
411type WrapFnRc<D, AbstractType> = unsafe fn(
412    &mut js::context::JSContext,
413    &<D as DomTypes>::GlobalScope,
414    Option<HandleObject>,
415    Rc<AbstractType>,
416) -> DomRoot<AbstractType>;
417
418/// Create the reflector for a new DOM object and yield ownership to the
419/// reflector.
420pub fn reflect_weak_referenceable_dom_object_with_cx_and_wrap<D, AbstractType, GlobalType>(
421    cx: &mut JSContext,
422    obj: Rc<AbstractType>,
423    global: &GlobalType,
424    wrap: WrapFnRc<D, AbstractType>,
425) -> DomRoot<AbstractType>
426where
427    D: DomTypes,
428    AbstractType: DomObject,
429    GlobalType: DerivedFrom<D::GlobalScope>,
430    Rc<AbstractType>: From<Rc<AbstractType>>,
431    DomRoot<AbstractType>: From<DomRoot<AbstractType>>,
432{
433    let global_scope = global.upcast();
434    unsafe { wrap(cx, global_scope, None, obj) }
435}