diff --git a/components/layout/display_list/background.rs b/components/layout/display_list/background.rs index 6e4751f3012..efe82be20fb 100644 --- a/components/layout/display_list/background.rs +++ b/components/layout/display_list/background.rs @@ -9,11 +9,11 @@ #![deny(unsafe_code)] -// FIXME(rust-lang/rust#26264): Remove GenericEndingShape and GenericGradientItem. +// FIXME(rust-lang/rust#26264): Remove GenericEndingShape, GenericGradientItem, +// GenericBackgroundSize and GenericBorderImageSideWidth. use app_units::Au; use display_list::ToLayout; -use display_list::items::WebRenderImageInfo; use euclid::{Point2D, Rect, SideOffsets2D, Size2D, Vector2D}; use model::{self, MaybeAuto}; use style::computed_values::background_attachment::single_value::T as BackgroundAttachment; @@ -23,20 +23,22 @@ use style::computed_values::border_image_outset::T as BorderImageOutset; use style::properties::ComputedValues; use style::properties::style_structs::{self, Background}; use style::values::Either; -use style::values::computed::{Angle, GradientItem, BackgroundSize as ComputedBackgroundSize}; +use style::values::computed::{Angle, GradientItem, BackgroundSize}; +use style::values::computed::{BorderImageWidth, BorderImageSideWidth}; use style::values::computed::{LengthOrNumber, LengthOrPercentage, LengthOrPercentageOrAuto}; use style::values::computed::{NumberOrPercentage, Percentage, Position}; use style::values::computed::image::{EndingShape, LineDirection}; use style::values::generics::NonNegative; -use style::values::generics::background::BackgroundSize; +use style::values::generics::background::BackgroundSize as GenericBackgroundSize; +use style::values::generics::border::{BorderImageSideWidth as GenericBorderImageSideWidth}; use style::values::generics::image::{Circle, Ellipse, ShapeExtent}; use style::values::generics::image::EndingShape as GenericEndingShape; use style::values::generics::image::GradientItem as GenericGradientItem; use style::values::specified::background::BackgroundRepeatKeyword; use style::values::specified::position::{X, Y}; -use webrender_api::{BorderDetails, BorderRadius, BorderSide, BorderStyle, ColorF, ExtendMode}; -use webrender_api::{Gradient, GradientStop, LayoutSize, NinePatchBorder, NinePatchBorderSource}; -use webrender_api::{NormalBorder, RadialGradient}; +use webrender_api::{BorderRadius, BorderSide, BorderStyle, BorderWidths, ColorF}; +use webrender_api::{ExtendMode, Gradient, GradientStop, LayoutSize, NinePatchBorder}; +use webrender_api::{NinePatchBorderSource, NormalBorder, RadialGradient}; /// A helper data structure for gradients. #[derive(Clone, Copy)] @@ -94,14 +96,14 @@ pub fn get_cyclic(arr: &[T], index: usize) -> &T { /// For a given area and an image compute how big the /// image should be displayed on the background. fn compute_background_image_size( - bg_size: ComputedBackgroundSize, + bg_size: BackgroundSize, bounds_size: Size2D, intrinsic_size: Option>, ) -> Size2D { match intrinsic_size { None => match bg_size { - BackgroundSize::Cover | BackgroundSize::Contain => bounds_size, - BackgroundSize::Explicit { width, height } => Size2D::new( + GenericBackgroundSize::Cover | GenericBackgroundSize::Contain => bounds_size, + GenericBackgroundSize::Explicit { width, height } => Size2D::new( MaybeAuto::from_style(width.0, bounds_size.width) .specified_or_default(bounds_size.width), MaybeAuto::from_style(height.0, bounds_size.height) @@ -115,16 +117,20 @@ fn compute_background_image_size( let bounds_aspect_ratio = bounds_size.width.to_f32_px() / bounds_size.height.to_f32_px(); match (bg_size, image_aspect_ratio < bounds_aspect_ratio) { - (BackgroundSize::Contain, false) | (BackgroundSize::Cover, true) => Size2D::new( - bounds_size.width, - bounds_size.width.scale_by(image_aspect_ratio.recip()), - ), - (BackgroundSize::Contain, true) | (BackgroundSize::Cover, false) => Size2D::new( - bounds_size.height.scale_by(image_aspect_ratio), - bounds_size.height, - ), + (GenericBackgroundSize::Contain, false) | (GenericBackgroundSize::Cover, true) => { + Size2D::new( + bounds_size.width, + bounds_size.width.scale_by(image_aspect_ratio.recip()), + ) + }, + (GenericBackgroundSize::Contain, true) | (GenericBackgroundSize::Cover, false) => { + Size2D::new( + bounds_size.height.scale_by(image_aspect_ratio), + bounds_size.height, + ) + }, ( - BackgroundSize::Explicit { + GenericBackgroundSize::Explicit { width, height: NonNegative(LengthOrPercentageOrAuto::Auto), }, @@ -135,7 +141,7 @@ fn compute_background_image_size( Size2D::new(width, width.scale_by(image_aspect_ratio.recip())) }, ( - BackgroundSize::Explicit { + GenericBackgroundSize::Explicit { width: NonNegative(LengthOrPercentageOrAuto::Auto), height, }, @@ -145,7 +151,7 @@ fn compute_background_image_size( .specified_or_default(own_size.height); Size2D::new(height.scale_by(image_aspect_ratio), height) }, - (BackgroundSize::Explicit { width, height }, _) => Size2D::new( + (GenericBackgroundSize::Explicit { width, height }, _) => Size2D::new( MaybeAuto::from_style(width.0, bounds_size.width) .specified_or_default(own_size.width), MaybeAuto::from_style(height.0, bounds_size.height) @@ -795,32 +801,60 @@ pub fn calculate_inner_border_radii( radii } -/// Given an image and a border style constructs a border image. +/// Create the image border details. /// -/// See: https://drafts.csswg.org/css-backgrounds-3/#border-images -pub fn build_image_border_details( - webrender_image: WebRenderImageInfo, +/// For a fragment with a given with a given "bounds" that has a "border_width" +/// for all four sides calculate the outset for the image border. Call the +/// "build_source" function to get the image which is either a gradient +/// or an image. The supplied "fallback_size" is used to size gradients +/// and other images without an intrinsic size. The actual size of the image +/// is also returned. Apply all other attributes for border images and return +/// the "NinePatchBorder". +pub fn build_border_image_details( + bounds: Rect, + border_width: SideOffsets2D, border_style_struct: &style_structs::Border, - outset: SideOffsets2D, -) -> Option { - let corners = &border_style_struct.border_image_slice.offsets; + build_source: F, +) -> Option<(NinePatchBorder, BorderWidths)> +where + F: FnOnce(Size2D) -> Option<(NinePatchBorderSource, Size2D)>, +{ + let border_image_outset = + calculate_border_image_outset(border_style_struct.border_image_outset, border_width); + let border_image_area = bounds.outer_rect(border_image_outset).size; + let border_image_width = calculate_border_image_width( + &border_style_struct.border_image_width, + border_width.to_layout(), + border_image_area, + ); let border_image_repeat = &border_style_struct.border_image_repeat; - if let Some(image_key) = webrender_image.key { - Some(BorderDetails::NinePatch(NinePatchBorder { - source: NinePatchBorderSource::Image(image_key), - width: webrender_image.width, - height: webrender_image.height, - slice: SideOffsets2D::new( - corners.0.resolve(webrender_image.height), - corners.1.resolve(webrender_image.width), - corners.2.resolve(webrender_image.height), - corners.3.resolve(webrender_image.width), - ), - fill: border_style_struct.border_image_slice.fill, - repeat_horizontal: border_image_repeat.0.to_layout(), - repeat_vertical: border_image_repeat.1.to_layout(), - outset: outset, - })) + let border_image_fill = border_style_struct.border_image_slice.fill; + let border_image_slice = &border_style_struct.border_image_slice.offsets; + + if let Some((source, size)) = build_source(border_image_area) { + Some(( + NinePatchBorder { + source: source, + width: size.width, + height: size.height, + slice: SideOffsets2D::new( + border_image_slice.0.resolve(size.height), + border_image_slice.1.resolve(size.width), + border_image_slice.2.resolve(size.height), + border_image_slice.3.resolve(size.width), + ), + fill: border_image_fill, + repeat_horizontal: border_image_repeat.0.to_layout(), + repeat_vertical: border_image_repeat.1.to_layout(), + outset: SideOffsets2D::new( + border_image_outset.top.to_f32_px(), + border_image_outset.right.to_f32_px(), + border_image_outset.bottom.to_f32_px(), + border_image_outset.left.to_f32_px(), + ), + }, + border_image_width, + )) } else { None } @@ -833,7 +867,7 @@ fn calculate_border_image_outset_side(outset: LengthOrNumber, border_width: Au) } } -pub fn calculate_border_image_outset( +fn calculate_border_image_outset( outset: BorderImageOutset, border: SideOffsets2D, ) -> SideOffsets2D { @@ -844,3 +878,28 @@ pub fn calculate_border_image_outset( calculate_border_image_outset_side(outset.3, border.left), ) } + +fn calculate_border_image_width_side( + border_image_width: BorderImageSideWidth, + border_width: f32, + total_length: Au, +) -> f32 { + match border_image_width { + GenericBorderImageSideWidth::Length(v) => v.to_used_value(total_length).to_f32_px(), + GenericBorderImageSideWidth::Number(x) => border_width * x, + GenericBorderImageSideWidth::Auto => border_width, + } +} + +fn calculate_border_image_width( + width: &BorderImageWidth, + border: BorderWidths, + border_area: Size2D, +) -> BorderWidths { + BorderWidths { + left: calculate_border_image_width_side(width.3, border.left, border_area.width), + top: calculate_border_image_width_side(width.0, border.top, border_area.height), + right: calculate_border_image_width_side(width.1, border.right, border_area.width), + bottom: calculate_border_image_width_side(width.2, border.bottom, border_area.height), + } +} diff --git a/components/layout/display_list/builder.rs b/components/layout/display_list/builder.rs index 4a9a77a7fb1..82d162c58ee 100644 --- a/components/layout/display_list/builder.rs +++ b/components/layout/display_list/builder.rs @@ -15,10 +15,10 @@ use block::BlockFlow; use canvas_traits::canvas::{CanvasMsg, FromLayoutMsg}; use context::LayoutContext; use display_list::ToLayout; -use display_list::background::{build_border_radius, build_image_border_details}; -use display_list::background::{calculate_border_image_outset, calculate_inner_border_radii}; -use display_list::background::{compute_background_clip, compute_background_placement}; -use display_list::background::{convert_linear_gradient, convert_radial_gradient, get_cyclic}; +use display_list::background::{build_border_radius, build_border_image_details}; +use display_list::background::{calculate_inner_border_radii, compute_background_clip}; +use display_list::background::{compute_background_placement, convert_linear_gradient}; +use display_list::background::{convert_radial_gradient, get_cyclic}; use display_list::background::simple_normal_border; use display_list::items::{BaseDisplayItem, BLUR_INFLATION_FACTOR, ClipScrollNode}; use display_list::items::{ClipScrollNodeIndex, ClipScrollNodeType, ClippingAndScrolling}; @@ -71,8 +71,8 @@ use style_traits::cursor::CursorKind; use table_cell::CollapsedBordersForCell; use webrender_api::{self, BorderDetails, BorderRadius, BorderSide, BoxShadowClipMode, ColorF}; use webrender_api::{ExternalScrollId, FilterOp, GlyphInstance, ImageRendering, LayoutRect}; -use webrender_api::{LayoutSize, LayoutTransform, LayoutVector2D, LineStyle, NormalBorder}; -use webrender_api::{StickyOffsetBounds, ScrollSensitivity}; +use webrender_api::{LayoutSize, LayoutTransform, LayoutVector2D, LineStyle}; +use webrender_api::{NinePatchBorderSource, NormalBorder, StickyOffsetBounds, ScrollSensitivity}; fn establishes_containing_block_for_absolute( flags: StackingContextCollectionFlags, @@ -1263,6 +1263,21 @@ impl FragmentDisplayListBuilding for Fragment { display_list_section: DisplayListSection, clip: Rect, ) { + fn convert_image_to_border( + image: WebRenderImageInfo, + ) -> Option<(NinePatchBorderSource, Size2D)> { + image.key.map(|key| { + ( + NinePatchBorderSource::Image(key), + Size2D::new(image.width, image.height), + ) + }) + } + + fn convert_size_au_to_u32(size: Size2D) -> Size2D { + Size2D::new(size.width.to_px() as u32, size.height.to_px() as u32) + } + let mut border = style.logical_border_width(); if let Some(inline_info) = inline_info { @@ -1276,10 +1291,6 @@ impl FragmentDisplayListBuilding for Fragment { }, BorderPaintingMode::Hidden => return, } - if border.is_zero() { - // TODO: check if image-border-outset is zero - return; - } let border_style_struct = style.get_border(); let mut colors = SideOffsets2D::new( @@ -1319,110 +1330,102 @@ impl FragmentDisplayListBuilding for Fragment { let border_radius = build_border_radius(bounds, border_style_struct); let border_widths = border.to_physical(style.writing_mode); - let outset = - calculate_border_image_outset(border_style_struct.border_image_outset, border_widths); - let outset_layout = SideOffsets2D::new( - outset.top.to_f32_px(), - outset.right.to_f32_px(), - outset.bottom.to_f32_px(), - outset.left.to_f32_px(), - ); - let size = bounds.outer_rect(outset).size; + let mut layout_border_width = border_widths.to_layout(); let mut stops = Vec::new(); let details = match border_style_struct.border_image_source { - Either::First(_) => Some(BorderDetails::Normal(NormalBorder { - left: BorderSide { - color: style.resolve_color(colors.left).to_layout(), - style: border_style.left.to_layout(), - }, - right: BorderSide { - color: style.resolve_color(colors.right).to_layout(), - style: border_style.right.to_layout(), - }, - top: BorderSide { - color: style.resolve_color(colors.top).to_layout(), - style: border_style.top.to_layout(), - }, - bottom: BorderSide { - color: style.resolve_color(colors.bottom).to_layout(), - style: border_style.bottom.to_layout(), - }, - radius: border_radius, - })), - Either::Second(Image::Gradient(ref gradient)) => Some(match gradient.kind { - GradientKind::Linear(angle_or_corner) => { - let (wr_gradient, linear_stops) = convert_linear_gradient( - style, - bounds.size, - &gradient.items[..], - angle_or_corner, - gradient.repeating, - ); - stops = linear_stops; - BorderDetails::NinePatch(webrender_api::NinePatchBorder { - source: webrender_api::NinePatchBorderSource::Gradient(wr_gradient), - width: 0, - height: 0, - slice: SideOffsets2D::zero(), - fill: false, - repeat_horizontal: webrender_api::RepeatMode::Stretch, - repeat_vertical: webrender_api::RepeatMode::Stretch, - outset: outset_layout, - }) - }, - GradientKind::Radial(shape, center, _angle) => { - let (wr_gradient, radial_stops) = convert_radial_gradient( - style, - bounds.size, - &gradient.items[..], - shape, - center, - gradient.repeating, - ); - stops = radial_stops; - BorderDetails::NinePatch(webrender_api::NinePatchBorder { - source: webrender_api::NinePatchBorderSource::RadialGradient(wr_gradient), - width: 0, - height: 0, - slice: SideOffsets2D::zero(), - fill: false, - repeat_horizontal: webrender_api::RepeatMode::Stretch, - repeat_vertical: webrender_api::RepeatMode::Stretch, - outset: outset_layout, - }) - }, - }), - Either::Second(Image::PaintWorklet(ref paint_worklet)) => self - .get_webrender_image_for_paint_worklet(state, style, paint_worklet, size) - .and_then(|image| { - build_image_border_details(image, border_style_struct, outset_layout) - }), - Either::Second(Image::Rect(..)) => { - // TODO: Handle border-image with `-moz-image-rect`. - None + Either::First(_) => { + if border_widths == SideOffsets2D::zero() { + None + } else { + Some(BorderDetails::Normal(NormalBorder { + left: BorderSide { + color: style.resolve_color(colors.left).to_layout(), + style: border_style.left.to_layout(), + }, + right: BorderSide { + color: style.resolve_color(colors.right).to_layout(), + style: border_style.right.to_layout(), + }, + top: BorderSide { + color: style.resolve_color(colors.top).to_layout(), + style: border_style.top.to_layout(), + }, + bottom: BorderSide { + color: style.resolve_color(colors.bottom).to_layout(), + style: border_style.bottom.to_layout(), + }, + radius: border_radius, + })) + } }, - Either::Second(Image::Element(..)) => { - // TODO: Handle border-image with `-moz-element`. - None + Either::Second(ref image) => { + build_border_image_details( + bounds, + border_widths, + border_style_struct, + |fallback_size| match image { + Image::Url(ref image_url) => image_url + .url() + .and_then(|url| { + state.layout_context.get_webrender_image_for_url( + self.node, + url.clone(), + UsePlaceholder::No, + ) + }).and_then(convert_image_to_border), + Image::PaintWorklet(ref paint_worklet) => self + .get_webrender_image_for_paint_worklet( + state, + style, + paint_worklet, + fallback_size, + ).and_then(convert_image_to_border), + Image::Gradient(ref gradient) => match gradient.kind { + GradientKind::Linear(angle_or_corner) => { + let (wr_gradient, linear_stops) = convert_linear_gradient( + style, + fallback_size, + &gradient.items[..], + angle_or_corner, + gradient.repeating, + ); + stops = linear_stops; + Some(( + NinePatchBorderSource::Gradient(wr_gradient), + convert_size_au_to_u32(fallback_size), + )) + }, + GradientKind::Radial(shape, center, _angle) => { + let (wr_gradient, radial_stops) = convert_radial_gradient( + style, + fallback_size, + &gradient.items[..], + shape, + center, + gradient.repeating, + ); + stops = radial_stops; + Some(( + NinePatchBorderSource::RadialGradient(wr_gradient), + convert_size_au_to_u32(fallback_size), + )) + }, + }, + _ => None, + }, + ).map(|(details, border)| { + // The image's border width can differ from the "simple" color border width. + layout_border_width = border; + BorderDetails::NinePatch(details) + }) }, - Either::Second(Image::Url(ref image_url)) => image_url - .url() - .and_then(|url| { - state.layout_context.get_webrender_image_for_url( - self.node, - url.clone(), - UsePlaceholder::No, - ) - }).and_then(|image| { - build_image_border_details(image, border_style_struct, outset_layout) - }), }; if let Some(details) = details { state.add_display_item(DisplayItem::Border(CommonDisplayItem::with_data( base, webrender_api::BorderDisplayItem { - widths: border_widths.to_layout(), + widths: layout_border_width, details, }, stops, diff --git a/tests/wpt/metadata/MANIFEST.json b/tests/wpt/metadata/MANIFEST.json index f4144818dff..e990cbd4c20 100644 --- a/tests/wpt/metadata/MANIFEST.json +++ b/tests/wpt/metadata/MANIFEST.json @@ -106531,6 +106531,18 @@ {} ] ], + "css/css-backgrounds/border-image-width-008.html": [ + [ + "/css/css-backgrounds/border-image-width-008.html", + [ + [ + "/css/css-backgrounds/border-image-width-008-ref.html", + "==" + ] + ], + {} + ] + ], "css/css-backgrounds/border-radius-001.xht": [ [ "/css/css-backgrounds/border-radius-001.xht", @@ -243889,6 +243901,11 @@ {} ] ], + "css/css-backgrounds/border-image-width-008-ref.html": [ + [ + {} + ] + ], "css/css-backgrounds/border-radius-001-ref.xht": [ [ {} @@ -518585,6 +518602,14 @@ "61726c00873739c076812f72645d8324494ff44c", "reftest" ], + "css/css-backgrounds/border-image-width-008-ref.html": [ + "9a066443920726b79d69a0301d814b5211b7df0a", + "support" + ], + "css/css-backgrounds/border-image-width-008.html": [ + "a35c3f65c54ef34b8bd83b5c18f483302e607a26", + "reftest" + ], "css/css-backgrounds/border-images.html": [ "930a1df3b7fda4098f36cc9691a544f46e2311d5", "visual" diff --git a/tests/wpt/metadata/css/css-backgrounds/border-image-width-005.xht.ini b/tests/wpt/metadata/css/css-backgrounds/border-image-width-005.xht.ini deleted file mode 100644 index 014f638f7ce..00000000000 --- a/tests/wpt/metadata/css/css-backgrounds/border-image-width-005.xht.ini +++ /dev/null @@ -1,3 +0,0 @@ -[border-image-width-005.xht] - type: reftest - expected: FAIL diff --git a/tests/wpt/metadata/css/css-backgrounds/border-image-width-006.xht.ini b/tests/wpt/metadata/css/css-backgrounds/border-image-width-006.xht.ini deleted file mode 100644 index 8e76931c4c7..00000000000 --- a/tests/wpt/metadata/css/css-backgrounds/border-image-width-006.xht.ini +++ /dev/null @@ -1,3 +0,0 @@ -[border-image-width-006.xht] - type: reftest - expected: FAIL diff --git a/tests/wpt/metadata/css/css-paint-api/geometry-border-image-002.https.html.ini b/tests/wpt/metadata/css/css-paint-api/geometry-border-image-002.https.html.ini deleted file mode 100644 index 159e1221517..00000000000 --- a/tests/wpt/metadata/css/css-paint-api/geometry-border-image-002.https.html.ini +++ /dev/null @@ -1,4 +0,0 @@ -[geometry-border-image-002.https.html] - type: reftest - expected: FAIL - bug: https://github.com/servo/servo/issues/17860 diff --git a/tests/wpt/metadata/css/css-paint-api/geometry-border-image-003.https.html.ini b/tests/wpt/metadata/css/css-paint-api/geometry-border-image-003.https.html.ini deleted file mode 100644 index 36e1fa1632f..00000000000 --- a/tests/wpt/metadata/css/css-paint-api/geometry-border-image-003.https.html.ini +++ /dev/null @@ -1,4 +0,0 @@ -[geometry-border-image-003.https.html] - type: reftest - expected: FAIL - bug: https://github.com/servo/servo/issues/17860 diff --git a/tests/wpt/web-platform-tests/css/css-backgrounds/border-image-width-008-ref.html b/tests/wpt/web-platform-tests/css/css-backgrounds/border-image-width-008-ref.html new file mode 100644 index 00000000000..9a066443920 --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-backgrounds/border-image-width-008-ref.html @@ -0,0 +1,16 @@ + + +border-image with different widths + + +
+ diff --git a/tests/wpt/web-platform-tests/css/css-backgrounds/border-image-width-008.html b/tests/wpt/web-platform-tests/css/css-backgrounds/border-image-width-008.html new file mode 100644 index 00000000000..a35c3f65c54 --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-backgrounds/border-image-width-008.html @@ -0,0 +1,20 @@ + + +border-image-width has the same effect as a border-width and the image is displayed even if border-width is zero + + + + +
+