Source code

Revision control

Other Tools

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 https://mozilla.org/MPL/2.0/. */
4
5
//! Parsed representations of [DOM attributes][attr].
6
//!
8
9
use crate::properties::PropertyDeclarationBlock;
10
use crate::shared_lock::Locked;
11
use crate::str::str_join;
12
use crate::str::{read_exponent, read_fraction, HTML_SPACE_CHARACTERS};
13
use crate::str::{read_numbers, split_commas, split_html_space_chars};
14
use crate::values::specified::Length;
15
use crate::{Atom, LocalName, Namespace, Prefix};
16
use app_units::Au;
17
use cssparser::{self, Color, RGBA};
18
use euclid::num::Zero;
19
use num_traits::ToPrimitive;
20
use selectors::attr::AttrSelectorOperation;
21
use servo_arc::Arc;
22
use servo_url::ServoUrl;
23
use std::str::FromStr;
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(
68
String,
69
#[ignore_malloc_size_of = "Arc"] Arc<Locked<PropertyDeclarationBlock>>,
70
),
71
}
72
73
/// Shared implementation to parse an integer according to
76
fn do_parse_integer<T: Iterator<Item = char>>(input: T) -> Result<i64, ()> {
77
let mut input = input
78
.skip_while(|c| HTML_SPACE_CHARACTERS.iter().any(|s| s == c))
79
.peekable();
80
81
let sign = match input.peek() {
82
None => return Err(()),
83
Some(&'-') => {
84
input.next();
85
-1
86
},
87
Some(&'+') => {
88
input.next();
89
1
90
},
91
Some(_) => 1,
92
};
93
94
let (value, _) = read_numbers(input);
95
96
value.and_then(|value| value.checked_mul(sign)).ok_or(())
97
}
98
99
/// Parse an integer according to
101
pub fn parse_integer<T: Iterator<Item = char>>(input: T) -> Result<i32, ()> {
102
do_parse_integer(input).and_then(|result| result.to_i32().ok_or(()))
103
}
104
105
/// Parse an integer according to
107
pub fn parse_unsigned_integer<T: Iterator<Item = char>>(input: T) -> Result<u32, ()> {
108
do_parse_integer(input).and_then(|result| result.to_u32().ok_or(()))
109
}
110
111
/// Parse a floating-point number according to
113
pub fn parse_double(string: &str) -> Result<f64, ()> {
114
let trimmed = string.trim_matches(HTML_SPACE_CHARACTERS);
115
let mut input = trimmed.chars().peekable();
116
117
let (value, divisor, chars_skipped) = match input.peek() {
118
None => return Err(()),
119
Some(&'-') => {
120
input.next();
121
(-1f64, -1f64, 1)
122
},
123
Some(&'+') => {
124
input.next();
125
(1f64, 1f64, 1)
126
},
127
_ => (1f64, 1f64, 0),
128
};
129
130
let (value, value_digits) = if let Some(&'.') = input.peek() {
131
(0f64, 0)
132
} else {
133
let (read_val, read_digits) = read_numbers(input);
134
(
135
value * read_val.and_then(|result| result.to_f64()).unwrap_or(1f64),
136
read_digits,
137
)
138
};
139
140
let input = trimmed
141
.chars()
142
.skip(value_digits + chars_skipped)
143
.peekable();
144
145
let (mut value, fraction_digits) = read_fraction(input, divisor, value);
146
147
let input = trimmed
148
.chars()
149
.skip(value_digits + chars_skipped + fraction_digits)
150
.peekable();
151
152
if let Some(exp) = read_exponent(input) {
153
value *= 10f64.powi(exp)
154
};
155
156
Ok(value)
157
}
158
159
impl AttrValue {
160
pub fn from_serialized_tokenlist(tokens: String) -> AttrValue {
161
let atoms =
162
split_html_space_chars(&tokens)
163
.map(Atom::from)
164
.fold(vec![], |mut acc, atom| {
165
if !acc.contains(&atom) {
166
acc.push(atom)
167
}
168
acc
169
});
170
AttrValue::TokenList(tokens, atoms)
171
}
172
173
pub fn from_comma_separated_tokenlist(tokens: String) -> AttrValue {
174
let atoms = split_commas(&tokens)
175
.map(Atom::from)
176
.fold(vec![], |mut acc, atom| {
177
if !acc.contains(&atom) {
178
acc.push(atom)
179
}
180
acc
181
});
182
AttrValue::TokenList(tokens, atoms)
183
}
184
185
pub fn from_atomic_tokens(atoms: Vec<Atom>) -> AttrValue {
186
// TODO(ajeffrey): effecient conversion of Vec<Atom> to String
187
let tokens = String::from(str_join(&atoms, "\x20"));
188
AttrValue::TokenList(tokens, atoms)
189
}
190
192
pub fn from_u32(string: String, default: u32) -> AttrValue {
193
let result = parse_unsigned_integer(string.chars()).unwrap_or(default);
194
let result = if result > UNSIGNED_LONG_MAX {
195
default
196
} else {
197
result
198
};
199
AttrValue::UInt(string, result)
200
}
201
202
pub fn from_i32(string: String, default: i32) -> AttrValue {
203
let result = parse_integer(string.chars()).unwrap_or(default);
204
AttrValue::Int(string, result)
205
}
206
208
pub fn from_double(string: String, default: f64) -> AttrValue {
209
let result = parse_double(&string).unwrap_or(default);
210
211
if result.is_normal() {
212
AttrValue::Double(string, result)
213
} else {
214
AttrValue::Double(string, default)
215
}
216
}
217
219
pub fn from_limited_i32(string: String, default: i32) -> AttrValue {
220
let result = parse_integer(string.chars()).unwrap_or(default);
221
222
if result < 0 {
223
AttrValue::Int(string, default)
224
} else {
225
AttrValue::Int(string, result)
226
}
227
}
228
230
pub fn from_limited_u32(string: String, default: u32) -> AttrValue {
231
let result = parse_unsigned_integer(string.chars()).unwrap_or(default);
232
let result = if result == 0 || result > UNSIGNED_LONG_MAX {
233
default
234
} else {
235
result
236
};
237
AttrValue::UInt(string, result)
238
}
239
240
pub fn from_atomic(string: String) -> AttrValue {
241
let value = Atom::from(string);
242
AttrValue::Atom(value)
243
}
244
245
pub fn from_resolved_url(base: &ServoUrl, url: String) -> AttrValue {
246
let joined = base.join(&url).ok();
247
AttrValue::ResolvedUrl(url, joined)
248
}
249
250
pub fn from_legacy_color(string: String) -> AttrValue {
251
let parsed = parse_legacy_color(&string).ok();
252
AttrValue::Color(string, parsed)
253
}
254
255
pub fn from_dimension(string: String) -> AttrValue {
256
let parsed = parse_length(&string);
257
AttrValue::Dimension(string, parsed)
258
}
259
260
pub fn from_nonzero_dimension(string: String) -> AttrValue {
261
let parsed = parse_nonzero_length(&string);
262
AttrValue::Dimension(string, parsed)
263
}
264
265
/// Assumes the `AttrValue` is a `TokenList` and returns its tokens
266
///
267
/// ## Panics
268
///
269
/// Panics if the `AttrValue` is not a `TokenList`
270
pub fn as_tokens(&self) -> &[Atom] {
271
match *self {
272
AttrValue::TokenList(_, ref tokens) => tokens,
273
_ => panic!("Tokens not found"),
274
}
275
}
276
277
/// Assumes the `AttrValue` is an `Atom` and returns its value
278
///
279
/// ## Panics
280
///
281
/// Panics if the `AttrValue` is not an `Atom`
282
pub fn as_atom(&self) -> &Atom {
283
match *self {
284
AttrValue::Atom(ref value) => value,
285
_ => panic!("Atom not found"),
286
}
287
}
288
289
/// Assumes the `AttrValue` is a `Color` and returns its value
290
///
291
/// ## Panics
292
///
293
/// Panics if the `AttrValue` is not a `Color`
294
pub fn as_color(&self) -> Option<&RGBA> {
295
match *self {
296
AttrValue::Color(_, ref color) => color.as_ref(),
297
_ => panic!("Color not found"),
298
}
299
}
300
301
/// Assumes the `AttrValue` is a `Dimension` and returns its value
302
///
303
/// ## Panics
304
///
305
/// Panics if the `AttrValue` is not a `Dimension`
306
pub fn as_dimension(&self) -> &LengthOrPercentageOrAuto {
307
match *self {
308
AttrValue::Dimension(_, ref l) => l,
309
_ => panic!("Dimension not found"),
310
}
311
}
312
313
/// Assumes the `AttrValue` is a `ResolvedUrl` and returns its value.
314
///
315
/// ## Panics
316
///
317
/// Panics if the `AttrValue` is not a `ResolvedUrl`
318
pub fn as_resolved_url(&self) -> Option<&ServoUrl> {
319
match *self {
320
AttrValue::ResolvedUrl(_, ref url) => url.as_ref(),
321
_ => panic!("Url not found"),
322
}
323
}
324
325
/// Return the AttrValue as its integer representation, if any.
326
/// This corresponds to attribute values returned as `AttrValue::UInt(_)`
327
/// by `VirtualMethods::parse_plain_attribute()`.
328
///
329
/// ## Panics
330
///
331
/// Panics if the `AttrValue` is not a `UInt`
332
pub fn as_uint(&self) -> u32 {
333
if let AttrValue::UInt(_, value) = *self {
334
value
335
} else {
336
panic!("Uint not found");
337
}
338
}
339
340
/// Return the AttrValue as a dimension computed from its integer
341
/// representation, assuming that integer representation specifies pixels.
342
///
343
/// This corresponds to attribute values returned as `AttrValue::UInt(_)`
344
/// by `VirtualMethods::parse_plain_attribute()`.
345
///
346
/// ## Panics
347
///
348
/// Panics if the `AttrValue` is not a `UInt`
349
pub fn as_uint_px_dimension(&self) -> LengthOrPercentageOrAuto {
350
if let AttrValue::UInt(_, value) = *self {
351
LengthOrPercentageOrAuto::Length(Au::from_px(value as i32))
352
} else {
353
panic!("Uint not found");
354
}
355
}
356
357
pub fn eval_selector(&self, selector: &AttrSelectorOperation<&String>) -> bool {
358
// FIXME(SimonSapin) this can be more efficient by matching on `(self, selector)` variants
359
// and doing Atom comparisons instead of string comparisons where possible,
360
// with SelectorImpl::AttrValue changed to Atom.
361
selector.eval_str(self)
362
}
363
}
364
365
impl ::std::ops::Deref for AttrValue {
366
type Target = str;
367
368
fn deref(&self) -> &str {
369
match *self {
370
AttrValue::String(ref value) |
371
AttrValue::TokenList(ref value, _) |
372
AttrValue::UInt(ref value, _) |
373
AttrValue::Double(ref value, _) |
374
AttrValue::Length(ref value, _) |
375
AttrValue::Color(ref value, _) |
376
AttrValue::Int(ref value, _) |
377
AttrValue::ResolvedUrl(ref value, _) |
378
AttrValue::Declaration(ref value, _) |
379
AttrValue::Dimension(ref value, _) => &value,
380
AttrValue::Atom(ref value) => &value,
381
}
382
}
383
}
384
385
impl PartialEq<Atom> for AttrValue {
386
fn eq(&self, other: &Atom) -> bool {
387
match *self {
388
AttrValue::Atom(ref value) => value == other,
389
_ => other == &**self,
390
}
391
}
392
}
393
395
pub fn parse_nonzero_length(value: &str) -> LengthOrPercentageOrAuto {
396
match parse_length(value) {
397
LengthOrPercentageOrAuto::Length(x) if x == Au::zero() => LengthOrPercentageOrAuto::Auto,
398
LengthOrPercentageOrAuto::Percentage(x) if x == 0. => LengthOrPercentageOrAuto::Auto,
399
x => x,
400
}
401
}
402
403
/// Parses a [legacy color][color]. If unparseable, `Err` is returned.
404
///
406
pub fn parse_legacy_color(mut input: &str) -> Result<RGBA, ()> {
407
// Steps 1 and 2.
408
if input.is_empty() {
409
return Err(());
410
}
411
412
// Step 3.
413
input = input.trim_matches(HTML_SPACE_CHARACTERS);
414
415
// Step 4.
416
if input.eq_ignore_ascii_case("transparent") {
417
return Err(());
418
}
419
420
// Step 5.
421
if let Ok(Color::RGBA(rgba)) = cssparser::parse_color_keyword(input) {
422
return Ok(rgba);
423
}
424
425
// Step 6.
426
if input.len() == 4 {
427
if let (b'#', Ok(r), Ok(g), Ok(b)) = (
428
input.as_bytes()[0],
429
hex(input.as_bytes()[1] as char),
430
hex(input.as_bytes()[2] as char),
431
hex(input.as_bytes()[3] as char),
432
) {
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) = (
480
&input[..length],
481
&input[length..length * 2],
482
&input[length * 2..],
483
);
484
485
// Step 13.
486
if length > 8 {
487
red = &red[length - 8..];
488
green = &green[length - 8..];
489
blue = &blue[length - 8..];
490
length = 8
491
}
492
493
// Step 14.
494
while length > 2 && red[0] == b'0' && green[0] == b'0' && blue[0] == b'0' {
495
red = &red[1..];
496
green = &green[1..];
497
blue = &blue[1..];
498
length -= 1
499
}
500
501
// Steps 15-20.
502
return Ok(RGBA::new(
503
hex_string(red).unwrap(),
504
hex_string(green).unwrap(),
505
hex_string(blue).unwrap(),
506
255,
507
));
508
509
fn hex(ch: char) -> Result<u8, ()> {
510
match ch {
511
'0'..='9' => Ok((ch as u8) - b'0'),
512
'a'..='f' => Ok((ch as u8) - b'a' + 10),
513
'A'..='F' => Ok((ch as u8) - b'A' + 10),
514
_ => Err(()),
515
}
516
}
517
518
fn hex_string(string: &[u8]) -> Result<u8, ()> {
519
match string.len() {
520
0 => Err(()),
521
1 => hex(string[0] as char),
522
_ => {
523
let upper = hex(string[0] as char)?;
524
let lower = hex(string[1] as char)?;
525
Ok((upper << 4) | lower)
526
},
527
}
528
}
529
}
530
531
/// Parses a [dimension value][dim]. If unparseable, `Auto` is returned.
532
///
534
// TODO: this function can be rewritten to return Result<LengthPercentage, _>
535
pub fn parse_length(mut value: &str) -> LengthOrPercentageOrAuto {
536
// Steps 1 & 2 are not relevant
537
538
// Step 3
539
value = value.trim_start_matches(HTML_SPACE_CHARACTERS);
540
541
// Step 4
542
if value.is_empty() {
543
return LengthOrPercentageOrAuto::Auto;
544
}
545
546
// Step 5
547
if value.starts_with('+') {
548
value = &value[1..]
549
}
550
551
// Steps 6 & 7
552
match value.chars().nth(0) {
553
Some('0'..='9') => {},
554
_ => return LengthOrPercentageOrAuto::Auto,
555
}
556
557
// Steps 8 to 13
558
// We trim the string length to the minimum of:
559
// 1. the end of the string
560
// 2. the first occurence of a '%' (U+0025 PERCENT SIGN)
561
// 3. the second occurrence of a '.' (U+002E FULL STOP)
562
// 4. the occurrence of a character that is neither a digit nor '%' nor '.'
563
// Note: Step 10 is directly subsumed by FromStr::from_str
564
let mut end_index = value.len();
565
let (mut found_full_stop, mut found_percent) = (false, false);
566
for (i, ch) in value.chars().enumerate() {
567
match ch {
568
'0'..='9' => continue,
569
'%' => {
570
found_percent = true;
571
end_index = i;
572
break;
573
},
574
'.' if !found_full_stop => {
575
found_full_stop = true;
576
continue;
577
},
578
_ => {
579
end_index = i;
580
break;
581
},
582
}
583
}
584
value = &value[..end_index];
585
586
if found_percent {
587
let result: Result<f32, _> = FromStr::from_str(value);
588
match result {
589
Ok(number) => return LengthOrPercentageOrAuto::Percentage((number as f32) / 100.0),
590
Err(_) => return LengthOrPercentageOrAuto::Auto,
591
}
592
}
593
594
match FromStr::from_str(value) {
595
Ok(number) => LengthOrPercentageOrAuto::Length(Au::from_f64_px(number)),
596
Err(_) => LengthOrPercentageOrAuto::Auto,
597
}
598
}
599
600
/// A struct that uniquely identifies an element's attribute.
601
#[derive(Clone, Debug)]
602
#[cfg_attr(feature = "servo", derive(MallocSizeOf))]
603
pub struct AttrIdentifier {
604
pub local_name: LocalName,
605
pub name: LocalName,
606
pub namespace: Namespace,
607
pub prefix: Option<Prefix>,
608
}