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.
This adds visit_edges(Cell::Visitor&) methods to various helper structs
that contain GC pointers, and makes sure they are called from owning
GC-heap-allocated objects as needed.
These were found by our Clang plugin after expanding its capabilities.
The added rules will be enforced by CI going forward.
Previously we only allowed the viewport itself to be the containing
block for fixed-position elements, but the specs give us a few other
situations. Many of these are the same as for absolute-positioned ones.
When content changes inside a layout node, we now reset intrinsic size
caches only up to the nearest absolutely positioned ancestor, rather
than all the way to the document root.
This optimization is safe because absolutely positioned elements don't
contribute to their ancestors' intrinsic sizes - they are skipped in
min/max content width calculations.
The needs_layout_update flag still propagates to all ancestors so the
document knows layout is needed. Only the cache reset is bounded.
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.
The Transformation class wasn't really accomplishing anything. It still
had to store StyleValues, so it was basically the same as
TransformationStyleValue, with extra steps to convert from one to the
other. So... let's just use TransformationStyleValue instead!
Apart from moving code around, the behavior has changed a bit. We now
actually acknowledge unresolvable parameters and return an error when
we try to produce a matrix from them. Previously we just skipped over
them, which was pretty wrong. This gets us an extra pass in the
typed-om test.
We also get some slightly different results with our transform
serialization, because we're not converting to CSSPixels and back.
This occured because the abspos containing block is in-between the
containing blocks of two regular ancestors in this case, so it was
being skipped. (The containing block of a table cell is the table
itself, not the table row.)
...when the style is `none` or `hidden`. `outline-width` is not affected
by `outline-style: none` at all.
In our codebase, that means doing the border-width conversion when
assigning to ComputedValues.
Corresponds to:
2a3d1e4d1009f11f2ef9
We already dealt with this for block level children, but inline level
children should also look at the anonymous wrapper's parent's available
space to be able to properly resolve percentage values, for example.
Storing these within `LengthPercentage`, `LengthBox`, and `Variant`
over-complicated things.
We also now use the correct `SerializationMode` when serializing `xywh`
and `rect`
We can deduplicate some code by using `assemble_coordinated_value_list`,
also moves this to a method in `ComputedProperties` to be in line with
other values
Computing the font for an element in `compute_font` is premature since
we are yet to apply animated properties - instead we should compute the
value on the fly (with a cache to avoid unnecessary work) to ensure we
are respecting the latest values
Since we resolve any relative lengths at compute time there's no need
for the value to be passed around as a `NumberOrCalculated` and we can
just resolve it within `ComputedProperties::font_variation_settings`.
The only place this is used it is used with value_or so there's no need
to return it is an `Optional`.
This is only used for loading fonts (which occurs during style
computation) so there's no need to store it in `ComputedValues`
Some of this is rearranged for clarity, but it's mostly the exact same
code. Steps 3, 10, 11, and 15 are new, but don't have any effect until
we implement downward-growing cells.
The style propagation logic in `NodeWithStyle::apply_style()`
was incomplete for anonymous nodes created during layout
(e.g., within `wrap_in_button_layout_tree_if_needed`).
1. **Non-inherited CSS values** were not being propagated to the
anonymous wrappers.
2. Propagation did not recurse into **nested anonymous wrappers**
(descendants).
This fix adds calls to `propagate_non_inherit_values(child)` and
`child.propagate_style_to_anonymous_wrappers()`, ensuring all computed
styles reach the entire anonymous wrapper hierarchy.
Previously, we only supported very basic numbers and a single level of
text positioning support in the `x`, `y`, `dx` and `dy` attributes in
`<text>` and `<tspan>` SVG elements.
This improves our support for them in the following ways:
* Any `length-percentage` or `number` type value is accepted;
* Nested `<text>` and `<tspan>` use the 'current text position'
concept to determine where the next text run should go;
* We expose the attributes' values through the API.
Though we still do not support:
* Applying the `rotate` attribute;
* Applying transformations on a per-character basis.
* Proper horizontal and vertical glyph advancing (we just use the path
bounding box for now).
CSS Text 3 gives `text-indent` a couple of optional keywords to control
which lines are affected. This commit parses them, but doesn't yet do
anything with them.
Use `calculate_inner_height()` and `calculate_inner_width()`, which
account for box-sizing, to resolve the item's size in max-content
contribution calculations.
When we generate pseudo elements, we create anonymous wrappers that
might end up in an InlineNode, even if they have `display: block` set.
This causes them not to be rendered.
Do not rely on inline continuation logic for these anonymous wrappers,
but rather find the first layout parent that's not an InlineNode and
insert it into that.
Fixes#5042.
There is no need to recreate this each time we need it when we can
instead create it once and then reuse it.
This is stop-gap since we should resolve colors to their computed forms
as part of StyleComputer::compute_properties
Reduces time spent in ColorResolutionContext::for_layout_node from ~1.1%
to ~0.1% when loading
https://en.wikipedia.org/wiki/2023_in_American_television
By doing this in computed properties rather than InlineLevelIterator we
only do it once per element rather than once per text fragment.
Reduces runtime of this process from ~15% to ~0.2% when loading
https://en.wikipedia.org/wiki/2023_in_American_television