Commit Graph

31 Commits

Author SHA1 Message Date
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
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
Jelle Raaijmakers
9d09b3ce34 LibWeb: Add Paintable method to scroll offset into view 2026-02-11 11:04:53 +01:00
Andreas Kling
706eb0018c LibGC+LibWeb: Mark two GC::Cell functions as MUST_UPCALL
Let's make sure that subclasses always call their base class when
overriding these virtuals:

- void finalize()
- void visit_edges(Visitor&)
2026-02-06 13:50:54 +01:00
Jelle Raaijmakers
856091fb56 LibWeb: Implement support for text-decoration in ::selection 2026-02-06 10:47:50 +00:00
Jelle Raaijmakers
48aa199f09 LibWeb: Implement support for text-shadow in ::selection 2026-02-06 10:47:50 +00:00
Jelle Raaijmakers
98b4feb406 LibWeb: Apply CSS ::selection styling to text and form controls
Previously, text selection always used the system highlight color. This
implements support for the ::selection pseudo-element's background-color
and color properties.

For form controls like <input> and <textarea>, the selection style is
looked up on the shadow host element, since the actual text lives inside
their shadow DOM.

The text painting logic has been refactored to split fragments into
styled spans (before selection, selected, after selection) so that each
portion can be rendered with its appropriate colors, taking care not to
allocate in 99%+ of fragment rendering cases.
2026-02-06 10:47:50 +00:00
InvalidUsernameException
c810e99842 LibWeb: Do not clip inspector overlays
When painting inspector overlays for a highlighted node, we want to
ignore any clipping that may be going on due to explicit CSS properties,
stacking contexts or similar. That makes sure our overlay is not
obscured by the clipping.

This fixes a regression from 009ddd4823.
2026-02-01 14:29:59 +01:00
InvalidUsernameException
27b88b7665 LibWeb: Apply visual context when painting non-boxes as well
When highlighting something that does not correspond to a
`PaintableBox`, we still need to apply `AccumulatedVisualContext`s. In
that case, search for the closest `PaintableBox` ancestor and apply its
`AccumulatedVisualContext`

This bug is most easily observed by highlighting a text node on a page
that has a scrollbar and is scrolled down at least a bit.

This fixes a regression from 009ddd4823.
2026-02-01 14:29:59 +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
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
Jelle Raaijmakers
d00571719f LibGC+LibJS+LibWeb: Add Visitor::visit(Optional<T>)
No functional changes.
2026-01-06 10:55:56 +01:00
Jelle Raaijmakers
aa1abe778a LibWeb: Misc. code improvements
More usage of `as_if<T>`, fewer unnecessary `const_cast`s, etc. No
functional changes.
2025-12-08 20:12:23 +01:00
Psychpsyo
2db3796fd3 LibWeb: Implement CSS perspective-origin 2025-11-21 11:14:28 +00:00
Aliaksandr Kalenik
9e838cffb4 LibWeb: Copy "is inert" attribute into Paintable
...instead of reaching into DOM tree during hit-testing in order to
figure out if an element is inert. This is a part of the effert to make
possible running hit-testing solely based on data contained by the
paintable tree.
2025-10-14 11:23:29 +02:00
Aliaksandr Kalenik
086ef9e339 LibWeb: Delete unused Paintable::combined_css_transform() 2025-09-26 20:11:32 +02:00
Andreas Kling
eff9989aeb LibWeb: Only update paint-only properties in affected subtrees
Before this change, we always updated paint-only properties for every
single paintable after layout or style changes.

This could get very expensive in large documents, so this patch makes
it something we can do partially based on "repaint" invalidations.

This cuts down time spent in paint-only property update when scrolling
https://imdb.com/ from 19% to 5%.
2025-09-24 23:59:41 +02:00
Andreas Kling
beb70d2112 LibWeb: Add Paintable::debug_description()
I keep reimplementing this function over and over when debugging stuff,
so let's just have it around.
2025-09-24 23:59:41 +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
Jelle Raaijmakers
c31eff6a47 Everywhere: Use Optional<T>::ensure() where useful
No functional changes.
2025-09-17 12:01:18 -04:00
Jelle Raaijmakers
64093f7d26 LibWeb: Detach paint tree from layout tree in one go
We used to only walk the paintable root tree from the layout root and
detach paintables from there. In some cases, this could leave paintables
behind, so we added another loop that iterates over all layout nodes and
detaches their paintables, if any remained.

Instead of traversing two trees like this, just traverse the layout tree
once and detach the inclusive descendant's paintables, similar to how we
deal with the DOM tree immediately after that.
2025-09-01 11:20:43 +01:00
Dan Vittegleo
c9fbc2db0b LibWeb: Implement CSS border radius overlapping algorithm
Fixes rendering of elements with large border-radius values by scaling
radii proportionally when they exceed element dimensions per CSS spec.

Co-authored-by: Samyat Gautam <thesamyatgautam@gmail.com>
2025-08-21 11:52:38 +02:00
Andreas Kling
f4ee341a14 Revert "LibWeb: Store visibility for Paintables"
This reverts commit 7dc8062283.

This did not propagate correctly to paintables whose style was inherited
from an ancestor, causing rendering artifacts on https://linear.app/
2025-08-20 09:14:58 +02:00
Andreas Kling
3b63582068 LibWeb: More thoroughly detach layout/paint trees from each other
Before committing a new layout (and thus building a new paint tree)
we now go through both the old paint tree and the layout tree and detach
them from each other.

This is a little extra work, but it ensures that there are no lingering
references across the trees, which we were apparently accumulating in
some cases on Discord, causing GC leaks.
2025-07-30 16:58:29 -04:00
Jelle Raaijmakers
7dc8062283 LibWeb: Store visibility for Paintables
For every invocation of `::before_paint()` and `::after_paint()`, we
would reach into the node's computed values to determine its visibility.
Let's just do this once during construction of the paintable instead,
since this was showing up in profiles.
2025-06-17 11:55:28 +02:00
Sam Atkins
bfd7ac1204 LibWeb+WebContent+UI: Support image cursors
The `cursor` property accepts a list of possible cursors, which behave
as a fallback: We use whichever cursor is the first available one. This
is a little complicated because initially, any remote images have not
loaded, so we need to use the fallback standard cursor, and then switch
to another when it loads.

So, ComputedValues stores a Vector of cursors, and then in EventHandler
we scan down that list until we find a cursor that's ready for use.

The spec defines cursors as being `<url>`, but allows for `<image>`
instead. That includes functions like `linear-gradient()`.

This commit implements image cursors in the Qt UI, but not AppKit.
2025-02-28 13:50:13 +01:00
Tim Ledbetter
cb405c773b LibWeb: Ensure inert elements are not visible for hit testing 2025-02-21 12:41:57 +00:00
Aliaksandr Kalenik
9f541c363d LibWeb: Allow stacking context to only be created by PaintableBox
For a while we used the wider Paintable type for stacking context,
because it was allowed to be created by InlinePaintable and
PaintableBox. Now, when InlinePaintable type is gone, we can use more
specific PaintableBox type for a stacking context.
2024-11-18 20:07:30 +01:00
Shannon Booth
f87041bf3a LibGC+Everywhere: Factor out a LibGC from LibJS
Resulting in a massive rename across almost everywhere! Alongside the
namespace change, we now have the following names:

 * JS::NonnullGCPtr -> GC::Ref
 * JS::GCPtr -> GC::Ptr
 * JS::HeapFunction -> GC::Function
 * JS::CellImpl -> GC::Cell
 * JS::Handle -> GC::Root
2024-11-15 14:49:20 +01:00
Timothy Flynn
93712b24bf Everywhere: Hoist the Libraries folder to the top-level 2024-11-10 12:50:45 +01:00