This simplifies handling (particularly around absolutization and
interpolation) and allows us to support calculated flex values (which
will come in a later commit).
Use the absolutely positioned box's own grid placement to resolve the
grid-area containing block rectangle instead of inheriting the nearest
in-flow grid item's area.
Keep the grid-specific static-position handling for axes with both
insets auto, but resolve mixed auto and explicit grid placement axes
against the augmented grid for direct children as well as descendants
inside grid items.
This fixes the imported abspos and alignment WPTs for values like
grid-row: 1 and grid-column: auto / 1 while keeping the reduced
descendant regressions passing.
Percentage row tracks in auto-height grids only participated in our
intrinsic sizing pass. We computed the grid container's intrinsic
height from that pass, but never reran row track sizing with the
now-known height, so percentage rows stayed content-sized and the
imported WPTs had recorded failures.
Reset the mutable track sizing state before rerunning the row pass,
rebuild row gap tracks against the resolved container height, and keep
the automatic content height from the intrinsic pass so parent layout
still resolves auto height correctly. Rebaseline the percentage-row
imports and the grid track parsing import that now improve with the
corrected percentage resolution.
Final grid item alignment runs after the grid area size has already
been resolved from the spanned tracks. Reusing
should_treat_height/width_as_auto() there misclassified percentage
preferred sizes as auto based on the outer grid container's own
definiteness, which broke fixed-track cases such as height:50% in a
100px row.
Restore the final alignment fast path to only special-case literal
auto sizes, so percentage preferred sizes resolve through
calculate_inner_width/height() against the computed grid area.
Add a text test that covers start/stretch behavior in both axes and
across single-track and spanning definite grid areas.
When an absolutely positioned non-BFC element (flex, grid, etc.) has
auto height, we pre-compute its height from content before running
inside layout. Previously, this content-derived height was marked as
"definite", which incorrectly allowed descendants to resolve percentage
heights against it. Per CSS 2.1 section 10.5, percentage heights should
only resolve when the containing block's height is specified explicitly.
The fix is to simply not set has_definite_height when the CSS height is
auto. This naturally prevents percentage resolution through all existing
paths (set_node, should_treat_height_as_auto, calculate_inner_height)
without needing any new flags or per-site checks.
Two additional fixes enable this:
- In flex line cross-size clamping, remove the contains_percentage()
guard that prevented percentage min/max-height from resolving. These
percentages resolve correctly via calculate_inner_height's containing
block lookup, since the abspos element's containing block always has
a definite height.
- In grid item alignment, check should_treat_height/width_as_auto for
percentage preferred sizes, so they're treated as auto when the grid
container's height is indefinite (CSS Grid section 6.6).
When a grid item has a preferred aspect-ratio, a definite width, and
non-stretch alignment, resolve its height through the aspect-ratio
instead of using fit-content sizing.
The fit-content height calculation was incorrectly using the available
width (grid area width) to compute the aspect-ratio-based height,
rather than using the item's actual resolved width. This caused items
like a 50px wide div with aspect-ratio: 1/1 to get a height of 784px
(the grid area width) instead of 50px.
When computing the minimum contribution of a grid item with a definite
minimum size (e.g. min-height: 3rem), we must convert the CSS value to
content-box size before adding margin-box sizes. With box-sizing:
border-box, the CSS value already includes padding and border, so
passing it directly to add_margin_box_sizes() double-counted them.
Use calculate_inner_width/calculate_inner_height to properly convert
the minimum size to content-box dimensions, consistent with how
preferred and maximum sizes are already handled in the grid track
sizing algorithm.
Previously, during grid track maximization we used the total number of
grid tracks as the denominator when distributing the available free
space. Using a larger denominator than expected meant that the
algorithm needed a larger number of iterations than necessary to
converge leading to rounding errors in the final track sizes.
Move grid area rectangle computation and validation from layout to the
CSS parser. Named grid areas that don't form filled-in rectangles now
correctly invalidate the declaration per spec.
The previous fix for scroll containers in grid unconditionally set the
min-content contribution to 0 in both dimensions. This caused grids with
height:min-content to collapse rows containing scroll container items to
0 height. Restrict the check to the column dimension only, since scroll
containers can overflow and scroll horizontally but must still
contribute their content height for correct row sizing.
Implement the `dense` keyword for `grid-auto-flow` so auto-placed items
backfill earlier gaps in the grid. The sparse/dense cursor logic is now
centralized in `place_grid_items()` step 4: dense resets the cursor to
the grid start before each search, while sparse keeps advancing forward.
Also fix a pre-existing bug where `find_unoccupied_place()` and several
placement helpers only checked if a single cell was unoccupied, ignoring
multi-cell spans. Add `OccupationGrid::is_area_occupied()` and use it
throughout to correctly verify the entire rectangular area is available.
Introduce AbsposContainingBlockInfo and a virtual
resolve_abspos_containing_block_info() method on FormattingContext that
computes the containing block rect, per-axis positioning mode (static
position vs inset-from-rect), and optional grid alignment — all in one
place.
The base implementation handles the common case: padding-box rect of
the containing block, with inline containing block support. GFC
overrides it to return the grid area rect with alignment info.
This replaces the per-context abspos loops that BFC, FFC, TFC, and GFC
each had, with a shared layout_absolutely_positioned_children() +
layout_absolutely_positioned_element() pair. The offset computation is
simplified from ~40 lines of branching to a data-driven dispatch on the
axis mode, and GFC's ~130-line custom layout method is replaced by a
~30-line resolve override.
Replace content_box_rect_in_static_position_ancestor_coordinate_space()
which walked the ancestor chain to compute the offset between an abspos
element's static position containing block and its actual containing
block.
Instead, add a cumulative_offset() method to UsedValues that computes
the absolute offset from the ICB to a box's content edge by walking the
containing block chain. For pre-populated nodes during partial relayout,
it returns a cached value from the paintable's absolute position.
The static-CB-to-actual-CB offset is now a simple subtraction of two
cumulative offsets, which also fixes a bug where table cells with
position:relative ancestors got incorrect static positions due to the
old function accumulating offsets in the wrong coordinate space.
Also route direct UsedValues::offset assignments in Flex, Grid, and
Table formatting contexts through set_content_offset().
`grid-template-rows: fit-content(100px)` had no effect when the grid
container lacked an explicit height, because the fit-content growth
limit clamping was gated behind `available_size.is_definite()`.
Fix by normalizing fit-content tracks with unresolvable percentage
arguments to max-content during track initialization, then applying
the fit-content clamp unconditionally.
Fixes https://github.com/LadybirdBrowser/ladybird/issues/7730
Instead, compute them on demand. This affects ReplacedBox and its
subclasses.
This commit is centered around a new Box::auto_content_box_size
method. It returns a SizeWithAspectRatio representing the natural
size of a replaced element, or the size derived from attributes
for text input and textarea. These values are used when the
corresponding axis is auto or indefinite.
Although introducing this API choke-point for sizing replaced and
replaced-like elements was the main goal, it's notable that layout
becomes more robust in the face of dynamic changes due to reduced
potential for stale size values (at the cost of extra calculations
and allocations).
Add optional tracing that prints a tree visualization of formatting
context `run()` invocations. This is useful for debugging layout issues
where you need to understand the nesting and order of layout passes,
or why a box receives unexpected available space.
Example output:
```
├─ BFC <Viewport<#document>> run(definite(800) x definite(600))
│ ├─ BFC <BlockContainer<HTML>> run(definite(800) x indefinite)
│ │ ├─ IFC <BlockContainer(anonymous)> run(definite(800) x indefinite)
│ │ ├─ GFC <Box<DIV.grid>> run(definite(800) x indefinite)
│ │ │ ├─ BFC <BlockContainer<DIV.item>> run(definite(400) x indefinite)
```
For grid items with auto preferred size, stretch/normal alignment, and
no auto margins, the final size is simply the containing block size
minus margin box sizes. We can compute this directly without calling
`calculate_fit_content_width/height`, which triggers expensive intrinsic
sizing layout passes.
Previously, GridFormattingContext handled its own min-height constraints
internally, which caused infinite recursion when min-height was an
intrinsic sizing keyword (e.g., min-height: max-content).
This change moves the responsibility to the parent formatting context
(BFC). When any box establishing an independent formatting context has
auto height but non-auto min-height:
1. BFC measures content height using a throwaway LayoutState
2. If content height < min-height, BFC passes min-height as definite
available height to the child formatting context
3. Child FC runs once with correct constraints, unaware of min-height
Fixes https://github.com/LadybirdBrowser/ladybird/issues/4261 which
is corresponding to stack overflow on https://claude.ai/
When an item is placed with an unpositioned start and a positioned end
(e.g., `grid-row: auto / 1`), allow the start position to be negative.
This correctly creates implicit tracks before the explicit grid.
When distributing item contributions among flexible tracks with
intrinsic min sizing functions, we were skipping distribution entirely
when total_flex == 0. However, tracks like `0fr` (equivalent to
minmax(auto, 0fr)) should still receive the item's contribution
distributed equally among them.
Now we count intrinsic flexible tracks separately and distribute
equally when all flex factors are zero.
This commit includes two interdependent changes that must be applied
together:
- Treat `fit-content()` tracks as having an intrinsic min sizing
function when the limit resolves to zero.
- When clamping growth limit to fit-content limit, use `max(base_size,
fit_content_limit)` instead of just `fit_content_limit` to preserve
intrinsic sizing contributions.
These changes are coupled because the first change causes
`fit-content(0)` tracks to participate in intrinsic sizing, while the
second ensures the base size from that sizing is not discarded during
clamping.
Rewrite
`increase_sizes_to_accommodate_spanning_items_crossing_flexible_tracks`
to follow spec steps more closely:
1. Process all items spanning flexible tracks together (not grouped by
span size), taking the maximum contribution per track
2. Distribute item contributions proportionally to flex factors
3. Subtract non-flexible tracks existing sizes from item contribution
before distributing to flexible tracks
When resolving grid placement like `grid-column: 1 / a`, the start
position from the line number was being incorrectly overwritten when
processing the end identifier. Now we only set start from end if the
start placement doesn't already have a line number.
No tests are affected by the change itself, but combined with the
upcoming commits it's going to result in the progress for
`grid-flex-track-intrinsic-sizes-002.html`.
This patch fixes an issue where grid items using `grid-area: <name>`
with no matching named grid area or lines would be incorrectly placed
at position 0.
According to https://www.w3.org/TR/css-grid-1/#line-placement:
1. When a `<custom-ident>` doesn't match any named line, placement
should fall back to the first implicit grid line
2. When the same `<custom-ident>` is given for both `grid-*-start` and
`grid-*-end` (which happens with `grid-area: name`), and both fall
back to the same implicit line, the resulting span is 1
Previously, the fallback values were hardcoded to 0 and 1, which placed
items in track 0. The fix changes the fallback to use
`m_explicit_line_count` (the first implicit line index) for both start
and end identifiers. When both reference the same line (start == end),
the existing "both positioned" logic now handles this by setting span=1
and adjusting end accordingly.
Use `calculate_inner_height()` and `calculate_inner_width()`, which
account for box-sizing, to resolve the item's size in max-content
contribution calculations.
Not every user of this requires an `auto` state, but most do.
This has quite a big diff but most of that is mechanical:
LengthPercentageOrAuto has `resolved_or_auto()` instead of `resolved()`,
and `to_px_or_zero()` instead of `to_px()`, to make their output
clearer.
We could skip doing item's intrinsic min-content layout if we know for
sure that there's no tracks with intrinsic sizing function to distribute
the min-content size to.
Now elements with position `absolute` properly resolve their position
inside parent elements with `grid`. I also imported some WPT tests
related to that topic.
Part 2 of resolving issues on https://hack4krak.pl
Fixes bug when we didn't use `tracks_to_grow_beyond_limits` and instead
distributed extra space to all affected tracks. Also implements missing
"when accommodating max-content" part.
...by another GFC. When searching for grid item that contains an
abspos box positioned relative to GFC, it's incorrect to assume that the
closest ancestor box whose parent establishes GFC is always the one we
are looking for, because there may be non-positioned GFC in between.
Fixes regression introduced in 80c8e78.
Before this change, `layout_absolutely_positioned_element()` in GFC
had an assumption that all contained by grid container abspos boxes were
also direct children of the grid container. This change adds handling
for the cases when it's not true and, in order to identify grid area
abspos box belongs to, we have to find ancestor grid item.
CSS grid specification states that for grid items with a replaced
element and a percentage preferred size or maximum size, the percentage
should be resolved against 0 during content-based minimum size
calculation. This makes sense, as it prevents replaced items from
overshooting their grid track while intrinsic track sizes are
calculated, and allows later track size resolution steps to scale
replaced items to fit their grid track.
FFC expects parent formatting context to mark size as definite if that's
the case, because otherwise it cannot figure cross line size correctly.
Fixes incorrect alignment when FFC is nested in GFC.
Progress on https://web.telegram.org/a/ layout.
This brings parsing of grid-row-* and grid-column-* properties (and
their associated shorthands) more inline with spec.
Changes:
- Only set omitted properties for combined-value shorthands (e.g.
`grid-row: a` rather than `grid-row: a / b`) if the single value is
`<custom-ident>`.
- `[ [ <integer [-∞,-1]> | <integer [1,∞]> ] && <custom-ident>? ]`:
- Properly resolve `calc`s for `<integer>` that rely on compute-time
information.
- `[ span && [ <integer [1,∞]> || <custom-ident> ] ]`
- Allow `calc`s for `<integer>`
- Allow `<custom-ident>`
There is still work to be done to properly use these parsed values.
Gains us 46 WPT tests.
`collapse_auto_fit_tracks_if_needed()` had a check that does collapsing
only if auto-fit is used like
`grid-template-columns: repeat(auto-fit, 1px);`, and it didn't work for
valid cases when `repeat(auto-fit)` is placed in the middle of
definition like `grid-template-columns: 1px repeat(auto-fit, 1px) 1px;`.
Spec says that definite grid container size should be used as free space
so there's no need to use `get_free_space()` that does iteration over
tracks and subtracts definite sizes from available space.