Implement outlines in layout-2020

This only covers the CSS2 properties: 'outline-width', 'outline-style',
'outline-color', and the shorthand 'outline'.

CSS UI 3 introduced 'outline-offset', which is left for a follow-up.

'outline-color: invert' isn't included either, but note CSS UI says that
conformant UAs may ignore the 'invert' value on platforms that do not
support color inversion of the pixels on the screen.

Tests that are now passing:
- /_mozilla/css/input_insertion_point_empty_a.html
- /_mozilla/css/outlines_simple_a.html
- /_mozilla/css/stacking_context_overflow_relative_outline_a.html
- /_mozilla/mozilla/getPropertyPriority.html
- /css/CSS2/ui/outline-color-001.xht
- /css/CSS2/ui/outline-color-002.xht
- /css/CSS2/ui/outline-color-007.xht
- /css/CSS2/ui/outline-color-008.xht
- /css/CSS2/ui/outline-color-013.xht
- /css/CSS2/ui/outline-color-018.xht
- /css/CSS2/ui/outline-color-023.xht
- /css/CSS2/ui/outline-color-024.xht
- /css/CSS2/ui/outline-color-025.xht
- /css/CSS2/ui/outline-color-031.xht
- /css/CSS2/ui/outline-color-036.xht
- /css/CSS2/ui/outline-color-041.xht
- /css/CSS2/ui/outline-color-046.xht
- /css/CSS2/ui/outline-color-047.xht
- /css/CSS2/ui/outline-color-048.xht
- /css/CSS2/ui/outline-color-049.xht
- /css/CSS2/ui/outline-color-050.xht
- /css/CSS2/ui/outline-color-051.xht
- /css/CSS2/ui/outline-color-052.xht
- /css/CSS2/ui/outline-color-053.xht
- /css/CSS2/ui/outline-color-054.xht
- /css/CSS2/ui/outline-color-058.xht
- /css/CSS2/ui/outline-color-059.xht
- /css/CSS2/ui/outline-color-061.xht
- /css/CSS2/ui/outline-color-062.xht
- /css/CSS2/ui/outline-color-069.xht
- /css/CSS2/ui/outline-color-070.xht
- /css/CSS2/ui/outline-color-071.xht
- /css/CSS2/ui/outline-color-072.xht
- /css/CSS2/ui/outline-color-073.xht
- /css/CSS2/ui/outline-color-074.xht
- /css/CSS2/ui/outline-color-075.xht
- /css/CSS2/ui/outline-color-079.xht
- /css/CSS2/ui/outline-color-081.xht
- /css/CSS2/ui/outline-color-082.xht
- /css/CSS2/ui/outline-color-089.xht
- /css/CSS2/ui/outline-color-090.xht
- /css/CSS2/ui/outline-color-091.xht
- /css/CSS2/ui/outline-color-092.xht
- /css/CSS2/ui/outline-color-093.xht
- /css/CSS2/ui/outline-color-094.xht
- /css/CSS2/ui/outline-color-095.xht
- /css/CSS2/ui/outline-color-099.xht
- /css/CSS2/ui/outline-color-101.xht
- /css/CSS2/ui/outline-color-102.xht
- /css/CSS2/ui/outline-color-109.xht
- /css/CSS2/ui/outline-color-110.xht
- /css/CSS2/ui/outline-color-111.xht
- /css/CSS2/ui/outline-color-112.xht
- /css/CSS2/ui/outline-color-113.xht
- /css/CSS2/ui/outline-color-114.xht
- /css/CSS2/ui/outline-color-115.xht
- /css/CSS2/ui/outline-color-119.xht
- /css/CSS2/ui/outline-color-121.xht
- /css/CSS2/ui/outline-color-122.xht
- /css/CSS2/ui/outline-color-130.xht
- /css/css-ui/outline-001.html
- /css/css-ui/outline-002.html
- /css/css-ui/outline-004.html
- /css/css-ui/outline-007.html
- /css/css-ui/outline-008.html
- /css/css-ui/outline-016.html
- /css/css-ui/outline-018.html
- /css/css-ui/outline-021.html
- /css/css-ui/outline-022.html
- /css/css-ui/outline-color-001.html
- /css/css-ui/outline-style-011.html
- /css/css-ui/outline-style-012.html
- /css/css-ui/outline-style-013.html
- /css/css-ui/outline-style-014.html
- /css/css-ui/parsing/outline-color-computed.html
- /css/css-ui/parsing/outline-color-valid-mandatory.html
- /css/css-ui/parsing/outline-shorthand.html
- /css/css-ui/parsing/outline-style-computed.html
- /css/css-ui/parsing/outline-style-valid.html
- /css/css-ui/parsing/outline-width-valid.html
- /css/css-ui/translucent-outline.html

Also improvements in:
- /_mozilla/mozilla/calc.html
- /css/css-ui/animation/outline-color-interpolation.html
- /css/css-ui/animation/outline-width-interpolation.html
- /css/css-ui/inheritance.html
- /css/css-ui/outline-017.html
- /css/css-ui/parsing/outline-valid-mandatory.html
- /css/css-ui/parsing/outline-width-computed.html
- /css/cssom/cssom-setProperty-shorthand.html
- /css/cssom/getComputedStyle-resolved-colors.html
- /css/cssom/serialize-values.html
- /css/cssom/shorthand-values.html

New failures:
- /css/CSS2/ui/outline-applies-to-005.xht
- /css/CSS2/ui/outline-applies-to-006.xht
- /css/CSS2/ui/outline-color-applies-to-005.xht
- /css/CSS2/ui/outline-color-applies-to-006.xht
- /css/CSS2/ui/outline-style-applies-to-005.xht
- /css/CSS2/ui/outline-style-applies-to-006.xht
- /css/CSS2/ui/outline-width-applies-to-005.xht
- /css/CSS2/ui/outline-width-applies-to-006.xht
  Al of these fail because tables are not implemented yet.
- /css/css-ui/outline-offset.html
  Fails because outline-offset is not implemented yet.
- /css/css-ui/outline-with-padding-001.html
  Fails because the outline doesn't include overflowing contents.
  I don't think this is required by the spec, Firefox fails too.
This commit is contained in:
Oriol Brufau 2023-04-27 23:40:08 +02:00
parent c92ea9774f
commit d67bf49bd9
106 changed files with 121 additions and 1020 deletions

View file

@ -4,6 +4,7 @@
use crate::context::LayoutContext;
use crate::display_list::conversions::ToWebRender;
use crate::display_list::stacking_context::StackingContextSection;
use crate::fragments::{BoxFragment, Fragment, Tag, TextFragment};
use crate::geom::{PhysicalPoint, PhysicalRect};
use crate::replaced::IntrinsicSizes;
@ -21,7 +22,7 @@ use style::computed_values::text_decoration_style::T as ComputedTextDecorationSt
use style::dom::OpaqueNode;
use style::properties::longhands::visibility::computed_value::T as Visibility;
use style::properties::ComputedValues;
use style::values::computed::{BorderStyle, Length, LengthPercentage};
use style::values::computed::{BorderStyle, Color, Length, LengthPercentage, OutlineStyle};
use style::values::specified::text::TextDecorationLine;
use style::values::specified::ui::CursorKind;
use style_traits::CSSPixel;
@ -115,11 +116,12 @@ impl Fragment {
&self,
builder: &mut DisplayListBuilder,
containing_block: &PhysicalRect<Length>,
section: StackingContextSection,
) {
match self {
Fragment::Box(b) => match b.style.get_inherited_box().visibility {
Visibility::Visible => {
BuilderForBoxFragment::new(b, containing_block).build(builder)
BuilderForBoxFragment::new(b, containing_block).build(builder, section)
},
Visibility::Hidden => (),
Visibility::Collapse => (),
@ -418,10 +420,14 @@ impl<'a> BuilderForBoxFragment<'a> {
})
}
fn build(&mut self, builder: &mut DisplayListBuilder) {
self.build_hit_test(builder);
self.build_background(builder);
self.build_border(builder);
fn build(&mut self, builder: &mut DisplayListBuilder, section: StackingContextSection) {
if section == StackingContextSection::Outline {
self.build_outline(builder);
} else {
self.build_hit_test(builder);
self.build_background(builder);
self.build_border(builder);
}
}
fn build_hit_test(&self, builder: &mut DisplayListBuilder) {
@ -555,18 +561,8 @@ impl<'a> BuilderForBoxFragment<'a> {
}
}
fn build_border(&mut self, builder: &mut DisplayListBuilder) {
let b = self.fragment.style.get_border();
let widths = SideOffsets2D::new(
b.border_top_width.px(),
b.border_right_width.px(),
b.border_bottom_width.px(),
b.border_left_width.px(),
);
if widths == SideOffsets2D::zero() {
return;
}
let side = |style, color| wr::BorderSide {
fn build_border_side(&mut self, style: BorderStyle, color: Color) -> wr::BorderSide {
wr::BorderSide {
color: rgba(self.fragment.style.resolve_color(color)),
style: match style {
BorderStyle::None => wr::BorderStyle::None,
@ -580,13 +576,26 @@ impl<'a> BuilderForBoxFragment<'a> {
BorderStyle::Inset => wr::BorderStyle::Inset,
BorderStyle::Outset => wr::BorderStyle::Outset,
},
};
}
}
fn build_border(&mut self, builder: &mut DisplayListBuilder) {
let border = self.fragment.style.get_border();
let widths = SideOffsets2D::new(
border.border_top_width.px(),
border.border_right_width.px(),
border.border_bottom_width.px(),
border.border_left_width.px(),
);
if widths == SideOffsets2D::zero() {
return;
}
let common = builder.common_properties(self.border_rect, &self.fragment.style);
let details = wr::BorderDetails::Normal(wr::NormalBorder {
top: side(b.border_top_style, b.border_top_color),
right: side(b.border_right_style, b.border_right_color),
bottom: side(b.border_bottom_style, b.border_bottom_color),
left: side(b.border_left_style, b.border_left_color),
top: self.build_border_side(border.border_top_style, border.border_top_color),
right: self.build_border_side(border.border_right_style, border.border_right_color),
bottom: self.build_border_side(border.border_bottom_style, border.border_bottom_color),
left: self.build_border_side(border.border_left_style, border.border_left_color),
radius: self.border_radius,
do_aa: true,
});
@ -594,6 +603,35 @@ impl<'a> BuilderForBoxFragment<'a> {
.wr
.push_border(&common, self.border_rect, widths, details)
}
fn build_outline(&mut self, builder: &mut DisplayListBuilder) {
let outline = self.fragment.style.get_outline();
let width = outline.outline_width.px();
if width == 0.0 {
return;
}
let outline_rect = self.border_rect.inflate(width, width);
let common = builder.common_properties(outline_rect, &self.fragment.style);
let widths = SideOffsets2D::new_all_same(width);
let style = match outline.outline_style {
// TODO: treating 'auto' as 'solid' is allowed by the spec,
// but we should do something better.
OutlineStyle::Auto => BorderStyle::Solid,
OutlineStyle::BorderStyle(s) => s,
};
let side = self.build_border_side(style, outline.outline_color);
let details = wr::BorderDetails::Normal(wr::NormalBorder {
top: side,
right: side,
bottom: side,
left: side,
radius: expand_radii(self.border_radius, width),
do_aa: true,
});
builder
.wr
.push_border(&common, outline_rect, widths, details)
}
}
fn rgba(rgba: cssparser::RGBA) -> wr::ColorF {
@ -699,6 +737,27 @@ fn inner_radii(mut radii: wr::BorderRadius, offsets: units::LayoutSideOffsets) -
radii
}
fn expand_radii(mut radii: wr::BorderRadius, increment: f32) -> wr::BorderRadius {
assert!(increment > 0.0, "increment must be positive");
let expand = |radius: &mut f32| {
// Expand the radius by the specified amount, but keeping sharp corners.
// TODO: this behavior is not continuous, it's being discussed in the CSSWG:
// https://github.com/w3c/csswg-drafts/issues/7103
if *radius > 0.0 {
*radius += increment;
}
};
expand(&mut radii.top_left.width);
expand(&mut radii.top_left.height);
expand(&mut radii.top_right.width);
expand(&mut radii.top_right.height);
expand(&mut radii.bottom_right.width);
expand(&mut radii.bottom_right.height);
expand(&mut radii.bottom_left.width);
expand(&mut radii.bottom_left.height);
radii
}
fn clip_for_radii(
radii: wr::BorderRadius,
rect: units::LayoutRect,

View file

@ -99,6 +99,7 @@ pub(crate) enum StackingContextSection {
BackgroundsAndBorders,
BlockBackgroundsAndBorders,
Content,
Outline,
}
pub(crate) struct StackingContextFragment {
@ -113,7 +114,7 @@ impl StackingContextFragment {
builder.current_space_and_clip = self.space_and_clip;
self.fragment
.borrow()
.build_display_list(builder, &self.containing_block);
.build_display_list(builder, &self.containing_block, self.section);
}
}
@ -424,6 +425,13 @@ impl StackingContext {
child_context.build_display_list(builder);
}
// Step 10: Outline
while child_fragments.peek().map_or(false, |child| {
child.section == StackingContextSection::Outline
}) {
child_fragments.next().unwrap().build_display_list(builder);
}
if pushed_context {
builder.wr.pop_stacking_context();
}
@ -671,6 +679,14 @@ impl BoxFragment {
containing_block: containing_block_info.rect,
fragment: fragment.clone(),
});
if self.style.get_outline().outline_width.px() > 0.0 {
stacking_context.fragments.push(StackingContextFragment {
space_and_clip: builder.current_space_and_clip,
section: StackingContextSection::Outline,
containing_block: containing_block_info.rect,
fragment: fragment.clone(),
});
}
// We want to build the scroll frame after the background and border, because
// they shouldn't scroll with the rest of the box content.