Commit graph

561 commits

Author SHA1 Message Date
Tim Ledbetter
0ec95a606f LibWeb: Use computed display value for ::first-letter layout wrappers
Previously, `::first-letter` elements were always wrapped in an inline
box, meaning these elements were not correctly blockified when floated.
2026-06-22 09:43:07 +02:00
Tim Ledbetter
360bfd66f6 LibWeb: Apply the font-variant-emoji property to font selection 2026-06-20 23:51:46 +02:00
Jelle Raaijmakers
d819152b4e LibGfx+LibWeb: Preserve text color for selections
Selecting text without custom ::selection styling changed the
foreground color of the selected content. This was especially visible
for links, where the text changed color but the underline did not.

The default selection style supplied both a selection background and a
foreground color from the palette or HighlightText system color. That
made ordinary selections behave as if the page had explicitly styled
::selection color.

Only provide a default selection background, so selected content keeps
its own foreground color unless CSS overrides it. Remove the now-unused
SelectionText palette role.
2026-06-18 22:43:26 +02:00
Tim Ledbetter
91d3d135eb LibWeb: Paint emoji glyph text-shadows in the shadow color 2026-06-18 22:42:29 +02:00
Tim Ledbetter
340ef361d8 LibWeb: Support anchor() in calc() trees
Previously, `anchor()`  was only resolved when it appeared bare in an
inset property. We now allow it to appear anywhere inside a `calc()`
tree.
2026-06-18 17:36:56 +01:00
Callum Law
d98c5d1b03 LibWeb: Inline image scaling mode computation
This allows us to remove the `frame_rect` accessor.

This also fixes a bug where we computed the scaling mode based on the
clipping rect rather than the draw rect for `ImagePaintable`
2026-06-18 10:44:25 +02:00
Tim Ledbetter
4ecfd0903f LibWeb: Allow transforms to apply to pseudo elements
Previously `is_transformable()` didn't take pseudo elements into
account.
2026-06-16 11:32:02 +02:00
Jelle Raaijmakers
cf3bb8c129 LibWeb: Distinguish first and last baseline sets in box_baseline()
box_baseline() applied CSS2's bottom margin edge rule for non-visible
overflow to every caller, so flex items with hidden overflow were
baseline-aligned by their margin edge instead of their text. CSS Align
scopes that rule to a box's last baseline set, while flex baseline
alignment and table cells use the first set, which always derives from
content. Parameterize box_baseline() on the requested baseline set and
propagate it through the recursive child lookup.
2026-06-16 10:39:02 +02:00
Shannon Booth
96aa4b3ad3 LibWeb: Only apply CSS transforms to transformable elements
Add Node::is_transformable() per the CSS Transforms spec and gate
transform handling on it, so non-replaced inline boxes are no longer
transformed.
2026-06-14 22:12:00 +02:00
Shannon Booth
de90aeb07c Tests/LibWeb: Re-import floats reftests with their mismatch references
Re-import these tests now that the importer supports multiple
references, so their mismatch *-notref.xht files are present instead
of pointing at unimported paths.
2026-06-14 22:12:00 +02:00
Tim Ledbetter
b0c25736f9 LibWeb: Scale image-set() natural size by the selected resolution
The <resolution> of the chosen image-set() option overrides the image's
natural resolution, so the image-set()'s natural dimensions are the
selected image's pixel dimensions divided by that resolution.
2026-06-13 21:27:40 +02:00
Jelle Raaijmakers
de664c4c12 LibWeb: Restore context after culled display list effects
Effect culling can stop a visual context switch after applying
some ancestor contexts. The player left those contexts on the painter
stack even though the target command was skipped.

Restore the painter stack back to the common ancestor when effect
culling aborts the switch.
2026-06-12 04:59:57 +01:00
Jelle Raaijmakers
7390818b5a LibWeb: Apply the z component of transform-origin
compute_transform() resolved transform-origin to a 2D point, so 3D
rotations pivoted around the element's own plane. Build the conjugation
T(0, 0, z) * M * T(0, 0, -z) into the matrix instead; this composes
with the 2D origin at paint time into the full 3D conjugation.
2026-06-11 22:13:24 +02:00
Jelle Raaijmakers
1a5b7f9ea2 LibWeb/CSS: Implement contrast-color() function 2026-06-09 17:23:26 +02:00
Tim Ledbetter
d19fb87441 LibWeb: Clip nested display lists to their destination rect
SVG image content drawn outside the image's viewport escaped the image
box, because `PaintNestedDisplayList` playback translated to the
destination rect without clipping to it. We now clip it during playback.
2026-06-08 21:17:02 +01:00
sideshowbarker
e356fd8188 LibWeb: Paint heavily-tiled non-image backgrounds as a single pattern
Problem: On https://turbovision.in6-addr.net a tiny-background-size +
background-repeat tile over a large painted area crashed WebContent.

Cause: BackgroundPainting was recording one paint command per tile for
any repeating background that’s not a decoded image. A small tile over a
large area could produce literally millions of commands — resulting in a
display list exceeding MAX_MESSAGE_PAYLOAD_SIZE, and tripping an assert.

Fix: When a repeating AbstractImageStyleValue would emit more tiles than
a fixed threshold, record a single tile into a nested display list and,
reusing some existing SVG pattern machinery, fill the coverage rect with
a PatternPaintStyle. We render the tile once, and repeat it with a
tiling shader — so the display list holds a single command regardless of
tile count. Normal backgrounds keep their exact per-tile painting.

Fixes https://github.com/LadybirdBrowser/ladybird/issues/9876
2026-06-06 09:34:09 +01:00
InvalidUsernameException
22866843bf Tests/LibWeb: Remove test expecation links from test expectations
These don't do anything and all look like copy-paste mistakes.
2026-06-04 17:52:59 +02:00
Tim Ledbetter
9de60cd4f9 LibWeb: Apply the text-anchor property to SVG textPath offset 2026-06-04 16:01:56 +02:00
Tim Ledbetter
ad7105b570 LibGfx+LibWeb: Implement SVGTextPathElement.startOffset 2026-06-04 16:01:56 +02:00
Tim Ledbetter
260064deef LibWeb: Fall back to xlink:href when resolving SVGTextPathElement path 2026-06-04 16:01:56 +02:00
InvalidUsernameException
4ae31dc777 LibWeb: Invalidate layout tree of parent when replacing CharacterData
When replacing the data in `CharacterData`, we were only invalidating
text-dependent cache and layout of the layout node(s) corresponding to
that `CharacterData`. This is not sufficient: With `::first-letter`
styling, `TextSliceNode`s are being created for the first letter and the
remainder of the text. So, contrary to regular `TextNode`s, these store
offsets into the text data determined while building the layout tree.
Subsequently these become stale when only invalidating layout, but not
the layout tree. Using such stale offsets with updated text data leads
to crashes in `TextNode::compute_text_for_rendering()` if the text got
shorter and incorrect behavior if it got longer.

To fix this, invalidate the layout tree of the parent of the affected
`TextSliceNode`s, causing a rebuild that creates new slice nodes with
the updated data.

This fixes a crash when typing in the search box on
https://search.brave.com/ where a first-letter style is used to
capitalize the first letter of the search suggestions descriptions. This
crash was a regression from b67d73a661.
2026-06-02 11:09:52 +01:00
Sam Atkins
c306dacf10 Tests: Import container-unit ref tests
Import the container-unit ref tests now that the engine passes them.
2026-06-01 08:27:17 +01:00
Andreas Kling
4cb58762df LibWeb: Apply visual effects in SVG mask subtrees
Build an accumulated visual context tree when recording SVG mask and
clip subtrees. Mask content can contain filters, opacity, and blending,
but the nested display list previously had an empty visual context tree.
Filtered mask contents therefore painted as if those effects did not
exist.

Resolve filters for the nested paintables and attach their accumulated
visual context before recording the mask display list. Add direct and
SVG use based coverage for alpha-inverting filters inside masks.
2026-05-31 15:49:58 +02:00
Andreas Kling
7b159143df LibWeb: Avoid redundant SVG use recloning
Skip the document-complete clone when a resolved SVG use element already
has a current shadow tree. Href processing creates that root for refs.
Replacing it during load can leave stale mask contents.

Still clone at document-complete for forward refs and changed refs.
Add coverage for SVG use in masks, pre-load subtree mutations, and
unchanged instance roots.
2026-05-31 15:49:58 +02:00
Brandon Rothweiler
b845783c01 LibWeb: Use premultiplied alpha when interpolating gradient color stops 2026-05-30 23:50:42 +02:00
Andreas Kling
3d3136f60d LibWeb: Paint replaced backgrounds in SVG foreignObject
Paint the background phase for replaced boxes reached through SVG
foreignObject traversal before foreground content. This lets img
background images render correctly when the containing SVG establishes a
stacking context.

Add a ref test for a paint-contained SVG with a foreignObject-hosted img
that uses a transparent SVG source and a CSS background image.
2026-05-30 21:36:47 +02:00
Andreas Kling
44205cd086 LibWeb: Allow positioned SVG roots in foreignObject
Do not force nested SVG roots back to position: static when they
are in HTML content inside a foreignObject. These SVG elements take
part in CSS box layout, so absolute positioning must be preserved.

Add a ref test that covers a nested masked foreignObject shape like
the Discord server icons, where ignoring the absolute offset clips the
icon four pixels too far inward.
2026-05-30 21:36:47 +02:00
Andreas Kling
114c8a7939 LibWeb: Paint CSS mask image layers with sizing
Store CSS mask image layers in computed values so painting can honor the
coordinated mask-position, mask-size, mask-repeat, mask-origin, and
mask-clip longhands. Paint the CSS mask display list through the same
layer resolution path used by backgrounds, but keep the nested mask
commands local to the mask rect so existing mask-image placement remains
unchanged.

Add ref coverage for a no-repeat mask image sized and positioned at the
bottom of an element, which previously painted as a full-element mask.
2026-05-30 21:36:47 +02:00
Tim Ledbetter
eb9d4f6a20 LibWeb: Apply transform attribute to inner <svg> elements
Previously, transforms were ignored for SVGSVGElements.
2026-05-30 18:32:15 +01:00
Tim Ledbetter
de9fbf1679 LibWeb: Honor explicit first and last color stop positions in gradients
When resolving the used positions of color stops, we were
unconditionally overwriting the first position with 0% and the last
position with 100%. We now only set these default values for color
stops without an explicit position.
2026-05-30 15:44:50 +01:00
Andreas Kling
b21005ea2f Tests: Wait for iframe paint in font face descriptor test
Wait for two animation frames after the iframe document's fonts are
ready before allowing the reftest harness to snapshot the page. The
parent document can otherwise unblock while the iframe content has not
painted yet, making the test compare rendered content against an empty
reference iframe.

This makes font-face-descriptor-relative-length-iframe.html stop flaking
on my machine, and hopefully CI.
2026-05-30 02:13:14 +02:00
Andreas Kling
3746fbf7f4 LibWeb: Use themed text selection colors
Use the page palette for default selection backgrounds instead of a
hardcoded blue color. Tint opaque selection backgrounds before painting.
Selected images stay visible through the overlay.

Use SelectionText for default selected text and HighlightText. Authored
::selection colors still take precedence. On macOS, use a light
translucent color matching other engines.

Update selection and system-color baselines for the themed colors. Add
macOS-specific screenshot baselines for tests whose expectations depend
on system colors.
2026-05-29 13:49:51 +02:00
Tim Ledbetter
7752bc5915 LibWeb: Stretch aspect-ratio flex items only when container is definite
A flex item with a preferred aspect ratio but no natural width or
height and no definite cross size is stretched to fill its container's
main size as a fallback. This was applied unconditionally, but when
the container itself is being measured for an intrinsic size, the
container's main size is indefinite and the size evaluated to zero.
This clobbered the max-content size the flex algorithm had already
computed as the intrinsic contribution.
2026-05-28 20:42:02 +02:00
Tim Ledbetter
8039419775 LibWeb: Account for floats when computing IFC abspos static position
Previously, when no previous sibling has a line box fragment, the
static position for inline-level elements defaulted to (0, 0), ignoring
float intrusion into the line box. We now use
`leftmost_inline_offset_at()` so the hypothetical box is placed on the
float-shortened line.
2026-05-28 10:40:21 +02:00
Tim Ledbetter
b0f926d16b LibWeb: Resolve SVG gradient references within shadow trees
Previously, the `linked_gradient()` lookup only searched the document
for gradient IDs, so gradients inheriting stops via href inside a
shadow DOM would fail to find the correct target and be rendered black.
We now check any containing shadow root first, before checking the
document for linked gradient IDs.
2026-05-28 10:39:13 +02:00
Andreas Kling
412a66ad5e LibWeb: Repaint iframe containers after render unblocking
When an embedded document was render-blocked, the parent display list
recording skipped drawing that iframe's compositor surface. Once the
child document later unblocked, only the child surface was repainted, so
the parent could keep replaying a display list that had no surface draw
command until another invalidation, such as resize, forced a recording.

Invalidate the containing iframe when a document's render-blocking set
becomes empty so the parent display list records the child surface. Add
a deterministic reftest that gates a render-blocking stylesheet on a
test-server signal, forcing the parent to paint the blocked iframe
before unblocking it.
2026-05-24 12:24:50 +02:00
Martin Chrástek
a404efa999 Tests: Add explicit UTF-8 charset declarations to existing tests 2026-05-23 11:57:33 +02:00
Andreas Kling
d98cea80e2 LibWeb: Let IFC publish inline-end static position
Store the static-position insertion point produced by an inline
formatting context and let block layout consume it for originally-inline
absolute boxes that follow an anonymous inline wrapper. This removes the
block formatting context's fragment-walking reconstruction of the same
position.

Keep the calculation in IFC aware of forced breaks, aligned inline
content, trailing inline margins, wrapped lines, RTL inline content, and
vertical writing modes.

Keep regression coverage for the static-position cases covered while
building this model.
2026-05-23 11:01:58 +02:00
Andreas Kling
d05b30a8dc LibWeb: Preserve inline-flex strut descent
When an inline flex fragment raises the line box baseline, include the
parent strut descent in the line box bottom and advance the next line
from that enlarged line box. This keeps following wrapped or broken
lines from overlapping the extra descent.

Add ref and text coverage for inline-flex line height and line
advancement.
2026-05-23 11:01:58 +02:00
Andreas Kling
a18805ceeb LibWeb: Account for transforms when culling effects
Skip the early effect culling optimization when the draw command has
coordinate-changing visual contexts before the target. Without this, a
filter or opacity layer can be rejected using an untransformed bounding
rect even though a later transform moves the pixels into view.

Add a reference test where an opacity layer starts below the viewport,
then a rotation brings it back on screen.
2026-05-22 21:25:21 +02:00
sideshowbarker
c1f38f92fe LibWeb: Keep repaints pending inside visibility:hidden iframes
Problem: An iframe whose content changes while it (or an ancestor)
has visibility:hidden isn’t painted once it becomes visible again.
The stale previous frame stays on screen until an unrelated
repaint (e.g., window resize) happens to occur.

Cause: set_needs_repaint() returns early for any document inside an
iframe with a visibility:hidden ancestor — discarding the request
entirely. The navigable’s needs_repaint flag is never set. Nothing
sets it again when the iframe becomes visible — so the rendering
loop keeps skipping it — and its display list stays stale.

Fix: Stop discarding the request in set_needs_repaint(). Instead,
skip painting hidden navigables in the rendering loop — while
leaving needs_repaint set. Once an ancestor iframe becomes
visible, the still-set flag makes the rendering loop paint the
navigable on the next frame.

Fixes https://github.com/LadybirdBrowser/ladybird/issues/9305
2026-05-22 09:58:10 +01:00
sideshowbarker
e4b05ccde7 LibWeb: Apply the CSS clip property to display:table elements
Problem: An absolutely-positioned element with “clip: rect(...)” stays
visible when “display: table” is also applied to it.

Cause: “display: table” elements generate an anonymous table-wrapper
box — and the “position” property is used on that wrapper rather than on
the table box itself. PaintableBox::get_clip_rect() only applies “clip”
to absolutely-positioned boxes — so it checks the table box, finds it’s
not absolutely positioned, and never applies the clip.

Fix: Transfer the “clip” property from the table box to the table-
wrapper box. That wrapper is the absolutely-positioned box — so
get_clip_rect() applies the clip correctly.

Fixes https://github.com/LadybirdBrowser/ladybird/issues/9349
2026-05-21 17:06:35 +02:00
Callum Law
199e82fc73 LibWeb: Forward non-synthetic pseudo-elt accesses to relevant element
This adds a new class `ElementReferencePseudoElement` which forwards
any accesses to the referenced element for "element-reference" pseudo
elements.

This allows us to, for instance:
 - Access the underlying `ComputedProperties` in getComputedStyle, which
   includes inline style.
 - Store and access `CustomPropertyData`
 - Access the layout node for "resolved value" computation
 - Apply animations from `update_animated_style_if_needed`

We register these with the originating element when calling
`set_associated_shadow_host_pseudo_element`, this requires us to append
the element to the tree beforehand since we need to be able to get the
shadow host.
2026-05-21 14:26:22 +01:00
Tim Ledbetter
b67d73a661 LibWeb: Apply ::first-letter pseudo-element styles
We now apply first letter styles by splitting text with a first-letter
style applied into 2 `TextSliceNode` objects.  The
`DOM::Text` layout  node always points at the non first-letter slice
and the first-letter slice is  reachable via
`TextSliceNode::first_letter_slice()`.

First letter splitting works by `TreeBuilder` walking a block
container's inline descendants to find the first typographic letter
unit per the pattern given in  css-pseudo level 4, which is then
wrapped in an anonymous inline box styled with the `::first-letter`
computed properties.

Consumers that map between DOM offsets and layout geometry
are updated to visit all slices of a `DOM::Text` through
`TextOffsetMapping`.
2026-05-20 12:09:19 +01:00
InvalidUsernameException
46e91c7ba6 LibWeb: Invalidate img-element when loaded from list of available images
When loading an image, there are two success paths: Either the image is
loaded through fetch, or it is part of the list of available images
already and doesn't need to be downloaded anymore.

Only the fetch code path was invalidating style and layout, meaning that
an image loaded from the list of available images was not visible until
something else caused an invalidation.

To fix this, perform the same invalidation in both code paths.

This fixes that most of the images on https://bleepingcomputer.com/ were
not loading until hovered. They are using
[`bLazy.JS`](https://github.com/dinbror/blazy), which loads the files on
a disconnected `img` element and then swaps out `src` or `srcset` on the
actual `img` element once the load has completed.

The `requestAnimationFrame`-dance in the testcase is there to ensure
that the test fails reliably without this change applied.
2026-05-18 12:54:49 +02:00
Shannon Booth
fbc8ab4dcd LibWeb: Paint flex items atomically in tree order
Treat non-positioned flex items like inline-blocks for painting, just
as we already do for grid items. This ensures later flex items cover
earlier items overflowing content instead of allowing earlier text to
paint over later backgrounds.
2026-05-17 10:15:38 +02:00
Aliaksandr Kalenik
d51611b36f LibWeb: Publish dirty canvas content before painting
Canvas frame publishing was tied to CanvasPaintable::paint(), so cached
paint commands could replay DrawExternalContent without updating the
ExternalContentSource first. That made dynamic canvas content depend on
whether the display list was re-recorded for the frame.

Move canvas presentation to the rendering update step after animation
frame callbacks and layout, immediately before navigables paint. The
canvas paintable now only records the external-content draw command,
which keeps display-list replay from skipping the resource update.

Add a ref test that draws red, lets the display list cache, then draws
green on a later animation frame. The final rendering must come from the
updated external content source while reusing the cached draw command.
2026-05-15 15:08:43 +02:00
Tim Ledbetter
4f33ab15e0 LibWeb: Don't apply self-alignment to grid items with auto margins
An auto margin on a grid item absorbs positive free space prior to
alignment via the box alignment properties, disabling the effects
of any self-alignment in that axis. We were applying both
auto-margin absorption and alignment additively, which doubled the
offset and pushed the item outside its grid area when, for example,
`margin-top: auto` was combined with `align-items: center`.
2026-05-14 13:29:40 +02:00
Shannon Booth
f58be8345a LibWeb: Paint inline-block floats during inline-level painting
Avoid recursing into atomic inline-level descendants during the ancestor
float sweep.

CSS 2.2 paints inline-block and inline-table boxes in the inline-level
painting step, as if they created their own stacking contexts. Paint
their internal floats during
BackgroundAndBordersForInlineLevelAndReplaced instead.
2026-05-13 10:08:54 +02:00
Luke Wilde
b279fbc40d LibWeb/Painting: Skip positioned descendants earlier in traversal
paint_descendants already skipped positioned descendants at stack level
zero so they're only painted by paint_internal's pass, but the SVG-root
and grid-item branches returned before reaching the skip, making them
be painted twice.

Move the skip above them to make it apply to all deferred positioned
descendants uniformly.

Fixes https://github.com/LadybirdBrowser/ladybird/issues/9277
2026-05-10 20:31:00 +02:00