Commit Graph

927 Commits

Author SHA1 Message Date
Aliaksandr Kalenik
cd6672eee0 LibWeb: Fix min-content collapsing to 0 with min-width: 0 descendant
During intrinsic sizing, compute_width() ran on block descendants with
an intrinsic-sizing available space. For a non-FC-establishing block
with auto width, used_width stayed auto, and the min-width clamp then
compared AvailableSize::min-content against min-width via operator<,
which always returns true when the left side is min-content. The clamp
fired with min-width: 0 and set content_width to 0 permanently.

Skip the min-width clamp when used_width is still auto, mirroring the
max-width clamp a few lines above which already no-ops via
to_px_or_zero. The real width is then set by the IntrinsicSizing branch
in layout_block_level_children.
2026-04-24 19:08:28 +02:00
Shannon Booth
b0acc18f9b LibWeb: Clamp limited grid item contributions by fixed max tracks
When resolving grid track sizes, limited min/max-content contributions
should be capped by fixed max track sizing functions, including the
argument to fit-content(). We were instead falling back to the grid
container maximum size, which allowed a grid item with overflowing
contents in a fit-content(0) row to inflate the intrinsic block size of
a nested grid.

That bogus intrinsic height could then be used for the grid's second row
sizing pass, causing unrelated flexible rows to absorb the extra space.
2026-04-23 09:23:02 +01:00
Aliaksandr Kalenik
0bfe4677ae LibWeb: Verify whitespace font has the glyph in font_for_space()
When inline layout emits a whitespace chunk, it previously selected the
surrounding text's font without checking whether that font actually
contains a glyph for the whitespace codepoint. On pages that use
`@font-face` rules sharded by `unicode-range` (e.g. a Roboto webfont
split across one file for Cyrillic letters and another for basic Latin),
the shard covering the letters is picked for an adjacent space even
though the space codepoint lives in a different shard. HarfBuzz then
shapes the space with a font that has no glyph for it and emits
`.notdef`, rendering spaces as tofu boxes.

Check `contains_glyph(space_code_point)` on each candidate in
`font_for_space()` and fall through to
`FontCascadeList::font_for_code_point()` for the whitespace codepoint
when no surrounding font has the glyph.

Fixes whitespace rendering on web.telegram.org/a.
2026-04-22 20:27:41 +02:00
Tim Ledbetter
d8825c89af LibWeb: Use caption content width for inline layout in table captions
Previously, `run_caption_layout()` passed the table's border-box width
as the available space to the caption's formatting context. The BFC then
used this width directly for inline line breaking, causing text to
overflow the caption's content box by the size of the caption's own
border and padding.
2026-04-22 15:35:07 +01:00
Zaggy1024
04cc5bced9 LibWeb: Update video elements' natural dimensions during playback
This tightens the implementation of video element sizing to the spec by
implementing two spec concepts:
- The media resource's natural width and height, and
- The video element's natural width and height.
The element's natural dimensions change based on the representation,
which has many inputs, so update checks are triggered from many
locations.

The resize event is fired when the media resource's natural dimensions
change, and the layout is invalidated if the element's natural
dimensions change.

Tests for a few important resize triggers have been added.
2026-04-21 19:11:24 -05:00
Tim Ledbetter
294a0efe2a LibWeb: Mark ellipsized fragments as hidden rather than removing them
Previously, when applying `text-overflow: ellipsis`, line box fragments
after the  ellipsis point were removed from the line box. This
invalidated fragment indices stored in `containing_line_box_fragment`,
causing an out-of-bounds access in `LayoutState::commit()` when
resolving atomic inline positions.

Instead of removing fragments, mark them as hidden. This preserves
index stability while preventing hidden fragments from being displayed.
2026-04-17 08:03:26 +02:00
Shannon Booth
4a460b1dda LibWeb: Apply calculated vertical-align offsets in LineBuilder
Fixes the alignment of the arrow for "Get in touch" on canonical.com
2026-04-15 21:49:40 +02:00
Callum Law
64ccb9a015 LibWeb: Make @counter-style tree-scoped
This was a pretty straightforward change of storing registered counter
styles on the relevant `StyleScope`s and resolving by following the
process to dereference a global tree-scoped name, the only things of
note are:
 - We only define predefined counter styles (e.g. decimal) on the
   document's scope (since otherwise overrides in outer scopes would
   themselves be overriden).
 - When registering counter styles we don't have the full list of
   extendable styles so we defer fallback to "decimal" for undefined
   styles until `CounterStyle::from_counter_style_definition`.
2026-04-15 11:07:38 +01:00
Callum Law
fedd1f8d55 LibWeb: Add method to get style scope from layout node 2026-04-15 11:07:38 +01:00
Callum Law
281028944e LibWeb: Store GridTrackSize::m_value as a StyleValue
This simplifies handling (particularly around absolutization and
interpolation) and allows us to support calculated flex values (which
will come in a later commit).
2026-04-09 21:41:49 +01:00
R-Goc
094e4bacf8 LibWeb: Deduplicate gap_to_px
This commit creates one common gap_to_px function. This resolves problem
in the unity build.
2026-04-09 08:33:40 -04:00
R-Goc
d1cd391017 LibWeb: Deduplicate inline_axis functions
This commit deduplicates inline_axis_is_reversed and
block_axis_is_reversed.
2026-04-09 08:33:40 -04:00
Shannon Booth
af03e07f90 LibWeb: Clamp replaced element min/max sizes in the content box space
The replaced element sizing code was comparing tentative used sizes
min-width/min-height and max-width/max-height. For box-sizing:
border-box, that mixes content-box and border-box measurements, which
can clamp replaced-like elements incorrectly.

This could make, for example search/text inputs with explicit height
and padding render too short.

Resolve min/max constraints with calculate_inner_width() and
calculate_inner_height() before clamping so the comparison uses
the same inner sizing space as the tentative replaced size.

Fixes the sizing of the search bar on:

https://tv.apple.com/se
2026-04-06 12:35:12 +02:00
Andreas Kling
1bfcf49ffe LibWeb: Honor reversed axes for flex overflow
Scrollable overflow still assumed a top-left scroll origin and only
added trailing padding on the physical bottom edge. That broke
scrollWidth and scrollHeight for flex containers whose main or cross
axis was reversed by writing-mode, direction, flex-direction, or
wrap-reverse.

Teach flex layout to place wrapped lines using the computed cross-axis
direction and to measure scrollable overflow from the container's
actual scroll origin so reachable reversed overflow is preserved, the
unreachable side is clipped, and end padding is added on the correct
physical edge.

Keep per-item cross-axis placement using the existing behavior.
Applying full cross-axis reversal there regressed baseline alignment
tests, and zero-sized boxes exactly at the scroll origin must still
contribute descendant overflow, so the unreachable-overflow checks
need strict comparisons.

This makes negative-overflow-002 and negative-overflow-003 pass and
improves negative-overflow, align-content-wrap-003, and
overflow-with-padding.
2026-04-05 10:42:32 +02:00
Andreas Kling
9526dfbef3 LibWeb: Use fragment extents for orthogonal inline widths
Inline formatting contexts in vertical writing modes were measuring
intrinsic width from the line box width. That width still tracks the
line-height-sized horizontal span, so shrink-to-fit abspos sizing could
stay at 50px even when the text fragments only covered 25px.

Measure the physical horizontal extent from the line box fragments
instead, including the float-aware block formatting context path. This
makes orthogonal inline content report the correct intrinsic width.
2026-04-05 00:03:22 +02:00
Andreas Kling
9450fcb2b0 LibWeb: Fix grid abspos descendant placement
Use the absolutely positioned box's own grid placement to resolve the
grid-area containing block rectangle instead of inheriting the nearest
in-flow grid item's area.

Keep the grid-specific static-position handling for axes with both
insets auto, but resolve mixed auto and explicit grid placement axes
against the augmented grid for direct children as well as descendants
inside grid items.

This fixes the imported abspos and alignment WPTs for values like
grid-row: 1 and grid-column: auto / 1 while keeping the reduced
descendant regressions passing.
2026-04-04 14:35:50 +02:00
Andreas Kling
6994463b1c LibWeb: Rerun row sizing for auto-height grids
Percentage row tracks in auto-height grids only participated in our
intrinsic sizing pass. We computed the grid container's intrinsic
height from that pass, but never reran row track sizing with the
now-known height, so percentage rows stayed content-sized and the
imported WPTs had recorded failures.

Reset the mutable track sizing state before rerunning the row pass,
rebuild row gap tracks against the resolved container height, and keep
the automatic content height from the intrinsic pass so parent layout
still resolves auto height correctly. Rebaseline the percentage-row
imports and the grid track parsing import that now improve with the
corrected percentage resolution.
2026-04-04 14:35:50 +02:00
Andreas Kling
5de6382d11 LibWeb: Fix flex abspos static positions on reversed cross axes
The static-position rectangle for absolutely positioned flex children
still mapped cross-axis flex-start and flex-end directly to physical
start and end. That broke cases where the flex cross axis is reversed
by writing mode, direction, or wrap-reverse.

Teach the flex formatting context to derive the cross-axis direction
from its logical axes and wrap mode, and use that when resolving
cross-axis alignment for abspos static positioning. This clears the
newly imported position-absolute-013 test and improves a broader set
of existing flex abspos WPT baselines.
2026-04-03 14:29:44 +02:00
Andreas Kling
0e41072db7 LibWeb: Fix remaining flex percentage-height WPTs
Handle flex main and cross axes in logical terms so percentage
height resolution keeps working in vertical writing modes and
other orthogonal cases. Also let resolvable percentage max
cross sizes clamp stretched flex items.

The same change improves a broader set of imported css-flexbox
tests than the original percentage-heights cases. Update the
affected expectations in the same change so the resulting
behavior stays documented and the suite remains green.
2026-04-03 14:29:44 +02:00
Andreas Kling
99dd13f7f7 LibWeb: Resolve grid item percentages against definite areas
Final grid item alignment runs after the grid area size has already
been resolved from the spanned tracks. Reusing
should_treat_height/width_as_auto() there misclassified percentage
preferred sizes as auto based on the outer grid container's own
definiteness, which broke fixed-track cases such as height:50% in a
100px row.

Restore the final alignment fast path to only special-case literal
auto sizes, so percentage preferred sizes resolve through
calculate_inner_width/height() against the computed grid area.

Add a text test that covers start/stretch behavior in both axes and
across single-track and spanning definite grid areas.
2026-04-03 14:29:44 +02:00
Andreas Kling
d75934edf5 LibWeb: Don't mark auto height as definite for abspos non-BFC elements
When an absolutely positioned non-BFC element (flex, grid, etc.) has
auto height, we pre-compute its height from content before running
inside layout. Previously, this content-derived height was marked as
"definite", which incorrectly allowed descendants to resolve percentage
heights against it. Per CSS 2.1 section 10.5, percentage heights should
only resolve when the containing block's height is specified explicitly.

The fix is to simply not set has_definite_height when the CSS height is
auto. This naturally prevents percentage resolution through all existing
paths (set_node, should_treat_height_as_auto, calculate_inner_height)
without needing any new flags or per-site checks.

Two additional fixes enable this:

- In flex line cross-size clamping, remove the contains_percentage()
  guard that prevented percentage min/max-height from resolving. These
  percentages resolve correctly via calculate_inner_height's containing
  block lookup, since the abspos element's containing block always has
  a definite height.

- In grid item alignment, check should_treat_height/width_as_auto for
  percentage preferred sizes, so they're treated as auto when the grid
  container's height is indefinite (CSS Grid section 6.6).
2026-04-03 14:29:44 +02:00
Jelle Raaijmakers
a3df1b42c9 LibWeb: Consider Size::is_none() in FFC as an unresolvable size
Prevents the WPT test
`css/css-anchor-position/anchor-size-in-flex-basis-crash.html` from
crashing.
2026-04-01 19:41:46 +01:00
Jelle Raaijmakers
4293c841f3 LibWeb: Implement CSS anchor positioning
When `position-anchor` is used, resolve the insets for that absolutely
positioned box.

Co-authored-by: Rob Ryan <rob@affclicks.com>
2026-04-01 19:41:46 +01:00
Jelle Raaijmakers
6068d94752 LibWeb: Update/remove unused includes
No functional changes.
2026-04-01 19:41:46 +01:00
Shannon Booth
c6809eb53d LibWeb/Layout: Use is_empty() when fragment text has nothing to trim
Treat an empty fragment text view as equivalent to is_null, as we
only need to determine that there is no text to trim.

This does not change trimming behavior as an empty text view reaches
the same result as the previous null only check, since the trimming loop
would not run.
2026-03-31 13:48:50 +01:00
Psychpsyo
269aaeea21 LibWeb: Move standalone SVG document layout handling out of BFC
I'm simplifying the BFC code in preparation for multicol layout.
2026-03-31 10:49:15 +02:00
Psychpsyo
ca99e65d83 LibWeb: Establish formatting context due to container-type 2026-03-30 15:43:39 +01:00
Callum Law
fe5d6471f0 LibWeb: Store GridTrackPlacement sub-values as StyleValues
Gets us one step closer to removing the `FooOrCalculated` classes
2026-03-30 14:05:10 +01:00
Andreas Kling
0e438c01d8 LibWeb: Compute CAPMIN from caption outer widths
Fixing float-related intrinsic width bookkeeping exposed that table
captions only contributed their raw min-content width to CAPMIN.
That ignored caption padding, borders, margins, and definite widths,
so captions could be laid out too narrow and wrap content that should
stay beside floats.

Compute each caption's min-content contribution as an outer width and
clamp it against the preferred outer width when the caption has a
definite width. Add a test for a fixed-width caption with a float and
rebaseline the existing padded-caption layout test.
2026-03-29 12:05:25 +02:00
Andreas Kling
8c6786d70b LibWeb: Ignore non-overlapping floats in intrinsic widths
BlockFormattingContext::greatest_child_width() tried to decide whether
a line overlapped a float by comparing a line-relative baseline
against float edges stored in containing-block coordinates. That also
used `||`, so later lines could keep inheriting float width even after
the float had ended.

Measure overlap using each line box's top and bottom edges in the same
coordinate space as the float margins instead. This fixes
shrink-to-fit and max-content sizing for inline children after short
floats.

Add left and right tests where a later forced-break line is longer
than the line next to the float.
2026-03-29 12:05:25 +02:00
Andreas Kling
2614790a93 LibWeb: Lower floats past opposite-side blockers
CSS2 requires moving a float downward until it fits beside existing
floats. We already handled same-side blockers, but opposite-side
blockers could still overlap, such as matching left and right 100%
floats.

Recompute same-side bookkeeping after lowering and measure
opposite-side intrusion in the current containing block's coordinate
space. Also account for the full inline space already occupied on the
current side after same-side stacking, so a float shifted next to an
earlier same-side float does not still overlap an opposite-side float.

Add tests covering inline and nested lowering, stale same-side line
state, staggered opposite-side stacks, and the left/right/left 600px
overlap case. Update the stacked clear-both expectation to match the
corrected placement.
2026-03-29 12:05:25 +02:00
Andreas Kling
d53ceee7c3 LibWeb: Fix clear property not clearing past all stacked floats
When multiple floats are stacked vertically (e.g. multiple width:100%
left floats), the float placement code clears current_boxes as each
new "float line" begins. This meant that clear_floating_boxes() only
saw the last stacked float, not all preceding ones.

Fix this by iterating all_boxes instead of current_boxes when computing
clearance. Also move set_content_y() before the FloatingBox creation so
the root-space rect stored in all_boxes has the correct Y position.
2026-03-29 12:05:25 +02:00
Jelle Raaijmakers
3d2571b46e LibWeb: Implement text-decoration-skip-ink painting
Use Skia's SkTextBlob::getIntercepts() to find where glyph outlines
cross the underline/overline band, then split the decoration line into
segments with gaps around those intersections.
2026-03-26 12:15:36 +00:00
Callum Law
b86377b9dc LibWeb: Clamp CSS <integer> value to i32 at parse time
This matches the behavior of other browsers. Previously we implemented
this at used-value time for z-index specifically.
2026-03-26 12:30:01 +01:00
Jelle Raaijmakers
c07e92fc7e LibWeb: Apply text-overflow ellipsis as line box post-processing
The previous implementation checked text-overflow and overflow-x
on the text node's direct parent during inline item iteration.
Since these are non-inherited properties, ellipsis only worked
for text directly inside the block container, not when wrapped
in inline elements like <span> or <a>.

Move ellipsis truncation to a post-processing step after line
boxes are constructed, checking the containing block instead.
2026-03-26 00:19:50 +00:00
Tim Ledbetter
d69d74e7ce LibWeb: Always shift line boxes below floats when content does not fit
Previously, this wasn't done when placing the first inline content in a
block, which caused long unbreakable words to overlap with floats
instead of being moved below them.
2026-03-25 21:28:57 +01:00
Luke Wilde
df32da5e86 LibWeb: Make every HTMLElement potentially form-associated
This can be the case for form-associated custom elements, where any
HTML element can be form-associated.
2026-03-25 13:18:15 +00:00
Callum Law
f347be8206 LibWeb: Store underlying RatioStyleValue values as StyleValues
Previously we stored a fully formed `Ratio` - this restricted us from
supporting calculated values which will be implemented in a later commit
2026-03-24 14:00:01 +00:00
Callum Law
b07ef9435e LibWeb: Remove fallback behavior from color_or_fallback
All of the properties using this method only accept color values so the
fallback behavior wasn't required
2026-03-24 13:56:01 +01:00
Callum Law
e2f2f813b6 LibWeb: Handle accent-color auto value explicitly
`accent-color` is the only user of the fallback functionality of
`color_or_fallback`, by handling this explicitly we can remove that
fallback functionality in a later commit.

Also includes a couple of improvements for `accent-color` specifically:
 - We don't set it in `ComputedValues` twice.
 - `ComputedProperties::accent_color` returns a non-optional value
   (since we always have one)
 - `ComputedProperties::accent_color` takes a `ColorResolutionContext`
   instead of generating one itself from a `LayoutNode`, this will allow
   us to reuse shared resolution contexts in the future
2026-03-24 13:56:01 +01:00
Jelle Raaijmakers
277512ce01 LibWeb: Implement CSS flexbox baseline aligned items 2026-03-24 05:13:49 +01:00
Andreas Kling
8836f28267 LibWeb: Don't walk past anonymous table cells for percentage resolution
When resolving percentage heights/widths against the containing block,
we walk past anonymous boxes to find the relevant ancestor. However,
anonymous table cells are proper containing blocks with their own
sizing semantics. Walking past them caused us to reach the viewport
and incorrectly resolve percentages against the viewport size.

Fix this in all affected places in FormattingContext:
- should_treat_height_as_auto()
- calculate_inner_height()
- should_treat_max_height_as_none()
- should_treat_max_width_as_none()
- compute_inset() percentage-as-auto check
- solve_replaced_size_constraint()
- compute_height_for_replaced_element()
2026-03-23 16:40:38 +01:00
Psychpsyo
98e1774eff LibWeb: Avoid division by zero in multicol calculations the right way
This got changed a while back to avoid a divide-by-0. That change was
not following the spec at all and would've resulted in incorrect values
being used, so this is the spec-compliant way of resolving that.

I originally didn't add spec comments to this algorithm since it felt
weird, but I hope these will demonstrate that this should not be changed
on a whim until we can import the relevant WPT tests for this behavior.
2026-03-23 12:09:29 +01:00
Psychpsyo
ce36e1f7df LibWeb: Do not allow margins collapsing through IFCs
Co-authored-by: ChaseKnowlden <haroldknowlden@gmail.com>
2026-03-23 11:34:33 +01:00
Andreas Kling
c5d7898b41 LibWeb: Resolve grid item height via aspect-ratio for non-stretch items
When a grid item has a preferred aspect-ratio, a definite width, and
non-stretch alignment, resolve its height through the aspect-ratio
instead of using fit-content sizing.

The fit-content height calculation was incorrectly using the available
width (grid area width) to compute the aspect-ratio-based height,
rather than using the item's actual resolved width. This caused items
like a 50px wide div with aspect-ratio: 1/1 to get a height of 784px
(the grid area width) instead of 50px.
2026-03-21 23:16:32 -05:00
Andreas Kling
cf3d9c1bae LibWeb: Fix static position of inline-level abspos elements in BFC
When an absolutely positioned element's display was inline-level
before blockification, and its preceding sibling is an anonymous
wrapper with inline children, the element's static position should
be at the same vertical position as that wrapper. This matches where
the element would have been placed if it were still inline.

Previously, the static position was always set to the current block
offset (after the preceding block), which placed the element below
the inline content instead of beside it. This caused absolutely
positioned ::after pseudo-elements (like dropdown chevrons) to appear
on the line below the text instead of at the same vertical position.
2026-03-21 21:42:44 -05:00
Andreas Kling
3b1bbd7360 LibWeb: Respect box-sizing when computing grid item minimum contribution
When computing the minimum contribution of a grid item with a definite
minimum size (e.g. min-height: 3rem), we must convert the CSS value to
content-box size before adding margin-box sizes. With box-sizing:
border-box, the CSS value already includes padding and border, so
passing it directly to add_margin_box_sizes() double-counted them.

Use calculate_inner_width/calculate_inner_height to properly convert
the minimum size to content-box dimensions, consistent with how
preferred and maximum sizes are already handled in the grid track
sizing algorithm.
2026-03-21 21:42:44 -05:00
Andreas Kling
b07a19e35f LibWeb: Account for letter-spacing when trimming trailing whitespace
When trimming trailing whitespace from line boxes, we were only
subtracting the glyph width of each trimmed character. However, during
text shaping, letter-spacing is added to each glyph's advance width.
We must subtract the full advance (glyph width + letter-spacing) to
get an accurate line width after trimming.

This also fixes a pre-existing precision bug where the glyph width
(a float) was truncated to int, leaving fractional phantom width in
the line box.

Together, these issues caused text with letter-spacing and trailing
HTML whitespace to have an underestimated intrinsic width, leading to
unexpected text wrapping in flex layouts.
2026-03-21 21:42:44 -05:00
Andreas Kling
b102a68746 LibWeb: Precompute contained boxes map for measure_scrollable_overflow
Build a HashMap from each containing block to the boxes it contains
before measuring scrollable overflow. This allows
measure_scrollable_overflow() to iterate only the relevant boxes
directly, rather than walking the entire layout subtree and filtering
by containing_block() at each node.
2026-03-21 18:20:09 -05:00
Tim Ledbetter
cd9a76902b LibWeb: Avoid applying viewbox transform multiple times for nested SVGs
The parent's viewBox transform includes both scaling and a centering
offset. For nested <svg> elements, this transform was applied once to
position the nested SVG's box, then again to position children inside
it, double-counting the centering offset.

We now apply the parent's transform to the nested SVG box upfront to
ensure the transform is only applied once.
2026-03-20 15:59:44 -05:00