Display lists kept canvas and nested navigable content alive through
ExternalContentSource objects. That made the resource graph depend on
process-local object identity instead of a stable surface handle, which
blocks compositor process isolation and made teardown-sensitive embedded
content harder to reason about.
Allocate CompositorSurfaceId values for canvases and child navigables,
publish their backing stores to the owning compositor, and paint them
with DrawCompositorSurface. Child navigables now publish to parent
compositors by CompositorContextId instead of raw object pointers, so
the in-process path uses the same stable addressing model required by a
remote compositor.
Clear and skip stale child surfaces during teardown, preserve Skia
canvas state while drawing compositor surfaces, and add display-list
coverage for canvas and iframe compositor surfaces. The nested navigable
async-scrolling baseline now expects DrawCompositorSurface.
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.
CompositorThread still owned the backing-store surfaces and allocation
helpers after BackingStoreManager moved into the compositor. That left
the thread class responsible for low-level surface creation details even
though the manager now decides when backing stores need to change.
BackingStoreManager was owned by Navigable and allocated on the GC heap,
which left backing-store sizing decisions on the main thread even though
the compositor thread was already responsible for allocating the actual
surfaces. That split ownership makes it harder to isolate the compositor
behind a process boundary.
Move the manager into LibWeb's compositor code and let CompositorThread
thread data own it. The main thread now reports viewport size changes
through a compositor command, and the compositor uses that message to
decide when to resize and publish backing stores. The delayed shrink
timer remains with the main-thread CompositorThread facade so it can use
the Core event loop, but it now only sends another viewport-size update.
This removes the GC edge from Navigable, drops stale WebContent includes
and keeps the existing resize padding and delayed shrink behavior.
Set an explicit resource cache limit on Skia GPU contexts and ask
Skia to perform deferred cleanup from the compositor at a throttled
cadence. When resource usage crosses high watermarks, request more
aggressive cleanup and purge scratch resources only as a last step.
Also cap the decoded frame Skia image cache by approximate bitmap
bytes and entry count so route changes and pointer-driven repaint
churn cannot keep an unbounded number of uploaded image textures
alive.
When a fresh main-thread scroll snapshot arrived while compositor-side
async scroll offsets were still pending adoption, we rebased the pending
delta onto the snapshot's current offset. If that snapshot had already
been reconciled with the compositor-visible position, the unadopted
delta was applied again and the scroll offset jumped.
Use the stored compositor scroll offset as the restoration target
instead. That value already includes the pending async movement, so
reapplying it is idempotent across display-list and scroll-state
updates.
Async scrolling kept only the compositor-visible absolute scroll offset
for pending scrolls. If script changed the same scrolling box before
the main thread adopted the pending offset, adoption could write the
stale absolute compositor offset back to the DOM and lose the script
update.
Track the unadopted compositor delta alongside the absolute offset
used for compositor presentation. When fresh scroll state reaches the
compositor, and when the main thread adopts pending async scrolls,
apply that delta on top of the current main-thread offset instead of
assigning the old absolute value.
Adoption can run during the event-loop scroll steps, before the later
rendering-update layout pass has made paintables safe to query. Keep
element adoption on the stored DOM scroll offset and apply viewport
deltas through the navigable, so a dirty layout tree does not trip the
paintable freshness invariant.
The async scrolling text test covers the script-scroll race and also
dirties layout before adoption. The old paintable-box adoption path
crashed in that case.
Async scrolling tests used requestAnimationFrame() as a proxy for the
compositor thread to return pending scroll updates to the main thread.
That waited for a rendering opportunity, so tests could observe stale
DOM scroll offsets when the compositor update had not been adopted yet.
Make internals.wheel() return a promise that resolves after a tracked
async scroll operation has been applied by Navigable. Tracking is opt-in
from the internals test API, so regular page wheel input and compositor
IPC keep the boolean async-scroll path without allocating operation IDs.
Tracked test scrolls are the only operations that record completions.
Update async scrolling and wheel propagation tests to await the wheel
promise directly instead of relying on animation frame timing in tests.
CompositorThread kept both m_queued_rasterization_tasks and
m_presented_bitmap_id_awaiting_ack to describe the same presentation
back-pressure state. Since bitmap-specific ready_to_paint
acknowledgements landed, the counter can only be zero or one, and every
meaningful transition is already guarded by the awaited bitmap id.
Use the pending bitmap id as the single source of truth for whether
the UI process still owns a presented backing store. Rename the
ready-to-paint handler to describe the ack it processes, remove the
unused condition variable, and update compositor debug logging to report
the awaited bitmap instead of a synthetic raster task count.
Nested scroll nodes were present in the async scroll tree, but the
compositor rejected any non-viewport wheel target and tracked only one
pending viewport offset. That prevented element scrollports from being
scrolled asynchronously and made compositor-side offsets impossible to
adopt after the main thread rebuilt scroll state.
Let async wheel commands target any scroll node selected by the tree.
Store every offset produced by a compositor scroll and reapply pending
offsets by stable ID across display-list and scroll-state updates. The
main thread now adopts viewport, element and pseudo-element offsets
before rendering-update observers run, including scroll event
bookkeeping for element scrollers.
Carry the wheel hit-test rejection reason through the enqueue path while
doing this, so main-thread and blocking-wheel regions remain explicit
rather than being collapsed into a missing target. Existing nested
async-scrolling text tests cover the successful nested scroll path.
Nested async scrolling lets the compositor accept element scroll nodes,
but the main-thread wheel path could ask a compositor tree from an
older document to handle a synthetic wheel. If that stale tree contained
a matching scroll node, the event was reported as handled while the
active document never received an adoptable scroll offset. That made
wheel tests observe no element scroll, and could let an iframe consume
a wheel that should have fallen back to its parent.
Pass the active document id into the main-thread async wheel enqueue
path and reject compositor hit-test results from any other document.
This preserves synchronous fallback until the current document's scroll
tree has reached the compositor.
The wheel consumption and iframe boundary tests now wait for async
scroll adoption before asserting DOM-visible scroll offsets, so they
cover both the nested async-scroll path and the boundary fallback.
Async wheel routing used scroll node scrollports as the hit-test
surface. That works while the scrollable content is the topmost content
under the pointer, but it ignores the rest of the painted scene. A
sibling with a higher z-index can visually cover a nested scroller
without being part of that scroller's subtree. In that case the
compositor could still pick the covered scroller from its scrollport
rect and consume the wheel event, even though normal hit testing would
target the covering element and let the page or an ancestor handle the
scroll.
Record wheel hit-test targets as display-list metadata in paint order
and resolve each target to the scroll frame that should receive wheel
deltas. The compositor can then walk those targets from front to back,
preserve non-passive wheel regions as main-thread barriers, and only
scroll the node associated with the topmost hit-testable box.
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.
Paint compositor-owned viewport scrollbars whenever async scrolling
state is available, instead of hiding them when there is no hover or
capture state.
Adopt pending async viewport scroll offsets before the rendering update
runs scroll steps and IntersectionObserver updates. Queue a rendering
update after the compositor applies an async viewport scroll so
compositor-only wheel scrolling can notify observers without waiting for
unrelated main-thread work.
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.
Route mouse events over the compositor IPC path while async scrolling is
enabled, and let the compositor capture viewport scrollbar drags before
the main-thread event path sees them. Dragging mutates the compositor
scroll tree and schedules an async-scroll present, so the viewport thumb
and content can move without a main-thread round trip.
Keep normal main-thread event handling for misses and preserve the wheel
bypass behavior for page events.
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.
Keep compositor-side scroll snapshots aligned with async viewport
scrolling when main-thread scroll state arrives while an async viewport
offset is pending. A stale scroll-state-only update could otherwise
replace the snapshot used for display list replay and wheel hit-testing
with an older viewport offset, even though the compositor had already
presented newer async scroll positions.
Teach AsyncScrollTree to set a node scroll offset directly and use it
when reconciling both display-list and scroll-state updates. Recompute
the main-thread viewport rect after display list recording as pending
async scroll adoption can move the viewport before presentation.
The synchronous wheel path treats wheel deltas as CSS-pixel scroll
distances, while the async compositor scroll tree mutates scroll state
stored in device pixels. Passing the same unscaled delta into the
compositor made async scrolling advance too little whenever
device pixels per CSSPixel was greater than 1, so scrolling felt slower
than with async scrolling disabled.
Convert wheel deltas before crossing the compositor boundary and make
the compositor IPC carry only the device-pixel position and delta it
needs. This keeps AsyncScrollTree device-pixel native and makes async
viewport scrolling match the synchronous path across high-DPI displays
and page zoom levels.
Bump a page-level wheel listener generation whenever non-passive wheel
listeners are added or removed. Store that generation in async scrolling
snapshots, and mark compositor wheel admission stale when listener state
changes before a current snapshot arrives.
This keeps the UI-process compositor wheel bypass from trusting an old
no-listener snapshot after script has installed a cancelable wheel
listener. Add async-scrolling coverage for the listener generation bump
and the fresh routing state after a blocking wheel listener is added.
Allow compositor wheel routing to stay enabled when the scroll tree also
contains non-viewport scroll nodes. The compositor still rejects an
individual wheel whose hit-test target is not the viewport, so element
scrollers continue to fall back to the main thread until their scroll
offsets can be adopted safely.
Move the routing admission helper next to the async scrolling state and
expose test-only internals for the routing result and wheel target. Add
coverage for a page that has both a viewport scroller and a nested
element scroller.
Add main-thread wheel regions to async scrolling snapshots for embedded
navigable containers. A viewport scroll node covers the iframe box, so
the compositor must explicitly treat that rectangle as a synchronous
routing boundary before it accepts a UI-process wheel bypass.
Hit-test those regions in both internals admission and the compositor's
async enqueue path. The enqueue path now stores the hit-tested scroll
target in the queued command, ensuring accepted input is applied to the
same target that passed admission instead of being reselected later.
Add a text test for iframe routing. A wheel over the nested navigable is
rejected for async scrolling, while a wheel over the parent viewport
remains admitted.
Trace the compositor state transitions that matter once presentation and
async scrolling share one path: page registration, backing-store swaps,
did_paint delivery, ready_to_paint acknowledgements, wheel admission,
and deferred presents waiting for reusable bitmaps.
Keep the logs under COMPOSITOR_DEBUG and use the [Compositor] prefix
consistently. The trace is about ownership and backpressure instead of
dumping every wheel event detail, so stalls can be diagnosed without
reintroducing noisy async-scrolling-only logging.
Use the snapshot from the previous commit to let CompositorThread apply
experimental viewport wheel deltas when async scrolling is enabled. The
event handler first performs synchronous admission on the main thread,
then enqueues a compositor scroll command instead of mutating live
document scroll state directly.
Rasterize accepted scrolls through the same compositor presentation
path added earlier. The compositor stores the newest async viewport
offset so the next main-thread display-list recording can adopt it
before repainting, preventing older paints from snapping the visible
position backward.
Keep DOM wheel dispatch on the main thread. When the compositor already
performed the default action, dispatch the wheel as non-cancelable and
suppress a second default scroll. Non-viewport targets, nested
scrollers, and pages with blocking wheel listeners stay synchronous.
Introduce a dedicated Compositor IPC channel between the UI process and
WebContent. Use it for backing-store setup, presented bitmap delivery,
and bitmap-specific ready_to_paint acknowledgements.
This makes CompositorThread the single owner of frame presentation
bookkeeping before async scrolling starts producing frames without the
main thread.
Remove old paint and backing-store messages from WebContentClient and
PageClient so the UI process no longer observes two presentation
protocols.
Create Libraries/LibWeb/Compositor and make the existing rendering
thread the first owner in that subsystem. Rename RenderingThread to
CompositorThread so later commits can grow it into the presentation
owner without leaving that vocabulary in HTML.
Keep display-list rasterization and delivery behavior unchanged in this
commit. Add COMPOSITOR_DEBUG to AK/Debug.h.in in the same step so
compositor diagnostics live beside the rest of the project-wide debug
toggles from the start of the stack.
2026-05-12 20:57:08 +02:00
Renamed from Libraries/LibWeb/HTML/RenderingThread.cpp (Browse further)