Skip to main content

script/dom/element/attributes/
storage.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
5use std::cell::Ref;
6
7use devtools_traits::AttrInfo;
8use html5ever::{LocalName, Namespace, Prefix};
9use script_bindings::cell::DomRefCell;
10use script_bindings::root::{Dom, DomRoot};
11use script_bindings::str::DOMString;
12use style::attr::{AttrIdentifier, AttrValue};
13
14use crate::dom::attr::Attr;
15use crate::dom::bindings::root::ToLayout;
16use crate::dom::element::Element;
17use crate::dom::node::node::NodeTraits;
18
19/// Lightweight attribute storage that avoids allocating a full DOM `Attr` node.
20#[derive(MallocSizeOf)]
21pub(crate) struct ContentAttributeData {
22    pub identifier: AttrIdentifier,
23    pub value: AttrValue,
24}
25
26impl ContentAttributeData {
27    #[inline]
28    pub(crate) fn local_name(&self) -> &LocalName {
29        &self.identifier.local_name.0
30    }
31
32    #[inline]
33    pub(crate) fn name(&self) -> &LocalName {
34        &self.identifier.name.0
35    }
36
37    #[inline]
38    pub(crate) fn namespace(&self) -> &Namespace {
39        &self.identifier.namespace.0
40    }
41
42    #[inline]
43    pub(crate) fn prefix(&self) -> Option<&Prefix> {
44        Some(&self.identifier.prefix.as_ref()?.0)
45    }
46
47    #[inline]
48    pub(crate) fn value(&self) -> &AttrValue {
49        &self.value
50    }
51}
52
53/// A reference to an attribute value, abstracting over direct and RefCell-borrowed access.
54pub(crate) enum AttrValueRef<'a> {
55    /// Direct reference to a value (from [`ContentAttributeData`]).
56    Direct(&'a AttrValue),
57    /// Borrowed from a [`DomRefCell`] (from [`Attr`]).
58    Borrowed(Ref<'a, AttrValue>),
59}
60
61impl std::ops::Deref for AttrValueRef<'_> {
62    type Target = AttrValue;
63
64    fn deref(&self) -> &AttrValue {
65        match self {
66            AttrValueRef::Direct(value) => value,
67            AttrValueRef::Borrowed(value) => value,
68        }
69    }
70}
71
72/// A reference to attribute data, either from a lightweight [`ContentAttributeData`]
73/// or from a full [`Attr`] DOM node. Provides the same accessor interface regardless
74/// of storage form.
75#[derive(Clone, Copy)]
76pub(crate) enum AttrRef<'a> {
77    /// Lightweight data (no DOM node allocated).
78    Raw(&'a ContentAttributeData),
79    /// Full Attr DOM node.
80    Dom(&'a Attr),
81}
82
83impl<'a> AttrRef<'a> {
84    #[inline]
85    pub(crate) fn local_name(&self) -> &'a LocalName {
86        match self {
87            AttrRef::Raw(data) => data.local_name(),
88            AttrRef::Dom(attr) => attr.local_name(),
89        }
90    }
91
92    #[inline]
93    pub(crate) fn name(&self) -> &'a LocalName {
94        match self {
95            AttrRef::Raw(data) => data.name(),
96            AttrRef::Dom(attr) => attr.name(),
97        }
98    }
99
100    #[inline]
101    pub(crate) fn namespace(&self) -> &'a Namespace {
102        match self {
103            AttrRef::Raw(data) => data.namespace(),
104            AttrRef::Dom(attr) => attr.namespace(),
105        }
106    }
107
108    #[inline]
109    pub(crate) fn prefix(&self) -> Option<&'a Prefix> {
110        match self {
111            AttrRef::Raw(data) => data.prefix(),
112            AttrRef::Dom(attr) => attr.prefix(),
113        }
114    }
115
116    #[inline]
117    pub(crate) fn value(&self) -> AttrValueRef<'a> {
118        match self {
119            AttrRef::Raw(data) => AttrValueRef::Direct(data.value()),
120            AttrRef::Dom(attr) => AttrValueRef::Borrowed(attr.value()),
121        }
122    }
123
124    /// Returns the underlying `&Attr` if this is a `Dom` reference.
125    /// Returns `None` for `Raw` data (no DOM node exists).
126    #[inline]
127    pub(crate) fn as_attr(&self) -> Option<&'a Attr> {
128        match self {
129            AttrRef::Dom(attr) => Some(attr),
130            AttrRef::Raw(_) => None,
131        }
132    }
133
134    /// Returns the attribute value as a `DOMString`, equivalent to `Attr::Value()`.
135    pub(crate) fn to_dom_string(self) -> DOMString {
136        DOMString::from(&**self.value())
137    }
138
139    /// Returns a summary for devtools, equivalent to `Attr::summarize()`.
140    pub(crate) fn summarize(&self) -> AttrInfo {
141        AttrInfo {
142            namespace: (**self.namespace()).to_owned(),
143            name: (**self.name()).to_owned(),
144            value: (**self.value()).to_owned(),
145        }
146    }
147
148    /// Returns the `AttrIdentifier` for this attribute.
149    pub(crate) fn identifier(&self) -> &AttrIdentifier {
150        match self {
151            AttrRef::Raw(data) => &data.identifier,
152            AttrRef::Dom(attr) => attr.identifier(),
153        }
154    }
155}
156
157/// A single attribute entry, either lightweight raw data or a full DOM Attr node.
158#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
159#[derive(MallocSizeOf)]
160pub(crate) enum AttributeEntry {
161    /// Lightweight data — no Attr DOM node allocated.
162    Raw(ContentAttributeData),
163    /// Full Attr DOM node.
164    Dom(Dom<Attr>),
165}
166
167impl AttributeEntry {
168    /// Get an `AttrRef` for this entry.
169    #[inline]
170    pub(crate) fn as_ref(&self) -> AttrRef<'_> {
171        match self {
172            AttributeEntry::Raw(data) => AttrRef::Raw(data),
173            AttributeEntry::Dom(attr) => AttrRef::Dom(attr),
174        }
175    }
176
177    /// Get the value of this attribute for layout.
178    #[expect(unsafe_code)]
179    #[inline]
180    pub(crate) fn value_for_layout(&self) -> &AttrValue {
181        match self {
182            AttributeEntry::Raw(data) => &data.value,
183            AttributeEntry::Dom(attr) => unsafe { attr.to_layout() }.value(),
184        }
185    }
186
187    /// Get the local name of this attribute for layout.
188    #[expect(unsafe_code)]
189    #[inline]
190    pub(crate) fn local_name_for_layout(&self) -> &LocalName {
191        match self {
192            AttributeEntry::Raw(data) => data.local_name(),
193            AttributeEntry::Dom(attr) => unsafe { attr.to_layout() }.local_name(),
194        }
195    }
196
197    /// Get the namespace of this attribute for layout.
198    #[expect(unsafe_code)]
199    #[inline]
200    pub(crate) fn namespace_for_layout(&self) -> &Namespace {
201        match self {
202            AttributeEntry::Raw(data) => data.namespace(),
203            AttributeEntry::Dom(attr) => unsafe { attr.to_layout() }.namespace(),
204        }
205    }
206}
207
208/// Storage for an element's attributes. Contains an internal `DomRefCell` so that
209/// `ensure_dom` can split its borrow around `Attr::new()` allocation, preventing
210/// GC-during-allocation panics from double-borrowing.
211#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
212#[derive(Default, MallocSizeOf)]
213pub(crate) struct AttributeStorage(DomRefCell<Vec<AttributeEntry>>);
214
215/// A borrowed view of attribute storage that provides convenient access
216/// to attributes as `AttrRef` items.
217pub(crate) struct AttributesBorrow<'a>(Ref<'a, Vec<AttributeEntry>>);
218
219impl<'a> AttributesBorrow<'a> {
220    /// Iterate over attributes as `AttrRef`.
221    #[inline]
222    pub(crate) fn iter(&self) -> impl Iterator<Item = AttrRef<'_>> + '_ {
223        self.0.iter().map(AttributeEntry::as_ref)
224    }
225
226    #[inline]
227    pub(crate) fn len(&self) -> usize {
228        self.0.len()
229    }
230
231    #[inline]
232    pub(crate) fn is_empty(&self) -> bool {
233        self.0.is_empty()
234    }
235
236    #[inline]
237    pub(crate) fn first(&self) -> Option<AttrRef<'_>> {
238        self.0.first().map(AttributeEntry::as_ref)
239    }
240
241    #[inline]
242    pub(crate) fn get(&self, index: usize) -> Option<AttrRef<'_>> {
243        self.0.get(index).map(AttributeEntry::as_ref)
244    }
245}
246
247impl AttributeStorage {
248    /// Borrow the attributes for read access with convenient `AttrRef` iteration.
249    #[inline]
250    pub(crate) fn borrow(&self) -> AttributesBorrow<'_> {
251        AttributesBorrow(self.0.borrow())
252    }
253
254    /// Borrow the underlying entries for layout access (unsafe).
255    #[expect(unsafe_code)]
256    #[inline]
257    pub(crate) unsafe fn borrow_for_layout(&self) -> &Vec<AttributeEntry> {
258        unsafe { self.0.borrow_for_layout() }
259    }
260
261    /// Push raw attribute data.
262    pub(crate) fn push_raw(&self, data: ContentAttributeData) {
263        self.0.borrow_mut().push(AttributeEntry::Raw(data));
264    }
265
266    /// Push a Dom Attr node.
267    pub(crate) fn push_dom(&self, attr: &Attr) {
268        self.0
269            .borrow_mut()
270            .push(AttributeEntry::Dom(Dom::from_ref(attr)));
271    }
272
273    /// Remove an attribute by index, returning the entry.
274    pub(crate) fn remove(&self, index: usize) -> AttributeEntry {
275        self.0.borrow_mut().remove(index)
276    }
277
278    /// Set an attribute entry by index.
279    #[cfg_attr(crown, expect(crown::unrooted_must_root))]
280    pub(crate) fn set(&self, index: usize, entry: AttributeEntry) {
281        self.0.borrow_mut()[index] = entry;
282    }
283
284    /// Ensure entry at index is a Dom Attr node, materializing if needed.
285    /// Returns a `DomRoot<Attr>`.
286    ///
287    /// This method carefully splits the mutable borrow around the `Attr::new()`
288    /// allocation so that GC tracing can safely borrow the storage.
289    #[cfg_attr(crown, expect(crown::unrooted_must_root))]
290    #[expect(unsafe_code)]
291    pub(crate) fn ensure_dom(&self, index: usize, element: &Element) -> DomRoot<Attr> {
292        // Fast path: already materialized.
293        if let AttributeEntry::Dom(attr) = &self.0.borrow()[index] {
294            return DomRoot::from_ref(&**attr);
295        }
296
297        // Extract the raw data, dropping the mutable borrow before allocating.
298        let data = {
299            let mut entries = self.0.borrow_mut();
300            let placeholder = AttributeEntry::Raw(ContentAttributeData {
301                identifier: AttrIdentifier {
302                    local_name: style::values::GenericAtomIdent(html5ever::local_name!("")),
303                    name: style::values::GenericAtomIdent(html5ever::local_name!("")),
304                    namespace: style::values::GenericAtomIdent(html5ever::ns!()),
305                    prefix: None,
306                },
307                value: AttrValue::String(String::new()),
308            });
309            let old = std::mem::replace(&mut entries[index], placeholder);
310            match old {
311                AttributeEntry::Raw(data) => data,
312                _ => unreachable!(),
313            }
314        };
315
316        // TODO: https://github.com/servo/servo/issues/42812
317        let mut cx = unsafe { script_bindings::script_runtime::temp_cx() };
318        let cx = &mut cx;
319        let doc = element.owner_document();
320        let attr = Attr::new(
321            cx,
322            &doc,
323            data.identifier.local_name.0,
324            data.value,
325            data.identifier.name.0,
326            data.identifier.namespace.0,
327            data.identifier.prefix.map(|p| p.0),
328            Some(element),
329        );
330
331        self.0.borrow_mut()[index] = AttributeEntry::Dom(Dom::from_ref(&*attr));
332        attr
333    }
334}
335
336// Safety: Only Dom entries contain GC-traced Dom<Attr> pointers.
337// Raw entries have no pointers to trace.
338#[expect(unsafe_code)]
339unsafe impl crate::dom::bindings::trace::JSTraceable for AttributeStorage {
340    unsafe fn trace(&self, trc: *mut js::jsapi::JSTracer) {
341        for entry in self.0.borrow().iter() {
342            if let AttributeEntry::Dom(attr) = entry {
343                unsafe { js::rust::Trace::trace(attr, trc) };
344            }
345        }
346    }
347}