From 84a88d3e13812d604891282140928f35b056de51 Mon Sep 17 00:00:00 2001 From: Neha Date: Fri, 29 Sep 2017 10:31:11 +0000 Subject: [PATCH 1/2] Parse srcset attribute --- components/script/dom/htmlimageelement.rs | 200 +++++++++++++++++- .../dom/webidls/HTMLImageElement.webidl | 4 +- components/style/str.rs | 3 +- 3 files changed, 203 insertions(+), 4 deletions(-) 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/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, From 4c427f4565e11ce560cd2502e248ed904a199028 Mon Sep 17 00:00:00 2001 From: Neha Date: Fri, 29 Sep 2017 10:32:31 +0000 Subject: [PATCH 2/2] Test cases for parse srcset attribute --- components/script/test.rs | 4 ++ tests/unit/script/htmlimageelement.rs | 84 +++++++++++++++++++++++++++ 2 files changed, 88 insertions(+) create mode 100644 tests/unit/script/htmlimageelement.rs 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/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); +}