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 == other
149    }
150}
151
152impl<'a, T: DomObject> PartialEq<UnrootedDom<'a, T>> for UnrootedDom<'a, T> {
153    fn eq(&self, other: &UnrootedDom<'a, T>) -> bool {
154        self.inner == other.inner
155    }
156}
157
158/// Trait that creates a specific struct from a raw DomObject.
159/// The implementer needs to be sure that this does not violate any lifetimes
160/// # Safety
161/// The dom object needs the lifetimes to be safe.
162/// Only [`LayoutDom`] should implement this.
163pub unsafe trait LayoutFromRaw<'dom, T: DomObject> {
164    fn from_raw(d: &'dom T) -> Self;
165}
166
167/// A holder that provides interior mutability for GC-managed values such as
168/// `Dom<T>`, with nullability represented by an enclosing Option wrapper.
169/// Essentially a `Cell<Option<Dom<T>>>`, but safer.
170///
171/// This should only be used as a field in other DOM objects; see warning
172/// on `Dom<T>`.
173#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
174#[derive(JSTraceable)]
175pub struct MutNullableDom<T: DomObject> {
176    ptr: UnsafeCell<Option<Dom<T>>>,
177}
178
179impl<T: DomObject> MutNullableDom<T> {
180    /// Create a new `MutNullableDom`.
181    pub fn new(initial: Option<&T>) -> MutNullableDom<T> {
182        assert_in_script();
183        MutNullableDom {
184            ptr: UnsafeCell::new(initial.map(Dom::from_ref)),
185        }
186    }
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 fn or_init<F>(&self, cb: F) -> DomRoot<T>
191    where
192        F: FnOnce() -> DomRoot<T>,
193    {
194        assert_in_script();
195        match self.get() {
196            Some(inner) => inner,
197            None => {
198                let inner = cb();
199                self.set(Some(&inner));
200                inner
201            },
202        }
203    }
204
205    /// Retrieve a copy of the inner optional `Dom<T>` as `LayoutDom<T>`.
206    /// For use by layout, which can't use safe types like Temporary.
207    /// # Safety
208    /// Needs to meet the safety requirements of [`lLayoutFromRaw`].
209    pub unsafe fn get_inner_as_layout<'dom, L: LayoutFromRaw<'dom, T>>(&'dom self) -> Option<L> {
210        assert_in_layout();
211        unsafe { (*self.ptr.get()).as_ref().map(|js| js.to_layout()) }
212    }
213
214    /// Get a rooted value out of this object
215    pub fn get(&self) -> Option<DomRoot<T>> {
216        assert_in_script();
217        unsafe { ptr::read(self.ptr.get()).map(|o| DomRoot::from_ref(&*o)) }
218    }
219
220    /// Get the `DomObject` without rooting it. Constructing an UnrootedDom. This is safe
221    /// as we take a reference to NoGC and bound the lifetime by NoGC bound. This implies that
222    /// while the `UnrootedDom` is alive we do not have a GC run.
223    #[cfg_attr(crown, expect(crown::unrooted_must_root))]
224    pub fn get_unrooted<'a>(&self, no_gc: &'a NoGC) -> Option<UnrootedDom<'a, T>> {
225        assert_in_script();
226        let ptr = unsafe { ptr::read(self.ptr.get()) };
227        ptr.map(|o| Dom::from_ref(&*o))
228            .map(|dom| UnrootedDom { inner: dom, no_gc })
229    }
230
231    /// Set this `MutNullableDom` to the given value.
232    pub fn set(&self, val: Option<&T>) {
233        assert_in_script();
234        unsafe {
235            *self.ptr.get() = val.map(|p| Dom::from_ref(p));
236        }
237    }
238
239    /// Gets the current value out of this object and sets it to `None`.
240    pub fn take(&self) -> Option<DomRoot<T>> {
241        let value = self.get();
242        self.set(None);
243        value
244    }
245
246    /// Sets the current value of this [`MutNullableDom`] to `None`.
247    pub fn clear(&self) {
248        self.set(None)
249    }
250
251    /// Runs the given callback on the object if it's not null.
252    pub fn if_is_some<F, R>(&self, cb: F) -> Option<&R>
253    where
254        F: FnOnce(&T) -> &R,
255    {
256        unsafe {
257            if let Some(ref value) = *self.ptr.get() {
258                Some(cb(value))
259            } else {
260                None
261            }
262        }
263    }
264}
265
266impl<T: DomObject> PartialEq for MutNullableDom<T> {
267    fn eq(&self, other: &Self) -> bool {
268        unsafe { *self.ptr.get() == *other.ptr.get() }
269    }
270}
271
272impl<T: DomObject> PartialEq<Option<&T>> for MutNullableDom<T> {
273    fn eq(&self, other: &Option<&T>) -> bool {
274        unsafe { *self.ptr.get() == other.map(Dom::from_ref) }
275    }
276}
277
278impl<T: DomObject> Default for MutNullableDom<T> {
279    fn default() -> MutNullableDom<T> {
280        assert_in_script();
281        MutNullableDom {
282            ptr: UnsafeCell::new(None),
283        }
284    }
285}
286
287impl<T: DomObject> MallocSizeOf for MutNullableDom<T> {
288    fn size_of(&self, _ops: &mut MallocSizeOfOps) -> usize {
289        // See comment on MallocSizeOf for Dom<T>.
290        0
291    }
292}