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 get_tokenlist_attribute(&self, local_name: &LocalName) -> Vec<Atom> {
161        self.with_attribute(&ns!(), local_name, |attribute| {
162            attribute.value().as_tokens().to_vec()
163        })
164        .unwrap_or_default()
165    }
166
167    pub(crate) fn set_tokenlist_attribute(
168        &self,
169        cx: &mut JSContext,
170        local_name: &LocalName,
171        value: DOMString,
172    ) {
173        self.set_attribute(
174            cx,
175            local_name,
176            AttrValue::from_serialized_tokenlist(value.into()),
177        );
178    }
179
180    pub(crate) fn get_uint_attribute(&self, local_name: &LocalName, default: u32) -> u32 {
181        self.with_attribute(&ns!(), local_name, |attribute| {
182            if let AttrValue::UInt(_, value) = *attribute.value() {
183                value
184            } else {
185                unreachable!("Expected an AttrValue::Int: implement parse_plain_attribute")
186            }
187        })
188        .unwrap_or(default)
189    }
190
191    /// Ensure that for styles, we clone the already-parsed property declaration block.
192    /// This does two things:
193    /// 1. It uses the same fast-path as CSSStyleDeclaration
194    /// 2. It also avoids the CSP checks when cloning (it shouldn't run any when cloning
195    ///    existing valid attributes)
196    fn compute_attribute_value_with_style_fast_path(&self, attr: AttrRef<'_>) -> AttrValue {
197        if *attr.local_name() == local_name!("style") {
198            let document = self.owner_document();
199
200            if let AttrValue::Declaration {
201                block,
202                lock,
203                serialization,
204            } = &*attr.value()
205            {
206                // Even though the property declaration block inside this AttrValue will
207                // be replaced, the serialization will be exactly the same, so preserve
208                // that instead of re-serializing.
209                let cloned_block = block.read_with(&lock.read()).clone();
210                return AttrValue::Declaration {
211                    block: ServoArc::new(lock.wrap(cloned_block)),
212                    lock: lock.clone(),
213                    serialization: serialization.clone(),
214                };
215            }
216
217            if let Some(ref pdb) = *self.style_attribute().borrow() {
218                let shared_lock = document.style_shared_author_lock();
219                let new_pdb = pdb.read_with(&shared_lock.read()).clone();
220                return AttrValue::Declaration {
221                    block: ServoArc::new(shared_lock.wrap(new_pdb)),
222                    lock: shared_lock.clone(),
223                    // The style attribute was not set via a declaration, so try to
224                    // preserve any serialization that existed before instead of
225                    // re-serializing.
226                    serialization: (**attr.value()).to_owned().into(),
227                };
228            }
229        }
230
231        attr.value().clone()
232    }
233
234    /// <https://dom.spec.whatwg.org/#concept-node-clone>
235    pub(crate) fn copy_all_attributes_to_other_element(
236        &self,
237        cx: &mut JSContext,
238        target_element: &Element,
239    ) {
240        // Step 2.5. For each attribute of node’s attribute list:
241        for attr in self.attrs().borrow().iter() {
242            // Step 2.5.1. Let copyAttribute be the result of cloning a single node given attribute, document, and null.
243            let new_value = self.compute_attribute_value_with_style_fast_path(attr);
244            // Step 2.5.2. Append copyAttribute to copy.
245            target_element.push_new_attribute(
246                cx,
247                attr.local_name().clone(),
248                new_value,
249                attr.name().clone(),
250                attr.namespace().clone(),
251                attr.prefix().cloned(),
252                AttributeMutationReason::ByCloning,
253            );
254        }
255    }
256}