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