Commit Graph

862 Commits

Author SHA1 Message Date
Aliaksandr Kalenik
edf1ca8f19 LibWeb: Restrict throwaway layout states to their subtree
Throwaway LayoutState instances used for intrinsic sizing should not
access nodes outside the laid-out subtree. Make this explicit by setting
a subtree root on each throwaway state, pre-populating only the
immediate containing block, and using try_get() for any ancestor
lookups — treating unavailable ancestors as indefinite rather than
silently populating them with incorrectly-resolved values.
2026-02-27 19:11:41 +01:00
Callum Law
858989e006 LibWeb: Make CounterStyle ref counted
Previously we just passed around a reference to the `CounterStyle`
stored on `Document::registered_counter_styles` but this won't be
possible for anonymous counter styles (i.e. those created by the
`<symbols()>` function)
2026-02-27 16:25:53 +00:00
Tim Ledbetter
a762f623bd LibWeb: Implement SVGPatternElement 2026-02-27 17:14:50 +01:00
Tim Ledbetter
000a746b78 LibWeb: Apply SVG transforms to image elements during layout 2026-02-27 17:14:50 +01:00
Tim Ledbetter
804287847a LibWeb: Add SVG paint fallback color support to CSS parsing 2026-02-27 17:14:50 +01:00
Aliaksandr Kalenik
a7993e240b LibWeb: Skip BFC abspos static position in intrinsic sizing
Avoid calculating static positions for absolutely positioned boxes
during intrinsic sizing, where the positions are based on intermediate
state and will be recalculated during normal layout. This is consistent
with how all other formatting contexts (IFC, TFC, GFC, FFC) handle
abspos boxes during intrinsic sizing.
2026-02-27 15:03:59 +01:00
Aliaksandr Kalenik
0c2212813c LibWeb: Use IntrinsicSizing mode for min-height measurement in BFC
When measuring content height to compare against min-height, a
throwaway formatting context was created with m_layout_mode (the
parent's layout mode, which could be Normal). This is a measurement
operation and should use LayoutMode::IntrinsicSizing instead,
consistent with the similar measurement in FormattingContext.cpp.
2026-02-27 12:48:27 +01:00
Psychpsyo
f7f95d2611 LibWeb: Do not remove inline start padding when collapsing whitespace 2026-02-27 12:05:03 +01:00
Aliaksandr Kalenik
d163c11612 LibWeb: Skip IFC abspos static position in intrinsic sizing
BFC, TFC, GFC, and FFC all skip absolutely positioned element handling
during intrinsic sizing. IFC was the only formatting context that
unconditionally calculated static positions for abspos elements.

This is unnecessary during intrinsic sizing because the positions are
based on intermediate state and will be recalculated during normal
layout. It also avoids creating UsedValues entries for abspos boxes
that serve no purpose during intrinsic sizing.
2026-02-27 11:43:53 +01:00
Andreas Kling
a146225331 LibWeb: Use unsafe layout/paintable accessors where appropriate
Add unsafe_layout_node(), unsafe_paintable(), and unsafe_paintable_box()
accessors that skip layout-staleness verification. These are for use in
contexts where accessing layout/paintable data is legitimate despite
layout not being up to date: tree construction, style recalculation,
painting, animation interpolation, DOM mutation, and invalidation
propagation.

Also add wrapper APIs on Node to centralize common patterns:
- set_needs_display() wraps if (unsafe_paintable()) ...set_needs_display
- set_needs_paint_only_properties_update() wraps similar
- set_needs_layout_update() wraps if (unsafe_layout_node()) ...

And add Document::layout_is_up_to_date() which checks whether layout
tree update flags are all clear.
2026-02-26 21:09:08 +01:00
Andreas Kling
ed26fdaa81 LibWeb: Remove ViewportClient from ImagePaintable and VideoBox
Neither of these classes did anything useful as ViewportClients:

- ImagePaintable called set_visible_in_viewport() which is a FIXME
  no-op in every ImageProvider implementation.
- VideoBox had an empty did_set_viewport_rect() with a FIXME comment.

More importantly, they caused a crash when a DOM node was adopted into
a different document: the old ImagePaintable/VideoBox would still be
registered with the old document, but their document() accessor (which
goes through the DOM node) would return the new document. During GC
finalization, unregister_viewport_client() would fail because it was
trying to unregister from the wrong document.

The only meaningful ViewportClient is HTMLImageElement (for responsive
image source selection), which already handles document adoption
correctly in adopted_from().

Fixes crash when loading https://msn.com/
2026-02-26 09:25:25 +01:00
Tim Ledbetter
f05bc7c0cd LibWeb: Implement dominant-baseline for SVG text
This property determines the default baseline used to align content
within the given box.
2026-02-26 09:23:23 +01:00
Tim Ledbetter
245eb7d91d LibWeb: Use margin box for vertical overlap checks in float placement
Previously, a float with `padding-top` would overlap a preceding float
instead of being placed beside it. The vertical overlap check was
comparing the new float's content box Y,which includes the padding
offset, against preceding floats' margin box rects, causing the check
to incorrectly conclude the floats do not overlap.
2026-02-25 12:03:31 +01:00
Tim Ledbetter
6b3d3468a1 LibWeb: Account for <col> span attribute during table grid formation
Previously, the column count was always incremented by 1. This led to a
mismatch with `compute_outer_content_sizes()`, which did use the `span`
attribute to advance the column index. This mismatch caused an
out-of-bounds access when the column index was greater than the
expected number of columns.
2026-02-24 12:14:54 +01:00
Callum Law
8d4084261a LibWeb: Resolve list item marker using registered counter styles 2026-02-23 11:21:09 +00:00
Callum Law
32b9ff21df LibWeb: Add generic int_from_style_value method
Reduces duplication in line with `number_from_style_value`,
`string_from_style_value` etc
2026-02-23 11:21:09 +00:00
Zaggy1024
21019c2fa9 LibWeb: Use UA shadow DOM for media elements' controls
Instead of using a custom paintable to draw the controls for video and
audio elements, we build them out of plain old HTML elements within a
shadow root.

This required a few hacks in the previous commits in order to allow a
replaced element to host children within a shadow root, but it's
fairly self-contained.

A big benefit is that we can drive all the UI updates off of plain old
DOM events (except the play button overlay on videos, which uses the
video element representation), so we can test our media and input event
handling more thoroughly. :^)

The control bar visibility is now more similar to how other browsers
handle it. It will show upon hovering over the element, but if the
cursor is kept still for more than a second, it will hide again. While
dragging, the controls remain visible, and will then hide after the
mouse button is released.

The icons have been redesigned from scratch, and the mute icon now
visualizes the volume level along with indicating the mute state.
2026-02-23 07:27:31 +01:00
Zaggy1024
dd0c821291 LibWeb: Allow replaced elements to host children
We add a new formatting context that simply runs layout for an
anonymous block formatting context within it. This allows replaced
elements to contain children, if the parent rewrites inline-flow to
inline-block.
2026-02-23 07:27:31 +01:00
Zaggy1024
7bc9824578 LibWeb: Remove useless moves of computed styles in AudioBox/VideoBox
Fixes a clang-tidy warning.
2026-02-23 07:27:31 +01:00
Aliaksandr Kalenik
eb210bb3af LibWeb: Replace OrderedHashMap with page-table Vector in LayoutState
Each NodeWithStyle is assigned a sequential layout index during the
pre-layout tree traversal. LayoutState stores UsedValues in a
PagedStore — a two-level page table indexed by layout_index that
gives O(1) lookup via two array accesses, with pages allocated
lazily on first write. UsedValues are stored directly in pages
(Optional<T>) rather than behind heap pointers, eliminating
per-entry malloc/free calls and improving cache locality.

This cuts ensure_used_values_for() from ~14% to ~7% in profiles
on https://www.nyan.cat/.
2026-02-23 01:13:35 +01:00
Tim Ledbetter
79d5fdc871 LibWeb: Avoid division by zero in multi-column column count calculation 2026-02-22 15:07:06 +01:00
Aliaksandr Kalenik
9f36972e06 LibWeb: Include zero-area boxes when measuring scrollable overflow
The CSS Overflow spec says scrollable overflow should include "the
scrollable overflow areas of all of the above boxes (including
zero-area boxes)", but we were skipping zero-area boxes entirely via
an early return. This meant elements like a position:relative container
that collapses to zero height (because its only child is absolutely
positioned) would never have their children's overflow counted.
2026-02-22 14:23:44 +01:00
Aliaksandr Kalenik
795222fab3 LibWeb: Validate grid-template-areas rectangles at parse time
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.
2026-02-21 21:46:34 +01:00
Andreas Kling
1b41c9109d LibWeb: Cache is_body() on layout nodes
Cache the result of the body element check as a bool set once during
NodeWithStyle construction, instead of calling document().body() (which
walks the children of <html>) on every call. This is called from
PaintableBox::paint_background() for every box on every frame.

This was 0.9% of CPU time while playing a YouTube video.
2026-02-21 15:53:22 +01:00
Tim Ledbetter
83bd30b957 LibWeb: Stop inline elements after table-cell being swallowed into table
The `is_ignorable_whitespace()` check in table fixup traverses anonymous
block wrappers to see if they contain only whitespace. It rejected
out-of-flow and text descendants but silently skipped in-flow non-text
elements like `<span>`, misclassifying wrappers with real content as
ignorable and absorbing them into the table structure.
2026-02-21 05:48:53 +00:00
Tim Ledbetter
8240fa0dc8 LibWeb: Resolve percentage table widths in wrapper width computation
Previously, a table with `width: 100%` and `margin: auto` whose content
was narrower than the viewport would be centered based on content
width rather than filling the containing block. Resizing the viewport
wider than the content would shift the table progressively further to
the right.

Co-authored-by: Aliaksandr Kalenik <kalenik.aliaksandr@gmail.com>
2026-02-21 05:16:56 +01:00
Aliaksandr Kalenik
e87fece8c6 LibWeb: Force position:static on non-root SVG elements
SVG elements (except the outermost <svg>) use SVG's coordinate system,
not the CSS box model, so CSS positioning doesn't apply to them.

This adds SVGElement::adjust_computed_style() to force position:static
on all SVG elements except the outermost <svg> element (which has no
owner_svg_element()). SVGSymbolElement's existing override now calls
Base::adjust_computed_style() to inherit this behavior.

With this in place, the FIXME in layout_absolutely_positioned_element()
for SVG boxes becomes unreachable and is replaced with
VERIFY_NOT_REACHED().
2026-02-21 05:12:55 +01:00
Aliaksandr Kalenik
65704e57fb LibWeb: Fix abspos replaced element constraint equations
The constraint equations for absolutely positioned replaced elements
only subtracted content width/height from the containing block size,
omitting padding and border.

Fixes https://github.com/LadybirdBrowser/ladybird/issues/7820
2026-02-19 12:56:40 +01:00
Aliaksandr Kalenik
a19db375b5 LibWeb: Only zero scroll container min-content in columns
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.
2026-02-19 03:07:15 +01:00
Aliaksandr Kalenik
b3231ea2a0 LibWeb: Make foreignObject establish a containing block for abspos
Absolutely positioned elements inside SVG foreignObject were being
positioned relative to an ancestor containing block outside the SVG,
instead of relative to the foreignObject itself. Per a W3C resolution
and the behavior of other browsers, foreignObject should establish a
containing block for absolutely and fixed positioned elements.

With this fix, the `has_abspos_with_external_containing_block` check
in `set_needs_layout_update()` and the abspos preservation loop in
`relayout_svg_root()` become dead code — remove both and simplify the
ancestor loops. Rename related tests to reflect the new behavior.

Fixes https://github.com/LadybirdBrowser/ladybird/issues/3241
2026-02-17 15:59:59 +01:00
Aliaksandr Kalenik
3c73457b26 LibWeb: Implement CSS Grid dense packing and fix span placement
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.
2026-02-16 20:00:17 +01:00
Callum Law
3d3d0a50b6 LibWeb: Add generic Length::from_style_value method 2026-02-16 12:09:23 +00:00
Callum Law
846493831d LibWeb: Add generic number_from_style_value method 2026-02-16 12:09:23 +00:00
Aliaksandr Kalenik
d61fe8622c LibWeb: Skip outside-subtree paintable creation during partial relayout
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.
2026-02-15 17:47:30 +01:00
Psychpsyo
a7267f711b LibWeb: Add overflow-clip-margin-* properties
The corner radius isn't quite right yet, but this gives us
another couple WPT passes for these.
2026-02-14 22:58:21 +01:00
Aliaksandr Kalenik
36ca93908a LibWeb: Unify abspos containing block resolution across FCs
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.
2026-02-14 22:50:24 +01:00
Tim Ledbetter
d49a2dcfa1 LibWeb: Implement word-break functionality using UAX#14 line breaking
We now use ICU's line break iterator to determine soft wrap
opportunities, enabling correct line breaking for CJK text.
2026-02-14 16:23:18 -05:00
Aliaksandr Kalenik
a3a6d73145 LibWeb: Replace static position ancestor walk with cumulative offset
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().
2026-02-14 18:44:50 +01:00
Aliaksandr Kalenik
085cf42712 LibWeb: Fix SVG partial relayout skipping abspos sibling updates
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.
2026-02-14 03:20:34 +01:00
Aliaksandr Kalenik
01334c4481 LibWeb: Fix partial relayout of abspos with containing block outside SVG
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.
2026-02-13 17:47:49 +01:00
Callum Law
05cafdb5d0 LibWeb: Remove none from counter-style-name-keyword
`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)
2026-02-12 10:33:09 +00:00
Callum Law
85b3a01d9e LibWeb: Avoid magic numbers in ListItemMarkerBox::relative_size() 2026-02-12 10:33:09 +00:00
Callum Law
87eef9e21a LibWeb: Always apply stroke-dasharray
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.
2026-02-12 10:26:43 +00:00
Aliaksandr Kalenik
901cc28272 LibWeb: Reduce recompilation impact of DOM/Document.h
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).
2026-02-11 20:02:28 +01:00
Jelle Raaijmakers
87ada9e887 LibWeb: Do not mark ScrollHandled as [[nodiscard]]
There's only one place where we don't `(void)` the result of these
methods, so let's not be too pedantic about it.
2026-02-11 11:04:53 +01:00
Callum Law
5b55c5051b LibWeb: Use marker line-height as minimum size for list item
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.
2026-02-11 11:04:32 +01:00
Callum Law
fd74fa024f LibWeb: Layout list item marker before setting offset in block container
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
2026-02-11 11:04:32 +01:00
Callum Law
379db7a42c LibWeb: Support animation-timeline scroll() value 2026-02-11 10:49:34 +01:00
Tim Ledbetter
6cf506d980 LibWeb: Allow table fixup to find internal table boxes in inline parents 2026-02-11 09:58:38 +01:00
Tim Ledbetter
66e9e2de4b LibWeb: Account for table padding in intrinsic table width calculation 2026-02-11 09:39:11 +01:00