Skip to main content

script_bindings/
dom.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/. */
4use std::cell::UnsafeCell;
5use std::ops::Deref;
6use std::{mem, ptr};
7
8use js::context::NoGC;
9use malloc_size_of::{MallocSizeOf, MallocSizeOfOps};
10
11use crate::DomObject;
12use crate::assert::{assert_in_layout, assert_in_script};
13use crate::conversions::DerivedFrom;
14use crate::inheritance::Castable;
15use crate::root::{Dom, DomRoot};
16
17pub trait ToLayout<'dom, T: DomObject, L: LayoutFromRaw<'dom, T>> {
18    /// Returns `LayoutDom<T>` containing the same pointer.
19    ///
20    /// # Safety
21    ///
22    /// The `self` parameter to this method must meet all the requirements of [`ptr::NonNull::as_ref`].
23    unsafe fn to_layout(&self) -> L;
24}
25
26impl<'dom, T: DomObject, L: LayoutFromRaw<'dom, T>> ToLayout<'dom, T, L> for Dom<T> {
27    unsafe fn to_layout(&self) -> L {
28        assert_in_layout();
29        L::from_raw(unsafe { self.as_ptr().as_ref().unwrap() })
30    }
31}
32
33/// A holder that provides interior mutability for GC-managed values such as
34/// `Dom<T>`.  Essentially a `Cell<Dom<T>>`, but safer.
35///
36/// This should only be used as a field in other DOM objects; see warning
37/// on `Dom<T>`.
38#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
39#[derive(JSTraceable)]
40pub struct MutDom<T: DomObject> {
41    val: UnsafeCell<Dom<T>>,
42}
43
44impl<T: DomObject> MutDom<T> {
45    /// Create a new `MutDom`.
46    pub fn new(initial: &T) -> MutDom<T> {
47        assert_in_script();
48        MutDom {
49            val: UnsafeCell::new(Dom::from_ref(initial)),
50        }
51    }
52
53    /// Set this `MutDom` to the given value.
54    pub fn set(&self, val: &T) {
55        assert_in_script();
56        unsafe {
57            *self.val.get() = Dom::from_ref(val);
58        }
59    }
60
61    /// Get the value in this `MutDom`.
62    pub fn get(&self) -> DomRoot<T> {
63        assert_in_script();
64        unsafe { DomRoot::from_ref(&*ptr::read(self.val.get())) }
65    }
66}
67
68impl<T: DomObject> MallocSizeOf for MutDom<T> {
69    fn size_of(&self, _ops: &mut MallocSizeOfOps) -> usize {
70        // See comment on MallocSizeOf for Dom<T>.
71        0
72    }
73}
74
75impl<T: DomObject> PartialEq for MutDom<T> {
76    fn eq(&self, other: &Self) -> bool {
77        unsafe { *self.val.get() == *other.val.get() }
78    }
79}
80
81impl<T: DomObject + PartialEq> PartialEq<T> for MutDom<T> {
82    fn eq(&self, other: &T) -> bool {
83        unsafe { **self.val.get() == *other }
84    }
85}
86
87/// A struct to make Unrooted Dom objects work. By taking a no_gc as reference, we ensure that the lifetime of this object
88/// is bounded by the lifetime of NoGC which enforces no gc happening.
89#[cfg_attr(crown, crown::unrooted_must_root_lint::allow_unrooted_interior)]
90pub struct UnrootedDom<'a, T: DomObject> {
91    inner: Dom<T>,
92    no_gc: &'a NoGC,
93}
94
95impl<'a, T: DomObject> UnrootedDom<'a, T> {
96    /// Construct an `UnrootedDom` with the lifetime of `NoGC`. This is safe, as `NoGC` implies no garbage collection will happen
97    #[cfg_attr(crown, expect(crown::unrooted_must_root))]
98    pub fn from_dom(object: Dom<T>, no_gc: &'a NoGC) -> UnrootedDom<'a, T> {
99        UnrootedDom {
100            inner: object,
101            no_gc,
102        }
103    }
104}
105
106impl<'a, T: DomObject> Deref for UnrootedDom<'a, T> {
107    type Target = Dom<T>;
108
109    fn deref(&self) -> &Self::Target {
110        &self.inner
111    }
112}
113
114/// Safety:
115/// We enforce the same lifetime as the given `UnrootedDom`, so the same
116/// guarantee about no GC happening in this lifetime.
117impl<'a, T: Castable> UnrootedDom<'a, T> {
118    /// Cast a DOM object root upwards to one of the interfaces it derives from.
119    pub fn upcast<U>(dom: UnrootedDom<'a, T>) -> UnrootedDom<'a, U>
120    where
121        U: Castable,
122        T: DerivedFrom<U>,
123    {
124        UnrootedDom {
125            inner: unsafe { mem::transmute::<Dom<T>, Dom<U>>(dom.inner) },
126            no_gc: dom.no_gc,
127        }
128    }
129
130    /// Cast a DOM object root downwards to one of the interfaces it might implement.
131    pub fn downcast<U>(dom: UnrootedDom<'a, T>) -> Option<UnrootedDom<'a, U>>
132    where
133        U: DerivedFrom<T>,
134    {
135        if dom.is::<U>() {
136            Some(UnrootedDom {
137                inner: unsafe { mem::transmute::<Dom<T>, Dom<U>>(dom.inner) },
138                no_gc: dom.no_gc,
139            })
140        } else {
141            None
142        }
143    }
144}
145
146impl<'a, T: DomObject> PartialEq<&T> for UnrootedDom<'a, T> {
147    fn eq(&self, other: &&T) -> bool {
148        self.inner == Dom::from_ref(*other)
149    }
150}
151
152/// Trait that creates a specific struct from a raw DomObject.
153/// The implementer needs to be sure that this does not violate any lifetimes
154/// # Safety
155/// The dom object needs the lifetimes to be safe.
156/// Only [`LayoutDom`] should implement this.
157pub unsafe trait LayoutFromRaw<'dom, T: DomObject> {
158    fn from_raw(d: &'dom T) -> Self;
159}
160
161/// A holder that provides interior mutability for GC-managed values such as
162/// `Dom<T>`, with nullability represented by an enclosing Option wrapper.
163/// Essentially a `Cell<Option<Dom<T>>>`, but safer.
164///
165/// This should only be used as a field in other DOM objects; see warning
166/// on `Dom<T>`.
167#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
168#[derive(JSTraceable)]
169pub struct MutNullableDom<T: DomObject> {
170    ptr: UnsafeCell<Option<Dom<T>>>,
171}
172
173impl<T: DomObject> MutNullableDom<T> {
174    /// Create a new `MutNullableDom`.
175    pub fn new(initial: Option<&T>) -> MutNullableDom<T> {
176        assert_in_script();
177        MutNullableDom {
178            ptr: UnsafeCell::new(initial.map(Dom::from_ref)),
179        }
180    }
181
182    /// Retrieve a copy of the current inner value. If it is `None`, it is
183    /// initialized with the result of `cb` first.
184    pub fn or_init<F>(&self, cb: F) -> DomRoot<T>
185    where
186        F: FnOnce() -> DomRoot<T>,
187    {
188        assert_in_script();
189        match self.get() {
190            Some(inner) => inner,
191            None => {
192                let inner = cb();
193                self.set(Some(&inner));
194                inner
195            },
196        }
197    }
198
199    /// Retrieve a copy of the inner optional `Dom<T>` as `LayoutDom<T>`.
200    /// For use by layout, which can't use safe types like Temporary.
201    /// # Safety
202    /// Needs to meet the safety requirements of [`lLayoutFromRaw`].
203    pub unsafe fn get_inner_as_layout<'dom, L: LayoutFromRaw<'dom, T>>(&'dom self) -> Option<L> {
204        assert_in_layout();
205        unsafe { (*self.ptr.get()).as_ref().map(|js| js.to_layout()) }
206    }
207
208    /// Get a rooted value out of this object
209    pub fn get(&self) -> Option<DomRoot<T>> {
210        assert_in_script();
211        unsafe { ptr::read(self.ptr.get()).map(|o| DomRoot::from_ref(&*o)) }
212    }
213
214    /// Get the `DomObject` without rooting it. Constructing an UnrootedDom. This is safe
215    /// as we take a reference to NoGC and bound the lifetime by NoGC bound. This implies that
216    /// while the `UnrootedDom` is alive we do not have a GC run.
217    #[cfg_attr(crown, expect(crown::unrooted_must_root))]
218    pub fn get_unrooted<'a>(&self, no_gc: &'a NoGC) -> Option<UnrootedDom<'a, T>> {
219        assert_in_script();
220        let ptr = unsafe { ptr::read(self.ptr.get()) };
221        ptr.map(|o| Dom::from_ref(&*o))
222            .map(|dom| UnrootedDom { inner: dom, no_gc })
223    }
224
225    /// Set this `MutNullableDom` to the given value.
226    pub fn set(&self, val: Option<&T>) {
227        assert_in_script();
228        unsafe {
229            *self.ptr.get() = val.map(|p| Dom::from_ref(p));
230        }
231    }
232
233    /// Gets the current value out of this object and sets it to `None`.
234    pub fn take(&self) -> Option<DomRoot<T>> {
235        let value = self.get();
236        self.set(None);
237        value
238    }
239
240    /// Sets the current value of this [`MutNullableDom`] to `None`.
241    pub fn clear(&self) {
242        self.set(None)
243    }
244
245    /// Runs the given callback on the object if it's not null.
246    pub fn if_is_some<F, R>(&self, cb: F) -> Option<&R>
247    where
248        F: FnOnce(&T) -> &R,
249    {
250        unsafe {
251            if let Some(ref value) = *self.ptr.get() {
252                Some(cb(value))
253            } else {
254                None
255            }
256        }
257    }
258}
259
260impl<T: DomObject> PartialEq for MutNullableDom<T> {
261    fn eq(&self, other: &Self) -> bool {
262        unsafe { *self.ptr.get() == *other.ptr.get() }
263    }
264}
265
266impl<T: DomObject> PartialEq<Option<&T>> for MutNullableDom<T> {
267    fn eq(&self, other: &Option<&T>) -> bool {
268        unsafe { *self.ptr.get() == other.map(Dom::from_ref) }
269    }
270}
271
272impl<T: DomObject> Default for MutNullableDom<T> {
273    fn default() -> MutNullableDom<T> {
274        assert_in_script();
275        MutNullableDom {
276            ptr: UnsafeCell::new(None),
277        }
278    }
279}
280
281impl<T: DomObject> MallocSizeOf for MutNullableDom<T> {
282    fn size_of(&self, _ops: &mut MallocSizeOfOps) -> usize {
283        // See comment on MallocSizeOf for Dom<T>.
284        0
285    }
286}