Commit Graph

81 Commits

Author SHA1 Message Date
Aliaksandr Kalenik
7e3b49e583 LibWeb: Remove unused cumulative_offset_of_enclosing_scroll_frame() 2026-02-23 08:59:11 +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
Tim Ledbetter
8c5d081cdd LibWeb: Ensure elements with non-invertible transforms are not rendered 2026-02-19 14:33:31 +00:00
Jelle Raaijmakers
9d09b3ce34 LibWeb: Add Paintable method to scroll offset into view 2026-02-11 11:04:53 +01:00
Jelle Raaijmakers
87ada9e887 LibWeb: Do not mark ScrollHandled as [[nodiscard]]
There's only one place where we don't `(void)` the result of these
methods, so let's not be too pedantic about it.
2026-02-11 11:04:53 +01:00
Aliaksandr Kalenik
3fc33f86dd Revert "LibWeb: Flatten non-3d transforms"
This reverts commit 63aeaa81e5.

The change broken image rendering on X.com and
https://browserbench.org/Speedometer3.1/
2026-02-10 19:19:14 +01:00
Psychpsyo
63aeaa81e5 LibWeb: Flatten non-3d transforms 2026-02-07 18:56:23 +01:00
Tim Ledbetter
2cd30af4df LibWeb: Only apply overflow clipping to applicable element types
According to the CSS spec, overflow properties only apply to block
containers, grid containers and flex containers.
2026-02-04 21:23:20 +01:00
Aliaksandr Kalenik
af42e8e768 LibWeb: Store StickyInsets on ScrollFrame
Previously `refresh_scroll_state()` reached back to PaintableBox for
sticky insets. Storing them on ScrollFrame makes it self-contained
for sticky offset computation.
2026-02-01 19:57:14 +01:00
Aliaksandr Kalenik
af58b00922 LibWeb: Restrict direct access to ScrollFrame's own_offset
Remove the public own_offset() getter from ScrollFrame and the
own_offset_for_frame_with_id() helper from ScrollState. The snapshot
creation in ScrollStateSnapshot now accesses m_own_offset directly
through a friend declaration. This ensures scroll offsets in the
painting pipeline are only read through snapshots.
2026-02-01 16:13:13 +01:00
Aliaksandr Kalenik
e2c6190e8c LibWeb: Use scroll state snapshot in compute_scrollbar_data()
Replace direct ScrollFrame access via own_scroll_frame_offset() with
ScrollStateSnapshot-based lookup. This aligns compute_scrollbar_data()
with the rest of the painting/hit-testing pipeline, which uses snapshots
for consistent, thread-safe scroll state.
2026-02-01 16:13:13 +01:00
Aliaksandr Kalenik
fc8b7bb7af LibWeb: Remove cached perspective matrix from PaintableBox
Compute the perspective matrix on the fly during visual context
assignment instead of caching it in PaintableBox. This reduces
PaintableBox's size and keeps the logic closer to where it's used.
2026-01-31 16:56:05 +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
Aliaksandr Kalenik
044d778c5c LibWeb: Precompute sticky constraints to avoid paintable tree lookups
Precompute the geometric data needed for sticky positioning during
scroll frame assignment. This avoids walking the paintable tree to query
containing blocks and ancestor geometry during scroll state refresh,
which runs on every scroll event.
2026-01-29 19:02:30 +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
660e1a62b2 LibWeb: Remove clip-path handling from PaintableBox::get_masking_area()
This is now handled by AccumulatedVisualContext.
2026-01-21 19:10:26 +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
Aliaksandr Kalenik
d4feeb1cad LibWeb: Preserve paintable tree across relayouts
Reuse existing paintables during relayout to reduce GC allocation
pressure. Each paintable subclass implements reset_for_relayout()
to clear state before reuse.
2026-01-21 10:00:17 +01:00
Aliaksandr Kalenik
9b3abe9c95 LibWeb: Remove unused PaintableBox::absolute_paint_rect()
This method was initially introduced to calculate the total paint area
including effects like box-shadows that paint outside the border box.
It was used in StackingContext for sizing bitmaps when painting
elements with opacity or transforms, and later for clip-path bounds.

This functionality is no longer needed as stacking context painting
and clip-path handling have been refactored to use different
approaches (AccumulatedVisualContext now handles clip-path).
2026-01-19 09:21:48 +01:00
Tim Ledbetter
f8d6314db6 LibWeb: Account for scroll offset in iframe mouse event coordinates
When delegating mouse events to iframes, coordinate transformations
were not accounting for scroll offsets. This caused clicks inside
iframes to be incorrectly positioned when the parent page was scrolled.
2026-01-19 06:22:18 +01:00
Andreas Kling
eb75220eb6 LibWeb: Add missing type-isolating GC allocators for some types
- Layout::BlockContainer
- Painting::PaintableBox
- Painting::SVGGraphicsPaintable
- Platform::TimerSerenity
2026-01-18 10:10:04 +01:00
Aliaksandr Kalenik
bb4e29be5c LibWeb: Remove ClipFrame
Remove the now-obsolete ClipFrame infrastructure:
- Delete ClipFrame.h and ClipFrame.cpp
- Remove assign_clip_frames() from ViewportPaintable
- Remove enclosing_clip_frame and own_clip_frame from PaintableBox
- Remove m_clip_state HashMap from ViewportPaintable

Clip handling is now fully managed through AccumulatedVisualContext
nodes with ClipData.
2026-01-15 19:50:53 +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
Aliaksandr Kalenik
10833640b1 LibWeb: Introduce AccumulatedVisualContext
Introduce AccumulatedVisualContext, a tree structure that tracks the
cumulative visual state (scroll offsets, clip regions, transforms,
perspective) for each paintable box.

Motivation:

Before this change, visual state was fragmented across multiple
mechanisms:
- ClipFrame: Tracked clip rectangles, each storing its own
  enclosing_scroll_frame_id to handle scroll offset adjustments
- scroll_frame_id: Passed separately to each display list command
- PushStackingContext: Stored transform matrices directly in the command
- Every display list command implemented translate_by() (45 methods
  total) to allow scroll offset adjustment during playback

This fragmentation led to:
- Complex, error-prone coordinate transformation logic scattered
  throughout the codebase
- Commands being mutated during playback to apply scroll offsets
- Duplicate logic between painting and hit testing for coordinate
  transformations

Solution:

AccumulatedVisualContext builds a tree where each node represents a
single visual operation:
- ScrollData: A scroll frame with its ID
- ClipData: A clip rectangle with optional border radii
- TransformData: A 4x4 transform matrix with its origin
- PerspectiveData: A perspective projection matrix

Each PaintableBox stores a reference to its accumulated context node.
The tree structure naturally captures the parent-child relationships,
so traversing from any node to the root gives the complete chain of
visual transformations.

Benefits this enables (in subsequent commits):
- Display list commands become immutable - no more translate_by()
- Single RefPtr<AccumulatedVisualContext> replaces separate
  scroll_frame_id and ClipFrame on commands
- LCA-based tree traversal during playback for efficient save/restore
- transform_point_for_hit_test() provides coordinate transformation for
  hit testing using the same structure
2026-01-15 19:50:53 +01:00
Jonathan Gamble
8f1cb4cbb0 LibWeb: Implement resizing for eligible elements and update scrollbars
Add ElementResizeAction to Page (maybe there's a better place). It's
just a mousemove delegate that updates styles on the target element.

Add ChromeMetrics for zoom-invariant chrome like scrollbar thumb
thickness, resize gripper size, paddings, etc. It's not user-stylable
but separates basic concerns in a way that a visually gifted
designer unlike myself can adjust to taste.

These values are pre-divided by zoom factor so that PaintableBox can
continue using device_pixels_per_css_pixel calls as normal.

The adjusted metrics are computed on demand from Page multiple times
per paint cycle, which is not ideal but avoids lifetime management and
atomics. Maybe someone with more surety about the painting flow control
can improve this, but it won't be a huge win. If profiling shows
this slowing paints, then Ladybird is in good shape.

Update PaintableBox to draw the resize gripper and deconflict
the scrollbars. Set apropriate cursors for scrollbars and gripper in
mousemove. We override EventHandler's cursor handling because nothing
should ever come between a man and his resize gripper.

Chrome metrics use the CSSPixels class. This is good because it's
broadly compatible but bad because they're actually different units
when zoom is not 1.0. If that's a problem, we could make a new type
or just use double.
2026-01-12 11:00:14 +00:00
Jonathan Gamble
555681bdb5 LibWeb: Split PaintableWithLines from PaintableBox
No functional changes. I just hope to improve code navigation.
2026-01-12 11:00:14 +00: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
Sam Atkins
c446281844 LibWeb/CSS: Remove Transformation in favor of TransformationStyleValue
The Transformation class wasn't really accomplishing anything. It still
had to store StyleValues, so it was basically the same as
TransformationStyleValue, with extra steps to convert from one to the
other. So... let's just use TransformationStyleValue instead!

Apart from moving code around, the behavior has changed a bit. We now
actually acknowledge unresolvable parameters and return an error when
we try to produce a matrix from them. Previously we just skipped over
them, which was pretty wrong. This gets us an extra pass in the
typed-om test.

We also get some slightly different results with our transform
serialization, because we're not converting to CSSPixels and back.
2025-12-19 14:51:53 +01:00
InvalidUsernameException
28ba610f32 Everywhere: Avoid large rebuilds when editing (Immutable)Bitmap headers
This reduces the number of recompiled files as follow:
- Bitmap.h: 1309 -> 101
- ImmutableBitmap.h: 1218 -> 75
2025-11-28 18:32:48 +01:00
Psychpsyo
2db3796fd3 LibWeb: Implement CSS perspective-origin 2025-11-21 11:14:28 +00:00
Psychpsyo
ada3810779 LibWeb: Rename transform_box_rect() to transform_reference_box()
This is closer to its name in the spec.
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
Andreas Kling
dfa796a4e4 LibJS+LibWeb: Use GC::Weak instead of AK::WeakPtr for GC-allocated types
This makes some common types like JS::Object smaller (by 8 bytes) and
yields a minor speed improvement on many benchmarks.
2025-10-17 17:22:16 +02:00
Aliaksandr Kalenik
81aeee3fb4 LibWeb: Get rid of PaintableBox::is_viewport()
This function used layout node pointer to check if it's corresponding to
viewport. There is no need for that, since `is_viewport_paintable()`
does exactly the same check without going through layout node.
2025-10-14 11:23:29 +02:00
Aliaksandr Kalenik
9e3e581e14 LibWeb: Delete unused m_combined_css_transform from PaintableBox 2025-10-10 16:58:51 +02:00
Aliaksandr Kalenik
72aaef5a0f LibWeb: Delete non-const layout_node_with_style_and_box_metrics()
...from PaintableBox. It was used exclusively to go to corresponding DOM
node which could be done via direct DOM node pointer owned by paintable.
2025-10-10 16:58:51 +02:00
Luke Wilde
adeedabf54 LibWeb: Don't class mousewheel as handled if scroll offset isn't updated
Before this change, you could only scroll the current hovered scroll
container, even if it was at the beginning or end and thus having no
effect.

Now, if it doesn't update, it will not be classed as handled and will
move onto the next scroll container.
2025-10-07 19:43:07 +02:00
Aliaksandr Kalenik
086ef9e339 LibWeb: Delete unused Paintable::combined_css_transform() 2025-09-26 20:11:32 +02:00
zac
e96122a58c LibWeb: Remove redundant for_each_fragment method in PaintableBox 2025-09-24 12:33:17 +01:00
InvalidUsernameException
fbf47e57d0 LibWeb: Paint inspector overlays as a separate pass
The overlay shown for the node hovered in the inspector is painted as
part of the normal tree traversal of all paintables. This works well in
most cases, but falls short in specific scenarios:
* If the hovered node or one of its ancestors establishes a stacking
  context and there is another element that establishes a stacking
  context close by or overlapping it, the overlay and especially the
  tooltip can become partially hidden behind the second element. Ditto
  for elements that act as if they established a stacking context.
* If the hovered node or one of its ancestors involves clipping, the
  clip is applied to the overlay and espicially the tooltip. This can
  cause them to be partially invisible.
* Similarly, if the hovered node or one of its ancestors has a defined
  mask, the mask is applied to the overlay, often making it mostly
  invisible.
* No overlays are shown for SVG nodes because they are painted
  differently from HTML documents.

Some of these problems may be fixable with the current system. But some
seem like they fundamentally cannot work fully when the overlays are
painted as part of the regular tree traversal.

Instead we pull out painting the overlay as a separate pass executed
after the tree traversal. This way we ensure that the overlays are
always painted last and therefore on top of everything else. This also
makes sure that the overlays are unaffected by clips and masks. And
since overlay painting is independent from painting the actual elements,
it just works as well.

However we need to be careful, because we still need to apply some of
the steps of the tree traversal to get the correct result. Namely we
need to apply scroll offsets and transforms. To do so, we collect all
ancestors of the hovered node and apply those as if we were in the
normal tree traversal.
2025-09-19 10:17:56 +02:00
InvalidUsernameException
0e7749e6cd LibWeb: Unify similar debug overlay code paths
The debug option 'Show Line Box Borders' and the inspector overlay for
text nodes are conceptually similar. However they use two different
code paths. This commits unifies both to use the same code.
2025-09-19 10:17:56 +02:00
Jelle Raaijmakers
9e9db9a9dd LibWeb: Store correct text offsets in PaintableFragment
Previously, we were collapsing whitespace in Layout::TextNode and then
passed the resulting string for further processing through ChunkIterator
-> InlineLevelIterator -> InlineFormattingContext -> LineBuilder ->
LineBoxFragment -> PaintableFragment. Our painting tree is where we deal
with things like range offsets into the underlying text nodes, but since
we modified the original string, the offsets were wrong.

This changes the way we generate fragments:

  * Layout::TextNode no longer collapses whitespace as part of its
    stored "text for rendering", but moves this logic to ChunkIterator
    which splits up this text into separate views whenever whitespace
    needs to be collapsed.

  * Layout::LineBox now only extends the last fragment if its end offset
    is equal to the new fragment's start offset. Otherwise, there's a
    gap caused by collapsing whitespace and we need to generate a
    separate fragment for that in order to have a correct start offset.

Some tests need new baselines because of the fixed start offsets.

Fixes #566.
2025-09-12 15:34:09 -04:00
Jelle Raaijmakers
75d41859fd LibWeb: Remove PWL::layout_node_with_style_and_box_metrics()
These methods already exist in PaintableBox.
2025-09-12 15:34:09 -04:00
Tim Ledbetter
5eff541804 LibWeb: Construct filter chain at paint time
This allows us to use style and box metrics when applying filters.
2025-08-29 10:15:24 +01:00
ayeteadoe
3df8e00d91 LibWeb: Enable EXPLICIT_SYMBOL_EXPORT 2025-08-23 16:04:36 -06:00
Tim Ledbetter
cfc6439c12 LibWeb: Use shape-rendering to control anti aliasing for SVG paths
Anti-aliasing is disabled if `shape-rendering` is set to
`optimizeSpeed` or `crispEdges`.
2025-08-19 09:47:28 +01:00
Aliaksandr Kalenik
61114f6d16 LibWeb: Rename PaintContext to DisplayListRecordingContext
PaintContext dates back to a time when display lists didn't exist and it
truly represented "paint context". Renaming it to better align with its
current role.
2025-08-01 05:25:56 -04:00
Timothy Flynn
67cc02ab59 LibWeb+UI: Add an explicit IPC to handle mouse leave events
The faux position we created here is adjusted by the device pixel ratio
later on, which would invoke integer overflow on screens with a DPR
greater than 1.

Instead of creating special data for a mouse move event, let's just add
an explicit leave event handler.
2025-07-28 21:26:33 +02:00
Aliaksandr Kalenik
eed47acb1f LibWeb: Expand ClipFrame into clip rectangles during display list replay
Until now, every paint phase of every PaintableBox injected its own
clipping sequence into the display list:
```
before_paint: Save
              AddClipRect (1)
              ...clip rectangles for each containing block with clip...
              AddClipRect (N)

paint:        ...paint phase items...

after_paint:  Restore
```

Because we ran that sequence for every phase of every box, Skia had to
rebuild clip stack `paint_phases * paintable_boxes` times. Worse,
usually most paint phases contribute no visible drawing at all, yet we
still had to emit clipping items because `before_paint()` has no way to
know that in advance.

This change takes a different approach:
- Clip information is now attached as metadata `ClipFrame` to each
  DisplayList item.
- `DisplayListPlayer` groups consecutive commands that share a
  `ClipFrame`, applying the clip once at the start of the group and
  restoring it once at the end.

Going from 10 ms to 5 ms in rasterization on Discord might not sound
like much, but keep in mind that for 60fps we have 16 ms per frame and
there is a lot more work besides display list rasterization we do in
each frame.

* https://discord.com/channels/1247070541085671459/1247090064480014443
  - DisplayList items:  81844  -> 3671
  - rasterize time:     10 ms  -> 5 ms
  - record time:        5 ms   -> 3 ms

* https://github.com/LadybirdBrowser/ladybird
  - DisplayList items:  7902  -> 1176
  - rasterize time:     4 ms  -> 4 ms
  - record time:        3 ms  -> 2 ms
2025-07-14 15:48:28 +02:00