From 8d06da8afd5709df38e2563584480e9a514e12a4 Mon Sep 17 00:00:00 2001 From: nupurbaghel Date: Wed, 4 Jul 2018 01:17:22 +0530 Subject: [PATCH] implement update_source_set, select_image_source --- components/script/dom/bindings/trace.rs | 2 + components/script/dom/htmlimageelement.rs | 286 +++++++++++++++++- .../values/specified/source_size_list.rs | 7 + .../the-img-element/adoption.html.ini | 7 +- .../fail-to-resolve.html.ini | 3 - .../update-the-source-set.html.ini | 81 +---- 6 files changed, 288 insertions(+), 98 deletions(-) diff --git a/components/script/dom/bindings/trace.rs b/components/script/dom/bindings/trace.rs index 06892ec84782e..20870c5f876ce 100644 --- a/components/script/dom/bindings/trace.rs +++ b/components/script/dom/bindings/trace.rs @@ -47,6 +47,7 @@ use dom::bindings::root::{Dom, DomRoot}; use dom::bindings::str::{DOMString, USVString}; use dom::bindings::utils::WindowProxyHandler; use dom::document::PendingRestyle; +use dom::htmlimageelement::SourceSet; use encoding_rs::{Decoder, Encoding}; use euclid::{Transform2D, Transform3D, Point2D, Vector2D, Rect, TypedSize2D, TypedScale}; use euclid::Length as EuclidLength; @@ -425,6 +426,7 @@ unsafe_no_jsmanaged_fields!(ScriptToConstellationChan); unsafe_no_jsmanaged_fields!(InteractiveMetrics); unsafe_no_jsmanaged_fields!(InteractiveWindow); unsafe_no_jsmanaged_fields!(CanvasId); +unsafe_no_jsmanaged_fields!(SourceSet); unsafe impl<'a> JSTraceable for &'a str { #[inline] diff --git a/components/script/dom/htmlimageelement.rs b/components/script/dom/htmlimageelement.rs index 668c66d773480..4ec57511f8d50 100644 --- a/components/script/dom/htmlimageelement.rs +++ b/components/script/dom/htmlimageelement.rs @@ -3,6 +3,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ use app_units::{Au, AU_PER_PX}; +use cssparser::{Parser, ParserInput}; use document_loader::{LoadType, LoadBlocker}; use dom::activation::Activatable; use dom::attr::Attr; @@ -12,6 +13,7 @@ use dom::bindings::codegen::Bindings::ElementBinding::ElementBinding::ElementMet use dom::bindings::codegen::Bindings::HTMLImageElementBinding; use dom::bindings::codegen::Bindings::HTMLImageElementBinding::HTMLImageElementMethods; use dom::bindings::codegen::Bindings::MouseEventBinding::MouseEventMethods; +use dom::bindings::codegen::Bindings::NodeBinding::NodeBinding::NodeMethods; use dom::bindings::codegen::Bindings::WindowBinding::WindowMethods; use dom::bindings::error::Fallible; use dom::bindings::inheritance::Castable; @@ -28,6 +30,8 @@ use dom::htmlareaelement::HTMLAreaElement; use dom::htmlelement::HTMLElement; use dom::htmlformelement::{FormControl, HTMLFormElement}; use dom::htmlmapelement::HTMLMapElement; +use dom::htmlpictureelement::HTMLPictureElement; +use dom::htmlsourceelement::HTMLSourceElement; use dom::mouseevent::MouseEvent; use dom::node::{Node, NodeDamage, document_from_node, window_from_node}; use dom::progressevent::ProgressEvent; @@ -40,6 +44,7 @@ use html5ever::{LocalName, Prefix}; use ipc_channel::ipc; use ipc_channel::router::ROUTER; use microtask::{Microtask, MicrotaskRunnable}; +use mime::{Mime, TopLevel}; use net_traits::{FetchResponseListener, FetchMetadata, NetworkError, FetchResponseMsg}; use net_traits::image::base::{Image, ImageMetadata}; use net_traits::image_cache::{CanRequestImages, ImageCache, ImageOrMetadataAvailable}; @@ -53,26 +58,49 @@ use servo_url::ServoUrl; use servo_url::origin::ImmutableOrigin; use std::cell::{Cell, RefMut}; use std::char; +use std::collections::HashSet; use std::default::Default; use std::i32; use std::sync::{Arc, Mutex}; -use style::attr::{AttrValue, LengthOrPercentageOrAuto, parse_double, parse_unsigned_integer}; +use style::attr::{AttrValue, LengthOrPercentageOrAuto, parse_double, parse_length, parse_unsigned_integer}; +use style::context::QuirksMode; +use style::media_queries::MediaList; +use style::parser::ParserContext; use style::str::is_ascii_digit; +use style::stylesheets::{CssRuleType, Origin}; +use style::values::specified::{AbsoluteLength, source_size_list::SourceSizeList}; +use style::values::specified::length::{Length, NoCalcLength}; +use style_traits::ParsingMode; use task_source::{TaskSource, TaskSourceName}; + enum ParseState { InDescriptor, InParens, AfterDescriptor, } -#[derive(Debug, PartialEq)] +pub struct SourceSet { + image_sources: Vec, + source_size: SourceSizeList, +} + +impl SourceSet { + fn new() -> SourceSet { + SourceSet { + image_sources: Vec::new(), + source_size: SourceSizeList::empty(), + } + } +} + +#[derive(Clone, Debug, PartialEq)] pub struct ImageSource { pub url: String, pub descriptor: Descriptor, } -#[derive(Debug, PartialEq)] +#[derive(Clone, Debug, PartialEq)] pub struct Descriptor { pub wid: Option, pub den: Option, @@ -112,6 +140,8 @@ pub struct HTMLImageElement { pending_request: DomRefCell, form_owner: MutNullableDom, generation: Cell, + #[ignore_malloc_size_of = "SourceSet"] + source_set: DomRefCell, } impl HTMLImageElement { @@ -356,20 +386,233 @@ impl HTMLImageElement { } /// - fn update_source_set(&self) -> Vec { + fn update_source_set(&self) { + // Step 1 + *self.source_set.borrow_mut() = SourceSet::new(); + + // Step 2 let elem = self.upcast::(); - // TODO: follow the algorithm - let src = elem.get_string_attribute(&local_name!("src")); - if src.is_empty() { - return vec![] + let parent = elem.upcast::().GetParentElement(); + let nodes; + let elements = match parent.as_ref() { + Some(p) => { + if p.is::() { + nodes = p.upcast::().children(); + nodes.filter_map(DomRoot::downcast::) + .map(|n| DomRoot::from_ref(&*n)).collect() + } else { + vec![DomRoot::from_ref(&*elem)] + } + } + None => { + vec![DomRoot::from_ref(&*elem)] + } + }; + + // Step 3 + let width = match elem.get_attribute(&ns!(), &local_name!("width")) { + Some(x) => { + match parse_length(&x.value()) { + LengthOrPercentageOrAuto::Length(x) =>{ + let abs_length = AbsoluteLength::Px(x.to_f32_px()); + Some(Length::NoCalc(NoCalcLength::Absolute(abs_length))) + }, + _ => None + } + }, + None => None + }; + + // Step 4 + for element in &elements { + // Step 4.1 + if *element == DomRoot::from_ref(&*elem) { + let mut source_set = SourceSet::new(); + // Step 4.1.1 + if let Some(x) = element.get_attribute(&ns!(), &local_name!("srcset")) { + source_set.image_sources = parse_a_srcset_attribute(&x.value()); + } + + // Step 4.1.2 + if let Some(x) = element.get_attribute(&ns!(), &local_name!("sizes")) { + source_set.source_size = + parse_a_sizes_attribute(DOMString::from_string(x.value().to_string())); + } + + // Step 4.1.3 + let src_attribute = element.get_string_attribute(&local_name!("src")); + let is_src_empty = src_attribute.is_empty(); + let no_density_source_of_1 = source_set.image_sources.iter() + .all(|source| source.descriptor.den != Some(1.)); + let no_width_descriptor = source_set.image_sources.iter() + .all(|source| source.descriptor.wid.is_none()); + if !is_src_empty && no_density_source_of_1 && no_width_descriptor { + source_set.image_sources.push(ImageSource { + url: src_attribute.to_string(), + descriptor: Descriptor { wid: None, den: None } + }) + } + + // Step 4.1.4 + self.normalise_source_densities(&mut source_set, width); + + // Step 4.1.5 + *self.source_set.borrow_mut() = source_set; + + // Step 4.1.6 + return; + } + // Step 4.2 + if !element.is::() { + continue; + } + + // Step 4.3 - 4.4 + let mut source_set = SourceSet::new(); + match element.get_attribute(&ns!(), &local_name!("srcset")) { + Some(x) => { + source_set.image_sources = parse_a_srcset_attribute(&x.value()); + } + _ => continue + } + + // Step 4.5 + if source_set.image_sources.is_empty() { + continue; + } + + // Step 4.6 + if let Some(x) = element.get_attribute(&ns!(), &local_name!("media")) { + if !self.matches_environment(x.value().to_string()) { + continue; + } + } + + // Step 4.7 + if let Some(x) = element.get_attribute(&ns!(), &local_name!("sizes")) { + source_set.source_size = + parse_a_sizes_attribute(DOMString::from_string(x.value().to_string())); + } + + // Step 4.8 + if let Some(x) = element.get_attribute(&ns!(), &local_name!("type")) { + // TODO Handle unsupported mime type + let mime = x.value().parse::(); + match mime { + Ok(m) => + match m { + Mime(TopLevel::Image, _, _) => (), + _ => continue + }, + _ => continue + } + } + + // Step 4.9 + self.normalise_source_densities(&mut source_set, width); + + // Step 4.10 + *self.source_set.borrow_mut() = source_set; + return; } - vec![src] + } + + fn evaluate_source_size_list(&self, source_size_list: &mut SourceSizeList, width: Option) -> Au { + let document = document_from_node(self); + let device = document.device(); + if !device.is_some() { + return Au(1); + } + let quirks_mode = document.quirks_mode(); + source_size_list.set_fallback_value(width); + source_size_list.evaluate(&device.unwrap(), quirks_mode) + } + + /// https://html.spec.whatwg.org/multipage/#matches-the-environment + fn matches_environment(&self, media_query: String) -> bool { + let document = document_from_node(self); + let device = document.device(); + if !device.is_some() { + return false; + } + let quirks_mode = document.quirks_mode(); + let document_url = &document.url(); + let context = ParserContext::new( + Origin::Author, + document_url, + Some(CssRuleType::Style), + ParsingMode::all(), + quirks_mode, + None, + ); + let mut parserInput = ParserInput::new(&media_query); + let mut parser = Parser::new(&mut parserInput); + let media_list = MediaList::parse(&context, &mut parser); + media_list.evaluate(&device.unwrap(), quirks_mode) + } + + /// + fn normalise_source_densities(&self, source_set: &mut SourceSet, width: Option) { + // Step 1 + let mut source_size = &mut source_set.source_size; + + // Find source_size_length for Step 2.2 + let source_size_length = self.evaluate_source_size_list(&mut source_size, width); + + // Step 2 + for imgsource in &mut source_set.image_sources { + // Step 2.1 + if imgsource.descriptor.den.is_some() { + continue; + } + // Step 2.2 + if imgsource.descriptor.wid.is_some() { + let wid = imgsource.descriptor.wid.unwrap(); + imgsource.descriptor.den = Some(wid as f64 / source_size_length.to_f64_px()); + } else { + //Step 2.3 + imgsource.descriptor.den = Some(1 as f64); + } + }; } /// - fn select_image_source(&self) -> Option { - // TODO: select an image source from source set - self.update_source_set().first().cloned() + fn select_image_source(&self) -> Option<(DOMString, f32)> { + // Step 1, 3 + self.update_source_set(); + let source_set = &*self.source_set.borrow_mut(); + let len = source_set.image_sources.len(); + + // Step 2 + if len == 0 { + return None; + } + + // Step 4 + let mut repeat_indices = HashSet::new(); + for outer_index in 0..len { + if repeat_indices.contains(&outer_index) { + continue; + } + let imgsource = &source_set.image_sources[outer_index]; + let pixel_density = imgsource.descriptor.den.unwrap(); + for inner_index in (outer_index + 1)..len { + let imgsource2 = &source_set.image_sources[inner_index]; + if pixel_density == imgsource2.descriptor.den.unwrap() { + repeat_indices.insert(inner_index); + } + } + } + let img_sources = &mut vec![]; + for outer_index in 0..len { + if !repeat_indices.contains(&outer_index) { + img_sources.push(&source_set.image_sources[outer_index]); + } + } + + // TODO Step 5 - select source based on pixel density + let selected_source = img_sources.remove(0).clone(); + Some((DOMString::from_string(selected_source.url), selected_source.descriptor.den.unwrap() as f32)) } fn init_image_request(&self, @@ -439,7 +682,7 @@ impl HTMLImageElement { Some(src) => { // Step 8. // TODO: Handle pixel density. - src + src.0 }, None => { // Step 9. @@ -619,6 +862,7 @@ impl HTMLImageElement { }), form_owner: Default::default(), generation: Default::default(), + source_set: DomRefCell::new(SourceSet::new()), } } @@ -745,6 +989,22 @@ impl LayoutHTMLImageElementHelpers for LayoutDom { } } +//https://html.spec.whatwg.org/multipage/#parse-a-sizes-attribute +pub fn parse_a_sizes_attribute(value: DOMString) -> SourceSizeList { + let mut input = ParserInput::new(&value); + let mut parser = Parser::new(&mut input); + let url = ServoUrl::parse("about:blank").unwrap(); + let context = ParserContext::new( + Origin::Author, + &url, + Some(CssRuleType::Style), + ParsingMode::empty(), + QuirksMode::NoQuirks, + None, + ); + SourceSizeList::parse(&context, &mut parser) +} + impl HTMLImageElementMethods for HTMLImageElement { // https://html.spec.whatwg.org/multipage/#dom-img-alt make_getter!(Alt, "alt"); diff --git a/components/style/values/specified/source_size_list.rs b/components/style/values/specified/source_size_list.rs index d41fc283bcc66..33078c0648407 100644 --- a/components/style/values/specified/source_size_list.rs +++ b/components/style/values/specified/source_size_list.rs @@ -18,6 +18,7 @@ use values::specified::{Length, NoCalcLength, ViewportPercentageLength}; /// A value for a ``: /// /// https://html.spec.whatwg.org/multipage/#source-size +#[derive(Debug)] pub struct SourceSize { condition: MediaCondition, value: Length, @@ -38,6 +39,7 @@ impl Parse for SourceSize { /// A value for a ``: /// /// https://html.spec.whatwg.org/multipage/#source-size-list +#[derive(Debug)] pub struct SourceSizeList { source_sizes: Vec, value: Option, @@ -52,6 +54,11 @@ impl SourceSizeList { } } + /// Set content of `value`, which can be used as fall-back during evaluate. + pub fn set_fallback_value(&mut self, width: Option) { + self.value = width; + } + /// Evaluate this to get the final viewport length. pub fn evaluate(&self, device: &Device, quirks_mode: QuirksMode) -> Au { let matching_source_size = self.source_sizes diff --git a/tests/wpt/metadata/html/semantics/embedded-content/the-img-element/adoption.html.ini b/tests/wpt/metadata/html/semantics/embedded-content/the-img-element/adoption.html.ini index 28a4f9efd7c7d..cc5121db0aa79 100644 --- a/tests/wpt/metadata/html/semantics/embedded-content/the-img-element/adoption.html.ini +++ b/tests/wpt/metadata/html/semantics/embedded-content/the-img-element/adoption.html.ini @@ -1,6 +1,5 @@ [adoption.html] type: testharness - expected: TIMEOUT [img (src only)] expected: FAIL @@ -11,13 +10,13 @@ expected: FAIL [img (srcset 1 cand)] - expected: TIMEOUT + expected: FAIL [img (srcset 1 cand), parent is picture] - expected: TIMEOUT + expected: FAIL [img (srcset 1 cand), previous sibling is source] - expected: TIMEOUT + expected: FAIL [adopt a cloned img in template] expected: FAIL diff --git a/tests/wpt/metadata/html/semantics/embedded-content/the-img-element/update-the-image-data/fail-to-resolve.html.ini b/tests/wpt/metadata/html/semantics/embedded-content/the-img-element/update-the-image-data/fail-to-resolve.html.ini index a516a68bd64a7..03c73fba72ef4 100644 --- a/tests/wpt/metadata/html/semantics/embedded-content/the-img-element/update-the-image-data/fail-to-resolve.html.ini +++ b/tests/wpt/metadata/html/semantics/embedded-content/the-img-element/update-the-image-data/fail-to-resolve.html.ini @@ -3,9 +3,6 @@ [] expected: FAIL - [] - expected: FAIL - [] expected: FAIL diff --git a/tests/wpt/metadata/html/semantics/embedded-content/the-img-element/update-the-source-set.html.ini b/tests/wpt/metadata/html/semantics/embedded-content/the-img-element/update-the-source-set.html.ini index 3912c76b5961a..8f6ce5ae7bed5 100644 --- a/tests/wpt/metadata/html/semantics/embedded-content/the-img-element/update-the-source-set.html.ini +++ b/tests/wpt/metadata/html/semantics/embedded-content/the-img-element/update-the-source-set.html.ini @@ -1,107 +1,32 @@ [update-the-source-set.html] type: testharness - [] - expected: FAIL - - [] - expected: FAIL - - [] - expected: FAIL - - [] - expected: FAIL - [] expected: FAIL - [] - expected: FAIL - - [] - expected: FAIL - - [] - expected: FAIL - - [] - expected: FAIL - - [] - expected: FAIL - - [] - expected: FAIL - [] expected: FAIL - [] - expected: FAIL - - [] - expected: FAIL - - [] - expected: FAIL - - [] - expected: FAIL - - [] - expected: FAIL - - [] - expected: FAIL - [] expected: FAIL [] expected: FAIL - [] - expected: FAIL - [] expected: FAIL [] expected: FAIL - [] - expected: FAIL - - [] - expected: FAIL - - [] - expected: FAIL - - [] - expected: FAIL - - [] - expected: FAIL - - [] - expected: FAIL - - [] - expected: FAIL - - [] - expected: FAIL - [] expected: FAIL - [] + [] expected: FAIL - [] + [] expected: FAIL - [] + [] expected: FAIL