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, UnsafeCell};
28use std::default::Default;
29use std::hash::{Hash, Hasher};
30use std::{mem, ptr};
31
32use js::jsapi::{Heap, JSObject, JSTracer, Value};
33use js::rust::HandleValue;
34use layout_api::TrustedNodeAddress;
35use malloc_size_of::{MallocSizeOf, MallocSizeOfOps};
36pub(crate) use script_bindings::root::*;
37use style::thread_state;
38
39use crate::dom::bindings::conversions::DerivedFrom;
40use crate::dom::bindings::inheritance::Castable;
41use crate::dom::bindings::reflector::DomObject;
42use crate::dom::bindings::trace::JSTraceable;
43use crate::dom::node::Node;
44
45pub(crate) trait ToLayout<T> {
46    /// Returns `LayoutDom<T>` containing the same pointer.
47    ///
48    /// # Safety
49    ///
50    /// The `self` parameter to this method must meet all the requirements of [`ptr::NonNull::as_ref`].
51    unsafe fn to_layout(&self) -> LayoutDom<'_, T>;
52}
53
54impl<T: DomObject> ToLayout<T> for Dom<T> {
55    unsafe fn to_layout(&self) -> LayoutDom<'_, T> {
56        assert_in_layout();
57        LayoutDom {
58            value: unsafe { self.as_ptr().as_ref().unwrap() },
59        }
60    }
61}
62
63/// An unrooted reference to a DOM object for use in layout. `Layout*Helpers`
64/// traits must be implemented on this.
65#[cfg_attr(crown, crown::unrooted_must_root_lint::allow_unrooted_interior)]
66#[repr(transparent)]
67pub(crate) struct LayoutDom<'dom, T> {
68    value: &'dom T,
69}
70
71impl<'dom, T> LayoutDom<'dom, T>
72where
73    T: Castable,
74{
75    /// Cast a DOM object root upwards to one of the interfaces it derives from.
76    pub(crate) fn upcast<U>(&self) -> LayoutDom<'dom, U>
77    where
78        U: Castable,
79        T: DerivedFrom<U>,
80    {
81        assert_in_layout();
82        LayoutDom {
83            value: self.value.upcast::<U>(),
84        }
85    }
86
87    /// Cast a DOM object downwards to one of the interfaces it might implement.
88    pub(crate) fn downcast<U>(&self) -> Option<LayoutDom<'dom, U>>
89    where
90        U: DerivedFrom<T>,
91    {
92        assert_in_layout();
93        self.value.downcast::<U>().map(|value| LayoutDom { value })
94    }
95
96    /// Returns whether this inner object is a U.
97    pub(crate) fn is<U>(&self) -> bool
98    where
99        U: DerivedFrom<T>,
100    {
101        assert_in_layout();
102        self.value.is::<U>()
103    }
104}
105
106impl<T> LayoutDom<'_, T>
107where
108    T: DomObject,
109{
110    /// Get the reflector.
111    pub(crate) unsafe fn get_jsobject(&self) -> *mut JSObject {
112        assert_in_layout();
113        self.value.reflector().get_jsobject().get()
114    }
115}
116
117impl<T> Copy for LayoutDom<'_, T> {}
118
119impl<T> PartialEq for LayoutDom<'_, T> {
120    fn eq(&self, other: &Self) -> bool {
121        std::ptr::eq(self.value, other.value)
122    }
123}
124
125impl<T> Eq for LayoutDom<'_, T> {}
126
127impl<T> Hash for LayoutDom<'_, T> {
128    fn hash<H: Hasher>(&self, state: &mut H) {
129        (self.value as *const T).hash(state)
130    }
131}
132
133impl<T> Clone for LayoutDom<'_, T> {
134    #[inline]
135    #[allow(clippy::non_canonical_clone_impl)]
136    fn clone(&self) -> Self {
137        assert_in_layout();
138        *self
139    }
140}
141
142impl LayoutDom<'_, Node> {
143    /// Create a new JS-owned value wrapped from an address known to be a
144    /// `Node` pointer.
145    pub(crate) unsafe fn from_trusted_node_address(inner: TrustedNodeAddress) -> Self {
146        assert_in_layout();
147        let TrustedNodeAddress(addr) = inner;
148        LayoutDom {
149            value: unsafe { &*(addr as *const Node) },
150        }
151    }
152}
153
154/// A holder that provides interior mutability for GC-managed values such as
155/// `Dom<T>`.  Essentially a `Cell<Dom<T>>`, but safer.
156///
157/// This should only be used as a field in other DOM objects; see warning
158/// on `Dom<T>`.
159#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
160#[derive(JSTraceable)]
161pub(crate) struct MutDom<T: DomObject> {
162    val: UnsafeCell<Dom<T>>,
163}
164
165impl<T: DomObject> MutDom<T> {
166    /// Create a new `MutDom`.
167    pub(crate) fn new(initial: &T) -> MutDom<T> {
168        assert_in_script();
169        MutDom {
170            val: UnsafeCell::new(Dom::from_ref(initial)),
171        }
172    }
173
174    /// Set this `MutDom` to the given value.
175    pub(crate) fn set(&self, val: &T) {
176        assert_in_script();
177        unsafe {
178            *self.val.get() = Dom::from_ref(val);
179        }
180    }
181
182    /// Get the value in this `MutDom`.
183    pub(crate) fn get(&self) -> DomRoot<T> {
184        assert_in_script();
185        unsafe { DomRoot::from_ref(&*ptr::read(self.val.get())) }
186    }
187}
188
189impl<T: DomObject> MallocSizeOf for MutDom<T> {
190    fn size_of(&self, _ops: &mut MallocSizeOfOps) -> usize {
191        // See comment on MallocSizeOf for Dom<T>.
192        0
193    }
194}
195
196impl<T: DomObject> PartialEq for MutDom<T> {
197    fn eq(&self, other: &Self) -> bool {
198        unsafe { *self.val.get() == *other.val.get() }
199    }
200}
201
202impl<T: DomObject + PartialEq> PartialEq<T> for MutDom<T> {
203    fn eq(&self, other: &T) -> bool {
204        unsafe { **self.val.get() == *other }
205    }
206}
207
208pub(crate) fn assert_in_layout() {
209    debug_assert!(thread_state::get().is_layout());
210}
211
212/// A holder that provides interior mutability for GC-managed values such as
213/// `Dom<T>`, with nullability represented by an enclosing Option wrapper.
214/// Essentially a `Cell<Option<Dom<T>>>`, but safer.
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)]
219#[derive(JSTraceable)]
220pub(crate) struct MutNullableDom<T: DomObject> {
221    ptr: UnsafeCell<Option<Dom<T>>>,
222}
223
224impl<T: DomObject> MutNullableDom<T> {
225    /// Create a new `MutNullableDom`.
226    pub(crate) fn new(initial: Option<&T>) -> MutNullableDom<T> {
227        assert_in_script();
228        MutNullableDom {
229            ptr: UnsafeCell::new(initial.map(Dom::from_ref)),
230        }
231    }
232
233    /// Retrieve a copy of the current inner value. If it is `None`, it is
234    /// initialized with the result of `cb` first.
235    pub(crate) fn or_init<F>(&self, cb: F) -> DomRoot<T>
236    where
237        F: FnOnce() -> DomRoot<T>,
238    {
239        assert_in_script();
240        match self.get() {
241            Some(inner) => inner,
242            None => {
243                let inner = cb();
244                self.set(Some(&inner));
245                inner
246            },
247        }
248    }
249
250    /// Retrieve a copy of the inner optional `Dom<T>` as `LayoutDom<T>`.
251    /// For use by layout, which can't use safe types like Temporary.
252    #[cfg_attr(crown, allow(crown::unrooted_must_root))]
253    pub(crate) unsafe fn get_inner_as_layout(&self) -> Option<LayoutDom<'_, T>> {
254        assert_in_layout();
255        unsafe { (*self.ptr.get()).as_ref().map(|js| js.to_layout()) }
256    }
257
258    /// Get a rooted value out of this object
259    #[cfg_attr(crown, allow(crown::unrooted_must_root))]
260    pub(crate) fn get(&self) -> Option<DomRoot<T>> {
261        assert_in_script();
262        unsafe { ptr::read(self.ptr.get()).map(|o| DomRoot::from_ref(&*o)) }
263    }
264
265    /// Set this `MutNullableDom` to the given value.
266    pub(crate) fn set(&self, val: Option<&T>) {
267        assert_in_script();
268        unsafe {
269            *self.ptr.get() = val.map(|p| Dom::from_ref(p));
270        }
271    }
272
273    /// Gets the current value out of this object and sets it to `None`.
274    pub(crate) fn take(&self) -> Option<DomRoot<T>> {
275        let value = self.get();
276        self.set(None);
277        value
278    }
279
280    /// Runs the given callback on the object if it's not null.
281    pub(crate) fn if_is_some<F, R>(&self, cb: F) -> Option<&R>
282    where
283        F: FnOnce(&T) -> &R,
284    {
285        unsafe {
286            if let Some(ref value) = *self.ptr.get() {
287                Some(cb(value))
288            } else {
289                None
290            }
291        }
292    }
293}
294
295impl<T: DomObject> PartialEq for MutNullableDom<T> {
296    fn eq(&self, other: &Self) -> bool {
297        unsafe { *self.ptr.get() == *other.ptr.get() }
298    }
299}
300
301impl<T: DomObject> PartialEq<Option<&T>> for MutNullableDom<T> {
302    fn eq(&self, other: &Option<&T>) -> bool {
303        unsafe { *self.ptr.get() == other.map(Dom::from_ref) }
304    }
305}
306
307impl<T: DomObject> Default for MutNullableDom<T> {
308    #[cfg_attr(crown, allow(crown::unrooted_must_root))]
309    fn default() -> MutNullableDom<T> {
310        assert_in_script();
311        MutNullableDom {
312            ptr: UnsafeCell::new(None),
313        }
314    }
315}
316
317impl<T: DomObject> MallocSizeOf for MutNullableDom<T> {
318    fn size_of(&self, _ops: &mut MallocSizeOfOps) -> usize {
319        // See comment on MallocSizeOf for Dom<T>.
320        0
321    }
322}
323
324/// A holder that allows to lazily initialize the value only once
325/// `Dom<T>`, using OnceCell
326/// Essentially a `OnceCell<Dom<T>>`.
327///
328/// This should only be used as a field in other DOM objects; see warning
329/// on `Dom<T>`.
330#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
331pub(crate) struct DomOnceCell<T: DomObject> {
332    ptr: OnceCell<Dom<T>>,
333}
334
335impl<T> DomOnceCell<T>
336where
337    T: DomObject,
338{
339    /// Retrieve a copy of the current inner value. If it is `None`, it is
340    /// initialized with the result of `cb` first.
341    #[cfg_attr(crown, allow(crown::unrooted_must_root))]
342    pub(crate) fn init_once<F>(&self, cb: F) -> &T
343    where
344        F: FnOnce() -> DomRoot<T>,
345    {
346        assert_in_script();
347        self.ptr.get_or_init(|| Dom::from_ref(&cb()))
348    }
349}
350
351impl<T: DomObject> Default for DomOnceCell<T> {
352    #[cfg_attr(crown, allow(crown::unrooted_must_root))]
353    fn default() -> DomOnceCell<T> {
354        assert_in_script();
355        DomOnceCell {
356            ptr: OnceCell::new(),
357        }
358    }
359}
360
361impl<T: DomObject> MallocSizeOf for DomOnceCell<T> {
362    fn size_of(&self, _ops: &mut MallocSizeOfOps) -> usize {
363        // See comment on MallocSizeOf for Dom<T>.
364        0
365    }
366}
367
368#[cfg_attr(crown, allow(crown::unrooted_must_root))]
369unsafe impl<T: DomObject> JSTraceable for DomOnceCell<T> {
370    unsafe fn trace(&self, trc: *mut JSTracer) {
371        if let Some(ptr) = self.ptr.get() {
372            unsafe { ptr.trace(trc) };
373        }
374    }
375}
376
377impl<'dom, T> LayoutDom<'dom, T>
378where
379    T: 'dom + DomObject,
380{
381    /// Returns a reference to the interior of this JS object. The fact
382    /// that this is unsafe is what necessitates the layout wrappers.
383    pub(crate) fn unsafe_get(self) -> &'dom T {
384        assert_in_layout();
385        self.value
386    }
387
388    /// Transforms a slice of `Dom<T>` into a slice of `LayoutDom<T>`.
389    // FIXME(nox): This should probably be done through a ToLayout trait.
390    pub(crate) unsafe fn to_layout_slice(slice: &'dom [Dom<T>]) -> &'dom [LayoutDom<'dom, T>] {
391        // This doesn't compile if Dom and LayoutDom don't have the same
392        // representation.
393        let _ = mem::transmute::<Dom<T>, LayoutDom<T>>;
394        unsafe { &*(slice as *const [Dom<T>] as *const [LayoutDom<T>]) }
395    }
396}
397
398/// Converts a rooted `Heap<Value>` into a `HandleValue`.
399///
400/// This is only safe if the `Heap` is rooted (e.g., held inside a `Dom`-managed struct),
401/// and the `#[must_root]` crown lint is active to enforce rooting at compile time.
402/// Avoids repeating unsafe `from_raw` calls at each usage site.
403pub trait AsHandleValue<'a> {
404    fn as_handle_value(&'a self) -> HandleValue<'a>;
405}
406
407impl<'a> AsHandleValue<'a> for Heap<Value> {
408    #[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
409    fn as_handle_value(&'a self) -> HandleValue<'a> {
410        // SAFETY: `self` is assumed to be rooted, and `handle()` ties
411        // the lifetime to `&self`, which the compiler can enforce.
412        unsafe { HandleValue::from_marked_location(self.ptr.get() as *const _) }
413    }
414}