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::{LayoutDom, 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 {
185                let value: LayoutDom<'_, _> = attr.to_layout();
186                value
187            }
188            .value(),
189        }
190    }
191
192    /// Get the local name of this attribute for layout.
193    #[expect(unsafe_code)]
194    #[inline]
195    pub(crate) fn local_name_for_layout(&self) -> &LocalName {
196        match self {
197            AttributeEntry::Raw(data) => data.local_name(),
198            AttributeEntry::Dom(attr) => unsafe {
199                let value: LayoutDom<'_, _> = attr.to_layout();
200                value
201            }
202            .local_name(),
203        }
204    }
205
206    /// Get the namespace of this attribute for layout.
207    #[expect(unsafe_code)]
208    #[inline]
209    pub(crate) fn namespace_for_layout(&self) -> &Namespace {
210        match self {
211            AttributeEntry::Raw(data) => data.namespace(),
212            AttributeEntry::Dom(attr) => unsafe {
213                let value: LayoutDom<'_, _> = attr.to_layout();
214                value
215            }
216            .namespace(),
217        }
218    }
219}
220
221/// Storage for an element's attributes. Contains an internal `DomRefCell` so that
222/// `ensure_dom` can split its borrow around `Attr::new()` allocation, preventing
223/// GC-during-allocation panics from double-borrowing.
224#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
225#[derive(Default, MallocSizeOf)]
226pub(crate) struct AttributeStorage(DomRefCell<Vec<AttributeEntry>>);
227
228/// A borrowed view of attribute storage that provides convenient access
229/// to attributes as `AttrRef` items.
230pub(crate) struct AttributesBorrow<'a>(Ref<'a, Vec<AttributeEntry>>);
231
232impl<'a> AttributesBorrow<'a> {
233    /// Iterate over attributes as `AttrRef`.
234    #[inline]
235    pub(crate) fn iter(&self) -> impl Iterator<Item = AttrRef<'_>> + '_ {
236        self.0.iter().map(AttributeEntry::as_ref)
237    }
238
239    #[inline]
240    pub(crate) fn len(&self) -> usize {
241        self.0.len()
242    }
243
244    #[inline]
245    pub(crate) fn is_empty(&self) -> bool {
246        self.0.is_empty()
247    }
248
249    #[inline]
250    pub(crate) fn first(&self) -> Option<AttrRef<'_>> {
251        self.0.first().map(AttributeEntry::as_ref)
252    }
253
254    #[inline]
255    pub(crate) fn get(&self, index: usize) -> Option<AttrRef<'_>> {
256        self.0.get(index).map(AttributeEntry::as_ref)
257    }
258}
259
260impl AttributeStorage {
261    /// Borrow the attributes for read access with convenient `AttrRef` iteration.
262    #[inline]
263    pub(crate) fn borrow(&self) -> AttributesBorrow<'_> {
264        AttributesBorrow(self.0.borrow())
265    }
266
267    /// Borrow the underlying entries for layout access (unsafe).
268    #[expect(unsafe_code)]
269    #[inline]
270    pub(crate) unsafe fn borrow_for_layout(&self) -> &Vec<AttributeEntry> {
271        unsafe { self.0.borrow_for_layout() }
272    }
273
274    /// Push raw attribute data.
275    pub(crate) fn push_raw(&self, data: ContentAttributeData) {
276        self.0.borrow_mut().push(AttributeEntry::Raw(data));
277    }
278
279    /// Push a Dom Attr node.
280    pub(crate) fn push_dom(&self, attr: &Attr) {
281        self.0
282            .borrow_mut()
283            .push(AttributeEntry::Dom(Dom::from_ref(attr)));
284    }
285
286    /// Remove an attribute by index, returning the entry.
287    pub(crate) fn remove(&self, index: usize) -> AttributeEntry {
288        self.0.borrow_mut().remove(index)
289    }
290
291    /// Set an attribute entry by index.
292    #[cfg_attr(crown, expect(crown::unrooted_must_root))]
293    pub(crate) fn set(&self, index: usize, entry: AttributeEntry) {
294        self.0.borrow_mut()[index] = entry;
295    }
296
297    /// Ensure entry at index is a Dom Attr node, materializing if needed.
298    /// Returns a `DomRoot<Attr>`.
299    ///
300    /// This method carefully splits the mutable borrow around the `Attr::new()`
301    /// allocation so that GC tracing can safely borrow the storage.
302    #[cfg_attr(crown, expect(crown::unrooted_must_root))]
303    pub(crate) fn ensure_dom(
304        &self,
305        cx: &mut JSContext,
306        index: usize,
307        element: &Element,
308    ) -> DomRoot<Attr> {
309        // Fast path: already materialized.
310        if let AttributeEntry::Dom(attr) = &self.0.borrow()[index] {
311            return DomRoot::from_ref(&**attr);
312        }
313
314        // Extract the raw data, dropping the mutable borrow before allocating.
315        let data = {
316            let mut entries = self.0.borrow_mut();
317            let placeholder = AttributeEntry::Raw(ContentAttributeData {
318                identifier: AttrIdentifier {
319                    local_name: style::values::GenericAtomIdent(html5ever::local_name!("")),
320                    name: style::values::GenericAtomIdent(html5ever::local_name!("")),
321                    namespace: style::values::GenericAtomIdent(html5ever::ns!()),
322                    prefix: None,
323                },
324                value: AttrValue::String(String::new()),
325            });
326            let old = std::mem::replace(&mut entries[index], placeholder);
327            match old {
328                AttributeEntry::Raw(data) => data,
329                _ => unreachable!(),
330            }
331        };
332
333        let doc = element.owner_document();
334        let attr = Attr::new(
335            cx,
336            &doc,
337            data.identifier.local_name.0,
338            data.value,
339            data.identifier.name.0,
340            data.identifier.namespace.0,
341            data.identifier.prefix.map(|p| p.0),
342            Some(element),
343        );
344
345        self.0.borrow_mut()[index] = AttributeEntry::Dom(Dom::from_ref(&*attr));
346        attr
347    }
348}
349
350// Safety: Only Dom entries contain GC-traced Dom<Attr> pointers.
351// Raw entries have no pointers to trace.
352#[expect(unsafe_code)]
353unsafe impl crate::dom::bindings::trace::JSTraceable for AttributeStorage {
354    unsafe fn trace(&self, trc: *mut js::jsapi::JSTracer) {
355        for entry in self.0.borrow().iter() {
356            if let AttributeEntry::Dom(attr) = entry {
357                unsafe { js::rust::Trace::trace(attr, trc) };
358            }
359        }
360    }
361}