Commit graph

112 commits

Author SHA1 Message Date
Jelle Raaijmakers
d642f7d85a LibWeb: Do not dump number of children for StackingContexts
The number of children a SC has is not that useful in dumps, when all
its children are dumped anyway.
2026-06-18 10:50:08 +02:00
Callum Law
2335941e43 LibWeb: Register ImageObservers for all mask-images
As of 114c8a7 we paint all mask layers, not just the first, so we should
likewise observe all `mask-image` values, not just the first.
2026-06-18 10:44:25 +02:00
Aliaksandr Kalenik
ef58631dd5 LibWeb: Skip inline paint phase without inline content
The inline and replaced paint phase walks every descendant even for
block-only contexts. Track whether a stacking context has inline or
replaced descendants while the stacking-context tree is built, then
skip the phase when it cannot produce display-list commands. This adds
one bool to StackingContext.
2026-06-05 19:45:50 +01:00
Aliaksandr Kalenik
9327ab670e LibWeb: Skip float paint phase without floats
Stacking contexts already collect non-positioned floating descendants
while building the paint tree. Use that list to avoid walking the
whole context for the float paint phase when no float can be painted.
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
Aliaksandr Kalenik
578c7c43cf LibWeb: Cache async scrolling metadata commands
Async scrolling metadata was emitted before the display list cache
branch in StackingContext, so cached background display lists did not
own the compositor metadata commands. Cache hits replayed ordinary
paint commands, while only cache misses regenerated wheel hit-test
targets, scroll nodes, viewport scrollbar state, and sticky areas.

Record the metadata while capturing uncached background commands so the
cached sequence owns the same async scrolling compositor commands as
the original paint. The cache-disabled path still records metadata
directly before painting.
2026-05-31 02:34:11 +01: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
Andreas Kling
6b8bd2699a LibWeb: Record foreignObject descendant hit tests
Add coverage for hit testing an HTML child inside an SVG
foreignObject when the SVG root uses paint containment. This captures
the case from Discord guild icons, where the SVG root is positioned and
paint-contained around a masked foreignObject.

When an SVG root establishes a stacking context, we were painting its
children without entering the root SVG paint path. That skipped the root
visual context setup before reaching a foreignObject. The foreignObject
then painted HTML descendants as SVG boxes, so their background-phase
hit-test entries were not emitted and the SVG root could win hit testing
instead.

Use the normal SVG box paint path for stacking-context SVG roots, and
record descendant hit-test items when painting a foreignObject. Keep the
existing SVG rendering path for the descendants so masked foreignObject
content is not repainted through full CSS stacking-context phases.
2026-05-30 21:36:47 +02: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
Aliaksandr Kalenik
b36e6c9b97 Compositor+LibWeb: Pass AVC trees separately from display lists
Display lists owned the accumulated visual context tree through a
ref-counted pointer. That tied visual-context state to display-list
lifetime and made compositor updates treat the two as one unit, even
though AVC trees need to become independently updateable compositor
state.

Make accumulated visual context trees plain versioned values, have each
display list store the compatible tree version, and pass the matching
tree alongside display-list updates and replay calls. Replay verifies
that the provided tree matches the display list before executing it.

This prepares the compositor for receiving AVC tree updates separately
from display-list updates: it now accepts the tree as a separate update
parameter, stores it next to the display list, and uses that stored tree
for replay and async-scroll hit testing. Nested display-list resources
carry their own tree snapshots for the same version check.
2026-05-27 18:29:42 +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
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
8906011a6b LibWeb: Synchronize display list resources via transactions
Display lists used to own the resource storage needed to replay their
command bytes. That kept the compositor tied to in-process object
ownership: sending a display list update also meant sharing the same
resource container with the recording side.

Move resource storage out of DisplayList and make display list updates
carry a transaction of resources to add and remove. Navigable now tracks
the resources referenced by the current display list, sends only the
delta to the compositor, and trims its recording-side storage to the
active set. The compositor applies those transactions to its own storage
before replacing the cached display list.

This still carries in-process resource objects, but it puts the
ownership boundary in the right place. Command bytes and resource
lifetime are now synchronized explicitly, which is the shape needed
before the compositor can receive serializable resource updates across a
process boundary.
2026-05-16 19:35:24 +02:00
Timothy Flynn
e6bb946e78 LibWeb: Add a forward declaration of ChromeWidget
Note that in Paintable.h, the use of ChromeWidget is not forward-
declarable. The full definition is needed for the generated struct
destructor.
2026-05-14 14:50:39 -04: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
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
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
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
Rob Ryan
711fb81808 LibWeb: Handle negative child stacking contexts during hit testing 2026-03-17 08:58:32 +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
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
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
0356f1b510 LibWeb: Assign visual context before paint() in paint_node()
Previously, PaintableWithLines switched the visual context partway
through paint() — using the element's own context for background/border
phases, then switching to the descendants' context mid-paint for
foreground content. Move this decision into paint_node() so the correct
context is set before paint() is called.

This is preparation for per-paintable display list command caching:
captured commands must be recorded under the correct visual context
from the start, with no mid-paint switches.
2026-03-04 19:35:45 +01:00
Jelle Raaijmakers
499ec26f97 LibWeb: Remove CSS pixel offsets from ScrollState
This was arguably put in a worse place by #8162; we mostly need the
device pixel offsets from the scroll state so keep track of those and
convert back to CSS pixels when necessary (i.e. scrollbar data).
2026-02-27 08:29:29 +01:00
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
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
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
Aliaksandr Kalenik
b41ed92505 LibWeb: Remove Document.h include from Layout/Viewport.h
Move the inline dom_node() method to Viewport.cpp so the header no
longer needs the full Document definition. Add explicit includes to
files that relied on the transitive dependency.
2026-02-08 18:51:13 +01:00
Aliaksandr Kalenik
2e4eebe4e5 LibWeb: Move CSS transform resolution to visual context assignment
Instead of computing and caching the transform matrix and transform
origin on PaintableBox during resolve_paint_properties(), compute them
inline during assign_accumulated_visual_contexts() where they are
actually consumed. This makes PaintableBox smaller by not wasting space
for properties that are only consumed during AccumulatedVisualContext
tree construction.
2026-01-31 16:56:05 +01:00
Andreas Kling
81942a84f3 LibWeb: Allow hit testing visible children of hidden stacking contexts
Apply the same fix from the previous commit to StackingContext hit test.
Hidden stacking context roots should still allow their visible children
to be hit.
2026-01-25 10:55:30 +01:00
Andreas Kling
d91c646788 LibWeb: Fix hit testing for text inside stacking context roots
When an element creates a stacking context (e.g. via position: relative
with z-index), its text fragments were not being hit tested. This was
because PaintableBox::hit_test() returns early when it has a stacking
context, and StackingContext::hit_test() only iterated child paintables,
not the stacking context root's own fragments.

Fix this by extracting fragment hit testing into a new method
hit_test_fragments() on PaintableWithLines, and calling it from
StackingContext::hit_test() when the stacking context root is a
PaintableWithLines.
2026-01-25 10:55:30 +01:00
Gingeh
7b3afbc11c LibWeb: Paint element overlay during Foreground paint phase
Also removed the FocusAndOverlay phase because it is now useless
2026-01-24 11:54:39 +01:00
Aliaksandr Kalenik
cb8ecb3c11 LibGfx+LibWeb: Move SVG mask/clip composition from CPU to GPU
Previously, both mask and clip-path were rendered to separate mutable
Gfx::Bitmap objects which forced CPU rasterization. They were then
combined using a CPU pixel-by-pixel operation before being returned
as an ImmutableBitmap.

Instead of including mask in the final bitmap as already rasterized
images, we now use display lists which opens opportunity to utilize
GPU if available.

Bitmap::apply_mask() and ApplyMaskBitmap display list command are no
longer used and have been removed.
2026-01-23 16:23:06 +01:00
Aliaksandr Kalenik
96a39aeaa6 LibWeb: Move effects application into AccumulatedVisualContext
Effects (opacity, blend mode, filters) must be applied in the parent's
coordinate space, before the element's transform. Previously this was
handled by manually switching to the parent's visual context when
applying effects at paint time.

By adding EffectsData to AccumulatedVisualContext and positioning it
before TransformData in the chain, effects are now naturally applied in
the correct order during display list replay, eliminating the special
case in StackingContext::paint().

For SVG filters that can generate content from empty elements (feFlood,
feImage, feTurbulence), a transparent FillRect command is emitted to
trigger the filter through the same AVC pipeline.
2026-01-21 16:19:18 +01:00
Aliaksandr Kalenik
69d5fa8cb7 LibWeb: Cache resolved CSS filters as paint-only properties
This moves filter resolution from display list recording time to
resolve_paint_properties(), caching the resolved values in CSS pixels
on PaintableBox. Device pixel conversion is now deferred until paint
time via to_gfx_filter().

This follows the existing pattern used for other paint-only properties
like box shadows and border radii.
2026-01-21 16:19:18 +01:00
Tim Ledbetter
cc559048cc LibWeb: Paint element outlines during Foreground paint phase
Previously, outlines for elements without their own stacking context
were painted during the `FocusAndOverlay` phase, which runs after all
child stacking contexts. This caused outlines to incorrectly appear
on top of elements with higher z-index.
2026-01-20 15:38:54 +01:00
Aliaksandr Kalenik
98afd82491 LibWeb: Integrate clip-path into AccumulatedVisualContext
Previously, clip-path was applied only during painting in
StackingContext::paint(), which meant hit testing did not respect
clip-path boundaries. Clicks outside the visible clipped region but
inside the element's bounding box would incorrectly register as hits.

By moving clip-path into AccumulatedVisualContext, it becomes part of
the same system that handles transforms, clips, and scroll offsets for
both painting and hit testing, ensuring consistent behavior.
2026-01-16 13:39:02 +01:00
Aliaksandr Kalenik
009ddd4823 LibWeb: Integrate AccumulatedVisualContext with display list
Integrate AccumulatedVisualContext with display list recording and
playback. This is the main commit of the refactoring that delivers the
architectural improvements enabled by AccumulatedVisualContext.

Recording changes:

Each display list command now stores a single
RefPtr<AccumulatedVisualContext> instead of separate scroll_frame_id
and ClipFrame. The recorder simply captures the current accumulated
context when appending commands.

The before_paint()/after_paint() hooks that pushed/popped scroll frame
IDs are replaced by directly setting accumulated_visual_context on the
recorder before painting each element.

Playback changes:

The display list player now uses LCA (Lowest Common Ancestor) based
traversal to switch between visual contexts efficiently. When
transitioning from context A to context B:

1. Find the LCA of A and B in the context tree
2. Pop (restore) states back to the LCA depth
3. Push (save + apply) states from LCA down to B

This approach minimizes redundant save/restore operations. For example,
when rendering siblings that share a common scroll container, the
player keeps that scroll state applied and only switches the divergent
parts of their context chains.

Key deletions:

- Remove translate_by() from all 45 display list commands - commands
  are now immutable
- Remove transform/perspective fields from PushStackingContext -
  transforms are tracked via AccumulatedVisualContext
- Remove push_scroll_frame_id()/pop_scroll_frame_id() from
  DisplayListRecorder
- Remove before_paint()/after_paint() hooks from Paintable
- Merge ApplyOpacity, ApplyCompositeAndBlendingOperator, ApplyFilter
  into single ApplyEffects command

Stacking context painting changes:

The StackingContext::paint() method is significantly simplified.
Instead of building a PushStackingContextParams struct with transform
matrices and pushing/popping stacking contexts, it now:

1. Sets the accumulated visual context (which already contains
   transforms)
2. Applies effects (opacity, blend mode, filters) if needed
3. Applies clip path if needed
4. Paints the content
5. Restores state

The visual state management that was interleaved throughout the
painting code is now handled uniformly by the context tree.
2026-01-15 19:50:53 +01:00
Andreas Kling
a9cc425cde LibJS+LibWeb: Add missing GC marking visits
This adds visit_edges(Cell::Visitor&) methods to various helper structs
that contain GC pointers, and makes sure they are called from owning
GC-heap-allocated objects as needed.

These were found by our Clang plugin after expanding its capabilities.
The added rules will be enforced by CI going forward.
2026-01-07 12:48:58 +01:00
InvalidUsernameException
fb9286eccb LibWeb: Apply clip frames even when transformation is identity
Clip frames for overflow were applied based on whether the box in
question had a non-identity matrix transformation associated with it.
That however is not correct, since specifying a no-op transform like
`scale(1)` still needs to apply clip overflow rectangles. So instead we
need to check whether the element associated with the box in question
has any CSS transforms.

This appears to have been a regression from
9bbc1cd618 and effectively reverts that
commit, but keeps its effect by unifying on the check for CSS transforms
instead.

This fixes some background boxes being rendered for the invisible items
of the carousels on https://computerbase.de/.
2025-12-01 17:46:44 +01:00
Psychpsyo
2db3796fd3 LibWeb: Implement CSS perspective-origin 2025-11-21 11:14:28 +00:00
Jelle Raaijmakers
7544066c0c LibWeb: Scale blur filter's radius by device pixel scale
The blur filter's effect was reduced on higher DPI displays.
2025-11-12 15:59:01 +01:00
Psychpsyo
eb21ea890c LibWeb: Implement CSS perspective property 2025-11-12 00:41:14 +01:00
Aliaksandr Kalenik
4853e2ffb1 LibWeb: Don't reach into layout node to check if paintable is SVG 2025-10-14 11:23:29 +02:00
Psychpsyo
2fcacd21f5 LibWeb: Compute bounds for paint-contained stacking contexts
This makes it so that the bounds for any paint-contained stacking
context are not derived from its children, but rather just set to
the rectangle that they will be clipped to anyways due to the paint
containment. Should make rendering faster on pages that use paint
containment.
2025-09-25 15:10:10 +02:00
Aliaksandr Kalenik
9bbc1cd618 LibWeb: Check if transform is identity instead of has_css_transform()
...in clip and scroll frames calculation algorithm.

Fix a regression from 719a50c where display-list recording disagreed
with the clipping logic about whether a stacking context is transformed.
`has_css_transform()` returns true whenever the computed transform is
not `none`, which differs from an identity-matrix check. These yield
different results for cases like `translate(0, 0)`.
2025-09-23 23:35:19 +02:00