diff --git a/components/layout/display_list_builder.rs b/components/layout/display_list_builder.rs index 27ff1f7eb84..8b967d54ec1 100644 --- a/components/layout/display_list_builder.rs +++ b/components/layout/display_list_builder.rs @@ -60,6 +60,10 @@ use table_cell::CollapsedBordersForCell; use url::Url; use util::opts; +fn get_cyclic(arr: &[T], index: usize) -> &T { + &arr[index % arr.len()] +} + pub struct DisplayListBuildState<'a> { pub layout_context: &'a LayoutContext<'a>, pub items: Vec, @@ -146,7 +150,7 @@ pub trait FragmentDisplayListBuilding { fn compute_background_image_size(&self, style: &ServoComputedValues, bounds: &Rect, - image: &WebRenderImageInfo) + image: &WebRenderImageInfo, index: usize) -> Size2D; /// Adds the display items necessary to paint the background image of this fragment to the @@ -157,7 +161,8 @@ pub trait FragmentDisplayListBuilding { display_list_section: DisplayListSection, absolute_bounds: &Rect, clip: &ClippingRegion, - image_url: &Url); + image_url: &Url, + background_index: usize); /// Adds the display items necessary to paint the background linear gradient of this fragment /// to the appropriate section of the display list. @@ -344,27 +349,32 @@ impl FragmentDisplayListBuilding for Fragment { if !border_radii.is_square() { clip.intersect_with_rounded_rect(absolute_bounds, &border_radii) } + let background = style.get_background(); // FIXME: This causes a lot of background colors to be displayed when they are clearly not // needed. We could use display list optimization to clean this up, but it still seems // inefficient. What we really want is something like "nearest ancestor element that // doesn't have a fragment". - let background_color = style.resolve_color(style.get_background().background_color); + let background_color = style.resolve_color(background.background_color); // 'background-clip' determines the area within which the background is painted. // http://dev.w3.org/csswg/css-backgrounds-3/#the-background-clip let mut bounds = *absolute_bounds; - match style.get_background().background_clip { - background_clip::T::border_box => {} - background_clip::T::padding_box => { + // This is the clip for the color (which is the last element in the bg array) + let color_clip = get_cyclic(&background.background_clip.0, + background.background_image.0.len() - 1); + + match *color_clip { + background_clip::single_value::T::border_box => {} + background_clip::single_value::T::padding_box => { let border = style.logical_border_width().to_physical(style.writing_mode); bounds.origin.x = bounds.origin.x + border.left; bounds.origin.y = bounds.origin.y + border.top; bounds.size.width = bounds.size.width - border.horizontal(); bounds.size.height = bounds.size.height - border.vertical(); } - background_clip::T::content_box => { + background_clip::single_value::T::content_box => { let border_padding = self.border_padding.to_physical(style.writing_mode); bounds.origin.x = bounds.origin.x + border_padding.left; bounds.origin.y = bounds.origin.y + border_padding.top; @@ -388,23 +398,26 @@ impl FragmentDisplayListBuilding for Fragment { // Implements background image, per spec: // http://www.w3.org/TR/CSS21/colors.html#background let background = style.get_background(); - match background.background_image.0 { - None => {} - Some(computed::Image::LinearGradient(ref gradient)) => { - self.build_display_list_for_background_linear_gradient(state, - display_list_section, - &bounds, - &clip, - gradient, - style); - } - Some(computed::Image::Url(ref image_url, ref _extra_data)) => { - self.build_display_list_for_background_image(state, - style, - display_list_section, - &bounds, - &clip, - image_url); + for (i, background_image) in background.background_image.0.iter().enumerate().rev() { + match background_image.0 { + None => {} + Some(computed::Image::LinearGradient(ref gradient)) => { + self.build_display_list_for_background_linear_gradient(state, + display_list_section, + &bounds, + &clip, + gradient, + style); + } + Some(computed::Image::Url(ref image_url, ref _extra_data)) => { + self.build_display_list_for_background_image(state, + style, + display_list_section, + &bounds, + &clip, + image_url, + i); + } } } } @@ -412,7 +425,8 @@ impl FragmentDisplayListBuilding for Fragment { fn compute_background_image_size(&self, style: &ServoComputedValues, bounds: &Rect, - image: &WebRenderImageInfo) + image: &WebRenderImageInfo, + index: usize) -> Size2D { // If `image_aspect_ratio` < `bounds_aspect_ratio`, the image is tall; otherwise, it is // wide. @@ -420,19 +434,22 @@ impl FragmentDisplayListBuilding for Fragment { let bounds_aspect_ratio = bounds.size.width.to_f64_px() / bounds.size.height.to_f64_px(); let intrinsic_size = Size2D::new(Au::from_px(image.width as i32), Au::from_px(image.height as i32)); - match (style.get_background().background_size.clone(), - image_aspect_ratio < bounds_aspect_ratio) { - (background_size::T::Contain, false) | (background_size::T::Cover, true) => { + let background_size = get_cyclic(&style.get_background().background_size.0, index).clone(); + match (background_size, image_aspect_ratio < bounds_aspect_ratio) { + (background_size::single_value::T::Contain, false) | + (background_size::single_value::T::Cover, true) => { Size2D::new(bounds.size.width, Au::from_f64_px(bounds.size.width.to_f64_px() / image_aspect_ratio)) } - (background_size::T::Contain, true) | (background_size::T::Cover, false) => { + (background_size::single_value::T::Contain, true) | + (background_size::single_value::T::Cover, false) => { Size2D::new(Au::from_f64_px(bounds.size.height.to_f64_px() * image_aspect_ratio), bounds.size.height) } - (background_size::T::Explicit(background_size::ExplicitSize { + (background_size::single_value::T::Explicit(background_size::single_value + ::ExplicitSize { width, height: LengthOrPercentageOrAuto::Auto, }), _) => { @@ -441,7 +458,8 @@ impl FragmentDisplayListBuilding for Fragment { Size2D::new(width, Au::from_f64_px(width.to_f64_px() / image_aspect_ratio)) } - (background_size::T::Explicit(background_size::ExplicitSize { + (background_size::single_value::T::Explicit(background_size::single_value + ::ExplicitSize { width: LengthOrPercentageOrAuto::Auto, height }), _) => { @@ -450,7 +468,8 @@ impl FragmentDisplayListBuilding for Fragment { Size2D::new(Au::from_f64_px(height.to_f64_px() * image_aspect_ratio), height) } - (background_size::T::Explicit(background_size::ExplicitSize { + (background_size::single_value::T::Explicit(background_size::single_value + ::ExplicitSize { width, height }), _) => { @@ -468,19 +487,22 @@ impl FragmentDisplayListBuilding for Fragment { display_list_section: DisplayListSection, absolute_bounds: &Rect, clip: &ClippingRegion, - image_url: &Url) { + image_url: &Url, + index: usize) { let background = style.get_background(); let fetch_image_data_as_well = !opts::get().use_webrender; let webrender_image = state.layout_context.get_webrender_image_for_url(image_url, UsePlaceholder::No, fetch_image_data_as_well); + if let Some((webrender_image, image_data)) = webrender_image { debug!("(building display list) building background image"); // Use `background-size` to get the size. let mut bounds = *absolute_bounds; - let image_size = self.compute_background_image_size(style, &bounds, &webrender_image); + let image_size = self.compute_background_image_size(style, &bounds, + &webrender_image, index); // Clip. // @@ -492,51 +514,54 @@ impl FragmentDisplayListBuilding for Fragment { let border = style.logical_border_width().to_physical(style.writing_mode); // Use 'background-origin' to get the origin value. - let (mut origin_x, mut origin_y) = match background.background_origin { - background_origin::T::padding_box => { - (Au(0), Au(0)) - } - background_origin::T::border_box => { - (-border.left, -border.top) - } - background_origin::T::content_box => { - let border_padding = self.border_padding.to_physical(self.style.writing_mode); - (border_padding.left - border.left, border_padding.top - border.top) - } + let origin = get_cyclic(&background.background_origin.0, index); + let (mut origin_x, mut origin_y) = match *origin { + background_origin::single_value::T::padding_box => { + (Au(0), Au(0)) + } + background_origin::single_value::T::border_box => { + (-border.left, -border.top) + } + background_origin::single_value::T::content_box => { + let border_padding = self.border_padding.to_physical(self.style.writing_mode); + (border_padding.left - border.left, border_padding.top - border.top) + } }; // Use `background-attachment` to get the initial virtual origin - let (virtual_origin_x, virtual_origin_y) = match background.background_attachment { - background_attachment::T::scroll => { - (absolute_bounds.origin.x, absolute_bounds.origin.y) - } - background_attachment::T::fixed => { - // If the ‘background-attachment’ value for this image is ‘fixed’, then - // 'background-origin' has no effect. - origin_x = Au(0); - origin_y = Au(0); - (Au(0), Au(0)) - } + let attachment = get_cyclic(&background.background_attachment.0, index); + let (virtual_origin_x, virtual_origin_y) = match *attachment { + background_attachment::single_value::T::scroll => { + (absolute_bounds.origin.x, absolute_bounds.origin.y) + } + background_attachment::single_value::T::fixed => { + // If the ‘background-attachment’ value for this image is ‘fixed’, then + // 'background-origin' has no effect. + origin_x = Au(0); + origin_y = Au(0); + (Au(0), Au(0)) + } }; + let position = *get_cyclic(&background.background_position.0, index); // Use `background-position` to get the offset. - let horizontal_position = model::specified(background.background_position.horizontal, - bounds.size.width - image_size.width); - let vertical_position = model::specified(background.background_position.vertical, - bounds.size.height - image_size.height); + let horizontal_position = model::specified(position.horizontal, + bounds.size.width - image_size.width); + let vertical_position = model::specified(position.vertical, + bounds.size.height - image_size.height); let abs_x = border.left + virtual_origin_x + horizontal_position + origin_x; let abs_y = border.top + virtual_origin_y + vertical_position + origin_y; // Adjust origin and size based on background-repeat - match background.background_repeat { - background_repeat::T::no_repeat => { + match *get_cyclic(&background.background_repeat.0, index) { + background_repeat::single_value::T::no_repeat => { bounds.origin.x = abs_x; bounds.origin.y = abs_y; bounds.size.width = image_size.width; bounds.size.height = image_size.height; } - background_repeat::T::repeat_x => { + background_repeat::single_value::T::repeat_x => { bounds.origin.y = abs_y; bounds.size.height = image_size.height; ImageFragmentInfo::tile_image(&mut bounds.origin.x, @@ -544,7 +569,7 @@ impl FragmentDisplayListBuilding for Fragment { abs_x, image_size.width.to_nearest_px() as u32); } - background_repeat::T::repeat_y => { + background_repeat::single_value::T::repeat_y => { bounds.origin.x = abs_x; bounds.size.width = image_size.width; ImageFragmentInfo::tile_image(&mut bounds.origin.y, @@ -552,31 +577,32 @@ impl FragmentDisplayListBuilding for Fragment { abs_y, image_size.height.to_nearest_px() as u32); } - background_repeat::T::repeat => { + background_repeat::single_value::T::repeat => { ImageFragmentInfo::tile_image(&mut bounds.origin.x, - &mut bounds.size.width, - abs_x, - image_size.width.to_nearest_px() as u32); + &mut bounds.size.width, + abs_x, + image_size.width.to_nearest_px() as u32); ImageFragmentInfo::tile_image(&mut bounds.origin.y, - &mut bounds.size.height, - abs_y, - image_size.height.to_nearest_px() as u32); + &mut bounds.size.height, + abs_y, + image_size.height.to_nearest_px() as u32); } }; // Create the image display item. let base = state.create_base_display_item(&bounds, - &clip, - self.node, - style.get_cursor(Cursor::Default), - display_list_section); + &clip, + self.node, + style.get_cursor(Cursor::Default), + display_list_section); state.add_display_item(DisplayItem::ImageClass(box ImageDisplayItem { - base: base, - webrender_image: webrender_image, - image_data: image_data.map(Arc::new), - stretch_size: Size2D::new(image_size.width, image_size.height), - image_rendering: style.get_inheritedbox().image_rendering.clone(), + base: base, + webrender_image: webrender_image, + image_data: image_data.map(Arc::new), + stretch_size: Size2D::new(image_size.width, image_size.height), + image_rendering: style.get_inheritedbox().image_rendering.clone(), })); + } } diff --git a/components/script/dom/element.rs b/components/script/dom/element.rs index 9c5b80fb103..2d57ddab4d6 100644 --- a/components/script/dom/element.rs +++ b/components/script/dom/element.rs @@ -362,8 +362,11 @@ impl LayoutElementHelpers for LayoutJS { if let Some(url) = background { hints.push(from_declaration( PropertyDeclaration::BackgroundImage(DeclaredValue::Value( - background_image::SpecifiedValue(Some( - specified::Image::Url(url, specified::UrlExtraData { }))))))); + background_image::SpecifiedValue(vec![ + background_image::single_value::SpecifiedValue(Some( + specified::Image::Url(url, specified::UrlExtraData { }) + )) + ]))))); } let color = if let Some(this) = self.downcast::() { diff --git a/components/style/properties/helpers.mako.rs b/components/style/properties/helpers.mako.rs index 532095bc00d..c522baf81b8 100644 --- a/components/style/properties/helpers.mako.rs +++ b/components/style/properties/helpers.mako.rs @@ -69,10 +69,10 @@ ${caller.body()} } pub mod computed_value { - use super::single_value; + pub use super::single_value::computed_value as single_value; #[derive(Debug, Clone, PartialEq)] #[cfg_attr(feature = "servo", derive(HeapSizeOf))] - pub struct T(pub Vec); + pub struct T(pub Vec); } impl ToCss for computed_value::T { diff --git a/components/style/properties/longhand/background.mako.rs b/components/style/properties/longhand/background.mako.rs index fbf4c8c3fd4..9ad9daf4262 100644 --- a/components/style/properties/longhand/background.mako.rs +++ b/components/style/properties/longhand/background.mako.rs @@ -10,7 +10,7 @@ ${helpers.predefined_type("background-color", "CSSColor", "::cssparser::Color::RGBA(::cssparser::RGBA { red: 0., green: 0., blue: 0., alpha: 0. }) /* transparent */", animatable=True)} -<%helpers:vector_longhand gecko_only="True" name="background-image" animatable="False"> +<%helpers:vector_longhand name="background-image" animatable="False"> use cssparser::ToCss; use std::fmt; use values::specified::Image; @@ -79,7 +79,7 @@ ${helpers.predefined_type("background-color", "CSSColor", } -<%helpers:vector_longhand name="background-position" gecko_only="True" animatable="True"> +<%helpers:vector_longhand name="background-position" animatable="True"> use cssparser::ToCss; use std::fmt; use values::LocalToCss; @@ -121,25 +121,21 @@ ${helpers.predefined_type("background-color", "CSSColor", ${helpers.single_keyword("background-repeat", "repeat repeat-x repeat-y no-repeat", vector=True, - gecko_only=True, animatable=False)} ${helpers.single_keyword("background-attachment", "scroll fixed" + (" local" if product == "gecko" else ""), vector=True, - gecko_only=True, animatable=False)} ${helpers.single_keyword("background-clip", "border-box padding-box content-box", vector=True, - gecko_only=True, animatable=False)} ${helpers.single_keyword("background-origin", "padding-box border-box content-box", vector=True, - gecko_only=True, animatable=False)} <%helpers:vector_longhand name="background-size" animatable="True"> diff --git a/components/style/properties/shorthand/background.mako.rs b/components/style/properties/shorthand/background.mako.rs index a7dff5047b3..60f0727442b 100644 --- a/components/style/properties/shorthand/background.mako.rs +++ b/components/style/properties/shorthand/background.mako.rs @@ -23,14 +23,14 @@ % endfor loop { - if background_color.is_none() { - if let Ok(value) = input.try(|input| background_color::parse(context, input)) { + if let Ok(value) = input.try(|input| background_color::parse(context, input)) { + if background_color.is_none() { background_color = Some(value); continue + } else { + // color can only be the last element + return Err(()) } - } else { - // color can only be the last element - return Err(()) } if position.is_none() { if let Ok(value) = input.try(|input| background_position::single_value::parse(context, input)) { @@ -118,6 +118,11 @@ .unwrap_or(0)); % endfor + // There should be at least one declared value + if len == 0 { + return Err(()) + } + let iter = repeat(None).take(len - 1).chain(once(Some(self.background_color))) % for name in "image position repeat size attachment origin clip".split(): .zip(extract_value(self.background_${name}).into_iter()