Auto merge of #29695 - Loirooriol:outline, r=mrobinson

Implement outlines in layout-2020

<!-- Please describe your changes on the following line: -->

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.

---
<!-- Thank you for contributing to Servo! Please replace each `[ ]` by `[X]` when the step is complete, and replace `___` with appropriate data: -->
- [X] `./mach build -d` does not report any errors
- [X] `./mach test-tidy` does not report any errors
- [X] These changes fix #29694 (GitHub issue number if applicable)

<!-- Either: -->
- [ ] There are tests for these changes OR
- [ ] These changes do not require tests because ___

<!-- Also, please make sure that "Allow edits from maintainers" checkbox is checked, so that we can help you if you get stuck somewhere along the way.-->

<!-- Pull requests that do not address these steps are welcome, but they will require additional verification as part of the review process. -->
This commit is contained in:
bors-servo 2023-05-02 12:29:47 +02:00 committed by GitHub
commit 0cffe557c2
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
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.

View file

@ -14,7 +14,7 @@ ${helpers.predefined_type(
"outline-color",
"Color",
"computed_value::T::currentcolor()",
engines="gecko servo-2013",
engines="gecko servo-2013 servo-2020",
initial_specified_value="specified::Color::currentcolor()",
animation_value_type="AnimatedColor",
ignored_when_colors_disabled=True,
@ -26,7 +26,6 @@ ${helpers.predefined_type(
"OutlineStyle",
"computed::OutlineStyle::none()",
engines="gecko servo-2013 servo-2020",
servo_2020_pref="layout.2020.unimplemented",
initial_specified_value="specified::OutlineStyle::none()",
animation_value_type="discrete",
spec="https://drafts.csswg.org/css-ui/#propdef-outline-style",
@ -37,7 +36,6 @@ ${helpers.predefined_type(
"BorderSideWidth",
"crate::values::computed::NonNegativeLength::new(3.)",
engines="gecko servo-2013 servo-2020",
servo_2020_pref="layout.2020.unimplemented",
initial_specified_value="specified::BorderSideWidth::Medium",
computed_type="crate::values::computed::NonNegativeLength",
animation_value_type="NonNegativeLength",

View file

@ -5,7 +5,7 @@
<%namespace name="helpers" file="/helpers.mako.rs" />
<%helpers:shorthand name="outline"
engines="gecko servo-2013"
engines="gecko servo-2013 servo-2020"
sub_properties="outline-color outline-style outline-width"
derive_serialize="True"
spec="https://drafts.csswg.org/css-ui/#propdef-outline">