script/dom/element/attributes/
accessors.rs1use std::sync::LazyLock;
6
7use html5ever::{LocalName, Namespace, local_name, ns};
8use js::context::JSContext;
9use servo_arc::Arc as ServoArc;
10use style::attr::AttrValue;
11use stylo_atoms::Atom;
12
13use crate::dom::bindings::codegen::UnionTypes::{TrustedHTMLOrString, TrustedScriptURLOrUSVString};
14use crate::dom::bindings::str::{DOMString, USVString};
15use crate::dom::element::Element;
16use crate::dom::element::attributes::storage::AttrRef;
17use crate::dom::element::storage::{
18 AttrName, AttrValueRef, AttributesBorrow, ContentAttributeData,
19};
20use crate::dom::node::NodeTraits;
21
22static EMPTY_CONTENTATTRIBUTE_DATA: LazyLock<ContentAttributeData> =
23 LazyLock::new(|| ContentAttributeData {
24 identifier: AttrName::new(local_name!(""), local_name!(""), ns!(), None),
25 value: AttrValue::Atom(atom!("")),
26 });
27
28pub(crate) struct AttrStrRefInner<'a> {
29 attributes_borrow: AttributesBorrow<'a>,
30 position: usize,
31}
32
33#[derive(Default)]
41pub(crate) struct AttrStrRef<'a>(Option<AttrStrRefInner<'a>>);
42
43impl<'a> AttrStrRef<'a> {
44 pub(in crate::dom::element) fn maybe_new(
47 attrs: AttributesBorrow<'a>,
48 namespace: &Namespace,
49 local_name: &LocalName,
50 ) -> Option<AttrStrRef<'a>> {
51 let position = attrs.iter().position(|attribute| {
52 attribute.local_name() == local_name && attribute.namespace() == namespace
53 });
54 position.map(|position| {
55 AttrStrRef(Some(AttrStrRefInner {
56 attributes_borrow: attrs,
57 position,
58 }))
59 })
60 }
61
62 pub(crate) fn as_attr_ref<'b>(&'b self) -> AttrRef<'b> {
64 if let Some(inner) = &self.0 {
65 inner.attributes_borrow.get(inner.position).unwrap()
66 } else {
67 AttrRef::Raw(&EMPTY_CONTENTATTRIBUTE_DATA)
68 }
69 }
70
71 pub(crate) fn value(&self) -> AttrValueRef<'_> {
73 self.as_attr_ref().value()
74 }
75
76 pub(crate) fn is_empty(&self) -> bool {
77 if let Some(inner) = self.0.as_ref() {
78 inner
79 .attributes_borrow
80 .get(inner.position)
81 .unwrap()
82 .value()
83 .is_empty()
84 } else {
85 true
86 }
87 }
88
89 pub(crate) fn eq_ignore_ascii_case(&self, other: &str) -> bool {
90 if let Some(inner) = self.0.as_ref() {
91 let attr_ref = inner.attributes_borrow.get(inner.position).unwrap();
92 let value = attr_ref.value();
93 value.eq_ignore_ascii_case(other)
94 } else {
95 other.is_empty()
96 }
97 }
98}
99
100impl Element {
101 pub(crate) fn get_attribute_string_value(&self, local_name: &LocalName) -> Option<String> {
105 debug_assert_eq!(
108 *local_name,
109 local_name.to_ascii_lowercase(),
110 "All namespace-less attribute accesses should use a lowercase ASCII name"
111 );
112
113 self.get_attribute_string_value_with_namespace(&ns!(), local_name)
114 }
115
116 pub(crate) fn get_attribute_string_ref<'a>(
119 &'a self,
120 local_name: &LocalName,
121 ) -> Option<AttrStrRef<'a>> {
122 debug_assert_eq!(
123 *local_name,
124 local_name.to_ascii_lowercase(),
125 "All namespace-less attribute accesses should use a lowercase ASCII name"
126 );
127 self.attribute_str_ref(&ns!(), local_name)
128 }
129
130 pub(crate) fn get_attribute_string_value_with_namespace(
131 &self,
132 namespace: &Namespace,
133 local_name: &LocalName,
134 ) -> Option<String> {
135 self.with_attribute(namespace, local_name, |attribute| {
136 String::from(&**attribute.value())
137 })
138 }
139
140 pub(crate) fn get_int_attribute(&self, local_name: &LocalName, default: i32) -> i32 {
141 self.with_attribute(&ns!(), local_name, |attribute| {
142 if let AttrValue::Int(_, value) = *attribute.value() {
143 value
144 } else {
145 unreachable!("Expected an AttrValue::Int: implement parse_plain_attribute")
146 }
147 })
148 .unwrap_or(default)
149 }
150
151 pub(crate) fn set_atomic_attribute(
152 &self,
153 cx: &mut JSContext,
154 local_name: &LocalName,
155 value: DOMString,
156 ) {
157 self.set_attribute(cx, local_name, AttrValue::from_atomic(value.into()));
158 }
159
160 pub(crate) fn set_bool_attribute(
161 &self,
162 cx: &mut JSContext,
163 local_name: &LocalName,
164 value: bool,
165 ) {
166 if self.has_attribute(local_name) == value {
167 return;
168 }
169 if value {
170 self.set_string_attribute(cx, local_name, DOMString::new());
171 } else {
172 self.remove_attribute(cx, &ns!(), local_name);
173 }
174 }
175
176 pub(crate) fn get_url_attribute(&self, local_name: &LocalName) -> USVString {
177 let Some(value) = self.get_attribute_string_ref(local_name) else {
178 return Default::default();
179 };
180 self.owner_document()
181 .encoding_parse_a_url(&value.as_attr_ref().value())
182 .map(|parsed| USVString(parsed.into_string()))
183 .unwrap_or_else(|_| USVString(value.as_attr_ref().value().to_string()))
184 }
185
186 pub(crate) fn set_url_attribute(
187 &self,
188 cx: &mut JSContext,
189 local_name: &LocalName,
190 value: USVString,
191 ) {
192 self.set_attribute(cx, local_name, AttrValue::String(value.into()));
193 }
194
195 pub(crate) fn get_trusted_type_url_attribute(
196 &self,
197 local_name: &LocalName,
198 ) -> TrustedScriptURLOrUSVString {
199 let Some(value) = self.get_attribute_string_ref(local_name) else {
200 return TrustedScriptURLOrUSVString::USVString(USVString::default());
201 };
202 let value_ref = value.as_attr_ref();
203 self.owner_document()
204 .encoding_parse_a_url(&value_ref.value())
205 .map(|parsed| TrustedScriptURLOrUSVString::USVString(USVString(parsed.into_string())))
206 .unwrap_or_else(|_| {
207 TrustedScriptURLOrUSVString::USVString(USVString(value_ref.value().to_string()))
208 })
209 }
210
211 pub(crate) fn get_trusted_html_attribute(&self, local_name: &LocalName) -> TrustedHTMLOrString {
212 TrustedHTMLOrString::String(self.get_string_attribute(local_name))
213 }
214
215 pub(crate) fn get_string_attribute(&self, local_name: &LocalName) -> DOMString {
216 self.get_attribute_string_value(local_name)
217 .map(|value| value.into())
218 .unwrap_or_default()
219 }
220
221 pub(crate) fn set_string_attribute(
222 &self,
223 cx: &mut JSContext,
224 local_name: &LocalName,
225 value: DOMString,
226 ) {
227 self.set_attribute(cx, local_name, value.str().to_string().into());
228 }
229
230 pub(crate) fn get_nullable_string_attribute(
233 &self,
234 local_name: &LocalName,
235 ) -> Option<DOMString> {
236 if self.has_attribute(local_name) {
237 Some(self.get_string_attribute(local_name))
238 } else {
239 None
240 }
241 }
242
243 pub(crate) fn set_nullable_string_attribute(
246 &self,
247 cx: &mut JSContext,
248 local_name: &LocalName,
249 value: Option<DOMString>,
250 ) {
251 match value {
252 Some(val) => {
253 self.set_string_attribute(cx, local_name, val);
254 },
255 None => {
256 self.remove_attribute(cx, &ns!(), local_name);
257 },
258 }
259 }
260
261 pub(crate) fn set_nullable_tokenlist_attribute(
262 &self,
263 cx: &mut JSContext,
264 local_name: &LocalName,
265 value: Option<DOMString>,
266 ) {
267 match value {
268 Some(string_value) => {
269 self.set_tokenlist_attribute(cx, local_name, string_value);
270 },
271 None => {
272 self.remove_attribute(cx, &ns!(), local_name);
273 },
274 }
275 }
276
277 pub(crate) fn any_tokenlist_attribute(
280 &self,
281 local_name: &LocalName,
282 f: impl Fn(&Atom) -> bool,
283 ) -> bool {
284 self.with_attribute(&ns!(), local_name, |attribute| {
285 attribute.value().as_tokens().iter().any(f)
286 })
287 .unwrap_or(false)
288 }
289
290 pub(crate) fn get_tokenlist_attribute(&self, local_name: &LocalName) -> Vec<Atom> {
291 self.with_attribute(&ns!(), local_name, |attribute| {
292 attribute.value().as_tokens().to_vec()
293 })
294 .unwrap_or_default()
295 }
296
297 pub(crate) fn set_tokenlist_attribute(
298 &self,
299 cx: &mut JSContext,
300 local_name: &LocalName,
301 value: DOMString,
302 ) {
303 self.set_attribute(
304 cx,
305 local_name,
306 AttrValue::from_serialized_tokenlist(value.into()),
307 );
308 }
309
310 pub(crate) fn get_uint_attribute(&self, local_name: &LocalName, default: u32) -> u32 {
311 self.with_attribute(&ns!(), local_name, |attribute| {
312 if let AttrValue::UInt(_, value) = *attribute.value() {
313 value
314 } else {
315 unreachable!("Expected an AttrValue::Int: implement parse_plain_attribute")
316 }
317 })
318 .unwrap_or(default)
319 }
320
321 fn compute_attribute_value_with_style_fast_path(&self, attr: AttrRef<'_>) -> AttrValue {
327 if *attr.local_name() == local_name!("style") {
328 let document = self.owner_document();
329
330 if let AttrValue::Declaration {
331 block,
332 lock,
333 serialization,
334 } = &*attr.value()
335 {
336 let cloned_block = block.read_with(&lock.read()).clone();
340 return AttrValue::Declaration {
341 block: ServoArc::new(lock.wrap(cloned_block)),
342 lock: lock.clone(),
343 serialization: serialization.clone(),
344 };
345 }
346
347 if let Some(ref pdb) = *self.style_attribute().borrow() {
348 let shared_lock = document.style_shared_author_lock();
349 let new_pdb = pdb.read_with(&shared_lock.read()).clone();
350 return AttrValue::Declaration {
351 block: ServoArc::new(shared_lock.wrap(new_pdb)),
352 lock: shared_lock.clone(),
353 serialization: (**attr.value()).to_owned().into(),
357 };
358 }
359 }
360
361 attr.value().clone()
362 }
363
364 pub(crate) fn copy_all_attributes_to_other_element(
366 &self,
367 cx: &mut JSContext,
368 target_element: &Element,
369 ) {
370 let attribute_count = self.attrs().borrow().len();
372 target_element.attrs().reserve_exact(attribute_count);
373 for attr in self.attrs().borrow().iter() {
374 let new_value = self.compute_attribute_value_with_style_fast_path(attr);
376 target_element.push_new_attribute(
378 cx,
379 attr.local_name().clone(),
380 new_value,
381 attr.name().clone(),
382 attr.namespace().clone(),
383 attr.prefix().cloned(),
384 );
385 }
386 }
387}