Skip to main content

script/dom/element/attributes/
attr.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::mem;
6use std::sync::LazyLock;
7
8use dom_struct::dom_struct;
9use html5ever::{LocalName, Namespace, Prefix, local_name, ns};
10use js::context::JSContext;
11use script_bindings::cell::{DomRefCell, Ref};
12use script_bindings::domstring::TracedDOMString;
13use style::attr::{AttrIdentifier, AttrValue};
14use style::values::GenericAtomIdent;
15
16use crate::dom::bindings::codegen::Bindings::AttrBinding::AttrMethods;
17use crate::dom::bindings::codegen::UnionTypes::TrustedHTMLOrTrustedScriptOrTrustedScriptURLOrString as TrustedTypeOrString;
18use crate::dom::bindings::error::Fallible;
19use crate::dom::bindings::root::{DomRoot, LayoutDom, MutNullableDom};
20use crate::dom::bindings::str::DOMString;
21use crate::dom::document::Document;
22use crate::dom::element::Element;
23use crate::dom::node::{Node, NodeTraits};
24use crate::dom::trustedtypes::trustedtypepolicyfactory::TrustedTypePolicyFactory;
25
26// https://dom.spec.whatwg.org/#interface-attr
27#[dom_struct]
28pub(crate) struct Attr {
29    node_: Node,
30    #[no_trace]
31    identifier: AttrIdentifier,
32    #[no_trace]
33    value: DomRefCell<AttrValue>,
34
35    /// the element that owns this attribute.
36    owner: MutNullableDom<Element>,
37}
38
39impl Attr {
40    fn new_inherited(
41        document: &Document,
42        local_name: LocalName,
43        value: AttrValue,
44        name: LocalName,
45        namespace: Namespace,
46        prefix: Option<Prefix>,
47        owner: Option<&Element>,
48    ) -> Attr {
49        Attr {
50            node_: Node::new_inherited(document),
51            identifier: AttrIdentifier {
52                local_name: GenericAtomIdent(local_name),
53                name: GenericAtomIdent(name),
54                namespace: GenericAtomIdent(namespace),
55                prefix: prefix.map(GenericAtomIdent),
56            },
57            value: DomRefCell::new(value),
58            owner: MutNullableDom::new(owner),
59        }
60    }
61    #[expect(clippy::too_many_arguments)]
62    pub(crate) fn new(
63        cx: &mut js::context::JSContext,
64        document: &Document,
65        local_name: LocalName,
66        value: AttrValue,
67        name: LocalName,
68        namespace: Namespace,
69        prefix: Option<Prefix>,
70        owner: Option<&Element>,
71    ) -> DomRoot<Attr> {
72        Node::reflect_node(
73            cx,
74            Box::new(Attr::new_inherited(
75                document, local_name, value, name, namespace, prefix, owner,
76            )),
77            document,
78        )
79    }
80
81    #[inline]
82    pub(crate) fn name(&self) -> &LocalName {
83        &self.identifier.name.0
84    }
85
86    #[inline]
87    pub(crate) fn namespace(&self) -> &Namespace {
88        &self.identifier.namespace.0
89    }
90
91    #[inline]
92    pub(crate) fn prefix(&self) -> Option<&Prefix> {
93        Some(&self.identifier.prefix.as_ref()?.0)
94    }
95}
96
97impl AttrMethods<crate::DomTypeHolder> for Attr {
98    /// <https://dom.spec.whatwg.org/#dom-attr-localname>
99    fn LocalName(&self) -> DOMString {
100        TracedDOMString::from(self.local_name().clone()).root()
101    }
102
103    /// <https://dom.spec.whatwg.org/#dom-attr-value>
104    fn Value(&self) -> DOMString {
105        // FIXME(ajeffrey): convert directly from AttrValue to DOMString
106        DOMString::from(&**self.value())
107    }
108
109    /// <https://dom.spec.whatwg.org/#set-an-existing-attribute-value>
110    fn SetValue(&self, cx: &mut JSContext, value: DOMString) -> Fallible<()> {
111        // Step 2. Otherwise:
112        if let Some(owner) = self.owner() {
113            // Step 2.1. Let element be attribute’s element.
114            // Step 2.2. Let verifiedValue be the result of calling
115            // get trusted type compliant attribute value with attribute’s local name,
116            // attribute’s namespace, element, and value. [TRUSTED-TYPES]
117            let value = TrustedTypePolicyFactory::get_trusted_types_compliant_attribute_value(
118                cx,
119                owner.namespace(),
120                owner.local_name(),
121                self.local_name(),
122                Some(self.namespace()),
123                TrustedTypeOrString::String(value),
124                &owner.owner_global(),
125            )?;
126            if let Some(owner) = self.owner() {
127                // Step 2.4. Change attribute to verifiedValue.
128                let value = owner.parse_attribute(self.namespace(), self.local_name(), value);
129                owner.change_attribute(cx, self, value);
130            } else {
131                // Step 2.3. If attribute’s element is null, then set attribute’s value to verifiedValue, and return.
132                self.set_value(value);
133            }
134        } else {
135            // Step 1. If attribute’s element is null, then set attribute’s value to value.
136            self.set_value(value);
137        }
138        Ok(())
139    }
140
141    /// <https://dom.spec.whatwg.org/#dom-attr-name>
142    fn Name(&self) -> DOMString {
143        TracedDOMString::from(self.name().clone()).root()
144    }
145
146    /// <https://dom.spec.whatwg.org/#dom-attr-namespaceuri>
147    fn GetNamespaceURI(&self) -> Option<DOMString> {
148        match *self.namespace() {
149            ns!() => None,
150            ref url => Some(TracedDOMString::from(url.clone()).root()),
151        }
152    }
153
154    /// <https://dom.spec.whatwg.org/#dom-attr-prefix>
155    fn GetPrefix(&self) -> Option<DOMString> {
156        self.prefix()
157            .map(|p| TracedDOMString::from(p.clone()).root())
158    }
159
160    /// <https://dom.spec.whatwg.org/#dom-attr-ownerelement>
161    fn GetOwnerElement(&self) -> Option<DomRoot<Element>> {
162        self.owner()
163    }
164
165    /// <https://dom.spec.whatwg.org/#dom-attr-specified>
166    fn Specified(&self) -> bool {
167        true // Always returns true
168    }
169}
170
171impl Attr {
172    /// Used to swap the attribute's value without triggering mutation events
173    pub(crate) fn swap_value(&self, value: &mut AttrValue) {
174        mem::swap(&mut *self.value.borrow_mut(), value);
175    }
176
177    pub(crate) fn identifier(&self) -> &AttrIdentifier {
178        &self.identifier
179    }
180
181    pub(crate) fn value(&self) -> Ref<'_, AttrValue> {
182        self.value.borrow()
183    }
184
185    fn set_value(&self, value: DOMString) {
186        *self.value.borrow_mut() = AttrValue::String(value.into());
187    }
188
189    pub(crate) fn local_name(&self) -> &LocalName {
190        &self.identifier.local_name
191    }
192
193    /// Sets the owner element. Should be called after the attribute is added
194    /// or removed from its older parent.
195    pub(crate) fn set_owner(&self, cx: &mut JSContext, owner: Option<&Element>) {
196        let ns = self.namespace();
197        match (self.owner(), owner) {
198            (Some(old), None) => {
199                // Already gone from the list of attributes of old owner.
200                assert!(
201                    old.get_attribute_with_namespace(cx, ns, &self.identifier.local_name)
202                        .as_deref() !=
203                        Some(self)
204                )
205            },
206            (Some(old), Some(new)) => assert_eq!(&*old, new),
207            _ => {},
208        }
209        self.owner.set(owner);
210    }
211
212    pub(crate) fn owner(&self) -> Option<DomRoot<Element>> {
213        self.owner.get()
214    }
215
216    pub(crate) fn qualified_name(&self) -> DOMString {
217        match self.prefix() {
218            Some(ref prefix) => DOMString::from(format!("{}:{}", prefix, &**self.local_name())),
219            None => TracedDOMString::from(self.local_name().clone()).root(),
220        }
221    }
222}
223
224#[expect(unsafe_code)]
225impl<'dom> LayoutDom<'dom, Attr> {
226    #[inline]
227    pub(crate) fn value(self) -> &'dom AttrValue {
228        unsafe { self.unsafe_get().value.borrow_for_layout() }
229    }
230
231    #[inline]
232    pub(crate) fn local_name(self) -> &'dom LocalName {
233        &self.unsafe_get().identifier.local_name.0
234    }
235
236    #[inline]
237    pub(crate) fn namespace(self) -> &'dom Namespace {
238        &self.unsafe_get().identifier.namespace.0
239    }
240}
241
242/// A helper function to check if attribute is relevant.
243pub(crate) fn is_relevant_attribute(namespace: &Namespace, local_name: &LocalName) -> bool {
244    // <https://svgwg.org/svg2-draft/linking.html#XLinkHrefAttribute>
245    namespace == &ns!() || (namespace == &ns!(xlink) && local_name == &local_name!("href"))
246}
247
248/// A help function to check if an attribute is a boolean attribute.
249pub(crate) fn is_boolean_attribute(name: &str) -> bool {
250    // The full list of attributes can be found in [1]. All attributes marked as "Boolean
251    // attribute" in the "Value" column are boolean attributes. Note that "hidden" is effectively
252    // treated as a boolean attribute, according to WPT test "test_global_boolean_attributes" in
253    // webdriver/tests/classic/get_element_attribute/get.py
254    //
255    // [1] <https://html.spec.whatwg.org/multipage/#attributes-3>
256    static BOOLEAN_ATTRIBUTES: LazyLock<[&str; 30]> = LazyLock::new(|| {
257        [
258            "allowfullscreen",
259            "alpha",
260            "async",
261            "autofocus",
262            "autoplay",
263            "checked",
264            "controls",
265            "default",
266            "defer",
267            "disabled",
268            "formnovalidate",
269            "hidden",
270            "inert",
271            "ismap",
272            "itemscope",
273            "loop",
274            "multiple",
275            "muted",
276            "nomodule",
277            "novalidate",
278            "open",
279            "playsinline",
280            "readonly",
281            "required",
282            "reversed",
283            "selected",
284            "shadowrootclonable",
285            "shadowrootcustomelementregistry",
286            "shadowrootdelegatesfocus",
287            "shadowrootserializable",
288        ]
289    });
290
291    BOOLEAN_ATTRIBUTES
292        .iter()
293        .any(|&boolean_attr| boolean_attr.eq_ignore_ascii_case(name))
294}