diff --git a/components/script/dom/htmlimageelement.rs b/components/script/dom/htmlimageelement.rs
index 1fea05fc0d9..027221a88f0 100644
--- a/components/script/dom/htmlimageelement.rs
+++ b/components/script/dom/htmlimageelement.rs
@@ -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,
+ pub den: Option,
+}
+
#[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(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 {
+ let mut url_len = 0;
+ let mut candidates: Vec = 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 = None;
+ let mut density: Option = None;
+ let mut future_compat_h: Option = 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
+}
diff --git a/components/script/test.rs b/components/script/test.rs
index 88baa21f42f..351c3447688 100644
--- a/components/script/test.rs
+++ b/components/script/test.rs
@@ -62,3 +62,7 @@ pub mod size_of {
size_of::()
}
}
+
+pub mod srcset {
+ pub use dom::htmlimageelement::{parse_a_srcset_attribute, ImageSource, Descriptor};
+}
diff --git a/components/style/str.rs b/components/style/str.rs
index a76bf98caf5..bff3f7b43cf 100644
--- a/components/style/str.rs
+++ b/components/style/str.rs
@@ -58,7 +58,8 @@ pub fn split_commas<'a>(s: &'a str) -> Filter, 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,
diff --git a/tests/unit/script/htmlimageelement.rs b/tests/unit/script/htmlimageelement.rs
index 5c9106b6c0f..6d62f25a500 100644
--- a/tests/unit/script/htmlimageelement.rs
+++ b/tests/unit/script/htmlimageelement.rs
@@ -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);
+}