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}