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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.