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