Commit graph

200 commits

Author SHA1 Message Date
sideshowbarker
c2db5c0dcb LibWeb: Fix a crash when selecting across an element with no layout box
Problem: Crash when dragging a text selection across an element with no
layout box (e.g., a display:contents element).

Cause: set_user_selection() looks for a user-select:contain ancestor by
walking up the tree via two while-loop conditions that called
layout_node()->user_select_used_value() for each element. But elements
without layout boxes have no layout nodes. So that can dereference null.

Fix: Check layout_node() in the tree-walking while conditions.

Fixes: https://github.com/LadybirdBrowser/ladybird/issues/10062
2026-06-14 17:40:21 +02:00
Andreas Kling
9340d2d1a3 LibWeb: Make layout nodes refcounted
Move the layout tree from GC allocation to refcounted ownership so
removed layout and paint subtrees are destroyed synchronously instead
of waiting for the next GC sweep. This dramatically reduces GC memory
usage peaks after layout tree churn and makes it easier for memory use
to fall back after large document updates.

Update layout factories, tree traversal, SVG layout node creation,
paintable back-pointers, and pseudo-element layout links to use RefPtr
ownership.

Make display: contents follow the same shape as Blink and WebKit: the
element itself does not create a layout node, and its children are
flattened into the nearest layout parent. Wrap direct non-whitespace
text in an anonymous inline node when the boxless element contributes
inherited style to that text.

Use an internal inline wrapper for display: contents pseudo-elements
so generated content can still participate in layout, painting, hit
testing, and pseudo-element queries. Keep CSSOM reporting the computed
display value from the pseudo style, not the internal wrapper.

Remove the retained out-of-tree layout node list and its testing hook,
since the flattened model does not need a side owner for boxless
elements. Add coverage for inherited text style, dynamic insertion
order, pseudo-element hit testing, and computed style queries.
2026-06-07 20:52:49 +02:00
Andreas Kling
d9d8c5ce89 LibWeb: Allow Option-generated text insertion
Track whether a keydown should perform text insertion separately from
the delivered code point. Native frontends and text-oriented test paths
can now mark events that came from text input, while shortcut-style key
events keep Alt-modified default insertion suppressed.

This lets macOS Option-generated text such as Option+A and Option+Space
insert into editable controls without making plain Alt shortcuts insert
their base character. Add coverage for Alt text, Ctrl+Alt text,
separator text, Ctrl-only shortcuts, and Alt shortcuts.
2026-06-03 13:51:32 +02:00
Andreas Kling
fe188b7d57 LibWeb: Make message drag selection more eager
Use selection-specific caret hit testing while starting and extending
mouse selections. The public caret-position API keeps its normal line
ranking, but selection drags now snap below-line movement to line edges
and prefer the previous line when starting in a nearby inter-line gap.

Add coverage for dragging from message text, after-text space, gutters,
author names, avatar-adjacent areas, and row bottoms so these inert
message regions reliably start selection.
2026-05-30 21:36:47 +02:00
Andreas Kling
acc86e9eb1 LibWeb: Add a caret hit-test debug overlay
Add a debug-menu toggle for caret hit testing at the mouse position.
Paint the insertion rect and log the result so selection bugs can be
inspected without temporary probes.

Request frames and repaint invalidation when the overlay state changes.
Also repaint when the caret rect moves within the same text node.
2026-05-30 13:50:48 +02:00
Andreas Kling
0c6bb97898 LibWeb: Resolve selection from retained hit-test data
Clamp mouse selection positions to the active scrollport during
autoscroll. This keeps selection stable when the pointer leaves the
viewport or crosses fixed page chrome while a drag is active.

Harden user-select boundary adjustment for document edge clamping. Avoid
null traversal results, and cover viewport and subtree edge cases with
regression tests.
2026-05-30 13:50:48 +02:00
Andreas Kling
7445cff5e8 LibWeb: Use retained data for hit testing
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.
2026-05-30 13:50:48 +02:00
Sam Atkins
30632e2dcb LibWeb+WebContent: Add node picker hit testing
Picking needs hit testing in the page process, where layout and event
targeting state live. Expose a small page-level query and WebContent IPC
entry point that returns the node id at a viewport position.

This lets DevTools ask WebContent what the picker is pointing at without
duplicating hit-test logic outside LibWeb.
2026-05-29 17:01:34 +01:00
Timothy Flynn
adf5e0a226 LibWeb+LibWebView+UI: Use the system selection text for primary pasting
Commit 218d82cb65 added support for
pasting text with the middle mouse button. But primary pasting is
actually meant to interact with the "selection" clipboard, not the
text clipboard.
2026-05-27 13:33:44 -04:00
Aliaksandr Kalenik
9ab311507e LibWeb+UI: Synthesize ctrl-wheel events for pinches
Trackpad pinches applied directly as visual viewport zoom, so pages
could not observe the gesture or cancel the browser default. Canvas
apps such as maps expect the ctrl-wheel path instead.
2026-05-27 12:53:08 +02:00
Andreas Kling
212330205f LibWeb: Fix link drag cancellation state
Clear mousedown activation state whenever primary mousedown tracking is
reset. Also route local link drag cancellation through the drag-and-drop
end path when Escape, viewport leave, or mouseup outside the viewport
ends the drag.

These paths can otherwise leave drag input suppression active after the
user has ended or canceled the drag, or skip the required dragend event
for source cleanup.

Add an internals mouseLeave primitive and a focused text test. It covers
stale :active state, dragend dispatch for cancellations, viewport leave,
outside-viewport mouseup, Escape canceling, and later click delivery
after each canceled drag.
2026-05-27 09:52:15 +02:00
Andreas Kling
75375e8cef LibWeb: Update hover after async scrolling stops
Remember the last mouse or wheel position seen by the event handler.
Schedule a hover refresh once async scrolling goes idle.
This lets hover state and boundary events follow content under a
stationary pointer after scrolling has stopped.

Add a text test that keeps hover on the old target while scrolling is
active. It then checks that hover moves after the idle update without
extra mousemove or pointermove events.
2026-05-22 14:33:39 +02:00
sideshowbarker
a99c2b4655 LibWeb: Avoid crashing when selecting text in a shadow root
Problem: Selecting text with the mouse across the boundary between two
nodes inside a shadow root crashes the browser.

Cause: To honor “user-select: contain”, set_user_selection() walks the
ancestor chain up from the selection anchor. For a selection inside a
shadow tree, the walk stops at the shadow root — which has no parent
node. The check after the walk then dereferences that node’s
layout_node(). A shadow root has no layout node — so that dereferences a
null pointer.

Fix: Null-check layout_node() in the two checks after the ancestor walk.
A node with no layout node isn’t a “user-select: contain” element — so
the selection is not clamped to it.

Fixes https://github.com/LadybirdBrowser/ladybird/issues/9332
2026-05-22 09:36:10 +01:00
Aliaksandr Kalenik
6b912038d3 LibWeb+WebContent: Route compositor through in-process IPC
LibWeb still exposed the concrete CompositorThread to Page,
Navigable, and EventHandler, so compositor IPC would have leaked the
thread implementation into callers. The old thread APIs also bundled
page presentation callbacks and main-thread wakeups into the same
object, which made it awkward for WebContent to put an actor boundary
in between.

Introduce CompositorHost and context handles as the caller-facing API,
and move shared compositor protocol values out of CompositorThread. Add
WebContentCompositor IPC endpoints and route PageHost through a paired
in-process transport. The actor owns CompositorThread with explicit
main-thread and UI presentation clients, while screenshot completion is
serialized on the WebContent event loop using request IDs.

The intention for introducing IPC here is to prepare for moving the
compositor thread into a separate process.
2026-05-21 11:45:06 +01:00
Shannon Booth
387cd6e2e2 LibGC: Default-construct RootVector from the global heap
Similar to GC::Root<T>, make GC::RootVector<T> constructible without
explicitly passing a Heap.

This is implemented by having RootVectorBase use GC::Heap::the() for
heap-free construction.
2026-05-20 20:37:55 +02:00
Andreas Kling
5fedacb7de LibWeb: Move textarea Home and End by line
Make unmodified Home and End in textarea use the current line
boundary instead of the whole control. Keep modified Home and End on
the existing whole-control path so Ctrl+Home and Ctrl+End still jump
across the textarea.

Update the textarea keyboard navigation test to cover the fixed line
movement and the preserved modified-key behavior.
2026-05-20 12:26:20 +02:00
Andreas Kling
720e8e6db0 LibWeb: Populate platform mouse event data
Set platform pointer events as primary mouse pointer events. This lets
pages recognize real mouse input from pointermove before they install
mouse tracking behavior.

Also thread platform mouse coordinates through hover target changes.
This makes mouseover, mouseout, mouseenter, mouseleave, and matching
pointer boundary events trusted and gives them the coordinate data from
the originating platform mouse event.

Cover both paths with UIEvents text tests.
2026-05-19 11:22:02 +02:00
Aliaksandr Kalenik
81b5b343d9 LibWeb: Share WebContent compositor thread across pages
Move compositor-thread ownership to WebContent's PageHost so every Page
object in one WebContent process registers its navigables on the same
compositor thread. This covers auxiliary pages created by window.open(),
while worker and SVG helper pages continue to skip compositor thread
creation.

Keep page presentation keyed by page id. Each presenting context records
the page id it presents for, and static compositor entry points route
ready-to-paint, async scrolling, and viewport scrollbar events to that
page's presenting context on the shared thread.
2026-05-18 20:11:31 +02:00
Andreas Kling
541828dbb1 LibWeb: Respect overflow axes for wheel scrolling
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.
2026-05-17 14:19:36 +02:00
Aliaksandr Kalenik
f743263871 LibWeb: Let internals.wheel await async scroll adoption
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.
2026-05-15 19:10:27 +02:00
Timothy Flynn
af7b2b5e8a LibWeb+LibWebView+WebContent: Add a setting to control primary pasting 2026-05-15 15:47:39 +02:00
Timothy Flynn
218d82cb65 LibWeb+LibWebView+WebContent: Support primary pasting with middle mouse
When the middle mouse button is clicked on a text input control or
contenteditable node, we now request the UI process to paste text into
that node.
2026-05-15 15:47:39 +02:00
Timothy Flynn
07e4c5a4b7 LibWeb: Organize EventHandler a bit
* Organize the order of method declarations/definitions (e.g. the mouse
  event handlers were nowhere near each other).

* De-publicize methods that are only used internally.

* Remove unused methods / includes.
2026-05-14 14:50:39 -04:00
Timothy Flynn
6ae9aa6e15 LibWeb: Move overly complex initializers outside of if statements
The inlined initializers were too difficult to read here, and did not
reduce lines of code.
2026-05-14 14:50:39 -04:00
Aliaksandr Kalenik
ace76854fc LibWeb: Reject stale async wheel targets
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.
2026-05-14 19:41:32 +02:00
Aliaksandr Kalenik
7bce509718 LibWeb: Recompute wheel default action target after layout updates
Wheel listeners can mutate layout before the wheel default action runs.
The old code kept using the paintable found before dispatch, so a
removed or moved scroller could still be treated as the default-action
target, and the viewport fallback used pre-dispatch layout state.

After dispatch, update layout and hit-test the wheel position again
before walking scrollable containing blocks. Fall back to viewport
scrolling only after that fresh target fails to consume the delta.
2026-05-14 19:41:32 +02:00
Aliaksandr Kalenik
82b016ed19 LibWeb: Rebuild async scrolling from display list
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.
2026-05-13 18:36:07 +02:00
Andreas Kling
9f35827aa0 LibWeb: Use double precision for wheel scroll deltas
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.
2026-05-13 11:00:59 +02:00
Aliaksandr Kalenik
0a41859ae9 LibWeb+LibWebView: Feed async scroll deltas in device pixels
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.
2026-05-12 22:35:54 +02:00
Andreas Kling
f07b55c2df LibWeb: Scroll the viewport on the compositor thread
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.
2026-05-12 20:57:08 +02:00
Shannon Booth
5adfd1c43a LibWeb/Bindings: Generate struct definitions from IDL dictionaries
Previously we were inconsistent by generating code for enum definitions
but not generating code for dictionaries. With future changes to the
IDL generator to expose helpers to convert to and from IDL values
this produced circular depdendencies. To solve this problem, also
generate the dictionary definitions in bindings headers.
2026-05-09 10:49:49 +02:00
Aliaksandr Kalenik
f8640d813a LibGfx+LibWeb: Make DecodedImageFrame a value type
DecodedImageFrame only wraps a ref-counted Bitmap and color-space
metadata. The frame object itself does not provide shared mutable
state or lifetime ownership beyond those members, so ref-counting it
adds an unnecessary layer of indirection.
2026-05-07 16:08:13 +02:00
Timothy Flynn
b221d7fe8b LibWeb+LibWebView+WebContent+UI: Add an action to cut text 2026-05-07 09:13:06 -04:00
Aliaksandr Kalenik
568b7ce7ea LibWeb: Make Paintable tree ref-counted
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.
2026-05-07 15:03:44 +02:00
Aliaksandr Kalenik
76c79ee522 LibGfx: Remove ImmutableBitmap
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.
2026-05-05 14:39:17 -05:00
Aliaksandr Kalenik
f916b3e11f LibWeb: Skip layout update in handle_mousemove when not needed
Only run update_layout() during mousemove handling when the result is
actually consumed, i.e. when the middle-button autoscroll handler or
mouse selection is active. This avoids forcing a synchronous layout
on every mousemove event in the common case.
2026-04-25 15:45:12 +02:00
Zaggy1024
22c1b72588 LibWeb: Prevent dragstart after a prevented mousedown or dragstart
Also, explicitly prevent drag events from firing when the context menu
opens. This will only be the case on macOS, since its context menu is
opened by Ctrl+mousedown. This replaces the prior exception preventing
drag events when Ctrl is held during mousedown.

Fixes #9018 and #9019
2026-04-22 07:34:18 -04:00
Andreas Kling
8233200bc3 LibWeb: Chain wheel scrolling past iframe limits
Let wheel scrolling over iframes fall through to the parent document
when the child document cannot make progress. This covers both iframes
with no scrollable viewport and iframes that are already at their
scroll boundary.

Treat nested wheel events as terminal only when the child document
actually consumes them or cancels them. For iframe viewport scrolling,
check whether the child viewport position changed before reporting the
wheel event as handled.
2026-04-17 18:52:12 +02:00
Sam Atkins
7c1d359790 LibWeb: Clean-up more input state after running each test
The clean-up in 71c457c36e turns out to
not be enough for all cases. So be more thorough and clear up anything
that could affect the next test.

In particular this fixes flakiness in `Text/input/select-text.html` but
hopefully it solves the issue for good!
2026-04-17 14:13:18 +01:00
Timothy Flynn
331815f327 LibWeb: Enable middle mouse autoscroll on middle mouse clicks
We previously supported autoscroll while the middle mouse button was
pressed. We now also support clicking the middle mouse button in-place
to begin autoscroll. Pressing any mouse button or the escape key will
exit this mode.
2026-04-15 13:37:43 -04:00
Timothy Flynn
79893b9cef LibWeb+LibWebView+WebContent: Add a setting to control autoscrolling 2026-04-13 13:01:45 -04:00
Timothy Flynn
1931636ac3 LibWeb: Support auto-scrolling contains when the middle mouse is pressed
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.
2026-04-13 13:01:45 -04:00
Timothy Flynn
39d7abee2f LibWeb: Do not initiate selections when ctrl is pressed on macOS
On macOS, this will have opened a context menu. On other platforms, this
would start a multi-region selection.
2026-04-09 10:08:06 -04:00
Timothy Flynn
57e711978d LibWeb: Do not initiate drag-and-drop when ctrl is pressed 2026-04-09 10:08:06 -04:00
Timothy Flynn
cfe7ddc805 LibWeb: Add support for handling drag-and-drop events of DOM elements
This allows dragging elements on the page and dropping them onto other
elements. This does not yet support dragging text.

The test added here is manual; the WPT tests rely heavily on WebDriver
actions.
2026-04-05 11:34:42 -04:00
Timothy Flynn
b7076c366d LibWeb: Work around a spec bug regarding cancelling dragenter events
The spec dictates that dragenter events must be cancelled in order for
drops to be accepted on the entered element. Web reality disagrees, as
all three major browsers do not have this requirement.
2026-04-05 11:34:42 -04:00
sasetz
e17d797bdb LibWeb: Remove forceful resetting cursor in nested navigables 2026-03-23 09:05:13 +01:00
Zaggy1024
07cf09a0f2 UI/AppKit+LibWeb: Handle the Ctrl+click context menu in EventHandler
Changing Ctrl+click to a secondary click is incorrect. It prevents
sites from using Ctrl+click themselves. Instead, just maybe open the
context menu in mousedown for primary clicks with Ctrl pressed.

Fixes the autofire shortcut not working in the Humble Mozilla Bundle's
asm.js FTL.
2026-03-17 04:01:29 -05:00
Zaggy1024
7236f5adfc LibWeb: Open the context menu on mousedown instead of mouseup
This matches the behavior on KDE, GNOME and macOS.

Windows will need an override to switch this to mouseup.
2026-03-17 04:01:29 -05:00
Zaggy1024
18c594aba5 LibWeb: Continue to fire mousemove/up when dragging outside the window
The spec specifies that we may do this. Other browsers target the html
element when the cursor leaves the window during a drag, so we do the
same.
2026-03-17 04:01:29 -05:00