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.
During partial subtree relayout, `LayoutState` pre-populates
`used_values_per_layout_node` with nodes outside the relayout subtree
(e.g. ancestor SVG graphics boxes) so dependent data can be resolved.
`LayoutState::commit()` was still creating paintables for all entries,
including those pre-populated outside-subtree nodes.
That produced extra disconnected paintables on ancestors across repeated
SVG partial relayouts.
Guard the main paintable-creation loop with
`m_subtree_root->is_inclusive_ancestor_of(node)` so only subtree nodes
get new paintables, while outside-subtree nodes keep their existing
state.
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().
When a CharacterData mutation inside a foreignObject triggered partial
SVG relayout, sibling absolutely positioned elements whose containing
block is outside the SVG were not being repositioned. This happened
because the check only walked ancestors of the changed node looking for
abspos elements — it never saw abspos siblings.
Fix by querying contained_abspos_children() on boxes outside the SVG
subtree, which finds all abspos elements regardless of their position
in the tree relative to the changed node.
When a text node changes inside an absolutely positioned element within
an SVG <foreignObject>, and the abspos element's containing block is
outside the SVG subtree, the layout invalidation was incorrectly
stopping at the SVG root boundary. This triggered partial SVG relayout,
which cannot re-layout the abspos element since it's laid out by its
containing block's formatting context (outside the SVG).
The previous check only tested whether `this` (the node triggering
invalidation, e.g. a text node) was absolutely positioned, missing the
case where an abspos *ancestor* in the path has its containing block
outside the SVG. Fix this by walking from `this` up to the SVG root and
checking every abspos node in the path. If any has a containing block
outside the SVG subtree, skip the SVG boundary so layout propagation
continues upward and a full layout runs.
`none` isn't a supported value for `<counter-style-name>` and is only
supported directly by `list-style-type` (i.e. not within `counter{s}()`
functions)
Previously we didn't apply the value of `stroke-dasharray` if it was
`none`.
We also move resolution of this property into `ComputedProperties` in
line with other properties.
Remove 11 heavy includes from Document.h that were only needed for
pointer/reference types (already forward-declared in Forward.h), and
extract the nested ViewportClient interface to a standalone header.
This reduces Document.h's recompilation cascade from ~1228 files to
~717 files (42% reduction). Headers like BrowsingContext.h that were
previously transitively included see even larger improvements (from
~1228 down to ~73 dependents).
Builds on 01518ba by using the marker's line height as the minimum
height for a list item in the case that all children are shorter, not
just if there are no children.
Before this change we would only account for the size of the list item
itself rather than it's marker which if the marker was larger than it's
associated list item could lead to markers overlapping
The CSS spec (CSS2 10.6.4 / CSS Position 5.3) says:
1. Compute tentative height without min/max-height
2. If result > max-height, re-solve with max-height as height
3. If result < min-height, re-solve with min-height as height
We already implemented this correctly for the width axis, but the
height axis tried to bake min/max constraints into the solve_for
lambda. This corrupted the constraint equation when solving for
height with auto height + min-height, since the height term couldn't
cancel itself out (it was inflated to min-height on subtraction but
stayed 0 on addition).
This caused abspos elements with top+bottom insets and min-height to
incorrectly get the min-height as their height, even when the
containing block was tall enough for a larger height.
Previously, any SVG geometry attribute change would mark the entire
document layout tree as dirty, triggering a full layout pass even though
only the SVG subtree was affected. This made SVG geometry animations
unnecessarily expensive.
Fix this by stopping `needs_layout_update` propagation at the SVGSVGBox
boundary and tracking dirty SVG roots separately on the Document. When
`update_layout()` finds that only SVG roots need relayout (and the
document layout root is clean), it runs SVGFormattingContext on each
dirty SVG root in a fresh LayoutState and commits the results directly,
bypassing the full document layout pass entirely.
This results in a substantial performance improvement on pages with
animated SVGs, such as https://www.cloudflare.com/,
https://www.duolingo.com/, and our GC graph explorer page.
Instead of walking the entire DOM document to clear paintable refs and
collect inline nodes, walk the layout subtree rooted at the commit root.
This removes an assumption that commit() is always called with the
layout tree root and serves as preparatory refactoring for partial SVG
layout.
Move the inline dom_node() method to Viewport.cpp so the header no
longer needs the full Document definition. Add explicit includes to
files that relied on the transitive dependency.
This reduces the recompilation cascade when Document.h is modified,
cutting off the transitive path through ~30 SVG element headers.
Move the inline try_resolve_url_to() template body in
SVGGraphicsElement.h to a non-template helper in the .cpp file to
avoid needing Document.h and ShadowRoot.h in the header.
Add explicit includes to files that relied on the transitive dependency.
When rendering selections, we want to extend the selection rect for
wrapped lines to show that there is whitespace present. We don't
actually store this whitespace in the fragments; it's purely a visual
clue.
This reflects how both Chrome and Firefox deal with selection ranges
over wrapped lines.
SVG root elements (SVGSVGBox) have intrinsic sizes determined solely
by their own attributes (width, height, viewBox), not by their
children. SVGFormattingContext::automatic_content_width/height() both
return 0 unconditionally, confirming children never contribute to the
SVG root's intrinsic size from the CSS layout perspective.
This means changes inside an SVG subtree cannot affect ancestor
intrinsic sizes, so we can stop the cache invalidation traversal at
SVG root boundaries, just like we already do for absolutely positioned
elements.
No observable behavior change — this is a refactoring of how SVG
transforms are computed for mask and clip content.
Previously, SVGGraphicsElement::get_transform() computed accumulated
transforms by walking the DOM tree. This didn't work correctly for masks
and clips because their DOM structure differs from layout: in the DOM,
mask/clip elements are siblings or ancestors of their targets, but in
the layout tree they become children of the target element. Walking the
DOM tree caused transforms from the target's ancestors to incorrectly
leak into mask/clip content.
The fix walks the layout tree instead of the DOM tree, which means
transform computation must happen during layout (where we have access to
the layout tree structure) rather than on-demand from the DOM element.
This moves the logic to SVGFormattingContext and removes get_transform()
since it can no longer serve its purpose — the DOM element only provides
element_transform() for its own transform now.
During layout, we walk up the layout tree and stop at mask/clip
boundaries, ensuring mask/clip content stays in its own coordinate
space. The target's accumulated transform is applied separately at paint
time.
Previously, horizontal padding, border, and margin were not being
resolved for table captions. This was visible in layout tests where
captions with padding showed 0 in the width metrics.
Previously, table captions were laid out using the parent's available
space before the table's width was calculated. This resulted in
captions not being properly constrained to the table's width.
`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).
Previously we would resolve font features
(https://drafts.csswg.org/css-fonts-4/#feature-variation-precedence)
per element, while this works for the current subset of the font feature
resolution algorithm that we support, some as yet unimplemented parts
require us to know whether we are resolving against a CSS @font-face
rule, and if so which one (e.g. applying descriptors from the @font-face
rule, deciding which @font-feature-values rules to apply, etc).
To achieve this we store the data required to resolve font features in a
struct and pass that to `FontComputer` which resolves the font features
and stores them with the computed `Font`.
We no longer need to invalidate the font shaping cache when features
change since the features are defined per font (and therefore won't ever
change).
Previously, `greatest_child_width()` was used, which finds the maximum
margin box width among direct children. This is incorrect for tables,
whose width is determined by column distribution rather than by simply
finding the widest child. We now return the table's computed content
width instead, which already holds the correct value after layout.
Previously `refresh_scroll_state()` reached back to PaintableBox for
sticky insets. Storing them on ScrollFrame makes it self-contained
for sticky offset computation.
Previously, has_scrollable_overflow was a purely geometric check, true
whenever content extended beyond the padding box regardless of the
overflow property. This caused unnecessary scroll frame allocation for
boxes with `overflow:visible`.
Per CSS Overflow 3, scrollable overflow is only defined for scroll
containers (overflow: auto/hidden/scroll). Gate the flag on
`is_scroll_container()` so that only actual scroll containers get scroll
frames assigned.