1use std::str::FromStr;
10use std::sync::OnceLock;
11
12use app_units::Au;
13use euclid::num::Zero;
14use num_traits::ToPrimitive;
15use selectors::attr::AttrSelectorOperation;
16use servo_arc::Arc;
17
18use super::shadow_parts::ShadowParts;
19use crate::color::parsing::parse_color_keyword;
20use crate::color::AbsoluteColor;
21use crate::derives::*;
22use crate::properties::PropertyDeclarationBlock;
23use crate::shared_lock::{Locked, SharedRwLock};
24use crate::str::{
25 read_exponent, read_fraction, read_numbers, split_commas, split_html_space_chars, str_join,
26 HTML_SPACE_CHARACTERS,
27};
28use crate::values::specified::color::Color;
29use crate::values::specified::LengthPercentage;
30use crate::values::AtomString;
31use crate::{Atom, LocalName, Namespace, Prefix};
32
33const UNSIGNED_LONG_MAX: u32 = 2147483647;
35
36#[derive(Clone, Copy, Debug, PartialEq)]
37#[cfg_attr(feature = "servo", derive(MallocSizeOf))]
38pub enum LengthOrPercentageOrAuto {
39 Auto,
40 Percentage(f32),
41 Length(Au),
42}
43
44#[derive(Clone, Debug)]
45#[cfg_attr(feature = "servo", derive(MallocSizeOf))]
46pub enum AttrValue {
47 String(String),
51 Atom(Atom),
52
53 TokenList(OnceLock<String>, Vec<Atom>),
57 UInt(OnceLock<String>, u32),
58 Int(OnceLock<String>, i32),
59 Double(OnceLock<String>, f64),
60 Declaration {
65 #[ignore_malloc_size_of = "Arc"]
66 block: Arc<Locked<PropertyDeclarationBlock>>,
67 lock: SharedRwLock,
68 serialization: OnceLock<String>,
69 },
70
71 LengthPercentage(String, Option<LengthPercentage>),
75 Color(String, Option<AbsoluteColor>),
76 Dimension(String, LengthOrPercentageOrAuto),
77 ResolvedUrl(
82 String,
83 #[ignore_malloc_size_of = "Arc"] Option<Arc<url::Url>>,
84 ),
85 ShadowParts(String, ShadowParts),
87}
88
89impl From<String> for AttrValue {
90 fn from(value: String) -> Self {
91 Self::String(value)
92 }
93}
94
95impl From<u32> for AttrValue {
96 fn from(value: u32) -> Self {
97 Self::UInt(OnceLock::new(), value)
98 }
99}
100
101impl From<i32> for AttrValue {
102 fn from(value: i32) -> Self {
103 Self::Int(OnceLock::new(), value)
104 }
105}
106
107impl From<f64> for AttrValue {
108 fn from(value: f64) -> Self {
109 Self::Double(OnceLock::new(), value)
110 }
111}
112
113impl From<Vec<Atom>> for AttrValue {
114 fn from(value: Vec<Atom>) -> Self {
115 Self::TokenList(OnceLock::new(), value)
116 }
117}
118
119fn do_parse_integer<T: Iterator<Item = char>>(input: T) -> Result<i64, ()> {
123 let mut input = input
124 .skip_while(|c| HTML_SPACE_CHARACTERS.iter().any(|s| s == c))
125 .peekable();
126
127 let sign = match input.peek() {
128 None => return Err(()),
129 Some(&'-') => {
130 input.next();
131 -1
132 },
133 Some(&'+') => {
134 input.next();
135 1
136 },
137 Some(_) => 1,
138 };
139
140 let (value, _) = read_numbers(input);
141
142 value.and_then(|value| value.checked_mul(sign)).ok_or(())
143}
144
145pub fn parse_integer<T: Iterator<Item = char>>(input: T) -> Result<i32, ()> {
148 do_parse_integer(input).and_then(|result| result.to_i32().ok_or(()))
149}
150
151pub fn parse_unsigned_integer<T: Iterator<Item = char>>(input: T) -> Result<u32, ()> {
154 do_parse_integer(input).and_then(|result| result.to_u32().ok_or(()))
155}
156
157pub fn parse_double(string: &str) -> Result<f64, ()> {
160 let trimmed = string.trim_matches(HTML_SPACE_CHARACTERS);
161 let mut input = trimmed.chars().peekable();
162
163 let (value, divisor, chars_skipped) = match input.peek() {
164 None => return Err(()),
165 Some(&'-') => {
166 input.next();
167 (-1f64, -1f64, 1)
168 },
169 Some(&'+') => {
170 input.next();
171 (1f64, 1f64, 1)
172 },
173 _ => (1f64, 1f64, 0),
174 };
175
176 let (value, value_digits) = if let Some(&'.') = input.peek() {
177 (0f64, 0)
178 } else {
179 let (read_val, read_digits) = read_numbers(input);
180 (
181 value * read_val.and_then(|result| result.to_f64()).unwrap_or(1f64),
182 read_digits,
183 )
184 };
185
186 let input = trimmed
187 .chars()
188 .skip(value_digits + chars_skipped)
189 .peekable();
190
191 let (mut value, fraction_digits) = read_fraction(input, divisor, value);
192
193 let input = trimmed
194 .chars()
195 .skip(value_digits + chars_skipped + fraction_digits)
196 .peekable();
197
198 if let Some(exp) = read_exponent(input) {
199 value *= 10f64.powi(exp)
200 };
201
202 Ok(value)
203}
204
205impl AttrValue {
206 pub fn from_serialized_tokenlist(tokens: String) -> AttrValue {
207 let atoms =
208 split_html_space_chars(&tokens)
209 .map(Atom::from)
210 .fold(vec![], |mut acc, atom| {
211 if !acc.contains(&atom) {
212 acc.push(atom)
213 }
214 acc
215 });
216 AttrValue::TokenList(tokens.into(), atoms)
217 }
218
219 pub fn from_comma_separated_tokenlist(tokens: String) -> AttrValue {
220 let atoms = split_commas(&tokens)
221 .map(Atom::from)
222 .fold(vec![], |mut acc, atom| {
223 if !acc.contains(&atom) {
224 acc.push(atom)
225 }
226 acc
227 });
228 AttrValue::TokenList(tokens.into(), atoms)
229 }
230
231 pub fn from_u32(string: String, default: u32) -> AttrValue {
233 let result = parse_unsigned_integer(string.chars()).unwrap_or(default);
234 let result = if result > UNSIGNED_LONG_MAX {
235 default
236 } else {
237 result
238 };
239 AttrValue::UInt(string.into(), result)
240 }
241
242 pub fn from_i32(string: String, default: i32) -> AttrValue {
243 let result = parse_integer(string.chars()).unwrap_or(default);
244 AttrValue::Int(string.into(), result)
245 }
246
247 pub fn from_double(string: String, default: f64) -> AttrValue {
249 let result = parse_double(&string).unwrap_or(default);
250
251 if result.is_normal() {
252 AttrValue::Double(string.into(), result)
253 } else {
254 AttrValue::Double(string.into(), default)
255 }
256 }
257
258 pub fn from_limited_i32(string: String, default: i32) -> AttrValue {
260 let result = parse_integer(string.chars()).unwrap_or(default);
261
262 if result < 0 {
263 AttrValue::Int(string.into(), default)
264 } else {
265 AttrValue::Int(string.into(), result)
266 }
267 }
268
269 pub fn from_limited_u32(string: String, default: u32) -> AttrValue {
271 let result = parse_unsigned_integer(string.chars()).unwrap_or(default);
272 let result = if result == 0 || result > UNSIGNED_LONG_MAX {
273 default
274 } else {
275 result
276 };
277 AttrValue::UInt(string.into(), result)
278 }
279
280 pub fn from_atomic(string: String) -> AttrValue {
281 AttrValue::Atom(string.into())
282 }
283
284 pub fn from_resolved_url(base: &Arc<::url::Url>, url: String) -> AttrValue {
285 let joined = base.join(&url).ok().map(Arc::new);
286 AttrValue::ResolvedUrl(url, joined)
287 }
288
289 pub fn from_legacy_color(string: String) -> AttrValue {
290 let parsed = parse_legacy_color(&string).ok();
291 AttrValue::Color(string, parsed)
292 }
293
294 pub fn from_dimension(string: String) -> AttrValue {
295 let parsed = parse_length(&string);
296 AttrValue::Dimension(string, parsed)
297 }
298
299 pub fn from_nonzero_dimension(string: String) -> AttrValue {
300 let parsed = parse_nonzero_length(&string);
301 AttrValue::Dimension(string, parsed)
302 }
303
304 pub fn from_shadow_parts(string: String) -> AttrValue {
305 let shadow_parts = ShadowParts::parse(&string);
306 AttrValue::ShadowParts(string, shadow_parts)
307 }
308
309 pub fn from_declaration(
310 block: Arc<Locked<PropertyDeclarationBlock>>,
311 lock: SharedRwLock,
312 ) -> AttrValue {
313 AttrValue::Declaration {
314 block,
315 lock,
316 serialization: OnceLock::new(),
317 }
318 }
319
320 pub fn as_tokens(&self) -> &[Atom] {
326 match *self {
327 AttrValue::TokenList(_, ref tokens) => tokens,
328 _ => panic!("Tokens not found"),
329 }
330 }
331
332 pub fn as_atom(&self) -> &Atom {
338 match *self {
339 AttrValue::Atom(ref value) => value,
340 _ => panic!("Atom not found"),
341 }
342 }
343
344 pub fn as_length_percentage(&self) -> Option<&LengthPercentage> {
350 match *self {
351 AttrValue::LengthPercentage(_, ref length_percentage) => length_percentage.as_ref(),
352 _ => panic!("LengthPercentage not found"),
353 }
354 }
355
356 pub fn as_color(&self) -> Option<&AbsoluteColor> {
362 match *self {
363 AttrValue::Color(_, ref color) => color.as_ref(),
364 _ => panic!("Color not found"),
365 }
366 }
367
368 pub fn as_dimension(&self) -> &LengthOrPercentageOrAuto {
374 match *self {
375 AttrValue::Dimension(_, ref l) => l,
376 _ => panic!("Dimension not found"),
377 }
378 }
379
380 pub fn as_resolved_url(&self) -> Option<&Arc<::url::Url>> {
386 match *self {
387 AttrValue::ResolvedUrl(_, ref url) => url.as_ref(),
388 _ => panic!("Url not found"),
389 }
390 }
391
392 pub fn as_int(&self) -> i32 {
400 if let AttrValue::Int(_, value) = *self {
401 value
402 } else {
403 panic!("Int not found");
404 }
405 }
406
407 pub fn as_uint(&self) -> u32 {
415 if let AttrValue::UInt(_, value) = *self {
416 value
417 } else {
418 panic!("Uint not found");
419 }
420 }
421
422 pub fn as_uint_px_dimension(&self) -> LengthOrPercentageOrAuto {
432 if let AttrValue::UInt(_, value) = *self {
433 LengthOrPercentageOrAuto::Length(Au::from_px(value as i32))
434 } else {
435 panic!("Uint not found");
436 }
437 }
438
439 pub fn as_shadow_parts(&self) -> &ShadowParts {
448 if let AttrValue::ShadowParts(_, value) = &self {
449 value
450 } else {
451 panic!("Not a shadowpart attribute");
452 }
453 }
454
455 pub fn eval_selector(&self, selector: &AttrSelectorOperation<&AtomString>) -> bool {
456 selector.eval_str(self)
460 }
461}
462
463impl ::std::ops::Deref for AttrValue {
464 type Target = str;
465
466 fn deref(&self) -> &str {
467 match self {
468 AttrValue::String(value) => &value,
469 AttrValue::Atom(atom) => &atom,
470 AttrValue::TokenList(serialization, tokens) => {
471 serialization.get_or_init(|| {
472 str_join(tokens, "\x20")
474 })
475 },
476 AttrValue::UInt(serialization, value) => {
477 serialization.get_or_init(|| value.to_string())
478 },
479 AttrValue::Double(serialization, value) => {
480 serialization.get_or_init(|| value.to_string())
481 },
482 AttrValue::Int(serialization, value) => serialization.get_or_init(|| value.to_string()),
483 AttrValue::Declaration {
484 block,
485 lock,
486 serialization,
487 } => serialization.get_or_init(|| {
488 let mut serialization = String::new();
489 block
490 .read_with(&lock.read())
491 .to_css(&mut serialization)
492 .expect("Should always be able to produce a valid serialization");
493 serialization
494 }),
495 AttrValue::LengthPercentage(serialization, _)
496 | AttrValue::Color(serialization, _)
497 | AttrValue::Dimension(serialization, _)
498 | AttrValue::ResolvedUrl(serialization, _)
499 | AttrValue::ShadowParts(serialization, _) => &serialization,
500 }
501 }
502}
503
504impl PartialEq<Atom> for AttrValue {
505 fn eq(&self, other: &Atom) -> bool {
506 match *self {
507 AttrValue::Atom(ref value) => value == other,
508 _ => other == &**self,
509 }
510 }
511}
512
513pub fn parse_nonzero_length(value: &str) -> LengthOrPercentageOrAuto {
515 match parse_length(value) {
516 LengthOrPercentageOrAuto::Length(x) if x == Au::zero() => LengthOrPercentageOrAuto::Auto,
517 LengthOrPercentageOrAuto::Percentage(x) if x == 0. => LengthOrPercentageOrAuto::Auto,
518 x => x,
519 }
520}
521
522pub fn parse_legacy_color(mut input: &str) -> Result<AbsoluteColor, ()> {
526 if input.is_empty() {
528 return Err(());
529 }
530
531 input = input.trim_matches(HTML_SPACE_CHARACTERS);
533
534 if input.eq_ignore_ascii_case("transparent") {
536 return Err(());
537 }
538
539 if let Ok(Color::Absolute(ref absolute)) = parse_color_keyword(input) {
541 return Ok(absolute.color);
542 }
543
544 if input.len() == 4 {
546 if let (b'#', Ok(r), Ok(g), Ok(b)) = (
547 input.as_bytes()[0],
548 hex(input.as_bytes()[1] as char),
549 hex(input.as_bytes()[2] as char),
550 hex(input.as_bytes()[3] as char),
551 ) {
552 return Ok(AbsoluteColor::srgb_legacy(r * 17, g * 17, b * 17, 1.0));
553 }
554 }
555
556 let mut new_input = String::new();
558 for ch in input.chars() {
559 if ch as u32 > 0xffff {
560 new_input.push_str("00")
561 } else {
562 new_input.push(ch)
563 }
564 }
565 let mut input = &*new_input;
566
567 for (char_count, (index, _)) in input.char_indices().enumerate() {
569 if char_count == 128 {
570 input = &input[..index];
571 break;
572 }
573 }
574
575 if input.as_bytes()[0] == b'#' {
577 input = &input[1..]
578 }
579
580 let mut new_input = Vec::new();
582 for ch in input.chars() {
583 if hex(ch).is_ok() {
584 new_input.push(ch as u8)
585 } else {
586 new_input.push(b'0')
587 }
588 }
589 let mut input = new_input;
590
591 while input.is_empty() || (input.len() % 3) != 0 {
593 input.push(b'0')
594 }
595
596 let mut length = input.len() / 3;
598 let (mut red, mut green, mut blue) = (
599 &input[..length],
600 &input[length..length * 2],
601 &input[length * 2..],
602 );
603
604 if length > 8 {
606 red = &red[length - 8..];
607 green = &green[length - 8..];
608 blue = &blue[length - 8..];
609 length = 8
610 }
611
612 while length > 2 && red[0] == b'0' && green[0] == b'0' && blue[0] == b'0' {
614 red = &red[1..];
615 green = &green[1..];
616 blue = &blue[1..];
617 length -= 1
618 }
619
620 return Ok(AbsoluteColor::srgb_legacy(
622 hex_string(red).unwrap(),
623 hex_string(green).unwrap(),
624 hex_string(blue).unwrap(),
625 1.0,
626 ));
627
628 fn hex(ch: char) -> Result<u8, ()> {
629 match ch {
630 '0'..='9' => Ok((ch as u8) - b'0'),
631 'a'..='f' => Ok((ch as u8) - b'a' + 10),
632 'A'..='F' => Ok((ch as u8) - b'A' + 10),
633 _ => Err(()),
634 }
635 }
636
637 fn hex_string(string: &[u8]) -> Result<u8, ()> {
638 match string.len() {
639 0 => Err(()),
640 1 => hex(string[0] as char),
641 _ => {
642 let upper = hex(string[0] as char)?;
643 let lower = hex(string[1] as char)?;
644 Ok((upper << 4) | lower)
645 },
646 }
647 }
648}
649
650pub fn parse_length(mut value: &str) -> LengthOrPercentageOrAuto {
655 value = value.trim_start_matches(HTML_SPACE_CHARACTERS);
659
660 match value.chars().nth(0) {
662 Some('0'..='9') => {},
663 _ => return LengthOrPercentageOrAuto::Auto,
664 }
665
666 let mut end_index = value.len();
674 let (mut found_full_stop, mut found_percent) = (false, false);
675 for (i, ch) in value.chars().enumerate() {
676 match ch {
677 '0'..='9' => continue,
678 '%' => {
679 found_percent = true;
680 end_index = i;
681 break;
682 },
683 '.' if !found_full_stop => {
684 found_full_stop = true;
685 continue;
686 },
687 _ => {
688 end_index = i;
689 break;
690 },
691 }
692 }
693 value = &value[..end_index];
694
695 if found_percent {
696 let result: Result<f32, _> = FromStr::from_str(value);
697 match result {
698 Ok(number) => return LengthOrPercentageOrAuto::Percentage((number as f32) / 100.0),
699 Err(_) => return LengthOrPercentageOrAuto::Auto,
700 }
701 }
702
703 match FromStr::from_str(value) {
704 Ok(number) => LengthOrPercentageOrAuto::Length(Au::from_f64_px(number)),
705 Err(_) => LengthOrPercentageOrAuto::Auto,
706 }
707}
708
709#[derive(Clone, Debug)]
711#[cfg_attr(feature = "servo", derive(MallocSizeOf))]
712pub struct AttrIdentifier {
713 pub local_name: LocalName,
714 pub name: LocalName,
715 pub namespace: Namespace,
716 pub prefix: Option<Prefix>,
717}