Commit Graph

558 Commits

Author SHA1 Message Date
Jelle Raaijmakers
90a211bf47 LibWeb: Use device-pixel coordinates in display list and AVC
Stop converting between CSS and device pixels as part of rendering - the
display list should be as simple as possible, so convert to DevicePixels
once when constructing the display list.
2026-02-26 07:43:00 +01:00
Aliaksandr Kalenik
abb392bd65 LibWeb: Remove redundant flush() in DisplayListPlayer::execute_impl()
The flush() call at the end of execute_impl() was accidentally left
behind in 2d2af9cd3b. That commit moved flushing into execute(), but
didn't remove the old call from execute_impl(). This caused every
nested display list to trigger a redundant GPU flush.

On an M4 MacBook, this improves Discord from ~65 FPS to 120 FPS.
2026-02-24 18:50:02 +01:00
Aliaksandr Kalenik
efbefb3b59 LibWeb: Skip display list commands under zero-area clips
Add a pre-computed `has_empty_effective_clip` flag on
AccumulatedVisualContext that propagates from parent to child. When a
clip rect or clip path has zero area, all descendant commands are
skipped at display list recording time in `DisplayList::append()`,
so they are never stored or executed.

This allows skipping ~10% of display list commands in the Discord app.
2026-02-24 16:41:20 +01:00
Aliaksandr Kalenik
d7a8db671b LibWeb: Skip overflow clip generation for SVG inner elements
Per the CSS Overflow spec, overflow properties apply only to block
containers, flex containers, and grid containers — not SVG graphics
elements. Add an `is<SVGPaintable>` check in
`overflow_property_applies()` to return false for SVG inner elements
like `<g>`, `<rect>`, `<path>`.

This doesn't affect `<svg>` elements (which use `SVGSVGPaintable`, a
direct `PaintableBox` subclass) or `<foreignObject>` (which uses
`SVGForeignObjectPaintable`, a `PaintableWithLines` subclass) — both
correctly keep their overflow clips.
2026-02-24 12:28:55 +01:00
Aliaksandr Kalenik
37ec6a08a8 LibWeb: Rename is_clip_or_mask to is_clip
The mask case was removed when AddMask was replaced with saveLayer+DstIn
compositing, so this flag now only applies to clip commands.
2026-02-24 07:14:16 +01:00
Aliaksandr Kalenik
2d2af9cd3b LibWeb: Replace m_surfaces stack with m_surface in DisplayListPlayer
After removing the AddMask display list command, no code path ever
pushes more than one surface onto the stack.
2026-02-24 07:14:16 +01:00
Aliaksandr Kalenik
0ac72e40d4 LibWeb: Remove the unused AddMask display list command
All mask call sites now use saveLayer+DstIn compositing, so the AddMask
command, its SkSL runtime shaders, and CachedRuntimeEffects are no
longer needed.
2026-02-24 07:14:16 +01:00
Aliaksandr Kalenik
5ef132ba1a LibWeb: Replace AddMask/clipShader with saveLayer+DstIn compositing
This applies the same pattern used for background-clip: text (commit
f2e6f70fbb).

Results in visible performance improvement in Discord app where
previously, according to profiles, we spent lots of time allocating
surfaces for masks.
2026-02-24 07:14:16 +01:00
Tim Ledbetter
73d948a028 LibWeb: Pre-allocate visual context chain vectors for hit testing
Also use inline storage for typical hit testing depths.
2026-02-23 17:30:25 +01:00
Callum Law
8d4084261a LibWeb: Resolve list item marker using registered counter styles 2026-02-23 11:21:09 +00:00
Aliaksandr Kalenik
7e3b49e583 LibWeb: Remove unused cumulative_offset_of_enclosing_scroll_frame() 2026-02-23 08:59:11 +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
e8182e7079 LibWeb: Move video element representation into the element class
This will be used in the shadow DOM controls to determine whether to
show the centered play button.
2026-02-23 07:27:31 +01:00
Aliaksandr Kalenik
55f4009163 LibWeb: Walk linked list directly in transform_rect_to_viewport()
The chain is already in element→root order, which matches the linked
list's natural traversal via parent pointers. No need to collect into
a Vector first.
2026-02-22 16:09:15 +01:00
Andreas Kling
057f11bf8f LibWeb: Cache absolute padding and border box rects on PaintableBox
These rects are computed from immutable layout data but were
recomputed from scratch on every call, with the CSSPixels saturating
arithmetic showing up as a significant cost in profiles.

Cache them lazily alongside the existing m_absolute_rect cache.

This was 3.3% of CPU time while playing a YouTube video.
2026-02-21 15:53:22 +01:00
Andreas Kling
8497da8a13 LibWeb: Cache CSS::Display on Paintable for O(1) display() lookups
Paintable::display() was chasing through layout_node().display() which
goes through computed_values().display() on every call. Since display
never changes after the Paintable is constructed, cache it as a member
set in the constructor.

This was 1.6% of CPU time while playing a YouTube video.
2026-02-21 15:53:22 +01:00
Andreas Kling
ea6c0e431a LibWeb: Inline CSSPixels methods and optimize device pixel math
Make CSSPixels::to_float(), to_double(), and to_int() constexpr inline
instead of out-of-line in the .cpp file. These are trivial one-liners
called in very hot painting paths.

Also optimize DevicePixelConverter::rounded_device_rect() to work
directly with CSSPixels values instead of constructing an intermediate
Rect<double> and scaling it.
2026-02-21 15:53:22 +01:00
Andreas Kling
7f830a0533 LibWeb: Pass ResolvedBackground by const reference in paint_background
It was passed by value, copying a Vector<ResolvedBackgroundLayerData>
on every call. This function is called for every PaintableBox on every
frame.
2026-02-21 15:53:22 +01:00
Andreas Kling
935f76f88e LibWeb: Cache visibility in Paintable::resolve_paint_properties()
Cache the result of the visibility+opacity check as a bit field
(m_visible) computed in resolve_paint_properties(), which already runs
before each paint when paint properties need updating.

This makes Paintable::is_visible() a simple inline bit field read
instead of chasing through layout_node->computed_values() every time.

This was 3.3% of CPU time while playing a YouTube video.
2026-02-21 03:51:28 +01:00
Timothy Flynn
a112eb4881 LibWeb: Ensure empty contenteditable boxes contain the hit test position
There are actually a couple of bugs here:

1. As of commit ebda8fcf11, editing hosts
   are now excluded from Node::is_editable. Since this special hit test
   handling is specifically for contenteditable nodes, we would not
   enter this branch for these nodes.

2. We were not checking if the contenteditable node actually contained
   the hit testing position. So if a page had multiple empty editable
   nodes, we would just return whichever was hit test first.

These bugs were exposed by 7c9b3c08fa.
This commit resulted in the text cursor hit test node being set as the
document focus node. If we returned the wrong result, we would not set
the correct node.

This was seen on discord, where clicking the message box would result in
the search box being focused.
2026-02-21 01:02:06 +00:00
Aliaksandr Kalenik
c7dc2ba0d3 LibWeb: Remove DrawPaintingSurface
No callers of draw_painting_surface remain after the previous commits
migrated canvas, video, and SVG to use ExternalContentSource or
ImmutableBitmap snapshots.
2026-02-20 18:41:33 +01:00
Aliaksandr Kalenik
004e5f851e LibWeb: Use ExternalContentSource for canvas painting
present() now snapshots the PaintingSurface into an ImmutableBitmap
and publishes it to the ExternalContentSource, so the rendering thread
never touches the live GPU surface — eliminating the data race
described in the ExternalContentSource commit (problem 1).

Canvas elements are registered with Page and presented once per frame
from the event loop, rather than on every individual draw call in
CRC2D::did_draw(). A dirty flag on HTMLCanvasElement ensures the
snapshot is only taken when content has actually changed, and makes
the present() call in CanvasPaintable::paint() a no-op when the
surface has already been snapshotted for the current frame.
2026-02-20 18:41:33 +01:00
Aliaksandr Kalenik
8a31ecdf39 LibWeb: Use ExternalContentSource for video painting
Publish new video frames to an ExternalContentSource, and switch
VideoPaintable from draw_scaled_immutable_bitmap to
draw_external_content.

Because DrawExternalContent reads the latest bitmap at replay time,
frame-only updates (no timeline or control change) now call
set_needs_display(InvalidateDisplayList::No) — skipping display list
rebuilds entirely. This addresses problem 2 from the previous commit.
2026-02-20 18:41:33 +01:00
Aliaksandr Kalenik
291078dd87 LibWeb: Introduce ExternalContentSource
Two related problems exist in the current display list architecture:

1. DrawPaintingSurface thread safety: CanvasPaintable::paint() records
   the *same* PaintingSurface that the canvas rendering context draws
   to. The rendering thread later reads from it, but the main thread
   may be concurrently drawing — a data race.

2. Video frames force display list rebuilds: each new video frame
   triggers set_needs_display() → full display list rebuild.

Both stem from display list commands holding direct references to
content (surface/bitmap) rather than going through an indirection
layer.

ExternalContentSource is a thread-safe, atomically-refcounted
container that holds an ImmutableBitmap snapshot. The accompanying
DrawExternalContent display list command reads from it during replay,
so producers can swap in new content without rebuilding the list.

Subsequent commits migrate canvas, video, and SVG painting to
ExternalContentSource and then remove DrawPaintingSurface.
2026-02-20 18:41:33 +01:00
Jelle Raaijmakers
ca45464c87 LibWeb: Scale down perspective transformation by DPR in DisplayList
By doing so, we attenuate the perspective transform on higher resolution
devices such as Retina displays (2x).

This fixes the perspective transform on sites such as
https://poke-holo.simey.me/.
2026-02-19 21:31:21 +01:00
Tim Ledbetter
fd24ca898c LibWeb: Skip hit-testing for elements with non-invertible transforms 2026-02-19 14:33:31 +00:00
Tim Ledbetter
8c5d081cdd LibWeb: Ensure elements with non-invertible transforms are not rendered 2026-02-19 14:33:31 +00:00
Callum Law
f0434655f9 LibWeb: Reduce recompilation from editing Enums.json
Reduces the recompilation caused by editing `Enums.json` from ~1528 to
~327
2026-02-19 11:27:06 +00:00
Jelle Raaijmakers
f2e6f70fbb LibWeb: Use saveLayer compositing for background-clip: text
Instead of rendering text glyphs into a separate mask surface and using
clipShader, paint the backgrounds first and then composite the text
glyphs via saveLayer with SkBlendMode::kDstIn. Skia's saveLayer
automatically sizes its backing at device resolution including CSS
transforms, so no manual scale computation is needed.

Fixes pixelation when zooming in on clipped backgrounds on e.g. the
title of https://modern-css.com/.
2026-02-17 18:59:33 +01:00
Jelle Raaijmakers
d5173fe6ca LibWeb: Inflate range rect by font ascenders/descenders
Chrome and Firefox inflate this rect to accommodate for the font's
ascenders and descenders, while the absolute rect for the fragment
remains unaffected. This fixes ascenders/descenders in text being
clipped when selecting text.
2026-02-17 10:51:48 +01:00
Jelle Raaijmakers
819bfa11ec LibWeb: Simplify primary offset translation in ::range_rect()
No functional changes.
2026-02-17 10:51:48 +01:00
Jelle Raaijmakers
8b32456473 LibWeb: Use explicit CSSPixels(float) constructor in ::range_rect()
No functional changes.
2026-02-17 10:51:48 +01:00
Jelle Raaijmakers
f831534589 LibWeb: Add explanatory comment to ::range_rect() on trailing whitespace 2026-02-17 10:51:48 +01:00
Jelle Raaijmakers
d2ad917fbc LibWeb: Simplify PaintableFragment::absolute_rect()
We can pass in offset() immediately. No functional changes.
2026-02-17 10:51:48 +01:00
Jelle Raaijmakers
8654e2caf4 LibWeb: Apply letter-spacing for selection rects and grapheme bounds
Fixes the selection rect on the title text of https://modern-css.com/
being misaligned with the actual characters.
2026-02-17 10:51:48 +01:00
Jelle Raaijmakers
aa2a95c775 LibWeb: Pass through background image painting even if area is empty
We should decide at a later stage, when we've calculated the final
background painting area, whether that area is empty and we can skip
painting the background.

Fixes #7973
2026-02-16 19:33:01 +01:00
Callum Law
9f7f623455 LibWeb: Store FilterOperation::DropShadow sub-values as StyleValues
This simplifies handling and brings it into line with other `StyleValue`
types
2026-02-16 12:09:23 +00:00
Callum Law
98e62cf86a LibWeb: Store FilterOperation::Blur::radius as StyleValue
This simplifies handling and brings it into line with other `StyleValue`
types
2026-02-16 12:09:23 +00:00
Callum Law
c529614e67 LibWeb: Store FilterOperation::HueRotate::angle as StyleValue
This simplifies handling and brings it into line with other `StyleValue`
types
2026-02-16 12:09:23 +00:00
Callum Law
cd799aa7e7 LibWeb: Forward declare CSS::FilterOperation structs
This means we don't need to include `FilterValueListStyleValue.h` in as
many places - reducing the rebuild from editing that file from 717 files
to 19.
2026-02-16 12:09:23 +00:00
Callum Law
3d3d0a50b6 LibWeb: Add generic Length::from_style_value method 2026-02-16 12:09:23 +00:00
Psychpsyo
30c1f2f938 LibWeb: Do not re-target hits to fragments with user-select:none
This makes text selection more usable around them, as demonstrated in
the added test case.
2026-02-16 08:39:28 +01:00
Psychpsyo
05c785b081 LibWeb: Only use overflow clip edge when overflow is set to clip 2026-02-14 22:58:21 +01:00
Psychpsyo
a7267f711b LibWeb: Add overflow-clip-margin-* properties
The corner radius isn't quite right yet, but this gives us
another couple WPT passes for these.
2026-02-14 22:58:21 +01:00
Psychpsyo
3253ddfcb2 LibWeb: Move clip data calculation into a helper 2026-02-14 22:58:21 +01:00
Psychpsyo
6ea528f0ec LibWeb: Do not create a layer when CSS isolation is set to isolate
This is entirely unnecessary. All that this property does is create a
stacking context.
2026-02-13 11:02:32 +00:00
Callum Law
05cafdb5d0 LibWeb: Remove none from counter-style-name-keyword
`none` isn't a supported value for `<counter-style-name>` and is only
supported directly by `list-style-type` (i.e. not within `counter{s}()`
functions)
2026-02-12 10:33:09 +00:00
Aliaksandr Kalenik
901cc28272 LibWeb: Reduce recompilation impact of DOM/Document.h
Remove 11 heavy includes from Document.h that were only needed for
pointer/reference types (already forward-declared in Forward.h), and
extract the nested ViewportClient interface to a standalone header.

This reduces Document.h's recompilation cascade from ~1228 files to
~717 files (42% reduction). Headers like BrowsingContext.h that were
previously transitively included see even larger improvements (from
~1228 down to ~73 dependents).
2026-02-11 20:02:28 +01:00
Jelle Raaijmakers
d3a2e4bbbb LibWeb: Draw caret for empty contenteditable elements
We don't have a text node in these, so no fragments either. Maintaining
a special case for this situation seems much simpler than reworking
`contenteditable`s to always have a fragment.
2026-02-11 11:17:27 +01:00
Jelle Raaijmakers
3bc4374344 LibWeb: Draw caret for empty <input>s and <textarea>s
Our change to generate spans in PaintableWithLines from fragments also
broke drawing the caret for empty <input>s and <textarea>s, since spans
was empty in that case.

Fix this by moving caret drawing to PaintableWithLines, and only
invoking it if we have a cursor position set.
2026-02-11 11:17:27 +01:00