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::hash::{Hash, Hasher};
6use std::marker::PhantomData;
7use std::ops::Deref;
8use std::{mem, ptr};
9
10use js::context::NoGC;
11use malloc_size_of::{MallocSizeOf, MallocSizeOfOps};
12
13use crate::DomObject;
14use crate::assert::assert_in_script;
15use crate::conversions::DerivedFrom;
16use crate::inheritance::Castable;
17use crate::root::{Dom, DomRoot};
18
19/// A holder that provides interior mutability for GC-managed values such as
20/// `Dom<T>`.  Essentially a `Cell<Dom<T>>`, but safer.
21///
22/// This should only be used as a field in other DOM objects; see warning
23/// on `Dom<T>`.
24#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
25#[derive(JSTraceable)]
26pub struct MutDom<T: DomObject> {
27    val: UnsafeCell<Dom<T>>,
28}
29
30impl<T: DomObject> MutDom<T> {
31    /// Create a new `MutDom`.
32    pub fn new(initial: &T) -> MutDom<T> {
33        assert_in_script();
34        MutDom {
35            val: UnsafeCell::new(Dom::from_ref(initial)),
36        }
37    }
38
39    /// Set this `MutDom` to the given value.
40    pub fn set(&self, val: &T) {
41        assert_in_script();
42        unsafe {
43            *self.val.get() = Dom::from_ref(val);
44        }
45    }
46
47    /// Get the value in this `MutDom`.
48    pub fn get(&self) -> DomRoot<T> {
49        assert_in_script();
50        unsafe { DomRoot::from_ref(&*ptr::read(self.val.get())) }
51    }
52
53    /// Get the [`DomObject`] without rooting it as an [`UnrootedDom`]. This is safe as
54    /// the return value shares the lifetime of the provided [`NoGC`] This implies that
55    /// while the [`UnrootedDom`] is alive, garbage collection will not happen.
56    pub fn get_unrooted<'a>(&self, _: &'a NoGC) -> UnrootedDom<'a, T> {
57        assert_in_script();
58        UnrootedDom {
59            inner: unsafe { ptr::read(self.val.get()) },
60            _phantom: PhantomData,
61        }
62    }
63
64    /// Get a reference to the traced inner value of this [`MutDom`].
65    ///
66    /// # Safety
67    ///
68    /// - The caller *must not* modify the value of the [`MutDom`] while the
69    ///   reference is alive.
70    /// - The caller *must ensure* that no garbage collection happens while the
71    ///   reference is alive.
72    pub unsafe fn as_ref_unsafe(&self) -> &Dom<T> {
73        unsafe { &*self.val.get() }
74    }
75}
76
77impl<T: DomObject> MallocSizeOf for MutDom<T> {
78    fn size_of(&self, _ops: &mut MallocSizeOfOps) -> usize {
79        // See comment on MallocSizeOf for Dom<T>.
80        0
81    }
82}
83
84impl<T: DomObject> PartialEq for MutDom<T> {
85    fn eq(&self, other: &Self) -> bool {
86        unsafe { *self.val.get() == *other.val.get() }
87    }
88}
89
90impl<T: DomObject + PartialEq> PartialEq<T> for MutDom<T> {
91    fn eq(&self, other: &T) -> bool {
92        unsafe { **self.val.get() == *other }
93    }
94}
95
96/// A reference to a [`DomObject`] that can live on the stack unrooted by having it
97/// inherit the lifetime of a [`NoGC`], which is a token that ensures that garbage
98/// collection will not happen.
99#[cfg_attr(crown, crown::unrooted_must_root_lint::allow_unrooted_interior)]
100pub struct UnrootedDom<'a, T: DomObject> {
101    inner: Dom<T>,
102    _phantom: PhantomData<&'a ()>,
103}
104
105impl<'a, T: DomObject + std::fmt::Debug> std::fmt::Debug for UnrootedDom<'a, T> {
106    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
107        self.inner.fmt(f)
108    }
109}
110
111impl<'a, T: DomObject> Clone for UnrootedDom<'a, T> {
112    fn clone(&self) -> Self {
113        Self {
114            inner: self.inner.clone(),
115            _phantom: PhantomData,
116        }
117    }
118}
119
120impl<'a, T: DomObject> UnrootedDom<'a, T> {
121    /// Construct an [`UnrootedDom`] with the lifetime of the given [`NoGC`] token. It is
122    /// safe to keep the returned value on the stack as it cannot outlive the lifetime of
123    /// the token and the token should ensure that no garbage collection will take place
124    /// as long as it is alive.
125    #[cfg_attr(crown, expect(crown::unrooted_must_root))]
126    pub(crate) fn from_dom(object: Dom<T>, _no_gc: &'a NoGC) -> UnrootedDom<'a, T> {
127        UnrootedDom {
128            inner: object,
129            _phantom: PhantomData,
130        }
131    }
132
133    /// Construct an [`UnrootedDom`] with the lifetime of the given [`NoGC`] token. It is
134    /// safe to keep the returned value on the stack as it cannot outlive the lifetime of
135    /// the token and the token should ensure that no garbage collection will take place
136    /// as long as it is alive.
137    pub fn from_ref(object: &T, _no_gc: &'a NoGC) -> UnrootedDom<'a, T> {
138        UnrootedDom {
139            inner: Dom::from_ref(object),
140            _phantom: PhantomData,
141        }
142    }
143}
144
145impl<'a, T: DomObject> Deref for UnrootedDom<'a, T> {
146    type Target = Dom<T>;
147
148    fn deref(&self) -> &Self::Target {
149        &self.inner
150    }
151}
152
153impl<'a, T: Castable> UnrootedDom<'a, T> {
154    /// Cast a DOM object root upwards to one of the interfaces it derives from.
155    pub fn upcast<U>(dom: UnrootedDom<'a, T>) -> UnrootedDom<'a, U>
156    where
157        U: Castable,
158        T: DerivedFrom<U>,
159    {
160        UnrootedDom {
161            inner: unsafe { mem::transmute::<Dom<T>, Dom<U>>(dom.inner) },
162            _phantom: PhantomData,
163        }
164    }
165
166    /// Cast a DOM object root downwards to one of the interfaces it might implement.
167    pub fn downcast<U>(dom: UnrootedDom<'a, T>) -> Option<UnrootedDom<'a, U>>
168    where
169        U: DerivedFrom<T>,
170    {
171        if dom.is::<U>() {
172            Some(UnrootedDom {
173                inner: unsafe { mem::transmute::<Dom<T>, Dom<U>>(dom.inner) },
174                _phantom: PhantomData,
175            })
176        } else {
177            None
178        }
179    }
180}
181
182impl<'a, T: DomObject> PartialEq<T> for UnrootedDom<'a, T> {
183    fn eq(&self, other: &T) -> bool {
184        self.inner == other
185    }
186}
187
188/// Forwards to `impl PartialEq for Dom<T>` which compares by pointer address
189impl<'a, 'b, T: DomObject> PartialEq<UnrootedDom<'a, T>> for UnrootedDom<'b, T> {
190    fn eq(&self, other: &UnrootedDom<'a, T>) -> bool {
191        self.inner == other.inner
192    }
193}
194
195impl<'a, T: DomObject> Eq for UnrootedDom<'a, T> {}
196
197/// Forwards to `impl Hash for Dom<T>` which hashes the pointer address
198impl<'a, T: DomObject> Hash for UnrootedDom<'a, T> {
199    fn hash<H: Hasher>(&self, state: &mut H) {
200        self.inner.hash(state);
201    }
202}
203
204/// A holder that provides interior mutability for GC-managed values such as
205/// `Dom<T>`, with nullability represented by an enclosing Option wrapper.
206/// Essentially a `Cell<Option<Dom<T>>>`, but safer.
207///
208/// This should only be used as a field in other DOM objects; see warning
209/// on `Dom<T>`.
210#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
211#[derive(JSTraceable)]
212pub struct MutNullableDom<T: DomObject> {
213    ptr: UnsafeCell<Option<Dom<T>>>,
214}
215
216impl<T: DomObject> MutNullableDom<T> {
217    /// Create a new `MutNullableDom`.
218    pub fn new(initial: Option<&T>) -> MutNullableDom<T> {
219        assert_in_script();
220        MutNullableDom {
221            ptr: UnsafeCell::new(initial.map(Dom::from_ref)),
222        }
223    }
224
225    /// Retrieve a copy of the current inner value. If it is `None`, it is
226    /// initialized with the result of `cb` first.
227    pub fn or_init<F>(&self, cb: F) -> DomRoot<T>
228    where
229        F: FnOnce() -> DomRoot<T>,
230    {
231        assert_in_script();
232        match self.get() {
233            Some(inner) => inner,
234            None => {
235                let inner = cb();
236                self.set(Some(&inner));
237                inner
238            },
239        }
240    }
241
242    /// Get a rooted ([`DomRoot`]) reference to the value contained in this
243    /// [`MutNullableDom`].
244    pub fn get(&self) -> Option<DomRoot<T>> {
245        assert_in_script();
246        unsafe { ptr::read(self.ptr.get()).map(|o| DomRoot::from_ref(&*o)) }
247    }
248
249    /// Get a reference to the traced inner value of this [`MutNullableDom`].
250    ///
251    /// # Safety
252    ///
253    /// - The caller *must not* modify the value of the [`MutNullableDom`] while the
254    ///   reference is alive.
255    /// - The caller *must ensure* that no garbage collection happens while the
256    ///   reference is alive.
257    pub unsafe fn as_ref_unsafe(&self) -> Option<&Dom<T>> {
258        unsafe { (*self.ptr.get()).as_ref() }
259    }
260
261    /// Get the `DomObject` without rooting it. Constructing an UnrootedDom. This is safe
262    /// as we take a reference to NoGC and bound the lifetime by NoGC bound. This implies that
263    /// while the `UnrootedDom` is alive we do not have a GC run.
264    #[cfg_attr(crown, expect(crown::unrooted_must_root))]
265    pub fn get_unrooted<'a>(&self, _: &'a NoGC) -> Option<UnrootedDom<'a, T>> {
266        assert_in_script();
267        let ptr = unsafe { ptr::read(self.ptr.get()) };
268        ptr.map(|traced_value| Dom::from_ref(&*traced_value))
269            .map(|dom| UnrootedDom {
270                inner: dom,
271                _phantom: PhantomData,
272            })
273    }
274
275    /// Set this `MutNullableDom` to the given value.
276    pub fn set(&self, val: Option<&T>) {
277        assert_in_script();
278        unsafe {
279            *self.ptr.get() = val.map(|p| Dom::from_ref(p));
280        }
281    }
282
283    /// Gets the current value out of this object and sets it to `None`.
284    pub fn take(&self) -> Option<DomRoot<T>> {
285        let value = self.get();
286        self.set(None);
287        value
288    }
289
290    /// Sets the current value of this [`MutNullableDom`] to `None`.
291    pub fn clear(&self) {
292        self.set(None)
293    }
294
295    /// Runs the given callback on the object if it's not null.
296    pub fn if_is_some<F, R>(&self, cb: F) -> Option<&R>
297    where
298        F: FnOnce(&T) -> &R,
299    {
300        unsafe {
301            if let Some(ref value) = *self.ptr.get() {
302                Some(cb(value))
303            } else {
304                None
305            }
306        }
307    }
308}
309
310impl<T: DomObject> PartialEq for MutNullableDom<T> {
311    fn eq(&self, other: &Self) -> bool {
312        unsafe { *self.ptr.get() == *other.ptr.get() }
313    }
314}
315
316impl<T: DomObject> PartialEq<Option<&T>> for MutNullableDom<T> {
317    fn eq(&self, other: &Option<&T>) -> bool {
318        unsafe { *self.ptr.get() == other.map(Dom::from_ref) }
319    }
320}
321
322impl<T: DomObject> Default for MutNullableDom<T> {
323    fn default() -> MutNullableDom<T> {
324        assert_in_script();
325        MutNullableDom {
326            ptr: UnsafeCell::new(None),
327        }
328    }
329}
330
331impl<T: DomObject> MallocSizeOf for MutNullableDom<T> {
332    fn size_of(&self, _ops: &mut MallocSizeOfOps) -> usize {
333        // See comment on MallocSizeOf for Dom<T>.
334        0
335    }
336}