Skip to content

Commit fa65fc9

Browse files
committed
LibWeb: Harden replaced intrinsic sizing
Preserve the 300px fallback when cyclic minimum widths resolve against zero, and use it for ratio-less min-content sizing. Compress calculated cyclic preferred and maximum sizes through the zero-resolved minimum contribution. Size normal-aligned leaf replaced grid items with replaced rules even when they lack natural dimensions, while retaining axis-specific stretching for replaced boxes with children. Resolve percentage block constraints against the final grid area. Add a 58-case matrix covering both axes, partial intrinsic data, ratio sources, calculations, box sizing, and conflicting constraints.
1 parent 7b5df21 commit fa65fc9

5 files changed

Lines changed: 358 additions & 30 deletions

File tree

Libraries/LibWeb/Layout/FormattingContext.cpp

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -189,8 +189,6 @@ static Optional<CSSPixels> max_content_size_for_replaced_element_without_natural
189189
// If it has no preferred aspect ratio:
190190
// For both the min-content size and max-content size:
191191
// If the box has a <length> as its computed minimum size (min-width/min-height) in that dimension, use that size.
192-
if (is_width && constraints.minimum_width.has_value())
193-
return constraints.minimum_width.value();
194192
auto const& min_size = is_width ? box.computed_values().min_width() : box.computed_values().min_height();
195193
if (min_size.is_length_percentage() && !min_size.contains_percentage())
196194
return min_size.to_px(0);
@@ -2814,15 +2812,26 @@ CSSPixels FormattingContext::calculate_min_content_width(Layout::Box const& box,
28142812
// FIXME: If the box also has a preferred aspect ratio, then this min-content contribution is
28152813
// floored by any <length-percentage> minimum size from the opposite axis—resolving any
28162814
// such percentage against zero—transferred through the preferred aspect ratio.
2817-
if (auto const& width = box.computed_values().width(); width.is_percentage())
2818-
return width.to_px(0);
2819-
if (auto const& max_width = box.computed_values().max_width(); max_width.is_percentage())
2820-
return max_width.to_px(0);
2815+
// Note: The min-content contribution is, as always, also floored by the minimum size in its own axis.
2816+
if (box.computed_values().width().contains_percentage() || box.computed_values().max_width().contains_percentage()) {
2817+
auto const& min_width = box.computed_values().min_width();
2818+
if (!min_width.is_length_percentage())
2819+
return 0;
2820+
2821+
auto zero_percentage_basis_constraints = containing_block_constraints;
2822+
zero_percentage_basis_constraints.percentage_basis_width = 0;
2823+
return calculate_inner_width(box, AvailableSize::make_min_content(), min_width, zero_percentage_basis_constraints);
2824+
}
28212825
}
28222826
if (auto transferred_width = calculate_transferred_width_for_replaced_element(box, containing_block_constraints); transferred_width.has_value())
28232827
return transferred_width.value();
2824-
if (auto auto_size = box.auto_content_box_size(); auto_size.has_width())
2828+
auto auto_size = box.auto_content_box_size();
2829+
if (auto_size.has_width())
28252830
return auto_size.width.value();
2831+
if (box.is_replaced_box() && !box.has_preferred_aspect_ratio()) {
2832+
if (auto fallback_width = max_content_size_for_replaced_element_without_natural_size(box, auto_size, m_state.get(box), SizeDimension::Width); fallback_width.has_value())
2833+
return fallback_width.value();
2834+
}
28262835

28272836
// Boxes with no children have zero intrinsic width.
28282837
if (!box.has_children())

Libraries/LibWeb/Layout/GridFormattingContext.cpp

Lines changed: 31 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2244,6 +2244,12 @@ void GridFormattingContext::resolve_grid_item_sizes(GridDimension dimension)
22442244
CSSPixels containing_block_size = containing_block_size_for_item(item, dimension);
22452245
Alignment alignment = alignment_for_item(item.box, dimension);
22462246

2247+
// https://drafts.csswg.org/css-grid-1/#grid-item-sizing
2248+
// A grid item is sized within the containing block defined by its grid area.
2249+
auto grid_area_constraints = grid_area_constraints_for_item(item);
2250+
if (dimension == GridDimension::Row)
2251+
grid_area_constraints.percentage_basis_height = containing_block_size;
2252+
22472253
auto const& preferred_size = item.preferred_size(dimension);
22482254

22492255
struct ItemAlignment {
@@ -2315,28 +2321,38 @@ void GridFormattingContext::resolve_grid_item_sizes(GridDimension dimension)
23152321
AvailableSize::make_definite(clamp_to_max_dimension_value(containing_block_size_for_item(item, GridDimension::Row)))
23162322
};
23172323

2318-
auto calculate_inner_size = [this, &item, dimension, available_space](CSS::Size const& size) {
2324+
auto calculate_inner_size = [this, &item, dimension, available_space, grid_area_constraints](CSS::Size const& size) {
23192325
if (dimension == GridDimension::Column)
2320-
return calculate_inner_width(item.box, available_space.width, size, grid_area_constraints_for_item(item));
2321-
return calculate_inner_height(item.box, available_space, size, grid_area_constraints_for_item(item));
2326+
return calculate_inner_width(item.box, available_space.width, size, grid_area_constraints);
2327+
return calculate_inner_height(item.box, available_space, size, grid_area_constraints);
23222328
};
23232329

2324-
auto tentative_size_for_replaced_element = [this, &item, dimension, available_space](CSS::Size const& size) {
2330+
auto tentative_size_for_replaced_element = [this, &item, dimension, available_space, grid_area_constraints](CSS::Size const& size) {
23252331
if (dimension == GridDimension::Column)
2326-
return tentative_width_for_replaced_element(item.box, size, available_space, grid_area_constraints_for_item(item));
2327-
return tentative_height_for_replaced_element(item.box, size, available_space, grid_area_constraints_for_item(item));
2332+
return tentative_width_for_replaced_element(item.box, size, available_space, grid_area_constraints);
2333+
return tentative_height_for_replaced_element(item.box, size, available_space, grid_area_constraints);
23282334
};
23292335

23302336
ItemAlignment used_alignment;
2331-
auto hint = item.box.auto_content_box_size();
2332-
bool has_replaced_size_hint_in_this_axis = false;
2337+
auto natural_size = item.box.auto_content_box_size();
2338+
bool has_natural_size_in_relevant_axis = false;
23332339
if (dimension == GridDimension::Column) {
2334-
has_replaced_size_hint_in_this_axis = hint.has_width() || (hint.has_height() && item.box.has_preferred_aspect_ratio());
2340+
has_natural_size_in_relevant_axis = natural_size.has_width() || (natural_size.has_height() && item.box.has_preferred_aspect_ratio());
23352341
} else {
2336-
has_replaced_size_hint_in_this_axis = hint.has_height() || (hint.has_width() && item.box.has_preferred_aspect_ratio());
2342+
has_natural_size_in_relevant_axis = natural_size.has_height() || (natural_size.has_width() && item.box.has_preferred_aspect_ratio());
23372343
}
23382344

2339-
if (item.box.is_replaced_box() && has_replaced_size_hint_in_this_axis) {
2345+
auto use_replaced_sizing = item.box.is_replaced_box() && has_natural_size_in_relevant_axis;
2346+
2347+
// https://drafts.csswg.org/css-grid-1/#grid-item-sizing
2348+
// If the grid item has no preferred aspect ratio, and no natural size in the relevant axis (if it is a replaced
2349+
// element), the grid item is sized as for 'align-self: stretch'.
2350+
// INTEROP: Blink, WebKit, and Gecko instead use replaced sizing for normal-aligned replaced items. Match that
2351+
// behavior for leaf replaced boxes, while retaining our existing behavior for controls with children.
2352+
if (item.box.is_replaced_box() && alignment == Alignment::Normal && !item.box.is_replaced_box_with_children())
2353+
use_replaced_sizing = true;
2354+
2355+
if (use_replaced_sizing) {
23402356
auto tentative_size = tentative_size_for_replaced_element(preferred_size);
23412357
used_alignment = try_compute_size(tentative_size, item.preferred_size(dimension));
23422358
} else {
@@ -2372,14 +2388,14 @@ void GridFormattingContext::resolve_grid_item_sizes(GridDimension dimension)
23722388
} else if (preferred_size.is_auto() || preferred_size.is_fit_content()) {
23732389
CSSPixels fit_content_size;
23742390
if (dimension == GridDimension::Column) {
2375-
fit_content_size = calculate_fit_content_width(item.box, available_space, grid_area_constraints_for_item(item));
2391+
fit_content_size = calculate_fit_content_width(item.box, available_space, grid_area_constraints);
23762392
} else if (preferred_size.is_auto() && item.box.has_preferred_aspect_ratio() && *item.box.preferred_aspect_ratio() != 0 && item.used_values.has_definite_width()) {
23772393
// NB: When the item has a preferred aspect ratio and a definite width, resolve the
23782394
// height through the aspect ratio instead of using fit-content sizing, which would
23792395
// incorrectly use the available width (grid area width) instead of the item's width.
2380-
fit_content_size = item.used_values.content_width() / *item.box.preferred_aspect_ratio();
2396+
fit_content_size = calculate_inner_height(item.box, available_space, CSS::Size::make_auto(), grid_area_constraints);
23812397
} else {
2382-
fit_content_size = calculate_fit_content_height(item.box, available_space, grid_area_constraints_for_item(item));
2398+
fit_content_size = calculate_fit_content_height(item.box, available_space, grid_area_constraints);
23832399
}
23842400
used_alignment = try_compute_size(fit_content_size, preferred_size);
23852401
} else {
@@ -2388,7 +2404,7 @@ void GridFormattingContext::resolve_grid_item_sizes(GridDimension dimension)
23882404
}
23892405
}
23902406

2391-
bool should_treat_maximum_size_as_none = dimension == GridDimension::Column ? should_treat_max_width_as_none(item.box, available_space.width, grid_area_constraints_for_item(item)) : should_treat_max_height_as_none(item.box, available_space.height, grid_area_constraints_for_item(item));
2407+
bool should_treat_maximum_size_as_none = dimension == GridDimension::Column ? should_treat_max_width_as_none(item.box, available_space.width, grid_area_constraints) : should_treat_max_height_as_none(item.box, available_space.height, grid_area_constraints);
23922408
if (!should_treat_maximum_size_as_none) {
23932409
auto const& maximum_size = item.maximum_size(dimension);
23942410
auto max_size_px = calculate_inner_size(maximum_size);

Tests/LibWeb/Layout/expected/grid/replaced-item-preferred-ratio-source-matrix.txt

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -78,15 +78,15 @@ Viewport <#document> at [0,0] [0+0+0 800 0+0+0] [0+0+0 600 0+0+0] [BFC] children
7878
SVGSVGBox <svg> at [0,0] [0+0+0 32 0+0+0] [0+0+0 32 0+0+0] [SVG] children: not-inline
7979
BlockContainer <(anonymous)> at [8,1676] [0+0+0 784 0+0+0] [0+0+0 0 0+0+0] children: inline
8080
TextNode <#text> (not painted)
81-
Box <div> at [8,1676] [0+0+0 0 0+0+784] [0+0+0 32 0+0+0] [GFC] children: not-inline
82-
ImageBox <img.cyclic-zero-minimum> at [8,1676] [0+0+0 0 0+0+0] [0+0+0 32 0+0+0] children: not-inline
81+
Box <div> at [8,1676] [0+0+0 300 0+0+484] [0+0+0 32 0+0+0] [GFC] children: not-inline
82+
ImageBox <img.cyclic-zero-minimum> at [8,1676] [0+0+0 300 0+0+0] [0+0+0 32 0+0+0] children: not-inline
8383
(SVG-as-image isolated context)
8484
Viewport <#document> at [0,0] [0+0+0 32 0+0+0] [0+0+0 32 0+0+0] [BFC] children: not-inline
8585
SVGSVGBox <svg> at [0,0] [0+0+0 32 0+0+0] [0+0+0 32 0+0+0] [SVG] children: not-inline
8686
BlockContainer <(anonymous)> at [8,1708] [0+0+0 784 0+0+0] [0+0+0 0 0+0+0] children: inline
8787
TextNode <#text> (not painted)
88-
Box <div> at [8,1708] [0+0+0 10 0+0+774] [0+0+0 2 0+0+0] [GFC] children: not-inline
89-
ImageBox <img.cyclic-calculated-minimum> at [8,1708] [0+0+0 15 0+0+0] [0+0+0 2 0+0+0] children: not-inline
88+
Box <div> at [8,1708] [0+0+0 300 0+0+484] [0+0+0 2 0+0+0] [GFC] children: not-inline
89+
ImageBox <img.cyclic-calculated-minimum> at [8,1708] [0+0+0 300 0+0+0] [0+0+0 2 0+0+0] children: not-inline
9090
(SVG-as-image isolated context)
9191
Viewport <#document> at [0,0] [0+0+0 32 0+0+0] [0+0+0 32 0+0+0] [BFC] children: not-inline
9292
SVGSVGBox <svg> at [0,0] [0+0+0 32 0+0+0] [0+0+0 32 0+0+0] [SVG] children: not-inline
@@ -192,11 +192,11 @@ ViewportPaintable (Viewport<#document>) [0,0 800x600] overflow: [0,0 808x1884]
192192
Paintable (Box<DIV>) [8,1644 200x32]
193193
ImagePaintable (ImageBox<IMG>.css-ratio.conflicting-heights) [8,1644 200x100]
194194
PaintableWithLines (BlockContainer(anonymous)) [8,1676 784x0]
195-
Paintable (Box<DIV>) [8,1676 0x32]
196-
ImagePaintable (ImageBox<IMG>.cyclic-zero-minimum) [8,1676 0x32]
195+
Paintable (Box<DIV>) [8,1676 300x32]
196+
ImagePaintable (ImageBox<IMG>.cyclic-zero-minimum) [8,1676 300x32]
197197
PaintableWithLines (BlockContainer(anonymous)) [8,1708 784x0]
198-
Paintable (Box<DIV>) [8,1708 10x2]
199-
ImagePaintable (ImageBox<IMG>.cyclic-calculated-minimum) [8,1708 15x2]
198+
Paintable (Box<DIV>) [8,1708 300x2]
199+
ImagePaintable (ImageBox<IMG>.cyclic-calculated-minimum) [8,1708 300x2]
200200
PaintableWithLines (BlockContainer(anonymous)) [8,1710 784x0]
201201
Paintable (Box<DIV>) [8,1710 0x0]
202202
ImagePaintable (ImageBox<IMG>.css-ratio.cyclic-zero-minimum) [8,1710 0x0]
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
no intrinsic data: track 300, image 300x150
2+
cyclic minimum: track 300, image 300x150
3+
calculated cyclic minimum: track 300, image 300x150
4+
calculated cyclic minimum below fallback: track 300, image 350x150
5+
calculated cyclic minimum above fallback: track 400, image 600x150
6+
fixed minimum: track 400, image 400x150
7+
calculated minimum: track 400, image 400x150
8+
fixed maximum: track 100, image 100x150
9+
calculated maximum: track 100, image 100x150
10+
fixed preferred: track 100, image 100x150
11+
calculated preferred: track 100, image 100x150
12+
conflicting fixed min max: track 200, image 200x150
13+
cyclic maximum: track 300, image 150x150
14+
calculated cyclic maximum: track 300, image 170x150
15+
cyclic preferred: track 300, image 150x150
16+
calculated cyclic preferred: track 300, image 170x150
17+
conflicting cyclic min max: track 300, image 150x150
18+
opposite-axis minimum: track 300, image 300x200
19+
opposite-axis cyclic minimum: track 300, image 300x150
20+
border-box cyclic minimum: track 320, image 320x170
21+
natural width and cyclic minimum: track 120, image 120x150
22+
natural height and cyclic minimum: track 300, image 300x80
23+
natural dimensions and cyclic minimum: track 120, image 120x80
24+
CSS ratio and cyclic minimum: track 0, image 0x0
25+
auto CSS ratio and cyclic minimum: track 0, image 0x0
26+
min-content no intrinsic data: track 300, image 300x150
27+
min-content cyclic preferred: track 0, image 0x150
28+
min-content calculated cyclic preferred: track 0, image 20x150
29+
min-content cyclic maximum: track 0, image 0x150
30+
min-content calculated cyclic maximum: track 0, image 20x150
31+
min-content cyclic minimum: track 300, image 300x150
32+
min-content calculated cyclic minimum: track 300, image 300x150
33+
min-content calculated minimum above fallback: track 400, image 600x150
34+
min-content fixed minimum: track 400, image 400x150
35+
min-content fixed maximum: track 100, image 100x150
36+
min-content fixed preferred: track 100, image 100x150
37+
min-content conflicting fixed min max: track 200, image 200x150
38+
min-content natural width cyclic minimum: track 120, image 120x150
39+
min-content natural width calculated minimum: track 200, image 300x150
40+
min-content natural width calculated preferred: track 0, image 20x150
41+
min-content CSS ratio: track 0, image 0x0
42+
min-content CSS ratio cyclic preferred: track 0, image 0x0
43+
block no intrinsic data: track 150, image 300x150
44+
block cyclic minimum: track 150, image 300x150
45+
block calculated cyclic minimum: track 150, image 300x150
46+
block calculated minimum above fallback: track 200, image 300x300
47+
block fixed minimum: track 200, image 300x200
48+
block fixed maximum: track 100, image 300x100
49+
block calculated maximum: track 100, image 300x100
50+
block cyclic maximum: track 150, image 300x75
51+
block calculated cyclic maximum: track 150, image 300x95
52+
block cyclic preferred: track 150, image 300x75
53+
block calculated cyclic preferred: track 150, image 300x95
54+
block border-box cyclic minimum: track 170, image 320x170
55+
block border-box ratio from width: track 50, image 100x50
56+
block natural width: track 150, image 120x150
57+
block natural height: track 80, image 300x80
58+
block natural dimensions: track 80, image 120x80

0 commit comments

Comments
 (0)