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