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
66unsafe impl<'dom, T: DomObject> LayoutFromRaw<'dom, T> for LayoutDom<'dom, T> {
67    fn from_raw(d: &'dom T) -> Self {
68        LayoutDom { value: d }
69    }
70}
71
72impl<'dom, T> LayoutDom<'dom, T>
73where
74    T: 'dom + DomObject,
75{
76    /// Returns a reference to the interior of this JS object. The fact
77    /// that this is unsafe is what necessitates the layout wrappers.
78    pub fn unsafe_get(self) -> &'dom T {
79        assert_in_layout();
80        self.value
81    }
82
83    /// Transforms a slice of `Dom<T>` into a slice of `LayoutDom<T>`.
84    // FIXME(nox): This should probably be done through a ToLayout trait.
85    pub(crate) unsafe fn to_layout_slice(slice: &'dom [Dom<T>]) -> &'dom [LayoutDom<'dom, T>] {
86        // This doesn't compile if Dom and LayoutDom don't have the same
87        // representation.
88        let _ = mem::transmute::<Dom<T>, LayoutDom<T>>;
89        unsafe { &*(slice as *const [Dom<T>] as *const [LayoutDom<T>]) }
90    }
91}
92
93impl<'dom, T> LayoutDom<'dom, T>
94where
95    T: Castable,
96{
97    /// Cast a DOM object root upwards to one of the interfaces it derives from.
98    pub(crate) fn upcast<U>(&self) -> LayoutDom<'dom, U>
99    where
100        U: Castable,
101        T: DerivedFrom<U>,
102    {
103        assert_in_layout();
104        LayoutDom {
105            value: self.value.upcast::<U>(),
106        }
107    }
108
109    /// Cast a DOM object downwards to one of the interfaces it might implement.
110    pub(crate) fn downcast<U>(&self) -> Option<LayoutDom<'dom, U>>
111    where
112        U: DerivedFrom<T>,
113    {
114        assert_in_layout();
115        self.value.downcast::<U>().map(|value| LayoutDom { value })
116    }
117
118    /// Returns whether this inner object is a U.
119    pub(crate) fn is<U>(&self) -> bool
120    where
121        U: DerivedFrom<T>,
122    {
123        assert_in_layout();
124        self.value.is::<U>()
125    }
126
127    /// Get a reference to the internal value.
128    ///
129    /// ## SAFETY
130    /// This function effectively circumvents all the safety provided by `LayoutDom` as it allows
131    /// performing arbitrary (potentially mutating) operations on the value. Use with caution!
132    pub(crate) unsafe fn as_ref(self) -> &'dom T {
133        self.value
134    }
135}
136
137impl<T> LayoutDom<'_, T>
138where
139    T: DomObject,
140{
141    /// Get the reflector.
142    pub(crate) unsafe fn get_jsobject(&self) -> *mut JSObject {
143        assert_in_layout();
144        self.value.reflector().get_jsobject().get()
145    }
146}
147
148impl<T> Copy for LayoutDom<'_, T> {}
149
150impl<T> PartialEq for LayoutDom<'_, T> {
151    fn eq(&self, other: &Self) -> bool {
152        std::ptr::eq(self.value, other.value)
153    }
154}
155
156impl<T> Eq for LayoutDom<'_, T> {}
157
158impl<T> Hash for LayoutDom<'_, T> {
159    fn hash<H: Hasher>(&self, state: &mut H) {
160        (self.value as *const T).hash(state)
161    }
162}
163
164#[expect(clippy::non_canonical_clone_impl)]
165impl<T> Clone for LayoutDom<'_, T> {
166    #[inline]
167    fn clone(&self) -> Self {
168        assert_in_layout();
169        *self
170    }
171}
172
173/// A holder that allows to lazily initialize the value only once
174/// `Dom<T>`, using OnceCell
175/// Essentially a `OnceCell<Dom<T>>`.
176///
177/// This should only be used as a field in other DOM objects; see warning
178/// on `Dom<T>`.
179#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
180pub(crate) struct DomOnceCell<T: DomObject> {
181    ptr: OnceCell<Dom<T>>,
182}
183
184impl<T> DomOnceCell<T>
185where
186    T: DomObject,
187{
188    /// Retrieve a copy of the current inner value. If it is `None`, it is
189    /// initialized with the result of `cb` first.
190    pub(crate) fn init_once<F>(&self, cb: F) -> &T
191    where
192        F: FnOnce() -> DomRoot<T>,
193    {
194        assert_in_script();
195        self.ptr.get_or_init(|| Dom::from_ref(&cb()))
196    }
197}
198
199impl<T: DomObject> Default for DomOnceCell<T> {
200    fn default() -> DomOnceCell<T> {
201        assert_in_script();
202        DomOnceCell {
203            ptr: OnceCell::new(),
204        }
205    }
206}
207
208impl<T: DomObject> MallocSizeOf for DomOnceCell<T> {
209    fn size_of(&self, _ops: &mut MallocSizeOfOps) -> usize {
210        // See comment on MallocSizeOf for Dom<T>.
211        0
212    }
213}
214
215unsafe impl<T: DomObject> JSTraceable for DomOnceCell<T> {
216    unsafe fn trace(&self, trc: *mut JSTracer) {
217        if let Some(ptr) = self.ptr.get() {
218            unsafe { ptr.trace(trc) };
219        }
220    }
221}
222
223/// Converts a rooted `Heap<Value>` into a `HandleValue`.
224///
225/// This is only safe if the `Heap` is rooted (e.g., held inside a `Dom`-managed struct),
226/// and the `#[must_root]` crown lint is active to enforce rooting at compile time.
227/// Avoids repeating unsafe `from_raw` calls at each usage site.
228pub trait AsHandleValue<'a> {
229    fn as_handle_value(&'a self) -> HandleValue<'a>;
230}
231
232impl<'a> AsHandleValue<'a> for Heap<Value> {
233    #[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
234    fn as_handle_value(&'a self) -> HandleValue<'a> {
235        // SAFETY: `self` is assumed to be rooted, and `handle()` ties
236        // the lifetime to `&self`, which the compiler can enforce.
237        unsafe { HandleValue::from_marked_location(self.ptr.get() as *const _) }
238    }
239}