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