diff --git a/components/script/dom/htmlimageelement.rs b/components/script/dom/htmlimageelement.rs
index 49db07a3242c..788df8daa09d 100644
--- a/components/script/dom/htmlimageelement.rs
+++ b/components/script/dom/htmlimageelement.rs
@@ -55,9 +55,28 @@ use std::cell::{Cell, RefMut};
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::str::is_ascii_digit;
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 {
@@ -739,6 +758,11 @@ impl HTMLImageElementMethods for HTMLImageElement {
// https://html.spec.whatwg.org/multipage/#dom-img-src
make_setter!(SetSrc, "src");
+ // https://html.spec.whatwg.org/multipage/#parsing-a-srcset-attribute
+ make_url_getter!(Srcset, "srcset");
+ // https://html.spec.whatwg.org/multipage/#parsing-a-srcset-attribute
+ make_setter!(SetSrcset, "srcset");
+
// https://html.spec.whatwg.org/multipage/#dom-img-crossOrigin
fn GetCrossOrigin(&self) -> Option {
reflect_cross_origin_attribute(self.upcast::())
@@ -978,3 +1002,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/dom/webidls/HTMLImageElement.webidl b/components/script/dom/webidls/HTMLImageElement.webidl
index 4f5be4daf7cf..597a5b605db9 100644
--- a/components/script/dom/webidls/HTMLImageElement.webidl
+++ b/components/script/dom/webidls/HTMLImageElement.webidl
@@ -9,8 +9,8 @@ interface HTMLImageElement : HTMLElement {
attribute DOMString alt;
[CEReactions]
attribute DOMString src;
- // [CEReactions]
- // attribute DOMString srcset;
+ [CEReactions]
+ attribute DOMString srcset;
[CEReactions]
attribute DOMString? crossOrigin;
[CEReactions]
diff --git a/components/script/test.rs b/components/script/test.rs
index bdd84d681b86..4ce1b46f82da 100644
--- a/components/script/test.rs
+++ b/components/script/test.rs
@@ -58,3 +58,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 a76bf98caf5b..bff3f7b43cfe 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
new file mode 100644
index 000000000000..796e63c2e2b1
--- /dev/null
+++ b/tests/unit/script/htmlimageelement.rs
@@ -0,0 +1,84 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use script::test::srcset::{Descriptor, ImageSource, parse_a_srcset_attribute};
+
+#[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);
+}