Commit graph

216 commits

Author SHA1 Message Date
Andreas Kling
a1bc2a6223 LibWeb: Use Utf16StringBuilder for UTF-16 text assembly
Replace the remaining UTF-16-mode StringBuilder instances in LibWeb
with Utf16StringBuilder when they assemble Utf16String values or UTF-16
text views. This keeps those paths in UTF-16 throughout and uses
explicit ASCII append helpers for ASCII-only pieces.

Keep StringBuilder in place for JSON, markup, CSS serialization, and
other byte-oriented string construction paths.
2026-06-22 16:10:40 +02:00
Andreas Kling
4e047ae97b LibWeb: Keep pinch zoom out of client rects
Keep the visual viewport transform out of Element client rects and
IntersectionObserver geometry. Pinch zoom should change the visual
viewport, but not the layout viewport coordinates exposed through DOM
geometry APIs.

Thread an opt-out through rectangle mapping so paint and hit testing
still use the full visual transform while web-observable geometry can
stay in layout viewport coordinates. This matches the Blink and WebKit
page scale model and keeps responsive script from treating pinch zoom
like a relayout.

Add coverage for getBoundingClientRect() under pinch zoom and visual
viewport IntersectionObserver geometry.
2026-06-16 02:03:59 +02:00
Andreas Kling
6eff400823 LibWeb: Avoid scrollbar painting without overflow data
Sticky inline paintables can have overflow:scroll without owning
scrollable overflow data. They cannot produce scrollbar geometry.

Return no scrollbar data when there is no overflow rectangle. Add a
crash test for a sticky inline bdi element.
2026-06-08 01:04:08 +02:00
Aliaksandr Kalenik
bab8138236 LibWeb: Skip box hit-test records in irrelevant phases
PaintableBox::record_hit_test_items() is called for every paint phase,
but it only emits box hit-test entries during the background and
overlay passes. Return before touching the hit-test display list during
the other phases so cached paint replay stops repeating no-op work.
The hyperfine delta is within noise.
2026-06-05 19:45:50 +01:00
Aliaksandr Kalenik
78cda6a97e LibWeb: Reserve visual viewport AVC root
The accumulated visual context tree used index 0 as a null sentinel, so
visual viewport transforms were only represented by adding a normal
transform node when the transform was non-identity. That made callers
treat index 0 as a special no-context value and kept the tree shape
dependent on the visual viewport state.

Reserve index 0 as the visual viewport transform node instead. AVC
traversal, display-list replay, hit testing, debug dumping, and root
paint state now treat that node as a real root. Rebaseline the affected
display-list and async-scrolling text expectations so the explicit root
node appears in AVC dumps.
2026-06-03 02:12:39 +01:00
Sam Atkins
8c60d20dfc LibWeb: Keep grid template used values outside DevTools data
Resolved grid-template-columns and grid-template-rows values are needed
for getComputedStyle(), even when DevTools is not connected. Store them
separately from GridLayoutData again so the later lazy DevTools data
work can skip inspector-only geometry without changing CSSOM behavior.

Partly reverts 540b53ac1a.
2026-06-01 17:39:32 +01:00
Sam Atkins
a08b05a8e8 LibWeb: Recompute container-unit dependent styles
When a query container changes size or type, descendants that use
container-relative units need the same post-layout style refresh as
elements matched by `@container` rules.

Drop the style-scope gate from the invalidation and getComputedStyle()
paths so any element flagged as depending on a size query container gets
restyled after layout.
2026-06-01 08:27:17 +01:00
Aliaksandr Kalenik
08e448b119 LibWeb: Shrink wheel hit-test display-list targets
Every compositor wheel hit-test target carried a Gfx::CornerRadii
payload even though most targets have square corners. On YouTube this
made wheel hit-test targets the largest display-list bucket, with
thousands of targets paying for empty radii.

Keep CompositorWheelHitTestTarget as the compact square-corner command
and add CompositorWheelHitTestTargetWithCornerRadii for the rounded
case. Painting emits the larger command only when the computed corner
radii are non-empty, and async scrolling normalizes both payloads back
into the same target state.

A YouTube recording with 5,483 square-corner wheel hit-test targets used
80-byte compact commands instead of the 112-byte rounded command,
reducing the top-level display list by about 175 KiB for that page.
2026-05-31 18:26:23 +01:00
Aliaksandr Kalenik
95d030df8a LibWeb: Use ByteBuffer for captured display list commands
DisplayListCommandSequence became a thin ByteBuffer wrapper after cached
paint data started storing command bytes directly. It still carried the
command alignment constant and command-header iterator, but it no longer
provided ownership or validation behavior beyond DisplayList itself.

Move the alignment constant and command-header iterator to DisplayList.
Command capture now returns a ByteBuffer directly. PaintableBox installs
cached commands from that byte buffer view while preserving replay and
cache-reference behavior from the consolidated cache holder.
2026-05-31 03:48:34 +01:00
Aliaksandr Kalenik
7744991071 LibWeb: Consolidate PaintableBox paint cache storage
PaintableBox kept an inline array of six optional command-sequence slots
for per-phase paint caching. That made every box pay 336 bytes even when
it never cached paint commands. Cached boxes could allocate a separate
ByteBuffer for each populated phase.

Move storage behind a lazy CachedPaintData holder. The holder records
explicit phase presence and offset/size spans into a single ByteBuffer.
Empty cached phases stay distinct from absent phases. Replay copies
command bytes into the frame display list, and cache references now use
the cached bytes view instead of a retained command object.

On the release arm64 build, sizeof(PaintableBox) drops from 944 to 616
bytes. The old inline holder was 336 bytes; the new inline field is one
8-byte pointer, so uncached boxes are 328 bytes smaller. Cached boxes
now use one consolidated byte buffer for all cached phases.
2026-05-31 03:48:34 +01:00
Aliaksandr Kalenik
d725c36129 LibWeb: Store cached display list commands without resources
Cached display list command sequences used to carry their own
DisplayListResourceStorage. That kept resource ID sets and referenced
fonts, images, video frames, and nested display lists alive on every
cached phase, even though the command bytes already contain enough
information to discover those references when they are needed.

This makes cached command sequences store only command bytes. Resource
references are collected transiently from those bytes when a cache entry
is installed or invalidated. The navigable's central display list
resource storage now keeps cache reference counts, so compositor pruning
retains resources used by live cached commands without duplicating
storage in each sequence.
2026-05-31 00:21:25 +01:00
Andreas Kling
7445cff5e8 LibWeb: Use retained data for hit testing
Build a hit-test display list while recording paint output. Use it as
source of truth for point hit testing instead of recursively walking the
paintable tree in reverse paint order.

The retained list records target paintables, visual context indices,
border radii, caret rects, and line metadata needed by hit testing. It
also keeps a spatial index so point queries inspect nearby items before
checking containment in paint order.

Refresh scroll state before hit testing so visual context transforms use
current scroll offsets. Add text tests for rounded hit regions and
selection across non-text content.
2026-05-30 13:50:48 +02:00
Sam Atkins
5b2c649eda LibWeb/Painting: Paint flexbox inspector overlays 2026-05-28 12:58:46 +01:00
Sam Atkins
fb34ca41c0 LibWeb: Preserve flex layout geometry for DevTools 2026-05-28 12:58:46 +01:00
Sam Atkins
28e1e1b160 LibWeb/Painting: Implement painting a debug overlay for grids
Paint grid overlays with the same visual context as the normal inspector
overlay, so scroll frames and transforms affect highlighted grids the
same way they affect page content.

Also support optional line numbers, named areas, infinite lines, and
track size labels.

We introduce a paint_with_inspector_overlay_context() helper to share
the significant amount of code to paint overlays with the correct
context.
2026-05-27 17:47:50 +01:00
Sam Atkins
540b53ac1a LibWeb: Preserve grid layout geometry for DevTools
Store grid container layout data on the grid container's paintable box
during layout.

The data includes computed track, line, and named-area geometry for
Firefox DevTools protocol serialization. It also carries the resolved
grid-template-columns and grid-template-rows values used by
getComputedStyle(), so PaintableBox does not need separate grid
template storage.
2026-05-27 17:47:50 +01:00
Aliaksandr Kalenik
7c6a12367c LibWeb: Propagate blocking wheel regions to descendants
Async scrolling only recorded compositor blocking wheel event regions
for the paintable attached to the element with the listener. Sites that
attach a non-passive wheel listener to an otherwise non-painting
container, then put the visible interactive content in descendants,
could still let the compositor scroll the page before the main thread
handled the wheel event.
2026-05-27 16:23:04 +01:00
Aliaksandr Kalenik
c78dd59cc3 LibWeb: Move async scrolling recording to paintables
Async scrolling display-list items were still recorded by helper
functions in LibWeb/Compositor even though they are produced while
walking the paint tree. That kept paint-time knowledge about boxes,
viewport state, and wheel-event regions in the compositor-facing code.

Move viewport setup and final metadata emission to ViewportPaintable,
and move per-box scroll-node, hit-test, scrollbar, wheel-region, and
sticky-area item recording to PaintableBox. AsyncScrollingState now
keeps the compositor-side conversion and lookup logic for consuming the
recorded metadata.
2026-05-25 00:45:24 +02:00
Sam Atkins
585849fcf0 LibWeb: Parse and evaluate @container <size-feature>s
e.g., `@container (width >= 300px) {}` and similar.

During style computation, flag any elements whose style depends on a
size container. Then re-evaluate their style after the initial layout
has been computed and size containers have a size. This may take
multiple passes, as these may have further descendants that depend on
their size, etc. We limit this to 8 passes currently.

SizeFeature itself is very similar to MediaFeature, but queries the
container element instead. There are only 6 size features specified, so
they're hard-coded instead of generated from JSON.

Also add a counter test for the narrower restyle path.
2026-05-20 13:00:50 +01:00
Andreas Kling
541828dbb1 LibWeb: Respect overflow axes for wheel scrolling
Keep the compositor scroll node max offset as the real scroll range,
even for axes that cannot be scrolled by wheel input. Track wheel
scrollability separately so hidden axes are skipped during async wheel
scrolling without clamping away an existing programmatic offset.

Use the viewport-propagated root and body overflow values when deciding
whether viewport axes accept wheel input. Apply wheel deltas only on
axes that can be wheel-scrolled in async metadata and main-thread wheel
default actions, while preserving CSSOM scroll offsets on hidden axes.

Add async scrolling coverage for hidden-axis wheel targeting, preserved
programmatic hidden-axis offsets, and body overflow-x: hidden blocking a
horizontal viewport wheel scroll despite pseudo-element overflow.
2026-05-17 14:19:36 +02:00
Psychpsyo
c86a9a5963 LibWeb: Make PaintableBox fragmentable 2026-05-15 13:26:24 +01:00
Aliaksandr Kalenik
82b016ed19 LibWeb: Rebuild async scrolling from display list
Use compositor hit-test commands in the display list to rebuild async
wheel targets, and serialize the async scroll metadata needed to
reconstruct AsyncScrollingState from the same display-list snapshot.

Driving the async scroll tree off the display list rather than a
separately collected tree has a few benefits:

- No additional full paintable tree traversal is required, since the
  information needed by the compositor is gathered while recording
  the display list.
- The display list is already serializable, so the async scroll tree
  no longer needs its own serialization path.
- It is more debuggable, as the existing display list dump now also
  covers the data used to reconstruct the async scroll tree.
- In the future we will want to include other areas that can
  interfere with hit-testing; recording them during display list
  construction makes it straightforward to preserve a hit-testing
  order that matches painting order.
2026-05-13 18:36:07 +02:00
Andreas Kling
84e20ec6f3 LibWeb: Snap compositor-owned viewport scrollbars on hover
Collect both regular and enlarged viewport scrollbar geometry so the
compositor can expand the overlay while hovered or captured. Keep the
visibility decision on compositor-owned input state: the overlay appears
while a viewport scrollbar is hovered or captured, and disappears when
that state clears.

Keep the existing non-viewport scrollbar path unchanged.
2026-05-13 13:15:45 +02:00
Andreas Kling
a422db9dfb LibWeb: Paint viewport scrollbars in the compositor
Move async-scrolling viewport scrollbar painting out of the page
display list and into the compositor overlay. The main thread now
collects immutable scrollbar geometry and style alongside the async
scrolling snapshot, while the compositor applies the current scroll
snapshot when painting the thumb.

Keep the existing display-list path for nested scroll containers and
for pages that are not using async scrolling.
2026-05-13 13:15:45 +02:00
Andreas Kling
9f35827aa0 LibWeb: Use double precision for wheel scroll deltas
Wheel deltas were truncated to int at the platform input boundary,
which dropped the sub-pixel tail of trackpad momentum scrolls. Each
NSEvent's scrollingDeltaY arrived as a CGFloat, got cast to int, and
flowed through IPC, EventHandler, and PaintableBox::scroll_by as int,
losing fractional information that never came back.

Widen Web::MouseEvent::wheel_delta_{x,y} to double and propagate
through Page, EventHandler, Paintable, PaintableBox, and the AppKit,
Qt, and GTK input paths.
2026-05-13 11:00:59 +02:00
Aliaksandr Kalenik
537f289c0c LibGfx+LibWeb: Move display-list corner types to LibGfx
Prep for moving display-list rasterization out of LibWeb by moving the
device-pixel corner radius, corner clip, and anti-aliasing types to
LibGfx.

Keep BorderRadiiData in LibWeb as the CSS-pixel representation and
convert it to Gfx corner radii when recording display-list commands.
2026-05-12 16:30:26 +02:00
mikiubo
b9ed64d1ce LibWeb: Add dedicated overflow-clip-margin style value
Store overflow-clip-margin as a structured style value instead of
collapsing it into generic keyword/length lists during parsing.

Keep omitted visual-box as a distinct state so painting can apply
the spec default per element type (content-box for replaced,
padding-box otherwise), while preserving canonical serialization.
2026-05-12 14:09:13 +02:00
Aliaksandr Kalenik
5e13bd37aa LibWeb: Resolve scrollbar thumb colors when recording
Keep CSS initial-value knowledge on the recording side. Display-list
playback should consume concrete colors only, so resolve the default
overlay scrollbar thumb alpha before emitting PaintScrollBar commands
instead of checking CSS::InitialValues while rasterizing.
2026-05-11 20:31:33 +02:00
Tim Ledbetter
f29bc0c996 LibWeb: Use scroll compensation for background-attachment: fixed
Instead of baking the current scroll offset into the background
positioning area at record time, use the `ScrollCompensation` visual
context node to negate ancestor scroll frames dynamically at replay
time. This keeps the background fixed relative to the viewport even
when the display list is cached and replayed at different scroll
positions, and works correctly with arbitrarily nested scroll
containers.
2026-05-08 13:09:40 +02:00
Aliaksandr Kalenik
568b7ce7ea LibWeb: Make Paintable tree ref-counted
The Paintable tree and its supplemental painting data structures were
GC allocated because that was the easiest way to manage it and avoid
leaks introduced by ref cycles. This included the Paintable subclasses
themselves plus StackingContext, ChromeWidget, Scrollbar, ResizeHandle,
and scroll-frame state.

We are now trying to reduce GC allocation churn on layout and painting
updates, so keeping this short-lived rendering tree outside the JS heap
is a better fit. Move Paintable to RefCountedTreeNode, make painting
helpers ref-counted or weakly reference Paintables, and update the
layout and event-handler call sites to use RefPtr/WeakPtr ownership.
2026-05-07 15:03:44 +02:00
Aliaksandr Kalenik
76c79ee522 LibGfx: Remove ImmutableBitmap
DecodedImageFrame now owns decoded bitmap pixels directly, so the
separate ImmutableBitmap wrapper no longer carries useful semantics.
Remove the class and pass decoded image frames or bitmaps at the
boundaries where pixels are actually required.

The Skia image cache now keys off DecodedImageFrame, matching the
display-list commands that paint decoded images. Video frames stay
owned by LibMedia, with the explicit YUV-to-bitmap conversion living
at HTMLVideoElement's decoded-frame entry point for canvas and WebGL
callers.
2026-05-05 14:39:17 -05:00
Tim Ledbetter
8f3992526d LibWeb: Avoid unnecessary scrollbar allocation during hit testing
Most paintable boxes do not expose scrollbars or resize handles. Check
for scrollable axes and resizability before constructing chrome metrics
or creating scrollbar widgets.

This keeps the common no-scrollbar case from creating a `HitTestResult`,
fetching ChromeMetrics, allocating `Scrollbar` objects, and recomputing
scrollbar geometry unnecessarily.
2026-04-29 04:34:07 +02:00
Timothy Flynn
1931636ac3 LibWeb: Support auto-scrolling contains when the middle mouse is pressed
When the middle mouse button is pressed, we can now scroll the pressed
container while moving the mouse around. We paint an indicator at the
pressed origin (as an overlay), as the scroll speed will depend upon the
distance between the moved mouse and that origin.
2026-04-13 13:01:45 -04:00
Aliaksandr Kalenik
4735908d41 LibWeb: Make ViewportPaintable::visual_context_tree() return reference
The visual context tree is expected to be non-null at all call sites.
Change the return type from raw pointer to reference with VERIFY(),
making the contract explicit and removing unnecessary null checks from
callers.
2026-04-08 02:41:29 +02:00
Tim Ledbetter
d9079d2078 LibWeb: Apply z-index only to positioned elements
`z-index` values other than `auto` also apply to grid and flex items
because they behave as if their `position` were `relative`.
2026-04-03 23:28:39 +02:00
Callum Law
cbc2bb7aa7 LibWeb: Don't pass layout node when resolving EdgeRect
We know that these lengths have already been absolutized at computed
value time
2026-03-30 14:05:10 +01:00
mikiubo
71c4bb1aa8 LibWeb: Support border-radius in clip-path: inset()
Implement support for the 'round' radii in 'clip-path: inset()'
by resolving and normalizing corner radii and generating a path
with elliptical arcs.

Add a screenshot test.
2026-03-21 02:44:49 +00:00
Zaggy1024
d94293cc25 LibWeb: Use an abstract class for scrollbar and resize handle input
This unifies the implementations of the element resize and the scroll
mouse inputs, so the actual code involved in handling the events can
be simplified, and it only should require one object per event.

The cursor override is now part of this ChromeWidget class as well, so
that the hit test's cursor doesn't need to be passed around as much.
2026-03-17 04:01:29 -05:00
Zaggy1024
9a39a8308b LibWeb: Prevent events on scrollbars when pointer-events is none
This prevents an assertion failure in EventHandler's mousemove and
mousedown handlers when hovering over a scrollbar with pointer-events
set to none.
2026-03-17 04:01:29 -05:00
Zaggy1024
2c2ad598a0 LibWeb: Use local coordinates for hit testing chrome
Without this fix, clicking a scrollbar nested within a scrolled box
would not register.
2026-03-17 04:01:29 -05:00
Aliaksandr Kalenik
3012c0fe72 LibWeb: Store scroll frames by value in contiguous storage
Replace per-frame heap-allocated RefCounted ScrollFrame objects with a
single contiguous Vector<ScrollFrame> inside ScrollState. All frames for
a viewport are now stored in one allocation, using type-safe
ScrollFrameIndex instead of RefPtr pointers.

This reduces allocation churn, improves cache locality, and moves
parent-chain traversal (cumulative offset, nearest scrolling ancestor)
into ScrollState — similar to how visual context nodes were recently
consolidated into AccumulatedVisualContextTree.
2026-03-12 12:06:40 +01:00
Aliaksandr Kalenik
9d2ebe90ed LibWeb: Store visual context nodes in arena-based tree
Replace per-node heap-allocated AtomicRefCounted
AccumulatedVisualContext objects with a single contiguous Vector inside
AccumulatedVisualContextTree. All nodes for a frame are now stored in
one allocation, using type-safe VisualContextIndex instead of RefPtr
pointers.

This reduces allocation churn, improves cache locality, and opens the
door for future snapshotting of visual context state — similar to how
scroll offsets are snapshotted today.
2026-03-11 11:16:36 +01:00
Aliaksandr Kalenik
834675aea3 LibWeb: Add PaintableBox::inverse_transform_point()
Extract the pattern of inverse-transforming a screen position (removing
the effect of CSS transforms) into a helper method. Used to compute
mouse event offsets relative to the target element.
2026-03-11 02:31:30 +01:00
Aliaksandr Kalenik
01a7c8e424 LibWeb: Add PaintableBox::transform_rect_to_viewport()
Extract the repeated pattern of transforming a rectangle from absolute
coordinates to viewport coordinates via the accumulated visual context
into a helper method.
2026-03-11 02:31:30 +01:00
Aliaksandr Kalenik
b217c5a496 LibWeb: Add PaintableBox::transform_point_to_local_for_descendants()
Similar to transform_point_to_local(), but uses the descendants' visual
context which accounts for the element's own scroll offset. Used during
fragment hit testing in PaintableWithLines.
2026-03-11 02:31:30 +01:00
Aliaksandr Kalenik
21876bd011 LibWeb: Add PaintableBox::transform_point_to_local()
Extract the repeated pattern of transforming a screen position to local
coordinates via the accumulated visual context into a helper method.
This returns an Optional, unlike the existing
transform_to_local_coordinates() which falls back to the original
position. The old method is now implemented in terms of the new one.
2026-03-11 02:31:30 +01:00
Jelle Raaijmakers
9814ea28e9 LibWeb: Propagate paint invalidation to anonymous child nodes
Elements that generate anonymous boxes such as `<button>` and
`<fieldset>` need to propagate paint invalidation to them in order to
properly invalidate the style of their inner contents.

Fixes #8347
2026-03-10 15:23:53 +01:00
Aliaksandr Kalenik
d49809cba3 LibWeb: Remove paint-only properties resolution phase
With per-paintable display list command caching now in place, the
separate paint-only properties resolution phase is no longer needed.
Resolution now happens inline during painting and its cost is amortized
since it only runs on cache miss.

Move all property resolution to point of consumption:
- is_visible() and visible_for_hit_testing() compute on the fly
- Filter resolution moved to assign_accumulated_visual_contexts()
- Border radii, outlines computed on access
- Box shadows, backdrop filter resolved inline during painting
- Background resolution moved into paint_background()
- Mask resolution moved to StackingContext::paint()
- Text fragment and SVG stroke properties resolved during painting
2026-03-04 19:35:45 +01:00
Aliaksandr Kalenik
5bfc4a3c41 LibWeb: Cache display list commands per paintable
Cache the display list commands produced by each PaintableBox's paint()
on a per-phase basis. On subsequent display list rebuilds, if a
paintable's cache is still valid, replay the recorded commands directly
— skipping paint() and all the property resolution it entails.

Besides saving time on property resolution, this also enables Skia to
reuse path tessellation results across frames — e.g. border paths are
preserved in the cache and don't need to be re-tessellated on every
repaint.
2026-03-04 19:35:45 +01:00
Aliaksandr Kalenik
eae94a8a46 LibWeb: Route repaint requests through paintables, not Document
Rename Document::set_needs_display() to set_needs_repaint() and make it
private. External callers must now go through Node/Paintable which
route the request to the document internally.

Fix one existing misuse in AnimationEffect that was calling
document-level set_needs_display() instead of routing through the
target element's paintable.

This is preparation for per-paintable display list command caching:
repaint requests must go through specific paintables so their cached
command lists can be invalidated.
2026-03-04 19:35:45 +01:00