Skip to main content

script/dom/bindings/
root.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//! Smart pointers for the JS-managed DOM objects.
6//!
7//! The DOM is made up of DOM objects whose lifetime is entirely controlled by
8//! the whims of the SpiderMonkey garbage collector. The types in this module
9//! are designed to ensure that any interactions with said Rust types only
10//! occur on values that will remain alive the entire time.
11//!
12//! Here is a brief overview of the important types:
13//!
14//! - `Root<T>`: a stack-based rooted value.
15//! - `DomRoot<T>`: a stack-based reference to a rooted DOM object.
16//! - `Dom<T>`: a reference to a DOM object that can automatically be traced by
17//!   the GC when encountered as a field of a Rust structure.
18//!
19//! `Dom<T>` does not allow access to their inner value without explicitly
20//! creating a stack-based root via the `root` method. This returns a `DomRoot<T>`,
21//! which causes the JS-owned value to be uncollectable for the duration of the
22//! `Root` object's lifetime. A reference to the object can then be obtained
23//! from the `Root` object. These references are not allowed to outlive their
24//! originating `DomRoot<T>`.
25//!
26
27use std::cell::OnceCell;
28use std::default::Default;
29use std::hash::{Hash, Hasher};
30use std::mem;
31
32use js::jsapi::{Heap, JSObject, JSTracer, Value};
33use js::rust::HandleValue;
34use layout_api::TrustedNodeAddress;
35use malloc_size_of::{MallocSizeOf, MallocSizeOfOps};
36use script_bindings::assert::{assert_in_layout, assert_in_script};
37pub(crate) use script_bindings::dom::*;
38use script_bindings::reflector::DomObject;
39pub(crate) use script_bindings::root::*;
40
41use crate::dom::bindings::conversions::DerivedFrom;
42use crate::dom::bindings::inheritance::Castable;
43use crate::dom::bindings::trace::JSTraceable;
44use crate::dom::node::Node;
45
46/// An unrooted reference to a DOM object for use in layout. `Layout*Helpers`
47/// traits must be implemented on this.
48#[cfg_attr(crown, crown::unrooted_must_root_lint::allow_unrooted_interior)]
49#[repr(transparent)]
50pub struct LayoutDom<'dom, T> {
51    value: &'dom T,
52}
53
54impl LayoutDom<'_, Node> {
55    /// Create a new JS-owned value wrapped from an address known to be a
56    /// `Node` pointer.
57    pub(crate) unsafe fn from_trusted_node_address(inner: TrustedNodeAddress) -> Self {
58        assert_in_layout();
59        let TrustedNodeAddress(addr) = inner;
60        LayoutDom {
61            value: unsafe { &*(addr as *const Node) },
62        }
63    }
64}
65
66impl<'dom, T> LayoutDom<'dom, T>
67where
68    T: 'dom + DomObject,
69{
70    /// Returns a reference to the interior of this JS object. The fact
71    /// that this is unsafe is what necessitates the layout wrappers.
72    pub fn unsafe_get(self) -> &'dom T {
73        assert_in_layout();
74        self.value
75    }
76
77    /// Transforms a slice of `Dom<T>` into a slice of `LayoutDom<T>`.
78    // FIXME(nox): This should probably be done through a ToLayout trait.
79    pub(crate) unsafe fn to_layout_slice(slice: &'dom [Dom<T>]) -> &'dom [LayoutDom<'dom, T>] {
80        // This doesn't compile if Dom and LayoutDom don't have the same
81        // representation.
82        let _ = mem::transmute::<Dom<T>, LayoutDom<T>>;
83        unsafe { &*(slice as *const [Dom<T>] as *const [LayoutDom<T>]) }
84    }
85}
86
87impl<'dom, T> LayoutDom<'dom, T>
88where
89    T: Castable,
90{
91    /// Cast a DOM object root upwards to one of the interfaces it derives from.
92    pub(crate) fn upcast<U>(&self) -> LayoutDom<'dom, U>
93    where
94        U: Castable,
95        T: DerivedFrom<U>,
96    {
97        assert_in_layout();
98        LayoutDom {
99            value: self.value.upcast::<U>(),
100        }
101    }
102
103    /// Cast a DOM object downwards to one of the interfaces it might implement.
104    pub(crate) fn downcast<U>(&self) -> Option<LayoutDom<'dom, U>>
105    where
106        U: DerivedFrom<T>,
107    {
108        assert_in_layout();
109        self.value.downcast::<U>().map(|value| LayoutDom { value })
110    }
111
112    /// Returns whether this inner object is a U.
113    pub(crate) fn is<U>(&self) -> bool
114    where
115        U: DerivedFrom<T>,
116    {
117        assert_in_layout();
118        self.value.is::<U>()
119    }
120
121    /// Get a reference to the internal value.
122    ///
123    /// ## SAFETY
124    /// This function effectively circumvents all the safety provided by `LayoutDom` as it allows
125    /// performing arbitrary (potentially mutating) operations on the value. Use with caution!
126    pub(crate) unsafe fn as_ref(self) -> &'dom T {
127        self.value
128    }
129}
130
131impl<T> LayoutDom<'_, T>
132where
133    T: DomObject,
134{
135    /// Get the reflector.
136    pub(crate) unsafe fn get_jsobject(&self) -> *mut JSObject {
137        assert_in_layout();
138        self.value.reflector().get_jsobject().get()
139    }
140}
141
142impl<T> Copy for LayoutDom<'_, T> {}
143
144impl<T> PartialEq for LayoutDom<'_, T> {
145    fn eq(&self, other: &Self) -> bool {
146        std::ptr::eq(self.value, other.value)
147    }
148}
149
150impl<T> Eq for LayoutDom<'_, T> {}
151
152impl<T> Hash for LayoutDom<'_, T> {
153    fn hash<H: Hasher>(&self, state: &mut H) {
154        (self.value as *const T).hash(state)
155    }
156}
157
158#[expect(clippy::non_canonical_clone_impl)]
159impl<T> Clone for LayoutDom<'_, T> {
160    #[inline]
161    fn clone(&self) -> Self {
162        assert_in_layout();
163        *self
164    }
165}
166
167pub(crate) trait ToLayout<'dom, T: DomObject> {
168    /// Get a reference to the contents of this smart pointer as a [`LayoutDom`],
169    /// for use during layout. Note that this should only be called in the course
170    /// of layout.
171    ///
172    /// # Safety
173    /// The return value holds a Rust reference to the underlying data, which should be
174    /// safe as long as `unsafe` is not used to override the lifetime in some way.
175    ///
176    /// - The caller *must not* modify the underlying DOM object via non-layout handles.
177    /// - The caller *must ensure* that garbage collection does not occur while the
178    ///   [`LayoutDom`] handle is alive.
179    unsafe fn to_layout(&self) -> LayoutDom<'dom, T>;
180}
181
182impl<'dom, T: DomObject> ToLayout<'dom, T> for Dom<T> {
183    unsafe fn to_layout(&self) -> LayoutDom<'dom, T> {
184        assert_in_layout();
185        LayoutDom {
186            value: unsafe { self.as_ptr().as_ref().unwrap() },
187        }
188    }
189}
190
191pub(crate) trait ToLayoutOptional<'dom, T: DomObject> {
192    /// Retrieve a copy of the inner optional `Dom<T>` as `LayoutDom<T>`.
193    /// For use by layout, which can't use safe types like Temporary.
194    ///
195    /// # Safety
196    /// The return value holds a Rust reference to the underlying data, which should be
197    /// safe as long as `unsafe` is not used to override the lifetime in some way.
198    ///
199    /// - The caller *must not* modify the underlying DOM object via non-layout handles.
200    /// - The caller *must ensure* that garbage collection does not occur while the
201    ///   [`LayoutDom`] handle is alive.
202    unsafe fn to_layout(&self) -> Option<LayoutDom<'dom, T>>;
203}
204
205impl<'dom, T: DomObject> ToLayoutOptional<'dom, T> for MutNullableDom<T> {
206    unsafe fn to_layout(&self) -> Option<LayoutDom<'dom, T>> {
207        assert_in_layout();
208        unsafe { self.as_ref_unsafe().map(|dom_ref| dom_ref.to_layout()) }
209    }
210}
211
212/// A holder that allows to lazily initialize the value only once
213/// `Dom<T>`, using OnceCell
214/// Essentially a `OnceCell<Dom<T>>`.
215///
216/// This should only be used as a field in other DOM objects; see warning
217/// on `Dom<T>`.
218#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
219pub(crate) struct DomOnceCell<T: DomObject> {
220    ptr: OnceCell<Dom<T>>,
221}
222
223impl<T> DomOnceCell<T>
224where
225    T: DomObject,
226{
227    /// Retrieve a copy of the current inner value. If it is `None`, it is
228    /// initialized with the result of `cb` first.
229    pub(crate) fn init_once<F>(&self, cb: F) -> &T
230    where
231        F: FnOnce() -> DomRoot<T>,
232    {
233        assert_in_script();
234        self.ptr.get_or_init(|| Dom::from_ref(&cb()))
235    }
236}
237
238impl<T: DomObject> Default for DomOnceCell<T> {
239    fn default() -> DomOnceCell<T> {
240        assert_in_script();
241        DomOnceCell {
242            ptr: OnceCell::new(),
243        }
244    }
245}
246
247impl<T: DomObject> MallocSizeOf for DomOnceCell<T> {
248    fn size_of(&self, _ops: &mut MallocSizeOfOps) -> usize {
249        // See comment on MallocSizeOf for Dom<T>.
250        0
251    }
252}
253
254unsafe impl<T: DomObject> JSTraceable for DomOnceCell<T> {
255    unsafe fn trace(&self, trc: *mut JSTracer) {
256        if let Some(ptr) = self.ptr.get() {
257            unsafe { ptr.trace(trc) };
258        }
259    }
260}
261
262/// Converts a rooted `Heap<Value>` into a `HandleValue`.
263///
264/// This is only safe if the `Heap` is rooted (e.g., held inside a `Dom`-managed struct),
265/// and the `#[must_root]` crown lint is active to enforce rooting at compile time.
266/// Avoids repeating unsafe `from_raw` calls at each usage site.
267pub trait AsHandleValue<'a> {
268    fn as_handle_value(&'a self) -> HandleValue<'a>;
269}
270
271impl<'a> AsHandleValue<'a> for Heap<Value> {
272    #[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
273    fn as_handle_value(&'a self) -> HandleValue<'a> {
274        // SAFETY: `self` is assumed to be rooted, and `handle()` ties
275        // the lifetime to `&self`, which the compiler can enforce.
276        unsafe { HandleValue::from_marked_location(self.ptr.get() as *const _) }
277    }
278}