Skip to main content

script/dom/element/attributes/
accessors.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 html5ever::{LocalName, Namespace, local_name, ns};
6use js::context::JSContext;
7use servo_arc::Arc as ServoArc;
8use style::attr::AttrValue;
9use stylo_atoms::Atom;
10
11use crate::dom::bindings::codegen::UnionTypes::{TrustedHTMLOrString, TrustedScriptURLOrUSVString};
12use crate::dom::bindings::str::{DOMString, USVString};
13use crate::dom::element::attributes::storage::AttrRef;
14use crate::dom::element::{AttributeMutationReason, Element};
15use crate::dom::node::NodeTraits;
16
17impl Element {
18    /// Callers should convert the `LocalName` to ASCII lowercase before calling.
19    /// <https://dom.spec.whatwg.org/#concept-element-attributes-get-by-name>
20    pub(crate) fn get_attribute_string_value(&self, local_name: &LocalName) -> Option<String> {
21        // Step 1. If element is in the HTML namespace and its node document is an HTML document,
22        // then set qualifiedName to qualifiedName in ASCII lowercase.
23        debug_assert_eq!(
24            *local_name,
25            local_name.to_ascii_lowercase(),
26            "All namespace-less attribute accesses should use a lowercase ASCII name"
27        );
28
29        self.get_attribute_string_value_with_namespace(&ns!(), local_name)
30    }
31
32    pub(crate) fn get_attribute_string_value_with_namespace(
33        &self,
34        namespace: &Namespace,
35        local_name: &LocalName,
36    ) -> Option<String> {
37        self.with_attribute(namespace, local_name, |attribute| {
38            String::from(&**attribute.value())
39        })
40    }
41
42    pub(crate) fn get_int_attribute(&self, local_name: &LocalName, default: i32) -> i32 {
43        self.with_attribute(&ns!(), local_name, |attribute| {
44            if let AttrValue::Int(_, value) = *attribute.value() {
45                value
46            } else {
47                unreachable!("Expected an AttrValue::Int: implement parse_plain_attribute")
48            }
49        })
50        .unwrap_or(default)
51    }
52
53    pub(crate) fn set_atomic_attribute(
54        &self,
55        cx: &mut JSContext,
56        local_name: &LocalName,
57        value: DOMString,
58    ) {
59        self.set_attribute(cx, local_name, AttrValue::from_atomic(value.into()));
60    }
61
62    pub(crate) fn set_bool_attribute(
63        &self,
64        cx: &mut JSContext,
65        local_name: &LocalName,
66        value: bool,
67    ) {
68        if self.has_attribute(local_name) == value {
69            return;
70        }
71        if value {
72            self.set_string_attribute(cx, local_name, DOMString::new());
73        } else {
74            self.remove_attribute(cx, &ns!(), local_name);
75        }
76    }
77
78    pub(crate) fn get_url_attribute(&self, local_name: &LocalName) -> USVString {
79        let Some(value) = self.get_attribute_string_value(local_name) else {
80            return Default::default();
81        };
82        self.owner_document()
83            .encoding_parse_a_url(&value)
84            .map(|parsed| USVString(parsed.into_string()))
85            .unwrap_or_else(|_| USVString(value))
86    }
87
88    pub(crate) fn set_url_attribute(
89        &self,
90        cx: &mut JSContext,
91        local_name: &LocalName,
92        value: USVString,
93    ) {
94        self.set_attribute(cx, local_name, AttrValue::String(value.into()));
95    }
96
97    pub(crate) fn get_trusted_type_url_attribute(
98        &self,
99        local_name: &LocalName,
100    ) -> TrustedScriptURLOrUSVString {
101        let Some(value) = self.get_attribute_string_value(local_name) else {
102            return TrustedScriptURLOrUSVString::USVString(USVString::default());
103        };
104        self.owner_document()
105            .encoding_parse_a_url(&value)
106            .map(|parsed| TrustedScriptURLOrUSVString::USVString(USVString(parsed.into_string())))
107            .unwrap_or_else(|_| TrustedScriptURLOrUSVString::USVString(USVString(value)))
108    }
109
110    pub(crate) fn get_trusted_html_attribute(&self, local_name: &LocalName) -> TrustedHTMLOrString {
111        TrustedHTMLOrString::String(self.get_string_attribute(local_name))
112    }
113
114    pub(crate) fn get_string_attribute(&self, local_name: &LocalName) -> DOMString {
115        self.get_attribute_string_value(local_name)
116            .map(|value| value.into())
117            .unwrap_or_default()
118    }
119
120    pub(crate) fn set_string_attribute(
121        &self,
122        cx: &mut JSContext,
123        local_name: &LocalName,
124        value: DOMString,
125    ) {
126        self.set_attribute(cx, local_name, value.str().to_string().into());
127    }
128
129    /// Used for string attribute reflections where absence of the attribute returns `null`,
130    /// e.g. `element.ariaLabel` returning `null` when the `aria-label` attribute is absent.
131    pub(crate) fn get_nullable_string_attribute(
132        &self,
133        local_name: &LocalName,
134    ) -> Option<DOMString> {
135        if self.has_attribute(local_name) {
136            Some(self.get_string_attribute(local_name))
137        } else {
138            None
139        }
140    }
141
142    /// Used for string attribute reflections where setting `null`/`undefined` removes the
143    /// attribute, e.g. `element.ariaLabel = null` removing the `aria-label` attribute.
144    pub(crate) fn set_nullable_string_attribute(
145        &self,
146        cx: &mut JSContext,
147        local_name: &LocalName,
148        value: Option<DOMString>,
149    ) {
150        match value {
151            Some(val) => {
152                self.set_string_attribute(cx, local_name, val);
153            },
154            None => {
155                self.remove_attribute(cx, &ns!(), local_name);
156            },
157        }
158    }
159
160    pub(crate) fn set_nullable_tokenlist_attribute(
161        &self,
162        cx: &mut JSContext,
163        local_name: &LocalName,
164        value: Option<DOMString>,
165    ) {
166        match value {
167            Some(string_value) => {
168                self.set_tokenlist_attribute(cx, local_name, string_value);
169            },
170            None => {
171                self.remove_attribute(cx, &ns!(), local_name);
172            },
173        }
174    }
175
176    /// Returns true if any attribute in the tokenlist fulfill `f`. Equivalent to
177    /// `get_tokenlist_attribute(name).iter().any(f)`.
178    pub(crate) fn any_tokenlist_attribute(
179        &self,
180        local_name: &LocalName,
181        f: impl Fn(&Atom) -> bool,
182    ) -> bool {
183        self.with_attribute(&ns!(), local_name, |attribute| {
184            attribute.value().as_tokens().iter().any(f)
185        })
186        .unwrap_or(false)
187    }
188
189    pub(crate) fn get_tokenlist_attribute(&self, local_name: &LocalName) -> Vec<Atom> {
190        self.with_attribute(&ns!(), local_name, |attribute| {
191            attribute.value().as_tokens().to_vec()
192        })
193        .unwrap_or_default()
194    }
195
196    pub(crate) fn set_tokenlist_attribute(
197        &self,
198        cx: &mut JSContext,
199        local_name: &LocalName,
200        value: DOMString,
201    ) {
202        self.set_attribute(
203            cx,
204            local_name,
205            AttrValue::from_serialized_tokenlist(value.into()),
206        );
207    }
208
209    pub(crate) fn get_uint_attribute(&self, local_name: &LocalName, default: u32) -> u32 {
210        self.with_attribute(&ns!(), local_name, |attribute| {
211            if let AttrValue::UInt(_, value) = *attribute.value() {
212                value
213            } else {
214                unreachable!("Expected an AttrValue::Int: implement parse_plain_attribute")
215            }
216        })
217        .unwrap_or(default)
218    }
219
220    /// Ensure that for styles, we clone the already-parsed property declaration block.
221    /// This does two things:
222    /// 1. It uses the same fast-path as CSSStyleDeclaration
223    /// 2. It also avoids the CSP checks when cloning (it shouldn't run any when cloning
224    ///    existing valid attributes)
225    fn compute_attribute_value_with_style_fast_path(&self, attr: AttrRef<'_>) -> AttrValue {
226        if *attr.local_name() == local_name!("style") {
227            let document = self.owner_document();
228
229            if let AttrValue::Declaration {
230                block,
231                lock,
232                serialization,
233            } = &*attr.value()
234            {
235                // Even though the property declaration block inside this AttrValue will
236                // be replaced, the serialization will be exactly the same, so preserve
237                // that instead of re-serializing.
238                let cloned_block = block.read_with(&lock.read()).clone();
239                return AttrValue::Declaration {
240                    block: ServoArc::new(lock.wrap(cloned_block)),
241                    lock: lock.clone(),
242                    serialization: serialization.clone(),
243                };
244            }
245
246            if let Some(ref pdb) = *self.style_attribute().borrow() {
247                let shared_lock = document.style_shared_author_lock();
248                let new_pdb = pdb.read_with(&shared_lock.read()).clone();
249                return AttrValue::Declaration {
250                    block: ServoArc::new(shared_lock.wrap(new_pdb)),
251                    lock: shared_lock.clone(),
252                    // The style attribute was not set via a declaration, so try to
253                    // preserve any serialization that existed before instead of
254                    // re-serializing.
255                    serialization: (**attr.value()).to_owned().into(),
256                };
257            }
258        }
259
260        attr.value().clone()
261    }
262
263    /// <https://dom.spec.whatwg.org/#concept-node-clone>
264    pub(crate) fn copy_all_attributes_to_other_element(
265        &self,
266        cx: &mut JSContext,
267        target_element: &Element,
268    ) {
269        // Step 2.5. For each attribute of node’s attribute list:
270        for attr in self.attrs().borrow().iter() {
271            // Step 2.5.1. Let copyAttribute be the result of cloning a single node given attribute, document, and null.
272            let new_value = self.compute_attribute_value_with_style_fast_path(attr);
273            // Step 2.5.2. Append copyAttribute to copy.
274            target_element.push_new_attribute(
275                cx,
276                attr.local_name().clone(),
277                new_value,
278                attr.name().clone(),
279                attr.namespace().clone(),
280                attr.prefix().cloned(),
281                AttributeMutationReason::ByCloning,
282            );
283        }
284    }
285}