1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
//! A shareable mutable container for the DOM.
use std::cell::{BorrowError, BorrowMutError};
pub use std::cell::{Ref, RefCell, RefMut};
use js::context::NoGC;
use js::jsapi::JSTracer;
use malloc_size_of::{MallocConditionalSizeOf, MallocSizeOfOps};
use crate::CustomTraceable;
use crate::assert::{assert_in_layout, assert_in_script};
/// A mutable field in the DOM.
///
/// This extends the API of `std::cell::RefCell` to allow unsafe access in
/// certain situations, with dynamic checking in debug builds.
#[derive(Clone, Debug, Default, MallocSizeOf, PartialEq)]
pub struct DomRefCell<T> {
value: RefCell<T>,
}
impl<T: MallocConditionalSizeOf> MallocConditionalSizeOf for DomRefCell<T> {
fn conditional_size_of(&self, ops: &mut MallocSizeOfOps) -> usize {
self.value.borrow().conditional_size_of(ops)
}
}
// Functionality specific to Servo's `DomRefCell` type
// ===================================================
impl<T> DomRefCell<T> {
/// Return a reference to the contents. For use in layout only.
///
/// # Safety
///
/// Unlike RefCell::borrow, this method is unsafe because it does not return a Ref, thus leaving
/// the borrow flag untouched. Mutably borrowing the RefCell while the reference returned by
/// this method is alive is undefined behaviour.
///
/// # Panics
///
/// Panics if this is called from anywhere other than the layout thread
///
/// Panics if the value is currently mutably borrowed.
#[expect(unsafe_code)]
pub unsafe fn borrow_for_layout(&self) -> &T {
assert_in_layout();
unsafe {
self.value
.try_borrow_unguarded()
.expect("cell is mutably borrowed")
}
}
/// Borrow the contents for the purpose of script deallocation.
///
/// # Safety
///
/// Unlike RefCell::borrow, this method is unsafe because it does not return a Ref, thus leaving
/// the borrow flag untouched. Mutably borrowing the RefCell while the reference returned by
/// this method is alive is undefined behaviour.
///
/// # Panics
///
/// Panics if this is called from anywhere other than the script thread.
#[expect(unsafe_code)]
#[allow(clippy::mut_from_ref)]
pub unsafe fn borrow_for_script_deallocation(&self) -> &mut T {
assert_in_script();
unsafe { &mut *self.value.as_ptr() }
}
/// Mutably borrow a cell for layout. Ideally this would use
/// `RefCell::try_borrow_mut_unguarded` but that doesn't exist yet.
///
/// # Safety
///
/// Unlike RefCell::borrow, this method is unsafe because it does not return a Ref, thus leaving
/// the borrow flag untouched. Mutably borrowing the RefCell while the reference returned by
/// this method is alive is undefined behaviour.
///
/// # Panics
///
/// Panics if this is called from anywhere other than the layout thread.
#[expect(unsafe_code)]
#[allow(clippy::mut_from_ref)]
pub unsafe fn borrow_mut_for_layout(&self) -> &mut T {
assert_in_layout();
unsafe { &mut *self.value.as_ptr() }
}
/// Mutably borrows the wrapped value.
///
/// The borrow lasts until the returned `RefMut` exits scope. The value
/// cannot be borrowed while this borrow is active.
///
/// By passing a `&NoGC` we statically prevent GC from being run while the borrow is active,
/// to prevent panic when tracing (which calls `borrow`).
///
/// # Example
///
/// In simple cases one can use `NoGC` to statically ensure no GC can happen in the whole DOM method:
///
/// ```
/// use js::context::{JSContext, NoGC};
/// use script_bindings::cell::DomRefCell;
/// fn DomMethod(no_gc: &NoGC, cell: &DomRefCell<usize>) {
/// let mut mutably_borrowed = cell.safe_borrow_mut(no_gc);
/// }
/// ```
///
/// But in more complex cases, method might trigger a GC, and thus require a `&mut JSContext`.
/// In that case `&JSContext` can be used in place of `NoGC`,
/// which will make `RefMut` bounded to the lifetime of the `&JSContext`
/// and thus prevent any GC from happening while it is alive.
///
/// ```
/// use js::context::{JSContext, NoGC};
/// use script_bindings::cell::DomRefCell;
/// fn GC(cx: &mut JSContext) {}
///
/// fn DomMethod(cell: &DomRefCell<usize>, cx: &mut JSContext) {
/// {
/// let mut mutably_borrowed = cell.safe_borrow_mut(cx);
/// // do something with mutably_borrowed
///
/// // only &JSContext is available here
/// } // mutably_borrowed goes out of scope here
/// // so one can now use &mut JSContext
/// GC(cx);
/// }
/// ```
///
/// ```compile_fail
/// use js::context::{JSContext, NoGC};
/// use script_bindings::cell::DomRefCell;
/// fn GC(cx: &mut JSContext) {}
///
/// fn DomMethod(cell: &DomRefCell<usize>, cx: &mut JSContext) {
/// {
/// let mut mutably_borrowed = cell.safe_borrow_mut(cx);
/// // do something with mutably_borrowed
///
/// // here one cannot use anything that might trigger a GC
/// // as that would require &mut JSContext
/// // but there is already existing &JSContext bounded at RefMut
/// GC(cx);
/// } // mutably_borrowed goes out of scope here
/// }
/// ```
///
/// # Panics
///
/// Panics if the value is currently borrowed.
#[track_caller]
pub fn safe_borrow_mut<'a: 'r, 'no_cx: 'r, 'r>(
&'a self,
_no_gc: &'no_cx NoGC,
) -> RefMut<'r, T> {
self.value.borrow_mut()
}
/// Mutably borrows the wrapped value.
///
/// The borrow lasts until the returned `RefMut` exits scope. The value
/// cannot be borrowed while this borrow is active.
///
/// By passing a `&NoGC` we statically prevent GC from being run while the borrow is active,
/// to prevent panic when tracing (which calls `borrow`).
///
/// Returns `None` if the value is currently borrowed.
pub fn safe_try_borrow_mut<'a: 'r, 'no_cx: 'r, 'r>(
&'a self,
_no_gc: &'no_cx NoGC,
) -> Result<RefMut<'r, T>, BorrowMutError> {
self.value.try_borrow_mut()
}
}
// Functionality duplicated with `std::cell::RefCell`
// ===================================================
impl<T> DomRefCell<T> {
/// Create a new `DomRefCell` containing `value`.
pub fn new(value: T) -> DomRefCell<T> {
DomRefCell {
value: RefCell::new(value),
}
}
/// Immutably borrows the wrapped value.
///
/// The borrow lasts until the returned `Ref` exits scope. Multiple
/// immutable borrows can be taken out at the same time.
///
/// # Panics
///
/// Panics if the value is currently mutably borrowed.
/// Panics if this is called from anywhere other than the script thread.
/// Use borrow_for_layout if the borrowed data might used during layout.
#[track_caller]
pub fn borrow(&self) -> Ref<'_, T> {
assert_in_script();
self.value.borrow()
}
/// Mutably borrows the wrapped value.
///
/// The borrow lasts until the returned `RefMut` exits scope. The value
/// cannot be borrowed while this borrow is active.
///
/// # Panics
///
/// Panics if the value is currently borrowed.
/// Panics if this is called from anywhere other than the script thread.
#[track_caller]
pub fn borrow_mut(&self) -> RefMut<'_, T> {
assert_in_script();
self.value.borrow_mut()
}
/// Attempts to immutably borrow the wrapped value.
///
/// The borrow lasts until the returned `Ref` exits scope. Multiple
/// immutable borrows can be taken out at the same time.
///
/// Returns `None` if the value is currently mutably borrowed.
///
/// # Panics
///
/// Panics if this is called off the script thread.
pub fn try_borrow(&self) -> Result<Ref<'_, T>, BorrowError> {
assert_in_script();
self.value.try_borrow()
}
/// Mutably borrows the wrapped value.
///
/// The borrow lasts until the returned `RefMut` exits scope. The value
/// cannot be borrowed while this borrow is active.
///
/// Returns `None` if the value is currently borrowed.
///
/// # Panics
///
/// Panics if this is called off the script thread.
pub fn try_borrow_mut(&self) -> Result<RefMut<'_, T>, BorrowMutError> {
assert_in_script();
self.value.try_borrow_mut()
}
}
impl<T: Default> DomRefCell<T> {
/// Takes the wrapped value, leaving `Default::default()` in its place.
///
/// # Panics
///
/// Panics if the value is currently borrowed.
pub fn take(&self) -> T {
self.value.take()
}
}
unsafe impl<T: CustomTraceable> CustomTraceable for DomRefCell<T> {
unsafe fn trace(&self, trc: *mut JSTracer) {
unsafe { (*self).borrow().trace(trc) }
}
}
unsafe impl<T: js::gc::Traceable> js::gc::Traceable for DomRefCell<T> {
unsafe fn trace(&self, trc: *mut JSTracer) {
unsafe { (*self).borrow().trace(trc) };
}
}