Commit Graph

943 Commits

Author SHA1 Message Date
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
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
bc60768cb0 LibWeb: Rewrite audio elements without controls to display:none
This is part of the rendering spec, but we had neglected to do this
before. It causes one WPT check to fail, but other browsers get the
same result on that check, so I guess we can call that a win. :^)
2026-02-23 07:27:31 +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
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
397fe9c58e LibWeb: Dump all paintables attached to a layout node in dump_tree
Instead of dumping only the single paintable passed into the function,
iterate over all paintables attached to its layout node. This makes
detached/disconnected paintables visible in the dump output, which is
useful for debugging incremental layout issues.

Rebaseline content-image-simple layout test: the anonymous InlineNode
for ::before content spanning multiple lines legitimately has a
separate PaintableWithLines per line, which now all appear in the dump.
2026-02-15 17:47:30 +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
Jelle Raaijmakers
c72de688cc LibWeb: Make HTMLInputElement's contents scrollable
If the input's contents exceeded the dimensions of the inner text, it
would simply get clipped. This allows the user to scroll the input
horizontally.
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
Tim Ledbetter
66e9e2de4b LibWeb: Account for table padding in intrinsic table width calculation 2026-02-11 09:39:11 +01:00
Andreas Kling
171dfa4db8 LibWeb: Use re-solve pattern for abspos height min/max constraints
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.
2026-02-10 11:58:15 +01:00
Psychpsyo
12862bea93 LibWeb: Unregister block container y-position update callback 2026-02-07 12:21:16 +01:00
Tim Ledbetter
0b346d1952 LibWeb: Distribute colspan cell width equally when baseline is zero
This matches the behavior of other engines.
2026-02-06 11:37:14 +00:00
Tim Ledbetter
3e319a51b4 LibWeb: Set automatic content height for table captions with auto height 2026-02-04 20:25:04 +00:00
Tim Ledbetter
04ec4c0256 LibWeb: Resolve horizontal box model metrics for table captions
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.
2026-02-03 14:47:51 +01:00
Tim Ledbetter
7d6ce58cf2 LibWeb: Constrain table caption available space to table's border-box
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.
2026-02-03 14:47:51 +01:00
Aliaksandr Kalenik
ced5dc51c4 LibWeb: Fix fit-content() row clamping with indefinite available height
`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
2026-02-02 20:33:05 +01:00
Tim Ledbetter
c864d2f22a Tests: Rebaseline Layout/input/quirks/input-in-pre-quirks-mode.html 2026-02-02 10:53:00 -05:00
Jonathan Gamble
d3cdeb3ac5 LibWeb: Implement auto_content_box_size for textarea and input
This allows default and attribute-based sizing when an axis is auto,
without overriding extrinsic sizing.
2026-02-02 14:36:49 +00:00
Jonathan Gamble
7c819460ce LibWeb: Fix grid axis hint for replaced items
Now with test!
2026-02-02 14:36:49 +00:00
Jonathan Gamble
4fb13c89cc LibWeb: Definite size overrides intrinsic aspect ratio contribution 2026-02-02 14:36:49 +00:00
Callum Law
5917a8c1c8 LibGfx+LibWeb: Allow setting FontVariationSettings for default font
While our default font supporting variations is unlikely, this is
nevertheless required for our fallback font to be considered equal to
it's non-default/fallback equivalent (i.e. `font-family: serif`) which
in turn is required for LineBuilder to merge chunks into a single
fragment.
2026-02-02 14:11:43 +00:00
Tim Ledbetter
cf208c8e5e LibWeb: Exclude UA internal shadow roots from percentage height quirk 2026-02-02 12:28:05 +00:00
Tim Ledbetter
dfeed4e9a6 LibWeb: Return computed content width from TFC automatic_content_width()
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.
2026-02-01 20:35:16 +01:00
Aliaksandr Kalenik
2e4eebe4e5 LibWeb: Move CSS transform resolution to visual context assignment
Instead of computing and caching the transform matrix and transform
origin on PaintableBox during resolve_paint_properties(), compute them
inline during assign_accumulated_visual_contexts() where they are
actually consumed. This makes PaintableBox smaller by not wasting space
for properties that are only consumed during AccumulatedVisualContext
tree construction.
2026-01-31 16:56:05 +01:00
Aliaksandr Kalenik
c9108a2ad5 LibWeb: Only set has_scrollable_overflow for scroll containers
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.
2026-01-29 14:59:32 +01:00
Tim Ledbetter
e04446802f LibWeb: Implement label activation behavior in the DOM layer
Previously, click handling for labels was handled in layout and
painting code. This change implements activation_behavior on
HTMLLabelElement, which clicks and focuses the element.
2026-01-27 18:35:38 +01:00
Andreas Kling
73c9d1a606 LibWeb: Implement the body element fills the html element quirk
In quirks mode, the body element expands to fill its parent (the html
element) when height is auto, per the quirks spec section 3.7.

This quirk applies when:
- The document is in quirks mode
- The body element has height: auto
- The body is not absolutely/fixed positioned
- The body is not floated
- The body is not inline-level
2026-01-26 16:48:21 +01:00
Andreas Kling
640ec0b64e LibWeb: Fix percentage height resolution in quirks mode
The quirks mode percentage height calculation quirk was incorrectly
applied to anonymous boxes (like the internal flex wrapper inside
buttons), causing buttons to collapse to zero height.

Per the quirks spec, the percentage height quirk:
- Only applies to DOM elements, not anonymous boxes
- Does not apply to flex/grid items (they resolve against their
  container)
- Does not apply to table-related display types

This patch:
1. Excludes anonymous boxes and flex/grid items from the quirk in
   should_treat_height_as_auto()
2. Adds quirks mode percentage height walk-up in
   calculate_inner_height() for inline-level boxes
3. Removes the incorrect flex/grid container exclusion from
   BlockFormattingContext (the quirk applies to containers, not items)
2026-01-26 16:48:21 +01:00
Andreas Kling
70ca7f20c3 Tests/LibWeb: Add layout tests for quirks mode percentage heights
Add tests to verify:
- Buttons have reasonable height in quirks mode (not collapsed)
- Flex containers with percentage height use quirks walk-up to viewport
- Flex items with percentage height do NOT use quirks walk-up
2026-01-26 16:48:21 +01:00
Jelle Raaijmakers
a6958e5a72 LibWeb: Do not generate marker boxes for list-style-type: none
This is expected by the spec and makes it a bit easier to reason about
our layout tree.
2026-01-26 16:41:42 +01:00
Jelle Raaijmakers
01518ba6a6 LibWeb: Use list item's line-height to provide space for marker
Empty list items should still have a default height if a marker is
present.

Fixes #3762.
2026-01-26 16:41:42 +01:00
Jelle Raaijmakers
19882e1ed6 LibWeb: Check available space for float: right boxes
For `float: left` boxes, we would take the available space into account
and push boxes down as required. This applies that same logic to `float:
right` boxes, where we would previously only compare their offset from
the edge using `>= 0`, which was almost always true.

Fixes #4750.
2026-01-26 16:41:42 +01:00
Tim Ledbetter
631e73676e LibWeb: Resolve margins for block-level buttons with width:auto
Previously, buttons with `display: block` and `width: auto` would take
an early return path in compute_width() that set the content width to
fit-content but skipped all margin resolution. This meant `margin: auto`
would not center the button horizontally.
2026-01-26 10:00:17 +01:00
Andreas Kling
492629e3d8 LibWeb: Don't resolve percentage heights against min-height
Per CSS 2.1 Section 10.5, percentage heights should only resolve when
the containing block's height is "specified explicitly". This means a
containing block with `height: auto` and `min-height: 50px` does NOT
provide a definite height for percentage resolution - the child's
`height: 100%` should be treated as `auto`.

Previously, we checked `available_space.height.is_indefinite()` to
determine if percentage heights should become auto. However, this
conflated "available layout space" with "containing block height for
percentage resolution" - these are distinct concepts.

Now we check the containing block's `has_definite_height()` flag, which
correctly reflects whether the containing block has an explicit height
property. This handles:

- Anonymous wrapper blocks (skip them to find real containing block)
- Quirks mode (has special percentage height handling)
- Absolutely positioned elements (excluded, different rules apply)

Also update `calculate_inner_height()` to use the containing block's
actual used height when resolving percentages with indefinite available
space, which fixes inline-block and similar cases.
2026-01-25 20:29:44 +01:00
Timothy Flynn
a9bd0ca96c Tests/LibWeb: Rebaseline maintain-single-backdrop-pseudo-element
This test was affected that a commit that was last tested in the PR
queue before the test was added.
2026-01-22 17:09:37 -05:00
Jelle Raaijmakers
6a29b8cc03 LibWeb: Derive inline-block baseline from nested content
Compute inline-block baselines by traversing into nested block children
to find the last in-flow line box, using correct offsets relative to the
margin box edge.

Also ensure inline-flex and inline-grid containers always derive their
baseline from content (per CSS Align), and add special handling for
<input> elements which have `overflow: clip` in the UA stylesheet but
should still align adjacent text with their internal content.
2026-01-22 19:36:09 +01:00
Sam Atkins
b6207201d6 LibWeb/Layout: Replace existing ::backdrop layout nodes when necessary
We had two issues with ::backdrop which this commit fixes:

::backdrop is unique in that it's the previous sibling to its
originating element, instead of a child of it. This means when that
element's layout node is thrown away, the ::backdrop's is not.

A second issue is that if we do a partial layout rebuild, the
originating element's layout node replaces its previous one, but we
would still append a new layout node for ::backdrop to the root, so it
would appear in front of the originating element.

A related issue is that clear_pseudo_element_nodes() got called on the
element after its ::backdrop had been assigned, so it would immediately
lose track of it again.

To solve this, we now always remove the ::backdrop's layout node. If we
need to create a new one, we insert it before the element's layout node
if it has one, otherwise we append as before. This ensures we only ever
have up to one layout node for the ::backdrop, and it appears behind
its originating element.

To support this, create_pseudo_element_if_needed() has a couple of
changes:
- It returns the node that was created.
- The caller can ask it not to insert the node, so that the caller can
  do so (which we use so that we can insert it in a specific place)
2026-01-22 13:52:31 +00:00
Jelle Raaijmakers
d597bba5cf LibWeb: Align anonymous table-cell flex/grid wrappers to top
Vertical-align padding should not apply to anonymous table-cells that
wrap a flex/grid container, as the container handles its own alignment.
2026-01-21 23:44:23 +01:00
Andreas Kling
dc79259970 LibWeb: Support inline elements as abspos containing blocks
CSS allows inline elements with `position: relative` (or other
containing-block-establishing properties) to serve as the containing
block for their absolutely positioned descendants. However, our layout
system stores containing blocks as `Box*`, which cannot represent
inline elements (they are `InlineNode`, not `Box`).

This patch adds a workaround: when computing containing blocks, we
also check if there's an inline element between the abspos element
and its Box containing block that should actually be the CSS
containing block. If found, we store it in a new member called
`m_inline_containing_block_if_applicable` and use it during abspos
layout to:

1. Compute the inline's fragment bounding box as the containing
   block rectangle (including padding, per CSS spec)
2. Resolve percentage-based insets against the inline's dimensions
3. Position the abspos element relative to the inline's location

Some details to be aware of:

- The inline containing block search happens in the function
  `recompute_containing_block()` by walking DOM ancestors (not layout
  tree ancestors, since the layout tree restructures blocks inside
  inlines as siblings)

- For pseudo-elements like `::after`, we start the search from the
  generating element itself, since it may be the inline containing
  block

- Fragment offsets are relative to their block container, so we
  translate the computed rect to the abspos element's containing
  block coordinate system by accumulating offsets up the ancestor
  chain

- When the abspos element uses static position (auto insets), we
  don't apply the inline rect translation since static position is
  already computed in the correct coordinate system

Long term, we want to refactor our "containing block" concept to
map more cleanly to the spec concept. That means turning it into
a rectangle instead of the box this rectangle was derived from.
That's an invasive change for another day though.
2026-01-19 17:34:46 +01:00
Andreas Kling
2a72c5049b Tests: Add layout test for abspos box in inline abspos containing block 2026-01-19 17:34:46 +01:00
Andreas Kling
de47fe86ba LibWeb: Resolve percentage max-width when calculating intrinsic height
When calculating the width to use for intrinsic height determination of
flex items in a column layout, we were unconditionally ignoring
percentage min-width and max-width values.

This was overly conservative - when the containing block has a definite
width, percentages can be resolved. We now check if the available width
is definite and resolve percentages in that case.

This fixes the hero layout on https://slack.com/
2026-01-19 12:53:13 +01:00
Andreas Kling
6bb48ac00b Tests: Add test for percentage max-width on flex item in column layout
This currently behaves the wrong way.
2026-01-19 12:53:13 +01:00
Andreas Kling
8078d53d4f LibWeb: Avoid intrinsic sizing of boxes that don't create new FC
When computing the width of a box with `width: auto` in an intrinsic
sizing context (min-content or max-content), we previously called
calculate_min/max_content_width() for every such box.

This was wasteful for boxes that don't establish their own formatting
context (e.g. plain divs in a BFC), since their intrinsic width is
simply the intrinsic width of their contents, which we're already
computing as part of the current layout pass.

By only computing intrinsic widths separately for boxes that actually
create a new formatting context (BFC, flex, grid, table, etc.), we
avoid creating redundant throwaway LayoutState objects and formatting
contexts.

For deeply nested structures with `width: max-content` on an ancestor,
this reduces the number of formatting contexts created from O(n) to
O(1), where n is the nesting depth.
2026-01-14 13:52:22 +01:00