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 http://mozilla.org/MPL/2.0/. */
4 
5 //! Parsed representations of [DOM attributes][attr].
6 //!
7 //! [attr]: https://dom.spec.whatwg.org/#interface-attr
8 
9 use {Atom, Prefix, Namespace, LocalName};
10 use app_units::Au;
11 use cssparser::{self, Color, RGBA};
12 use euclid::num::Zero;
13 use num_traits::ToPrimitive;
14 use properties::PropertyDeclarationBlock;
15 use selectors::attr::AttrSelectorOperation;
16 use servo_arc::Arc;
17 use servo_url::ServoUrl;
18 use shared_lock::Locked;
19 use std::str::FromStr;
20 use str::{HTML_SPACE_CHARACTERS, read_exponent, read_fraction};
21 use str::{read_numbers, split_commas, split_html_space_chars};
22 use str::str_join;
23 use values::specified::Length;
24 
25 // Duplicated from script::dom::values.
26 const UNSIGNED_LONG_MAX: u32 = 2147483647;
27 
28 #[derive(Clone, Copy, Debug, PartialEq)]
29 #[cfg_attr(feature = "servo", derive(MallocSizeOf))]
30 pub enum LengthOrPercentageOrAuto {
31     Auto,
32     Percentage(f32),
33     Length(Au),
34 }
35 
36 #[derive(Clone, Debug)]
37 #[cfg_attr(feature = "servo", derive(MallocSizeOf))]
38 pub enum AttrValue {
39     String(String),
40     TokenList(String, Vec<Atom>),
41     UInt(String, u32),
42     Int(String, i32),
43     Double(String, f64),
44     Atom(Atom),
45     Length(String, Option<Length>),
46     Color(String, Option<RGBA>),
47     Dimension(String, LengthOrPercentageOrAuto),
48 
49     /// Stores a URL, computed from the input string and a document's base URL.
50     ///
51     /// The URL is resolved at setting-time, so this kind of attribute value is
52     /// not actually suitable for most URL-reflecting IDL attributes.
53     ResolvedUrl(String, Option<ServoUrl>),
54 
55     /// Note that this variant is only used transitively as a fast path to set
56     /// the property declaration block relevant to the style of an element when
57     /// set from the inline declaration of that element (that is,
58     /// `element.style`).
59     ///
60     /// This can, as of this writing, only correspond to the value of the
61     /// `style` element, and is set from its relevant CSSInlineStyleDeclaration,
62     /// and then converted to a string in Element::attribute_mutated.
63     ///
64     /// Note that we don't necessarily need to do that (we could just clone the
65     /// declaration block), but that avoids keeping a refcounted
66     /// declarationblock for longer than needed.
67     Declaration(String,
68                 #[ignore_malloc_size_of = "Arc"]
69                 Arc<Locked<PropertyDeclarationBlock>>)
70 }
71 
72 /// Shared implementation to parse an integer according to
73 /// <https://html.spec.whatwg.org/multipage/#rules-for-parsing-integers> or
74 /// <https://html.spec.whatwg.org/multipage/#rules-for-parsing-non-negative-integers>
do_parse_integer<T: Iterator<Item=char>>(input: T) -> Result<i64, ()>75 fn do_parse_integer<T: Iterator<Item=char>>(input: T) -> Result<i64, ()> {
76     let mut input = input.skip_while(|c| {
77         HTML_SPACE_CHARACTERS.iter().any(|s| s == c)
78     }).peekable();
79 
80     let sign = match input.peek() {
81         None => return Err(()),
82         Some(&'-') => {
83             input.next();
84             -1
85         },
86         Some(&'+') => {
87             input.next();
88             1
89         },
90         Some(_) => 1,
91     };
92 
93     let (value, _) = read_numbers(input);
94 
95     value.and_then(|value| value.checked_mul(sign)).ok_or(())
96 }
97 
98 /// Parse an integer according to
99 /// <https://html.spec.whatwg.org/multipage/#rules-for-parsing-integers>.
parse_integer<T: Iterator<Item=char>>(input: T) -> Result<i32, ()>100 pub fn parse_integer<T: Iterator<Item=char>>(input: T) -> Result<i32, ()> {
101     do_parse_integer(input).and_then(|result| {
102         result.to_i32().ok_or(())
103     })
104 }
105 
106 /// Parse an integer according to
107 /// <https://html.spec.whatwg.org/multipage/#rules-for-parsing-non-negative-integers>
parse_unsigned_integer<T: Iterator<Item=char>>(input: T) -> Result<u32, ()>108 pub fn parse_unsigned_integer<T: Iterator<Item=char>>(input: T) -> Result<u32, ()> {
109     do_parse_integer(input).and_then(|result| {
110         result.to_u32().ok_or(())
111     })
112 }
113 
114 /// Parse a floating-point number according to
115 /// <https://html.spec.whatwg.org/multipage/#rules-for-parsing-floating-point-number-values>
parse_double(string: &str) -> Result<f64, ()>116 pub fn parse_double(string: &str) -> Result<f64, ()> {
117     let trimmed = string.trim_matches(HTML_SPACE_CHARACTERS);
118     let mut input = trimmed.chars().peekable();
119 
120     let (value, divisor, chars_skipped) = match input.peek() {
121         None => return Err(()),
122         Some(&'-') => {
123             input.next();
124             (-1f64, -1f64, 1)
125         }
126         Some(&'+') => {
127             input.next();
128             (1f64, 1f64, 1)
129         }
130         _ => (1f64, 1f64, 0)
131     };
132 
133     let (value, value_digits) = if let Some(&'.') = input.peek() {
134         (0f64, 0)
135     } else {
136         let (read_val, read_digits) = read_numbers(input);
137         (value * read_val.and_then(|result| result.to_f64()).unwrap_or(1f64), read_digits)
138     };
139 
140     let input = trimmed.chars().skip(value_digits + chars_skipped).peekable();
141 
142     let (mut value, fraction_digits) = read_fraction(input, divisor, value);
143 
144     let input = trimmed.chars().skip(value_digits + chars_skipped + fraction_digits).peekable();
145 
146     if let Some(exp) = read_exponent(input) {
147         value *= 10f64.powi(exp)
148     };
149 
150     Ok(value)
151 }
152 
153 impl AttrValue {
from_serialized_tokenlist(tokens: String) -> AttrValue154     pub fn from_serialized_tokenlist(tokens: String) -> AttrValue {
155         let atoms =
156             split_html_space_chars(&tokens)
157             .map(Atom::from)
158             .fold(vec![], |mut acc, atom| {
159                 if !acc.contains(&atom) { acc.push(atom) }
160                 acc
161             });
162         AttrValue::TokenList(tokens, atoms)
163     }
164 
from_comma_separated_tokenlist(tokens: String) -> AttrValue165     pub fn from_comma_separated_tokenlist(tokens: String) -> AttrValue {
166         let atoms = split_commas(&tokens).map(Atom::from)
167                                          .fold(vec![], |mut acc, atom| {
168                                              if !acc.contains(&atom) { acc.push(atom) }
169                                              acc
170                                          });
171         AttrValue::TokenList(tokens, atoms)
172     }
173 
from_atomic_tokens(atoms: Vec<Atom>) -> AttrValue174     pub fn from_atomic_tokens(atoms: Vec<Atom>) -> AttrValue {
175         // TODO(ajeffrey): effecient conversion of Vec<Atom> to String
176         let tokens = String::from(str_join(&atoms, "\x20"));
177         AttrValue::TokenList(tokens, atoms)
178     }
179 
180     // https://html.spec.whatwg.org/multipage/#reflecting-content-attributes-in-idl-attributes:idl-unsigned-long
from_u32(string: String, default: u32) -> AttrValue181     pub fn from_u32(string: String, default: u32) -> AttrValue {
182         let result = parse_unsigned_integer(string.chars()).unwrap_or(default);
183         let result = if result > UNSIGNED_LONG_MAX {
184             default
185         } else {
186             result
187         };
188         AttrValue::UInt(string, result)
189     }
190 
from_i32(string: String, default: i32) -> AttrValue191     pub fn from_i32(string: String, default: i32) -> AttrValue {
192         let result = parse_integer(string.chars()).unwrap_or(default);
193         AttrValue::Int(string, result)
194     }
195 
196     // https://html.spec.whatwg.org/multipage/#reflecting-content-attributes-in-idl-attributes:idl-double
from_double(string: String, default: f64) -> AttrValue197     pub fn from_double(string: String, default: f64) -> AttrValue {
198         let result = parse_double(&string).unwrap_or(default);
199 
200         if result.is_normal() {
201             AttrValue::Double(string, result)
202         } else {
203             AttrValue::Double(string, default)
204         }
205     }
206 
207     // https://html.spec.whatwg.org/multipage/#limited-to-only-non-negative-numbers
from_limited_i32(string: String, default: i32) -> AttrValue208     pub fn from_limited_i32(string: String, default: i32) -> AttrValue {
209         let result = parse_integer(string.chars()).unwrap_or(default);
210 
211         if result < 0 {
212             AttrValue::Int(string, default)
213         } else {
214             AttrValue::Int(string, result)
215         }
216     }
217 
218     // https://html.spec.whatwg.org/multipage/#limited-to-only-non-negative-numbers-greater-than-zero
from_limited_u32(string: String, default: u32) -> AttrValue219     pub fn from_limited_u32(string: String, default: u32) -> AttrValue {
220         let result = parse_unsigned_integer(string.chars()).unwrap_or(default);
221         let result = if result == 0 || result > UNSIGNED_LONG_MAX {
222             default
223         } else {
224             result
225         };
226         AttrValue::UInt(string, result)
227     }
228 
from_atomic(string: String) -> AttrValue229     pub fn from_atomic(string: String) -> AttrValue {
230         let value = Atom::from(string);
231         AttrValue::Atom(value)
232     }
233 
from_resolved_url(base: &ServoUrl, url: String) -> AttrValue234     pub fn from_resolved_url(base: &ServoUrl, url: String) -> AttrValue {
235         let joined = base.join(&url).ok();
236         AttrValue::ResolvedUrl(url, joined)
237     }
238 
from_legacy_color(string: String) -> AttrValue239     pub fn from_legacy_color(string: String) -> AttrValue {
240         let parsed = parse_legacy_color(&string).ok();
241         AttrValue::Color(string, parsed)
242     }
243 
from_dimension(string: String) -> AttrValue244     pub fn from_dimension(string: String) -> AttrValue {
245         let parsed = parse_length(&string);
246         AttrValue::Dimension(string, parsed)
247     }
248 
from_nonzero_dimension(string: String) -> AttrValue249     pub fn from_nonzero_dimension(string: String) -> AttrValue {
250         let parsed = parse_nonzero_length(&string);
251         AttrValue::Dimension(string, parsed)
252     }
253 
254     /// Assumes the `AttrValue` is a `TokenList` and returns its tokens
255     ///
256     /// ## Panics
257     ///
258     /// Panics if the `AttrValue` is not a `TokenList`
as_tokens(&self) -> &[Atom]259     pub fn as_tokens(&self) -> &[Atom] {
260         match *self {
261             AttrValue::TokenList(_, ref tokens) => tokens,
262             _ => panic!("Tokens not found"),
263         }
264     }
265 
266     /// Assumes the `AttrValue` is an `Atom` and returns its value
267     ///
268     /// ## Panics
269     ///
270     /// Panics if the `AttrValue` is not an `Atom`
as_atom(&self) -> &Atom271     pub fn as_atom(&self) -> &Atom {
272         match *self {
273             AttrValue::Atom(ref value) => value,
274             _ => panic!("Atom not found"),
275         }
276     }
277 
278     /// Assumes the `AttrValue` is a `Color` and returns its value
279     ///
280     /// ## Panics
281     ///
282     /// Panics if the `AttrValue` is not a `Color`
as_color(&self) -> Option<&RGBA>283     pub fn as_color(&self) -> Option<&RGBA> {
284         match *self {
285             AttrValue::Color(_, ref color) => color.as_ref(),
286             _ => panic!("Color not found"),
287         }
288     }
289 
290     /// Assumes the `AttrValue` is a `Length` and returns its value
291     ///
292     /// ## Panics
293     ///
294     /// Panics if the `AttrValue` is not a `Length`
as_length(&self) -> Option<&Length>295     pub fn as_length(&self) -> Option<&Length> {
296         match *self {
297             AttrValue::Length(_, ref length) => length.as_ref(),
298             _ => panic!("Length not found"),
299         }
300     }
301 
302     /// Assumes the `AttrValue` is a `Dimension` and returns its value
303     ///
304     /// ## Panics
305     ///
306     /// Panics if the `AttrValue` is not a `Dimension`
as_dimension(&self) -> &LengthOrPercentageOrAuto307     pub fn as_dimension(&self) -> &LengthOrPercentageOrAuto {
308         match *self {
309             AttrValue::Dimension(_, ref l) => l,
310             _ => panic!("Dimension not found"),
311         }
312     }
313 
314     /// Assumes the `AttrValue` is a `ResolvedUrl` and returns its value.
315     ///
316     /// ## Panics
317     ///
318     /// Panics if the `AttrValue` is not a `ResolvedUrl`
as_resolved_url(&self) -> Option<&ServoUrl>319     pub fn as_resolved_url(&self) -> Option<&ServoUrl> {
320         match *self {
321             AttrValue::ResolvedUrl(_, ref url) => url.as_ref(),
322             _ => panic!("Url not found"),
323         }
324     }
325 
326     /// Return the AttrValue as its integer representation, if any.
327     /// This corresponds to attribute values returned as `AttrValue::UInt(_)`
328     /// by `VirtualMethods::parse_plain_attribute()`.
329     ///
330     /// ## Panics
331     ///
332     /// Panics if the `AttrValue` is not a `UInt`
as_uint(&self) -> u32333     pub fn as_uint(&self) -> u32 {
334         if let AttrValue::UInt(_, value) = *self {
335             value
336         } else {
337             panic!("Uint not found");
338         }
339     }
340 
341     /// Return the AttrValue as a dimension computed from its integer
342     /// representation, assuming that integer representation specifies pixels.
343     ///
344     /// This corresponds to attribute values returned as `AttrValue::UInt(_)`
345     /// by `VirtualMethods::parse_plain_attribute()`.
346     ///
347     /// ## Panics
348     ///
349     /// Panics if the `AttrValue` is not a `UInt`
as_uint_px_dimension(&self) -> LengthOrPercentageOrAuto350     pub fn as_uint_px_dimension(&self) -> LengthOrPercentageOrAuto {
351         if let AttrValue::UInt(_, value) = *self {
352             LengthOrPercentageOrAuto::Length(Au::from_px(value as i32))
353         } else {
354             panic!("Uint not found");
355         }
356     }
357 
eval_selector(&self, selector: &AttrSelectorOperation<&String>) -> bool358     pub fn eval_selector(&self, selector: &AttrSelectorOperation<&String>) -> bool {
359         // FIXME(SimonSapin) this can be more efficient by matching on `(self, selector)` variants
360         // and doing Atom comparisons instead of string comparisons where possible,
361         // with SelectorImpl::AttrValue changed to Atom.
362         selector.eval_str(self)
363     }
364 }
365 
366 impl ::std::ops::Deref for AttrValue {
367     type Target = str;
368 
deref(&self) -> &str369     fn deref(&self) -> &str {
370         match *self {
371             AttrValue::String(ref value) |
372             AttrValue::TokenList(ref value, _) |
373             AttrValue::UInt(ref value, _) |
374             AttrValue::Double(ref value, _) |
375             AttrValue::Length(ref value, _) |
376             AttrValue::Color(ref value, _) |
377             AttrValue::Int(ref value, _) |
378             AttrValue::ResolvedUrl(ref value, _) |
379             AttrValue::Declaration(ref value, _) |
380             AttrValue::Dimension(ref value, _) => &value,
381             AttrValue::Atom(ref value) => &value,
382         }
383     }
384 }
385 
386 impl PartialEq<Atom> for AttrValue {
eq(&self, other: &Atom) -> bool387     fn eq(&self, other: &Atom) -> bool {
388         match *self {
389             AttrValue::Atom(ref value) => value == other,
390             _ => other == &**self,
391         }
392     }
393 }
394 
395 /// <https://html.spec.whatwg.org/multipage/#rules-for-parsing-non-zero-dimension-values>
parse_nonzero_length(value: &str) -> LengthOrPercentageOrAuto396 pub fn parse_nonzero_length(value: &str) -> LengthOrPercentageOrAuto {
397     match parse_length(value) {
398         LengthOrPercentageOrAuto::Length(x) if x == Au::zero() => LengthOrPercentageOrAuto::Auto,
399         LengthOrPercentageOrAuto::Percentage(x) if x == 0. => LengthOrPercentageOrAuto::Auto,
400         x => x,
401     }
402 }
403 
404 /// Parses a [legacy color][color]. If unparseable, `Err` is returned.
405 ///
406 /// [color]: https://html.spec.whatwg.org/multipage/#rules-for-parsing-a-legacy-colour-value
parse_legacy_color(mut input: &str) -> Result<RGBA, ()>407 pub fn parse_legacy_color(mut input: &str) -> Result<RGBA, ()> {
408     // Steps 1 and 2.
409     if input.is_empty() {
410         return Err(())
411     }
412 
413     // Step 3.
414     input = input.trim_matches(HTML_SPACE_CHARACTERS);
415 
416     // Step 4.
417     if input.eq_ignore_ascii_case("transparent") {
418         return Err(())
419     }
420 
421     // Step 5.
422     if let Ok(Color::RGBA(rgba)) = cssparser::parse_color_keyword(input) {
423         return Ok(rgba);
424     }
425 
426     // Step 6.
427     if input.len() == 4 {
428         if let (b'#', Ok(r), Ok(g), Ok(b)) =
429                 (input.as_bytes()[0],
430                 hex(input.as_bytes()[1] as char),
431                 hex(input.as_bytes()[2] as char),
432                 hex(input.as_bytes()[3] as char)) {
433             return Ok(RGBA::new(r * 17, g * 17, b * 17, 255))
434         }
435     }
436 
437     // Step 7.
438     let mut new_input = String::new();
439     for ch in input.chars() {
440         if ch as u32 > 0xffff {
441             new_input.push_str("00")
442         } else {
443             new_input.push(ch)
444         }
445     }
446     let mut input = &*new_input;
447 
448     // Step 8.
449     for (char_count, (index, _)) in input.char_indices().enumerate() {
450         if char_count == 128 {
451             input = &input[..index];
452             break
453         }
454     }
455 
456     // Step 9.
457     if input.as_bytes()[0] == b'#' {
458         input = &input[1..]
459     }
460 
461     // Step 10.
462     let mut new_input = Vec::new();
463     for ch in input.chars() {
464         if hex(ch).is_ok() {
465             new_input.push(ch as u8)
466         } else {
467             new_input.push(b'0')
468         }
469     }
470     let mut input = new_input;
471 
472     // Step 11.
473     while input.is_empty() || (input.len() % 3) != 0 {
474         input.push(b'0')
475     }
476 
477     // Step 12.
478     let mut length = input.len() / 3;
479     let (mut red, mut green, mut blue) = (&input[..length],
480                                           &input[length..length * 2],
481                                           &input[length * 2..]);
482 
483     // Step 13.
484     if length > 8 {
485         red = &red[length - 8..];
486         green = &green[length - 8..];
487         blue = &blue[length - 8..];
488         length = 8
489     }
490 
491     // Step 14.
492     while length > 2 && red[0] == b'0' && green[0] == b'0' && blue[0] == b'0' {
493         red = &red[1..];
494         green = &green[1..];
495         blue = &blue[1..];
496         length -= 1
497     }
498 
499     // Steps 15-20.
500     return Ok(RGBA::new(hex_string(red).unwrap(),
501                         hex_string(green).unwrap(),
502                         hex_string(blue).unwrap(),
503                         255));
504 
505     fn hex(ch: char) -> Result<u8, ()> {
506         match ch {
507             '0'...'9' => Ok((ch as u8) - b'0'),
508             'a'...'f' => Ok((ch as u8) - b'a' + 10),
509             'A'...'F' => Ok((ch as u8) - b'A' + 10),
510             _ => Err(()),
511         }
512     }
513 
514     fn hex_string(string: &[u8]) -> Result<u8, ()> {
515         match string.len() {
516             0 => Err(()),
517             1 => hex(string[0] as char),
518             _ => {
519                 let upper = hex(string[0] as char)?;
520                 let lower = hex(string[1] as char)?;
521                 Ok((upper << 4) | lower)
522             }
523         }
524     }
525 }
526 
527 /// Parses a [dimension value][dim]. If unparseable, `Auto` is returned.
528 ///
529 /// [dim]: https://html.spec.whatwg.org/multipage/#rules-for-parsing-dimension-values
530 // TODO: this function can be rewritten to return Result<LengthOrPercentage, _>
parse_length(mut value: &str) -> LengthOrPercentageOrAuto531 pub fn parse_length(mut value: &str) -> LengthOrPercentageOrAuto {
532     // Steps 1 & 2 are not relevant
533 
534     // Step 3
535     value = value.trim_left_matches(HTML_SPACE_CHARACTERS);
536 
537     // Step 4
538     if value.is_empty() {
539         return LengthOrPercentageOrAuto::Auto
540     }
541 
542     // Step 5
543     if value.starts_with('+') {
544         value = &value[1..]
545     }
546 
547     // Steps 6 & 7
548     match value.chars().nth(0) {
549         Some('0'...'9') => {},
550         _ => return LengthOrPercentageOrAuto::Auto,
551     }
552 
553     // Steps 8 to 13
554     // We trim the string length to the minimum of:
555     // 1. the end of the string
556     // 2. the first occurence of a '%' (U+0025 PERCENT SIGN)
557     // 3. the second occurrence of a '.' (U+002E FULL STOP)
558     // 4. the occurrence of a character that is neither a digit nor '%' nor '.'
559     // Note: Step 10 is directly subsumed by FromStr::from_str
560     let mut end_index = value.len();
561     let (mut found_full_stop, mut found_percent) = (false, false);
562     for (i, ch) in value.chars().enumerate() {
563         match ch {
564             '0'...'9' => continue,
565             '%' => {
566                 found_percent = true;
567                 end_index = i;
568                 break
569             }
570             '.' if !found_full_stop => {
571                 found_full_stop = true;
572                 continue
573             }
574             _ => {
575                 end_index = i;
576                 break
577             }
578         }
579     }
580     value = &value[..end_index];
581 
582     if found_percent {
583         let result: Result<f32, _> = FromStr::from_str(value);
584         match result {
585             Ok(number) => return LengthOrPercentageOrAuto::Percentage((number as f32) / 100.0),
586             Err(_) => return LengthOrPercentageOrAuto::Auto,
587         }
588     }
589 
590     match FromStr::from_str(value) {
591         Ok(number) => LengthOrPercentageOrAuto::Length(Au::from_f64_px(number)),
592         Err(_) => LengthOrPercentageOrAuto::Auto,
593     }
594 }
595 
596 /// A struct that uniquely identifies an element's attribute.
597 #[derive(Clone, Debug)]
598 #[cfg_attr(feature = "servo", derive(MallocSizeOf))]
599 pub struct AttrIdentifier {
600     pub local_name: LocalName,
601     pub name: LocalName,
602     pub namespace: Namespace,
603     pub prefix: Option<Prefix>,
604 }
605