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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
Extract the repeated pattern of transforming a rectangle from absolute
coordinates to viewport coordinates via the accumulated visual context
into a helper method.
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.
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.
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
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
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.
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.
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).
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.
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.
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.
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.
When the mouse is dragged from inside a scrollable container to outside
of it, we now automatically scroll the container so the selection can be
extended. Scroll speed scales with the distance past the scrollport
edge, capped at a maximum. Edges close to the viewport boundary get a
wider activation zone so the speed ramp works predictably even when the
mouse has limited room to move.
The logic is encapsulated in AutoScrollHandler, which EventHandler
creates lazily on mouse selection start.
If the scrollbar was made to be invisible, it should no longer have a
size. This actually disables the scrollbar functionality - previously it
was only invisible, but still worked - and allows hit testing to pass
through to elements and fragments behind it.