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 element be attribute’s element.
117            // Step 2.2. Let verifiedValue be the result of calling
118            // get trusted type compliant attribute value with attribute’s local name,
119            // attribute’s namespace, element, and value. [TRUSTED-TYPES]
120            let value = TrustedTypePolicyFactory::get_trusted_types_compliant_attribute_value(
121                owner.namespace(),
122                owner.local_name(),
123                self.local_name(),
124                Some(self.namespace()),
125                TrustedTypeOrString::String(value),
126                &owner.owner_global(),
127                can_gc,
128            )?;
129            if let Some(owner) = self.owner() {
130                // Step 2.4. Change attribute to verifiedValue.
131                let value = owner.parse_attribute(self.namespace(), self.local_name(), value);
132                owner.change_attribute(self, value, can_gc);
133            } else {
134                // Step 2.3. If attribute’s element is null, then set attribute’s value to verifiedValue, and return.
135                self.set_value(value);
136            }
137        } else {
138            // Step 1. If attribute’s element is null, then set attribute’s value to value.
139            self.set_value(value);
140        }
141        Ok(())
142    }
143
144    /// <https://dom.spec.whatwg.org/#dom-attr-name>
145    fn Name(&self) -> DOMString {
146        // FIXME(ajeffrey): convert directly from LocalName to DOMString
147        DOMString::from(&**self.name())
148    }
149
150    /// <https://dom.spec.whatwg.org/#dom-attr-namespaceuri>
151    fn GetNamespaceURI(&self) -> Option<DOMString> {
152        match *self.namespace() {
153            ns!() => None,
154            ref url => Some(DOMString::from(&**url)),
155        }
156    }
157
158    /// <https://dom.spec.whatwg.org/#dom-attr-prefix>
159    fn GetPrefix(&self) -> Option<DOMString> {
160        // FIXME(ajeffrey): convert directly from LocalName to DOMString
161        self.prefix().map(|p| DOMString::from(&**p))
162    }
163
164    /// <https://dom.spec.whatwg.org/#dom-attr-ownerelement>
165    fn GetOwnerElement(&self) -> Option<DomRoot<Element>> {
166        self.owner()
167    }
168
169    /// <https://dom.spec.whatwg.org/#dom-attr-specified>
170    fn Specified(&self) -> bool {
171        true // Always returns true
172    }
173}
174
175impl Attr {
176    /// Used to swap the attribute's value without triggering mutation events
177    pub(crate) fn swap_value(&self, value: &mut AttrValue) {
178        mem::swap(&mut *self.value.borrow_mut(), value);
179    }
180
181    pub(crate) fn identifier(&self) -> &AttrIdentifier {
182        &self.identifier
183    }
184
185    pub(crate) fn value(&self) -> Ref<'_, AttrValue> {
186        self.value.borrow()
187    }
188
189    fn set_value(&self, value: DOMString) {
190        *self.value.borrow_mut() = AttrValue::String(value.into());
191    }
192
193    pub(crate) fn local_name(&self) -> &LocalName {
194        &self.identifier.local_name
195    }
196
197    /// Sets the owner element. Should be called after the attribute is added
198    /// or removed from its older parent.
199    pub(crate) fn set_owner(&self, owner: Option<&Element>) {
200        let ns = self.namespace();
201        match (self.owner(), owner) {
202            (Some(old), None) => {
203                // Already gone from the list of attributes of old owner.
204                assert!(
205                    old.get_attribute(ns, &self.identifier.local_name)
206                        .as_deref() !=
207                        Some(self)
208                )
209            },
210            (Some(old), Some(new)) => assert_eq!(&*old, new),
211            _ => {},
212        }
213        self.owner.set(owner);
214    }
215
216    pub(crate) fn owner(&self) -> Option<DomRoot<Element>> {
217        self.owner.get()
218    }
219
220    pub(crate) fn summarize(&self) -> AttrInfo {
221        AttrInfo {
222            namespace: (**self.namespace()).to_owned(),
223            name: (**self.name()).to_owned(),
224            value: (**self.value()).to_owned(),
225        }
226    }
227
228    pub(crate) fn qualified_name(&self) -> DOMString {
229        match self.prefix() {
230            Some(ref prefix) => DOMString::from(format!("{}:{}", prefix, &**self.local_name())),
231            None => DOMString::from(&**self.local_name()),
232        }
233    }
234}
235
236#[allow(unsafe_code)]
237pub(crate) trait AttrHelpersForLayout<'dom> {
238    fn value(self) -> &'dom AttrValue;
239    fn as_str(&self) -> &'dom str;
240    fn to_tokens(self) -> Option<&'dom [Atom]>;
241    fn local_name(self) -> &'dom LocalName;
242    fn namespace(self) -> &'dom Namespace;
243}
244
245#[allow(unsafe_code)]
246impl<'dom> AttrHelpersForLayout<'dom> for LayoutDom<'dom, Attr> {
247    #[inline]
248    fn value(self) -> &'dom AttrValue {
249        unsafe { self.unsafe_get().value.borrow_for_layout() }
250    }
251
252    #[inline]
253    fn as_str(&self) -> &'dom str {
254        self.value()
255    }
256
257    #[inline]
258    fn to_tokens(self) -> Option<&'dom [Atom]> {
259        match *self.value() {
260            AttrValue::TokenList(_, ref tokens) => Some(tokens),
261            _ => None,
262        }
263    }
264
265    #[inline]
266    fn local_name(self) -> &'dom LocalName {
267        &self.unsafe_get().identifier.local_name.0
268    }
269
270    #[inline]
271    fn namespace(self) -> &'dom Namespace {
272        &self.unsafe_get().identifier.namespace.0
273    }
274}
275
276/// A helper function to check if attribute is relevant.
277pub(crate) fn is_relevant_attribute(namespace: &Namespace, local_name: &LocalName) -> bool {
278    // <https://svgwg.org/svg2-draft/linking.html#XLinkHrefAttribute>
279    namespace == &ns!() || (namespace == &ns!(xlink) && local_name == &local_name!("href"))
280}
281
282/// A help function to check if an attribute is a boolean attribute.
283pub(crate) fn is_boolean_attribute(name: &str) -> bool {
284    // The full list of attributes can be found in [1]. All attributes marked as "Boolean
285    // attribute" in the "Value" column are boolean attributes. Note that "hidden" is effectively
286    // treated as a boolean attribute, according to WPT test "test_global_boolean_attributes" in
287    // webdriver/tests/classic/get_element_attribute/get.py
288    //
289    // [1] <https://html.spec.whatwg.org/multipage/#attributes-3>
290    static BOOLEAN_ATTRIBUTES: LazyLock<[&str; 30]> = LazyLock::new(|| {
291        [
292            "allowfullscreen",
293            "alpha",
294            "async",
295            "autofocus",
296            "autoplay",
297            "checked",
298            "controls",
299            "default",
300            "defer",
301            "disabled",
302            "formnovalidate",
303            "hidden",
304            "inert",
305            "ismap",
306            "itemscope",
307            "loop",
308            "multiple",
309            "muted",
310            "nomodule",
311            "novalidate",
312            "open",
313            "playsinline",
314            "readonly",
315            "required",
316            "reversed",
317            "selected",
318            "shadowrootclonable",
319            "shadowrootcustomelementregistry",
320            "shadowrootdelegatesfocus",
321            "shadowrootserializable",
322        ]
323    });
324
325    BOOLEAN_ATTRIBUTES
326        .iter()
327        .any(|&boolean_attr| boolean_attr.eq_ignore_ascii_case(name))
328}