Commit Graph

764 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
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
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
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
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
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
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
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
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
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
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
2a72c5049b Tests: Add layout test for abspos box in inline abspos containing block 2026-01-19 17:34:46 +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
Aliaksandr Kalenik
5fdde04f78 Tests: Increase flex test coverage for upcoming optimization 2026-01-09 00:13:05 +01:00
Aliaksandr Kalenik
ef6829364a LibWeb: Skip intrinsic sizing for stretched grid items
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.
2026-01-08 19:37:14 +01:00
Aliaksandr Kalenik
bdf2dbff98 LibWeb: Move min-height handling for independent FCs to parent context
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/
2026-01-05 16:09:51 +01:00
Aliaksandr Kalenik
f1253c7139 LibWeb: Fix grid placement when using unresolved named grid lines
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.
2026-01-03 16:41:31 +01:00
Aliaksandr Kalenik
defbd0ec05 LibWeb/SVG: Percent-decode same-document <use> href fragments
`SVGUseElement::referenced_element()` previously passed
`m_href->fragment()` directly to `Document::get_element_by_id()`. URL
fragments are stored in their serialized form, so references to elements
whose IDs contain non-ASCII characters would fail to resolve.
2025-12-23 00:11:42 +01:00
Psychpsyo
5be1e9076a LibWeb: Fix crash when abspos element is relative to table row
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.)
2025-12-17 11:59:46 +01:00
Psychpsyo
3760818d58 LibWeb: Implement CSS-Multicol's 'Pseudo-Algorithm' 2025-12-15 09:45:48 +00:00
Sam Atkins
d5d1b5351e LibWeb: Hide stepper buttons on appearance: textfield number inputs
The spec says:

> For the purpose of this specification, they all have the same effect
  as auto. However, the host language may also take these values into
  account when defining the native appearance of the element.

https://drafts.csswg.org/css-ui/#typedef-appearance-compat-special

Firefox at least hides the stepper buttons when this is set.
2025-12-01 11:10:13 +00:00
Jelle Raaijmakers
39ad7833f0 LibWeb: Do not render SVG <symbol> elements unless part of <use>
We were always rendering <symbol> SVG elements, but we should only
render them if they are a child of a <use>'s shadow root. This caused
practically all instances of <symbol> to be drawn at least one time too
many.
2025-11-27 07:55:29 +01:00
Jelle Raaijmakers
2c5beeabe3 LibWeb: Improve support for SVG text positioning attributes
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).
2025-11-20 23:15:24 +01:00
Aliaksandr Kalenik
70b5496ecd LibWeb: Account for box-sizing in max-content contribution [GFC]
Use `calculate_inner_height()` and `calculate_inner_width()`, which
account for box-sizing, to resolve the item's size in max-content
contribution calculations.
2025-11-14 16:24:30 +01:00
Jelle Raaijmakers
c845e2cef7 LibWeb: Don't join anonymous pseudo wrappers for block node insertion
This can happen if pseudo elements are absolutely positioned, they need
the nearest non-inline parent to determine their position.
2025-11-11 10:49:49 +01:00
Jelle Raaijmakers
ac829cf60a LibWeb: Hoist anonymous wrappers out of parent inline nodes
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.
2025-11-11 10:49:49 +01:00
Andreas Kling
b28480f62d LibWeb: Propagate layout tree update in display: contents to parent
When an element has `display: contents` and it gets marked for a layout
tree rebuild, we actually have to mark its parent for rebuild as well.

The structure of the parent (and siblings) may change depending on how
the `display: contents` element changes (e.g position, display, etc.)
2025-11-08 11:20:32 +01:00
Andreas Kling
6894034774 LibWeb: Mark flex item main size definite if resolved from aspect-ratio
This matches the behavior of other engines and makes the cards on the
Apple App Store appear in the narrower layouts.
2025-11-07 16:42:32 +01:00
Jelle Raaijmakers
3741d3040c LibWeb: Support percentage values in min/max flex item size constraints
Originally, 7200b3a16c introduced a two-pass system to determine
hypothetical cross sizes. Later, this was partially reverted in
0084d992d4, but some code was left behind that caused resolution of
percentages in `{min/max}-{width/height}` size constraints not to work.

Through intrinsic sizing, we can potentially end up with a definite
available space for the items in the last FC run. At that point we
should be able to resolve percentages against the available space, but
we were never doing that.
2025-11-05 12:01:40 +01:00
Lorenz A
ffcd3a4bb2 LibWeb: Count the width of inline-blocks in InlineNodes only once
An BlockContainer inside an InlineNode is called from the
`for each in inclusive_subtree_of_type`  but is also a fragment
 of that InlineNode. Don't count the the Node twice.
2025-10-27 21:45:33 -07:00
Andreas Kling
d17f666a8c LibWeb: Better CSS inheritance for nodes that represent a pseudo-element
When we compute style for elements inside a UA-internal shadow tree that
represent a pseudo-element (e.g ::placeholder), we actually run the
StyleComputer machinery for (host element :: pseudo-element).

While that lets us match the correct selectors, it was incorrectly
applying CSS inheritance, since we'd also then inherit from whatever was
above the host element in the tree.

This patch fixes the issue by introducing an inheritance override in
AbstractElement and then using that to force inheritance from whatever
is actually directly above in the DOM for these elements instead of
jumping all the way up past the host.

This fixes an issue where `text-align: center` on input type=text
elements would render the main text centered but placeholder text was
still left-aligned.
2025-10-21 16:42:00 +02:00
Amish K. Naidu
884b7fcbf5 LibWeb: Peek after extracting next chunk in InlineLevelIterator
Peek is used to determine if the current chunk is
the last and if so, add trailing box metrics.
For this to work correctly, it must be done only
after calling next(), otherwise peek gives us the
current chunk.
2025-10-20 12:37:25 +01:00
Luke Wilde
11a1e97e40 LibWeb: Invalidate layout tree after removing element from top layer
Otherwise the layout tree will still contain the top layer element(s).

Fixes Steam Events & Announcements `<dialog>` modal visually not fully
disappearing upon removal.
2025-10-19 16:58:47 +02:00
Manuel Zahariev
125b13a0cb LibWeb/CSS: Test cases for content inside the ::marker pseudo-element 2025-10-10 12:02:16 +01:00
luizgfc
82bddd5209 Tests: Important custom properties precedence on resolution 2025-09-30 09:54:10 +01:00