1use super::shadow_parts::ShadowParts;
10use crate::color::{parsing::parse_color_keyword, AbsoluteColor};
11use crate::properties::PropertyDeclarationBlock;
12use crate::shared_lock::Locked;
13use crate::str::str_join;
14use crate::str::{read_exponent, read_fraction, HTML_SPACE_CHARACTERS};
15use crate::str::{read_numbers, split_commas, split_html_space_chars};
16use crate::values::specified::color::Color;
17use crate::values::specified::Length;
18use crate::values::AtomString;
19use crate::{Atom, LocalName, Namespace, Prefix};
20use app_units::Au;
21use euclid::num::Zero;
22use num_traits::ToPrimitive;
23use selectors::attr::AttrSelectorOperation;
24use servo_arc::Arc;
25use std::str::FromStr;
26
27const UNSIGNED_LONG_MAX: u32 = 2147483647;
29
30#[derive(Clone, Copy, Debug, PartialEq)]
31#[cfg_attr(feature = "servo", derive(MallocSizeOf))]
32pub enum LengthOrPercentageOrAuto {
33 Auto,
34 Percentage(f32),
35 Length(Au),
36}
37
38#[derive(Clone, Debug)]
39#[cfg_attr(feature = "servo", derive(MallocSizeOf))]
40pub enum AttrValue {
41 String(String),
42 TokenList(String, Vec<Atom>),
43 UInt(String, u32),
44 Int(String, i32),
45 Double(String, f64),
46 Atom(Atom),
47 Length(String, Option<Length>),
48 Color(String, Option<AbsoluteColor>),
49 Dimension(String, LengthOrPercentageOrAuto),
50
51 ResolvedUrl(
56 String,
57 #[ignore_malloc_size_of = "Arc"] Option<Arc<url::Url>>,
58 ),
59
60 Declaration(
73 String,
74 #[ignore_malloc_size_of = "Arc"] Arc<Locked<PropertyDeclarationBlock>>,
75 ),
76
77 ShadowParts(String, ShadowParts),
79}
80
81fn do_parse_integer<T: Iterator<Item = char>>(input: T) -> Result<i64, ()> {
85 let mut input = input
86 .skip_while(|c| HTML_SPACE_CHARACTERS.iter().any(|s| s == c))
87 .peekable();
88
89 let sign = match input.peek() {
90 None => return Err(()),
91 Some(&'-') => {
92 input.next();
93 -1
94 },
95 Some(&'+') => {
96 input.next();
97 1
98 },
99 Some(_) => 1,
100 };
101
102 let (value, _) = read_numbers(input);
103
104 value.and_then(|value| value.checked_mul(sign)).ok_or(())
105}
106
107pub fn parse_integer<T: Iterator<Item = char>>(input: T) -> Result<i32, ()> {
110 do_parse_integer(input).and_then(|result| result.to_i32().ok_or(()))
111}
112
113pub fn parse_unsigned_integer<T: Iterator<Item = char>>(input: T) -> Result<u32, ()> {
116 do_parse_integer(input).and_then(|result| result.to_u32().ok_or(()))
117}
118
119pub fn parse_double(string: &str) -> Result<f64, ()> {
122 let trimmed = string.trim_matches(HTML_SPACE_CHARACTERS);
123 let mut input = trimmed.chars().peekable();
124
125 let (value, divisor, chars_skipped) = match input.peek() {
126 None => return Err(()),
127 Some(&'-') => {
128 input.next();
129 (-1f64, -1f64, 1)
130 },
131 Some(&'+') => {
132 input.next();
133 (1f64, 1f64, 1)
134 },
135 _ => (1f64, 1f64, 0),
136 };
137
138 let (value, value_digits) = if let Some(&'.') = input.peek() {
139 (0f64, 0)
140 } else {
141 let (read_val, read_digits) = read_numbers(input);
142 (
143 value * read_val.and_then(|result| result.to_f64()).unwrap_or(1f64),
144 read_digits,
145 )
146 };
147
148 let input = trimmed
149 .chars()
150 .skip(value_digits + chars_skipped)
151 .peekable();
152
153 let (mut value, fraction_digits) = read_fraction(input, divisor, value);
154
155 let input = trimmed
156 .chars()
157 .skip(value_digits + chars_skipped + fraction_digits)
158 .peekable();
159
160 if let Some(exp) = read_exponent(input) {
161 value *= 10f64.powi(exp)
162 };
163
164 Ok(value)
165}
166
167impl AttrValue {
168 pub fn from_serialized_tokenlist(tokens: String) -> AttrValue {
169 let atoms =
170 split_html_space_chars(&tokens)
171 .map(Atom::from)
172 .fold(vec![], |mut acc, atom| {
173 if !acc.contains(&atom) {
174 acc.push(atom)
175 }
176 acc
177 });
178 AttrValue::TokenList(tokens, atoms)
179 }
180
181 pub fn from_comma_separated_tokenlist(tokens: String) -> AttrValue {
182 let atoms = split_commas(&tokens)
183 .map(Atom::from)
184 .fold(vec![], |mut acc, atom| {
185 if !acc.contains(&atom) {
186 acc.push(atom)
187 }
188 acc
189 });
190 AttrValue::TokenList(tokens, atoms)
191 }
192
193 pub fn from_atomic_tokens(atoms: Vec<Atom>) -> AttrValue {
194 let tokens = String::from(str_join(&atoms, "\x20"));
196 AttrValue::TokenList(tokens, atoms)
197 }
198
199 pub fn from_u32(string: String, default: u32) -> AttrValue {
201 let result = parse_unsigned_integer(string.chars()).unwrap_or(default);
202 let result = if result > UNSIGNED_LONG_MAX {
203 default
204 } else {
205 result
206 };
207 AttrValue::UInt(string, result)
208 }
209
210 pub fn from_i32(string: String, default: i32) -> AttrValue {
211 let result = parse_integer(string.chars()).unwrap_or(default);
212 AttrValue::Int(string, result)
213 }
214
215 pub fn from_double(string: String, default: f64) -> AttrValue {
217 let result = parse_double(&string).unwrap_or(default);
218
219 if result.is_normal() {
220 AttrValue::Double(string, result)
221 } else {
222 AttrValue::Double(string, default)
223 }
224 }
225
226 pub fn from_limited_i32(string: String, default: i32) -> AttrValue {
228 let result = parse_integer(string.chars()).unwrap_or(default);
229
230 if result < 0 {
231 AttrValue::Int(string, default)
232 } else {
233 AttrValue::Int(string, result)
234 }
235 }
236
237 pub fn from_limited_u32(string: String, default: u32) -> AttrValue {
239 let result = parse_unsigned_integer(string.chars()).unwrap_or(default);
240 let result = if result == 0 || result > UNSIGNED_LONG_MAX {
241 default
242 } else {
243 result
244 };
245 AttrValue::UInt(string, result)
246 }
247
248 pub fn from_atomic(string: String) -> AttrValue {
249 let value = Atom::from(string);
250 AttrValue::Atom(value)
251 }
252
253 pub fn from_resolved_url(base: &Arc<::url::Url>, url: String) -> AttrValue {
254 let joined = base.join(&url).ok().map(Arc::new);
255 AttrValue::ResolvedUrl(url, joined)
256 }
257
258 pub fn from_legacy_color(string: String) -> AttrValue {
259 let parsed = parse_legacy_color(&string).ok();
260 AttrValue::Color(string, parsed)
261 }
262
263 pub fn from_dimension(string: String) -> AttrValue {
264 let parsed = parse_length(&string);
265 AttrValue::Dimension(string, parsed)
266 }
267
268 pub fn from_nonzero_dimension(string: String) -> AttrValue {
269 let parsed = parse_nonzero_length(&string);
270 AttrValue::Dimension(string, parsed)
271 }
272
273 pub fn from_shadow_parts(string: String) -> AttrValue {
274 let shadow_parts = ShadowParts::parse(&string);
275 AttrValue::ShadowParts(string, shadow_parts)
276 }
277
278 pub fn as_tokens(&self) -> &[Atom] {
284 match *self {
285 AttrValue::TokenList(_, ref tokens) => tokens,
286 _ => panic!("Tokens not found"),
287 }
288 }
289
290 pub fn as_atom(&self) -> &Atom {
296 match *self {
297 AttrValue::Atom(ref value) => value,
298 _ => panic!("Atom not found"),
299 }
300 }
301
302 pub fn as_color(&self) -> Option<&AbsoluteColor> {
308 match *self {
309 AttrValue::Color(_, ref color) => color.as_ref(),
310 _ => panic!("Color not found"),
311 }
312 }
313
314 pub fn as_dimension(&self) -> &LengthOrPercentageOrAuto {
320 match *self {
321 AttrValue::Dimension(_, ref l) => l,
322 _ => panic!("Dimension not found"),
323 }
324 }
325
326 pub fn as_resolved_url(&self) -> Option<&Arc<::url::Url>> {
332 match *self {
333 AttrValue::ResolvedUrl(_, ref url) => url.as_ref(),
334 _ => panic!("Url not found"),
335 }
336 }
337
338 pub fn as_int(&self) -> i32 {
346 if let AttrValue::Int(_, value) = *self {
347 value
348 } else {
349 panic!("Int not found");
350 }
351 }
352
353 pub fn as_uint(&self) -> u32 {
361 if let AttrValue::UInt(_, value) = *self {
362 value
363 } else {
364 panic!("Uint not found");
365 }
366 }
367
368 pub fn as_uint_px_dimension(&self) -> LengthOrPercentageOrAuto {
378 if let AttrValue::UInt(_, value) = *self {
379 LengthOrPercentageOrAuto::Length(Au::from_px(value as i32))
380 } else {
381 panic!("Uint not found");
382 }
383 }
384
385 pub fn as_shadow_parts(&self) -> &ShadowParts {
394 if let AttrValue::ShadowParts(_, value) = &self {
395 value
396 } else {
397 panic!("Not a shadowpart attribute");
398 }
399 }
400
401 pub fn eval_selector(&self, selector: &AttrSelectorOperation<&AtomString>) -> bool {
402 selector.eval_str(self)
406 }
407}
408
409impl ::std::ops::Deref for AttrValue {
410 type Target = str;
411
412 fn deref(&self) -> &str {
413 match *self {
414 AttrValue::String(ref value)
415 | AttrValue::TokenList(ref value, _)
416 | AttrValue::UInt(ref value, _)
417 | AttrValue::Double(ref value, _)
418 | AttrValue::Length(ref value, _)
419 | AttrValue::Color(ref value, _)
420 | AttrValue::Int(ref value, _)
421 | AttrValue::ResolvedUrl(ref value, _)
422 | AttrValue::Declaration(ref value, _)
423 | AttrValue::ShadowParts(ref value, _)
424 | AttrValue::Dimension(ref value, _) => &value,
425 AttrValue::Atom(ref value) => &value,
426 }
427 }
428}
429
430impl PartialEq<Atom> for AttrValue {
431 fn eq(&self, other: &Atom) -> bool {
432 match *self {
433 AttrValue::Atom(ref value) => value == other,
434 _ => other == &**self,
435 }
436 }
437}
438
439pub fn parse_nonzero_length(value: &str) -> LengthOrPercentageOrAuto {
441 match parse_length(value) {
442 LengthOrPercentageOrAuto::Length(x) if x == Au::zero() => LengthOrPercentageOrAuto::Auto,
443 LengthOrPercentageOrAuto::Percentage(x) if x == 0. => LengthOrPercentageOrAuto::Auto,
444 x => x,
445 }
446}
447
448pub fn parse_legacy_color(mut input: &str) -> Result<AbsoluteColor, ()> {
452 if input.is_empty() {
454 return Err(());
455 }
456
457 input = input.trim_matches(HTML_SPACE_CHARACTERS);
459
460 if input.eq_ignore_ascii_case("transparent") {
462 return Err(());
463 }
464
465 if let Ok(Color::Absolute(ref absolute)) = parse_color_keyword(input) {
467 return Ok(absolute.color);
468 }
469
470 if input.len() == 4 {
472 if let (b'#', Ok(r), Ok(g), Ok(b)) = (
473 input.as_bytes()[0],
474 hex(input.as_bytes()[1] as char),
475 hex(input.as_bytes()[2] as char),
476 hex(input.as_bytes()[3] as char),
477 ) {
478 return Ok(AbsoluteColor::srgb_legacy(r * 17, g * 17, b * 17, 1.0));
479 }
480 }
481
482 let mut new_input = String::new();
484 for ch in input.chars() {
485 if ch as u32 > 0xffff {
486 new_input.push_str("00")
487 } else {
488 new_input.push(ch)
489 }
490 }
491 let mut input = &*new_input;
492
493 for (char_count, (index, _)) in input.char_indices().enumerate() {
495 if char_count == 128 {
496 input = &input[..index];
497 break;
498 }
499 }
500
501 if input.as_bytes()[0] == b'#' {
503 input = &input[1..]
504 }
505
506 let mut new_input = Vec::new();
508 for ch in input.chars() {
509 if hex(ch).is_ok() {
510 new_input.push(ch as u8)
511 } else {
512 new_input.push(b'0')
513 }
514 }
515 let mut input = new_input;
516
517 while input.is_empty() || (input.len() % 3) != 0 {
519 input.push(b'0')
520 }
521
522 let mut length = input.len() / 3;
524 let (mut red, mut green, mut blue) = (
525 &input[..length],
526 &input[length..length * 2],
527 &input[length * 2..],
528 );
529
530 if length > 8 {
532 red = &red[length - 8..];
533 green = &green[length - 8..];
534 blue = &blue[length - 8..];
535 length = 8
536 }
537
538 while length > 2 && red[0] == b'0' && green[0] == b'0' && blue[0] == b'0' {
540 red = &red[1..];
541 green = &green[1..];
542 blue = &blue[1..];
543 length -= 1
544 }
545
546 return Ok(AbsoluteColor::srgb_legacy(
548 hex_string(red).unwrap(),
549 hex_string(green).unwrap(),
550 hex_string(blue).unwrap(),
551 1.0,
552 ));
553
554 fn hex(ch: char) -> Result<u8, ()> {
555 match ch {
556 '0'..='9' => Ok((ch as u8) - b'0'),
557 'a'..='f' => Ok((ch as u8) - b'a' + 10),
558 'A'..='F' => Ok((ch as u8) - b'A' + 10),
559 _ => Err(()),
560 }
561 }
562
563 fn hex_string(string: &[u8]) -> Result<u8, ()> {
564 match string.len() {
565 0 => Err(()),
566 1 => hex(string[0] as char),
567 _ => {
568 let upper = hex(string[0] as char)?;
569 let lower = hex(string[1] as char)?;
570 Ok((upper << 4) | lower)
571 },
572 }
573 }
574}
575
576pub fn parse_length(mut value: &str) -> LengthOrPercentageOrAuto {
581 value = value.trim_start_matches(HTML_SPACE_CHARACTERS);
585
586 match value.chars().nth(0) {
588 Some('0'..='9') => {},
589 _ => return LengthOrPercentageOrAuto::Auto,
590 }
591
592 let mut end_index = value.len();
600 let (mut found_full_stop, mut found_percent) = (false, false);
601 for (i, ch) in value.chars().enumerate() {
602 match ch {
603 '0'..='9' => continue,
604 '%' => {
605 found_percent = true;
606 end_index = i;
607 break;
608 },
609 '.' if !found_full_stop => {
610 found_full_stop = true;
611 continue;
612 },
613 _ => {
614 end_index = i;
615 break;
616 },
617 }
618 }
619 value = &value[..end_index];
620
621 if found_percent {
622 let result: Result<f32, _> = FromStr::from_str(value);
623 match result {
624 Ok(number) => return LengthOrPercentageOrAuto::Percentage((number as f32) / 100.0),
625 Err(_) => return LengthOrPercentageOrAuto::Auto,
626 }
627 }
628
629 match FromStr::from_str(value) {
630 Ok(number) => LengthOrPercentageOrAuto::Length(Au::from_f64_px(number)),
631 Err(_) => LengthOrPercentageOrAuto::Auto,
632 }
633}
634
635#[derive(Clone, Debug)]
637#[cfg_attr(feature = "servo", derive(MallocSizeOf))]
638pub struct AttrIdentifier {
639 pub local_name: LocalName,
640 pub name: LocalName,
641 pub namespace: Namespace,
642 pub prefix: Option<Prefix>,
643}