mirror of
https://github.com/servo/servo.git
synced 2025-08-05 13:40:08 +01:00
Parse srcset attribute
This commit is contained in:
parent
77afc3f33a
commit
1f1ca3ac8d
4 changed files with 280 additions and 2 deletions
|
@ -57,15 +57,34 @@ use std::char;
|
|||
use std::default::Default;
|
||||
use std::i32;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use style::attr::{AttrValue, LengthOrPercentageOrAuto};
|
||||
use style::attr::{AttrValue, LengthOrPercentageOrAuto, parse_double, parse_unsigned_integer};
|
||||
use style::context::QuirksMode;
|
||||
use style::media_queries::MediaQuery;
|
||||
use style::parser::ParserContext;
|
||||
use style::str::is_ascii_digit;
|
||||
use style::values::specified::{Length, ViewportPercentageLength};
|
||||
use style::values::specified::length::NoCalcLength;
|
||||
use style_traits::ParsingMode;
|
||||
use task_source::TaskSource;
|
||||
|
||||
enum ParseState {
|
||||
InDescriptor,
|
||||
InParens,
|
||||
AfterDescriptor,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub struct ImageSource {
|
||||
pub url: String,
|
||||
pub descriptor: Descriptor,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub struct Descriptor {
|
||||
pub wid: Option<u32>,
|
||||
pub den: Option<f64>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, HeapSizeOf, JSTraceable)]
|
||||
#[allow(dead_code)]
|
||||
enum State {
|
||||
|
@ -1047,3 +1066,177 @@ fn image_dimension_setter(element: &Element, attr: LocalName, value: u32) {
|
|||
let value = AttrValue::Dimension(value.to_string(), dim);
|
||||
element.set_attribute(&attr, value);
|
||||
}
|
||||
|
||||
/// Collect sequence of code points
|
||||
pub fn collect_sequence_characters<F>(s: &str, predicate: F) -> (&str, &str)
|
||||
where F: Fn(&char) -> bool
|
||||
{
|
||||
for (i, ch) in s.chars().enumerate() {
|
||||
if !predicate(&ch) {
|
||||
return (&s[0..i], &s[i..])
|
||||
}
|
||||
}
|
||||
|
||||
return (s, "");
|
||||
}
|
||||
|
||||
/// Parse an `srcset` attribute - https://html.spec.whatwg.org/multipage/#parsing-a-srcset-attribute.
|
||||
pub fn parse_a_srcset_attribute(input: &str) -> Vec<ImageSource> {
|
||||
let mut url_len = 0;
|
||||
let mut candidates: Vec<ImageSource> = vec![];
|
||||
while url_len < input.len() {
|
||||
let position = &input[url_len..];
|
||||
let (spaces, position) = collect_sequence_characters(position, |c| *c == ',' || char::is_whitespace(*c));
|
||||
// add the length of the url that we parse to advance the start index
|
||||
let space_len = spaces.char_indices().count();
|
||||
url_len += space_len;
|
||||
if position.is_empty() {
|
||||
return candidates;
|
||||
}
|
||||
let (url, spaces) = collect_sequence_characters(position, |c| !char::is_whitespace(*c));
|
||||
// add the counts of urls that we parse to advance the start index
|
||||
url_len += url.chars().count();
|
||||
let comma_count = url.chars().rev().take_while(|c| *c == ',').count();
|
||||
let url: String = url.chars().take(url.chars().count() - comma_count).collect();
|
||||
// add 1 to start index, for the comma
|
||||
url_len += comma_count + 1;
|
||||
let (space, position) = collect_sequence_characters(spaces, |c| char::is_whitespace(*c));
|
||||
let space_len = space.len();
|
||||
url_len += space_len;
|
||||
let mut descriptors = Vec::new();
|
||||
let mut current_descriptor = String::new();
|
||||
let mut state = ParseState::InDescriptor;
|
||||
let mut char_stream = position.chars().enumerate();
|
||||
let mut buffered: Option<(usize, char)> = None;
|
||||
loop {
|
||||
let next_char = buffered.take().or_else(|| char_stream.next());
|
||||
if next_char.is_some() {
|
||||
url_len += 1;
|
||||
}
|
||||
match state {
|
||||
ParseState::InDescriptor => {
|
||||
match next_char {
|
||||
Some((_, ' ')) => {
|
||||
if !current_descriptor.is_empty() {
|
||||
descriptors.push(current_descriptor.clone());
|
||||
current_descriptor = String::new();
|
||||
state = ParseState::AfterDescriptor;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
Some((_, ',')) => {
|
||||
if !current_descriptor.is_empty() {
|
||||
descriptors.push(current_descriptor.clone());
|
||||
}
|
||||
break;
|
||||
}
|
||||
Some((_, c @ '(')) => {
|
||||
current_descriptor.push(c);
|
||||
state = ParseState::InParens;
|
||||
continue;
|
||||
}
|
||||
Some((_, c)) => {
|
||||
current_descriptor.push(c);
|
||||
}
|
||||
None => {
|
||||
if !current_descriptor.is_empty() {
|
||||
descriptors.push(current_descriptor.clone());
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
ParseState::InParens => {
|
||||
match next_char {
|
||||
Some((_, c @ ')')) => {
|
||||
current_descriptor.push(c);
|
||||
state = ParseState::InDescriptor;
|
||||
continue;
|
||||
}
|
||||
Some((_, c)) => {
|
||||
current_descriptor.push(c);
|
||||
continue;
|
||||
}
|
||||
None => {
|
||||
if !current_descriptor.is_empty() {
|
||||
descriptors.push(current_descriptor.clone());
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
ParseState::AfterDescriptor => {
|
||||
match next_char {
|
||||
Some((_, ' ')) => {
|
||||
state = ParseState::AfterDescriptor;
|
||||
continue;
|
||||
}
|
||||
Some((idx, c)) => {
|
||||
state = ParseState::InDescriptor;
|
||||
buffered = Some((idx, c));
|
||||
continue;
|
||||
}
|
||||
None => {
|
||||
if !current_descriptor.is_empty() {
|
||||
descriptors.push(current_descriptor.clone());
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut error = false;
|
||||
let mut width: Option<u32> = None;
|
||||
let mut density: Option<f64> = None;
|
||||
let mut future_compat_h: Option<u32> = None;
|
||||
for descriptor in descriptors {
|
||||
let (digits, remaining) = collect_sequence_characters(&descriptor, |c| is_ascii_digit(c) || *c == '.');
|
||||
let valid_non_negative_integer = parse_unsigned_integer(digits.chars());
|
||||
let has_w = remaining == "w";
|
||||
let valid_floating_point = parse_double(digits);
|
||||
let has_x = remaining == "x";
|
||||
let has_h = remaining == "h";
|
||||
if valid_non_negative_integer.is_ok() && has_w {
|
||||
let result = valid_non_negative_integer;
|
||||
error = result.is_err();
|
||||
if width.is_some() || density.is_some() {
|
||||
error = true;
|
||||
}
|
||||
if let Ok(w) = result {
|
||||
width = Some(w);
|
||||
}
|
||||
} else if valid_floating_point.is_ok() && has_x {
|
||||
let result = valid_floating_point;
|
||||
error = result.is_err();
|
||||
if width.is_some() || density.is_some() || future_compat_h.is_some() {
|
||||
error = true;
|
||||
}
|
||||
if let Ok(x) = result {
|
||||
density = Some(x);
|
||||
}
|
||||
} else if valid_non_negative_integer.is_ok() && has_h {
|
||||
let result = valid_non_negative_integer;
|
||||
error = result.is_err();
|
||||
if density.is_some() || future_compat_h.is_some() {
|
||||
error = true;
|
||||
}
|
||||
if let Ok(h) = result {
|
||||
future_compat_h = Some(h);
|
||||
}
|
||||
} else {
|
||||
error = true;
|
||||
}
|
||||
}
|
||||
if future_compat_h.is_some() && width.is_none() {
|
||||
error = true;
|
||||
}
|
||||
if !error {
|
||||
let descriptor = Descriptor { wid: width, den: density };
|
||||
let image_source = ImageSource { url: url, descriptor: descriptor };
|
||||
candidates.push(image_source);
|
||||
}
|
||||
}
|
||||
candidates
|
||||
}
|
||||
|
|
|
@ -62,3 +62,7 @@ pub mod size_of {
|
|||
size_of::<Text>()
|
||||
}
|
||||
}
|
||||
|
||||
pub mod srcset {
|
||||
pub use dom::htmlimageelement::{parse_a_srcset_attribute, ImageSource, Descriptor};
|
||||
}
|
||||
|
|
|
@ -58,7 +58,8 @@ pub fn split_commas<'a>(s: &'a str) -> Filter<Split<'a, char>, fn(&&str) -> bool
|
|||
s.split(',').filter(not_empty as fn(&&str) -> bool)
|
||||
}
|
||||
|
||||
fn is_ascii_digit(c: &char) -> bool {
|
||||
/// Character is ascii digit
|
||||
pub fn is_ascii_digit(c: &char) -> bool {
|
||||
match *c {
|
||||
'0'...'9' => true,
|
||||
_ => false,
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
|
||||
use script::test::DOMString;
|
||||
use script::test::sizes::{parse_a_sizes_attribute, Size};
|
||||
use script::test::srcset::{Descriptor, ImageSource, parse_a_srcset_attribute};
|
||||
use style::media_queries::{MediaQuery, MediaQueryType};
|
||||
use style::media_queries::Expression;
|
||||
use style::servo::media_queries::{ExpressionKind, Range};
|
||||
|
@ -99,3 +100,82 @@ fn extra_whitespace() {
|
|||
DOMString::from("(max-width: 900px) 1000px, (max-width: 900px) 50px"),
|
||||
None), a);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn no_value() {
|
||||
let new_vec = Vec::new();
|
||||
assert_eq!(parse_a_srcset_attribute(" "), new_vec);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn width_one_value() {
|
||||
let first_descriptor = Descriptor { wid: Some(320), den: None };
|
||||
let first_imagesource = ImageSource { url: "small-image.jpg".to_string(), descriptor: first_descriptor };
|
||||
let sources = &[first_imagesource];
|
||||
assert_eq!(parse_a_srcset_attribute("small-image.jpg, 320w"), sources);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn width_two_value() {
|
||||
let first_descriptor = Descriptor { wid: Some(320), den: None };
|
||||
let first_imagesource = ImageSource { url: "small-image.jpg".to_string(), descriptor: first_descriptor };
|
||||
let second_descriptor = Descriptor { wid: Some(480), den: None };
|
||||
let second_imagesource = ImageSource { url: "medium-image.jpg".to_string(), descriptor: second_descriptor };
|
||||
let sources = &[first_imagesource, second_imagesource];
|
||||
assert_eq!(parse_a_srcset_attribute("small-image.jpg 320w, medium-image.jpg 480w"), sources);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn width_three_value() {
|
||||
let first_descriptor = Descriptor { wid: Some(320), den: None };
|
||||
let first_imagesource = ImageSource { url: "smallImage.jpg".to_string(), descriptor: first_descriptor };
|
||||
let second_descriptor = Descriptor { wid: Some(480), den: None };
|
||||
let second_imagesource = ImageSource { url: "mediumImage.jpg".to_string(), descriptor: second_descriptor };
|
||||
let third_descriptor = Descriptor { wid: Some(800), den: None };
|
||||
let third_imagesource = ImageSource { url: "largeImage.jpg".to_string(), descriptor: third_descriptor };
|
||||
let sources = &[first_imagesource, second_imagesource, third_imagesource];
|
||||
assert_eq!(parse_a_srcset_attribute("smallImage.jpg 320w,
|
||||
mediumImage.jpg 480w,
|
||||
largeImage.jpg 800w"), sources);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn density_value() {
|
||||
let first_descriptor = Descriptor { wid: None, den: Some(1.0) };
|
||||
let first_imagesource = ImageSource { url: "small-image.jpg".to_string(), descriptor: first_descriptor };
|
||||
let sources = &[first_imagesource];
|
||||
assert_eq!(parse_a_srcset_attribute("small-image.jpg 1x"), sources);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn without_descriptor() {
|
||||
let first_descriptor = Descriptor { wid: None, den: None };
|
||||
let first_imagesource = ImageSource { url: "small-image.jpg".to_string(), descriptor: first_descriptor };
|
||||
let sources = &[first_imagesource];
|
||||
assert_eq!(parse_a_srcset_attribute("small-image.jpg"), sources);
|
||||
}
|
||||
|
||||
//Does not parse an ImageSource when both width and density descriptor present
|
||||
#[test]
|
||||
fn two_descriptor() {
|
||||
let empty_vec = Vec::new();
|
||||
assert_eq!(parse_a_srcset_attribute("small-image.jpg 320w 1.1x"), empty_vec);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn decimal_descriptor() {
|
||||
let first_descriptor = Descriptor { wid: None, den: Some(2.2) };
|
||||
let first_imagesource = ImageSource { url: "small-image.jpg".to_string(), descriptor: first_descriptor };
|
||||
let sources = &[first_imagesource];
|
||||
assert_eq!(parse_a_srcset_attribute("small-image.jpg 2.2x"), sources);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn different_descriptor() {
|
||||
let first_descriptor = Descriptor { wid: Some(320), den: None };
|
||||
let first_imagesource = ImageSource { url: "small-image.jpg".to_string(), descriptor: first_descriptor };
|
||||
let second_descriptor = Descriptor { wid: None, den: Some(2.2) };
|
||||
let second_imagesource = ImageSource { url: "medium-image.jpg".to_string(), descriptor: second_descriptor };
|
||||
let sources = &[first_imagesource, second_imagesource];
|
||||
assert_eq!(parse_a_srcset_attribute("small-image.jpg 320w, medium-image.jpg 2.2x"), sources);
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue