Commit Graph

167 Commits

Author SHA1 Message Date
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
Psychpsyo
12862bea93 LibWeb: Unregister block container y-position update callback 2026-02-07 12:21:16 +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
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
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
Psychpsyo
5c67ea640a LibWeb: Propagate overflow to viewport more correctly 2025-12-15 09:47:25 +00: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
Lorenz A
f54793315c LibWeb: Adjust buttons computed display style 2025-11-01 13:02:44 +00: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
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
Pascal Pomper
5b1eba7ac8 LibWeb: Position absolute block-level boxes below previous siblings
Whether an absbox is positioned below or to the right of its previous
sibling in an `InlineFormattingContext` is determined by the
display-outside value before blockification, so we store the
pre-blockification `display` value in `ComputedValues` to access it in
`InlineFormattingContext` and position the box accordingly.
2025-09-29 18:37:53 +02:00
zac
8c29b0a848 LibWeb/Tests: Add empty text chunk in empty editables
Otherwise the cursor won't get rendered in empty input fields.
2025-09-24 12:33:17 +01:00
Jelle Raaijmakers
9e9db9a9dd LibWeb: Store correct text offsets in PaintableFragment
Previously, we were collapsing whitespace in Layout::TextNode and then
passed the resulting string for further processing through ChunkIterator
-> InlineLevelIterator -> InlineFormattingContext -> LineBuilder ->
LineBoxFragment -> PaintableFragment. Our painting tree is where we deal
with things like range offsets into the underlying text nodes, but since
we modified the original string, the offsets were wrong.

This changes the way we generate fragments:

  * Layout::TextNode no longer collapses whitespace as part of its
    stored "text for rendering", but moves this logic to ChunkIterator
    which splits up this text into separate views whenever whitespace
    needs to be collapsed.

  * Layout::LineBox now only extends the last fragment if its end offset
    is equal to the new fragment's start offset. Otherwise, there's a
    gap caused by collapsing whitespace and we need to generate a
    separate fragment for that in order to have a correct start offset.

Some tests need new baselines because of the fixed start offsets.

Fixes #566.
2025-09-12 15:34:09 -04:00
Aliaksandr Kalenik
e2eb8716e5 LibWeb: Fix incorrect margin collapsing behavior [BFC]
This change removes premature reset of
`block_container_y_position_update_callback`. Also makes callback
private in `BlockMarginState`, because resetting it independently of
currently accumulated margins is incorrect.

Lots of test expectations are updated, but there is no visual
difference.

Fixes https://github.com/LadybirdBrowser/ladybird/issues/6074
2025-09-08 14:09:06 +02:00
Jelle Raaijmakers
c8d24d4966 LibWeb: Correctly position absolute inline boxes in last line
If we were calculating the static position for an absolutely positioned
inline box that resides in the last line of its containing block, we
would not have yet provided the fragments in that line with their
final positions. Additionally, we would always move the box beneath the
fragment, which was incorrect.

Fixes #5867.
2025-08-28 13:45:37 +02:00
Jelle Raaijmakers
f4b04beccd LibWeb: Show inline positioning in layout tree dump
Inline nodes in our layout tree have a position, so let's show it. By
centralizing the logic for this, block nodes now lose their redundant
'content-size' dump info which is already part of the box model dump.
2025-08-27 11:53:45 +01:00
Jelle Raaijmakers
e173b00db6 LibWeb: Always show box model info when dumping layout tree
This is useful information. Let's not hide it.
2025-08-27 11:53:45 +01:00
Andreas Kling
0d2800e411 LibWeb: Don't relocate fragments across atomic inline boundary
All fragments inside an atomic inline box should stay within that box,
otherwise we'll screw up the paint order and paint them behind things
that they're supposed to be on top of.

This fixes an issue with inline-block content not appearing on sites
like Google Docs and Reddit, among others.
2025-08-24 21:00:08 +02:00
Jelle Raaijmakers
9e29d0c040 LibWeb: Make button layout wrappers inherit styles correctly
There are some nuances to creating these wrappers, such as manually
propagating certain text styles that are not inherited by default. We
already have the logic for this in
`NodeWithStyle::create_anonymous_wrapper()`, so reuse that method in our
implementation of the button layout.

Fixes applying certain text styles (such as `text-decoration`) to the
text of a `<button>`.
2025-08-19 11:12:23 +02:00
Jelle Raaijmakers
f530ea5f7e Tests: Rename button test to something sensible
This had nothing to do with table cells.
2025-08-19 11:12:23 +02:00
Jelle Raaijmakers
ab3e9799d5 LibWeb: Allow negative margins to influence inline offset after float
In 89ba00304c, the box' X position was
capped at 0 to prevent negative X positions to act as if there were
intruding floats on the left side. Instead, we need to check whether the
left side float intrusion we are going to calculate matters at all -
because if there's no matching float box, the intrusion is always going
to be 0 and we don't need to take the box' X position into account.

Fixes the floating publication images on https://lexfridman.com/.
2025-07-28 12:11:56 +02:00
Andreas Kling
8d02f28cc2 LibWeb: Don't treat non-replaced sizes as 0 for min-content contrib
This behavior is part of the cyclic percentage contribution logic from
CSS-SIZING-3 which explicitly only applies to non-replaced boxes.

This fixes an issue on Discord where buttons in the settings UI were
cropped to a narrower width than intended.

Fixes #3572
2025-07-23 19:52:59 +02:00
Jelle Raaijmakers
115e5f42af LibWeb: Improve graphical list item marker positioning
While 788d5368a7 took care of better text
marker positioning, this improves graphical marker positioning instead.

By looking at how Firefox and Chrome render markers, it's clear that
there are three parts to positioning a graphical marker:

  * The containing space that the marker resides in;
  * The marker dimensions;
  * The distance between the marker and the start of the list item.

The space that the marker can be contained in, is the area to the left
of the list item with a height of the marker's line-height. The marker
dimensions are relative to the marker's font's pixel size: most of them
are a square at 35% of the font size, but the disclosure markers are
sized at 50% instead. Finally, the marker distance is always gauged at
50% of the font size.

So for example, a list item with `list-style-type: disc` and `font-size:
20px`, has 10px between its start and the right side of the marker, and
the marker's dimensions are 7x7.

The percentages I've chosen closely resemble how Firefox lays out its
list item markers.
2025-07-17 09:35:09 +01:00
Jelle Raaijmakers
788d5368a7 LibWeb: Improve list item marker positioning and alpha/roman text
This commit is a three-parter that is hard to separate without breaking
marker rendering:

  1. Any marker style that results in a string, except for a literal
     string (e.g. `list-style-type: "@"`), should get the string ". "
     appended. We forgot to do this for the alpha and roman types.

  2. Instead of using the "pixel size rounded up" from a font and adding
     an arbitrary 1 to that, we now use the exact pixel size for as long
     as possible to improve our vertical positioning of markers.

  3. Instead of always adding a "default marker width" to the marker
     content width, we now only do this if we did not have text metrics
     available (i.e. the marker style is not a text type). This greatly
     improves horizontal positioning of text markers.
2025-07-15 19:05:36 +01:00
Andreas Kling
11fa5fdd47 LibWeb: Honor box-sizing for block-level replaced element widths
Before this change, we were always behaving as if box-sizing were
content-box for block-level replaced element widths.

This fixes the squishy logo on https://videolan.org/
2025-07-14 11:16:13 +02:00
Jelle Raaijmakers
2998049fe9 LibWeb: Implement the unreachable scrollable overflow
Whenever we end up with a scrollable overflow rect that goes beyond
either of its axes (i.e. the rect has a negative X or Y position
relative to its parent's absolute padding box position), we need to clip
that rect to prevent going into the "unreachable scrollable overflow".

This fixes the horizontal scrolling on https://ladybird.org (gets more
pronounced if you make the window very narrow).
2025-07-11 08:23:46 +02:00
Andreas Kling
dab1fd265d test-web: Dump stacking context tree in layout test output
This will allow us to test (and catch regressions in) stacking context
tree construction and updates, etc.
2025-07-09 14:36:08 +02:00
Aliaksandr Kalenik
9e232a70c3 LibWeb: Allow descendant boxes to contribute in overflow rect of parent
...with inline children. This fixes an issue when we ignore abspos boxes
contained by PaintableWithLines while calculating overflow rect size.

Lots of layout tests are affected, because now PaintableWithLines has
overflow rect.

`Text/input/DOM/Element-set-scroll-left.html` is also affected and now
matches other browsers.
2025-07-06 17:10:18 +02:00
Jelle Raaijmakers
c03210e858 LibWeb: Dump PaintableBox dimensions for inline layout nodes
We were only dumping a PaintableBox' dimensions if its layout node was a
Layout::Box as well, causing us to not dump the dimensions of paintables
for inline nodes in the paintable tree.
2025-07-03 22:16:48 +02:00
Psychpsyo
c0e44820e9 Tests: Add doctypes to remaining test cases 2025-06-21 14:09:47 +02:00
Jelle Raaijmakers
30efcd0d00 LibWeb: Prevent time-traveling leading inline metrics
In `InlineLevelIterator`, whenever we call `skip_to_next()` and enter a
node with box model metrics, we could potentially accumulate leading and
trailing metrics. This lead to a weird situation where an element with
`display: inline-block` could adopt the leading metrics of an inline
element that follows it, since we perform the call to
`add_extra_box_model_metrics_to_item()` too late.

Move `skip_to_next()` down so it no longer interferes with the `Item`
we're creating.

The test expectation for
`atomic-inline-with-percentage-vertical-align.html` is updated, although
neither the old nor new results are 100% accurate since either box jumps
one pixel to the right.
2025-06-20 19:59:32 +02:00
Ruben
3263b629c6 LibWeb: Correct the calculation for right float intrusion
We used to subtract the maximum right margin of any containing box,
but we want to subtract the entire right margin instead. This yielded
incorrect intrusions for right floats not placed in the root.
2025-05-27 17:31:47 +02:00
Aziz Berkay Yesilyurt
6711438e0d LibWeb: Ensure anonymous wrappers inherit inline-block display
When restructuring a block node inside an inline parent, if the
nearest block ancestor is `display: inline-block`, ensure that
the generated anonymous wrappers also have `display: inline-block`.
This fixes layout issues with block elements nested
inside inline-block elements.
2025-05-23 11:16:06 +02:00
Ruben
09f5ce42f6 LibWeb: Ensure floats are correctly placed under the preceding float
Previously floats would be placed next to the highest float that
fitted the new float on its line. However, this violates the rule
that floats should be placed under the preceding float if it does
not fit next to it.
2025-05-23 10:58:15 +02:00
Jelle Raaijmakers
170e599bd9 LibWeb: Always set the Y offset of the current block in BFC
Originally part of a fix in 15103d172c, it
appears that this is no longer necessary and received a better fix in a
more recent commit. Resolves a visual regression with the ACID3 test.
2025-05-16 20:33:27 +02:00
Jelle Raaijmakers
123abe0e79 LibWeb: Overlap float space and left margins for all shared ancestors
We would only correct for the left margin of the containing block of the
child box that we are laying out, but we actually need to correct for
all left margins up until the containing block that contains both the
float and the child box.

Fixes #4639.
2025-05-09 14:13:02 +02:00
Jelle Raaijmakers
71665fa504 LibWeb: Scale font size by 1.15 for line-height: normal
Browsers such as Chrome and Firefox apply an arbitrary scale to the
current font size if `normal` is used for `line-height`. Firefox uses
1.2 while Chrome uses 1.15. Let's go with the latter for now, it's
relatively easy to change if we ever want to go back on that decision.

This also requires updating the expectations for a lot of layout tests.
The upside of this is that it's a bit easier to compare our layout
results to other browsers', especially Chrome.
2025-05-05 13:15:56 +02:00
Jelle Raaijmakers
6f800caeaa LibWeb: Don't mess up the order of nodes when wrapping inline contents
Whenever we introduce a block element in a container that at that point
has only had inline children, we create an anonymous wrapper for all the
inline elements so we can keep the invariant that each container
contains either inline or non-inline children. For some reason, we
ignore all the out-of-flow nodes since they are layed out separately and
it was thought that this shouldn't matter.

However, if we are dealing with inline blocks and floating blocks, the
order of the inline contents _including_ out-of-flow nodes becomes very
important: floating blocks need to take the order of nodes into account
when positioning themselves.

Fix this by simply hoisting the out-of-flow nodes into the anonymous
wrapper as well.

Fixes the order of blocks in #4212. The gap is still not present.
2025-04-24 19:12:50 +02:00
Jelle Raaijmakers
79352ad725 LibWeb: Remove application of vertical float clearance to BFC Y offset
In 15103d172c we applied any remaining vertical float clearance to the
BFC's current Y offset for the next block to layout, because a `<br
style="clear: left">` could introduce clearance that would otherwise be
ignored. However, a `<div>` that floats _and_ clears `right` also
introduces this clearance and it is obvious that this should not push
down any subsequent blocks to layout in the current BFC.

Turns out, we don't need this change anymore. Some other later change
also fixed the underlying issue, and by getting rid of the original fix
we can now render https://en.wikipedia.org/wiki/SerenityOS correctly
again.

Fixes #4418.
2025-04-23 13:24:58 +02:00
Jelle Raaijmakers
8257788a20 LibWeb: Ignore negative margins for margin box rect
Negative margins are processed through the `offset` in layout state, and
should not contribute to the margin box' rect's size or position.

Fixes #4249.
2025-04-11 02:34:28 +01:00
Jelle Raaijmakers
99df80f81e LibWeb: Only apply box offset if the box is not already the ancestor
When determining the content/margin box rects within their ancestor's
coordinate space, we were returning early if the passed in values
already belonged to the requested ancestor. Unfortunately, we had
already applied the used values' offset to the rect, which is the offset
to the ancestor's ancestor.

This simplifies the logic to always apply the rect offset after checking
if we've reached the ancestor. Fixes determining float intrusions inside
block elements with `margin: auto` set.

Fixes #4083.
2025-04-04 15:35:26 +02:00
Jelle Raaijmakers
6d911a6baa LibWeb: Resolve block height correctly after line break with clear: ..
If a block with inline children ends with a line break clearing any
floats, we not only need to take the introduced clearance into account
for the next line box, but the containing block needs to set the correct
height as well.

Since the spec calls for using the last line box' bottom as the resolved
height (if treated as auto), we now correctly apply the clearance to the
previous line box' bottom coordinate.

Fixes #4058.
2025-04-01 16:02:53 +02:00
Jelle Raaijmakers
45905d6f7b LibWeb: Use relative coordinates for available_space_for_line()
We were accidentally providing it with absolute Y-coordinates, messing
up stacked floating boxes that would otherwise intrude on each other.

Fixes #4160.
2025-04-01 15:26:12 +02:00
Jelle Raaijmakers
04ba375cc7 LibWeb: Only set line builder's block offset if clearance increases it
This prevents text from overlapping with each other if a `<br>` with
`clear: ..` is used in between.
2025-03-31 08:11:10 +01:00
Aliaksandr Kalenik
732a5cdc12 LibWeb: Respect min/max-content available size in auto width calculation
...for block level boxes. Otherwise we end up resolving auto width as
zero during intrinsic layout, which leads to incorrectly applied
max-width constraint.

Fixes https://github.com/LadybirdBrowser/ladybird/issues/4123
2025-03-28 02:13:41 +00:00
Aliaksandr Kalenik
fc45121b70 LibWeb: Size box as normal if it has aspect ratio but auto sizes
Otherwise we apply `calculate_stretch_fit_width()` instead of
calculating width based on the content inside the box, like other
browsers do.
2025-03-27 23:13:57 +00:00
Oliver Medhurst
b272bb3373 LibWeb: Fix ::marker placement in tree
Fixes ::marker (pseudo-element) placement in the tree for list-items.

Previously:
- ::before
- content
- ::marker
- ::after

Now (matches Firefox, afaik per-spec):
- ::marker
- ::before
- content
- ::after
2025-03-27 23:10:09 +00:00
Jelle Raaijmakers
15103d172c LibWeb: Apply remaining vertical float clearance to next block level box
Whenever we generate line boxes, we might end up with a residual
vertical float clearance by way of having a `<br>` with `clear: ..` set.
Set the Y offset of the next block level box to place by this vertical
clearance.

Relates to #4058.
2025-03-27 17:21:56 +00:00