#1184

Bug: WebKit crashes when drag-selecting text that auto-scrolls a diff (display:grid on [data-code])

OpenOLavrikopened 21d ago

Prerequisites

  • I have searched for duplicate or closed issues
  • I have read the contributing guidelines

Describe the issue

On WebKit (Safari, and WKWebView hosts such as Tauri/Electrobun), drag-selecting text in a FileDiff until the selection reaches the viewport edge and the view auto-scrolls crashes the web-content process (Safari shows "A problem repeatedly occurred"; in a WKWebView the whole page reloads/dies).

Conditions:

  • The diff must be tall enough to scroll.
  • It happens during a native text-selection drag that triggers auto-scroll (dragging to the top/bottom edge and holding). A selection fully inside the viewport (no scroll) is safe.
  • Chromium does not hard-crash (the view just scrolls away under the pointer); this is WebKit-specific.

Root cause (bisected): the crash is tied to [data-code] being display: grid. WebKit crashes on native selection + scroll inside a CSS grid. I injected CSS into the component's shadow root and re-ran the exact gesture on WebKit:

Shadow CSS overrideResult
[data-code]{ display: block }no crash
[data-code]{ display: flex; flex-direction: column }no crash
[data-code]{ contain: none } / paint / layout / strictstill crashes
[data-code]{ overflow: visible } / overflow: auto (self-scroll)still crashes
[data-code]{ content-visibility: visible }, isolation: isolate, will-change: transform, transform: translateZ(0)still crashes
[data-line] / [data-content] / [data-gutter] → display: blockstill crashes

So it is specifically display: grid on the scrolled element that holds the selectable lines — not virtualization (freezing the virtualizer/render does not help), not contain, not overflow, and not which element scrolls (a grid that scrolls itself also crashes).

Because [data-code] must be grid for the gutter/content columns + subgrid row alignment, there is no layout-preserving CSS workaround for consumers (display:block collapses the layout). This needs a change in Diffs (or a WebKit fix).

Suggested direction: render the selectable, scrolled diff lines without display:grid on the scroll element — e.g., a row-major table/flex layout, or manage selection internally (Monaco-style) — so native selection + auto-scroll no longer runs over a scrolled CSS grid.

Reproduction

No sandbox needed — reproduces on your own demo:

  1. Open Safari (or any WKWebView).
  2. Open a long FileDiff (enough lines to scroll), e.g. on https://diffs.com.
  3. Press inside the diff text and drag the selection to the bottom edge; keep holding so the diff auto-scrolls.
  4. The web-content process crashes.

Confirm the cause: before step 3, inject into the component's shadow root const s=new CSSStyleSheet(); s.replaceSync('[data-code]{display:block!important}'); shadowRoot.adoptedStyleSheets=[...shadowRoot.adoptedStyleSheets,s]; — the same gesture no longer crashes (layout breaks, but it proves the trigger).

I verified this with Playwright WebKit against a real app embedding @pierre/diffs; happy to share the spec if useful.

What browser(s) are you seeing the problem on?

Safari (WebKit), WKWebView (Tauri/Electrobun). Not Chrome.

What version of @pierre/diffs are you using?

1.5.1 (React FileDiff)

Activity

4
OLavrik21d ago

Correction / status update — please don't treat display:grid as the confirmed root cause yet.

I originally reported the trigger as [data-code] { display: grid }, based on a shadow-CSS bisection in our app (display:block/flex → no crash). That conclusion is not safe: with display:block the diff layout collapses and the code text is no longer rendered/selectable, so "no crash" there may simply mean "nothing to select". I'm retracting that specific claim.

What is still solid:

  • Crash signature (macOS 15.7.9, WebKit com.apple.WebKit.WebContent): EXC_BREAKPOINT / SIGTRAP — a RELEASE_ASSERT, not a segfault — with the faulting stack:
    WebCore  <deduplicated_symbol>            ← inlined assert
    WebCore::EventHandler::handleMouseDraggedEvent(MouseEventWithHitTestResults const&, CheckDragHysteresis)
    WebCore::EventHandler::handleMouseMoveEvent(...)
    WebCore::EventHandler::mouseMoved(...)
    WebKit::WebFrame::handleMouseEvent(...)
    i.e. it fires synchronously while WebKit extends the selection during a mouse drag. Identical across many runs.
  • Minimization (in our app, Playwright WebKit):
    • vertical auto-scroll of the outer scroller during the drag → crash; horizontal scrolling of [data-code] alone → no crash
    • ~60-line diff → no crash; 150 / 300 / 600 / 1000 / 1500 lines → crash
    • not required: lineDiffType: "word", review annotations/threads, deletions (a pure-addition new file crashes), multiple files
    • not the cause: row virtualization churn — freezing Pierre's render/virtualization (local patch of FileDiff/File/CodeView/Virtualizer render funnels, verified bundled and active) does not prevent the crash

Standalone reproduction is still missing. I built a minimal Vite + React + @pierre/diffs@1.5.1 page (served over http; page.setContent is not enough — selection auto-scroll never engages there) and progressively matched our app: split layout + lineDiffType:"word" + hunkSeparators + collapsedContextThreshold + disableFileHeader, lineAnnotations + renderAnnotation + renderGutterUtility, WorkerPoolContextProvider, pure-addition 1500-line diff, 1280×720 / devices["Desktop Safari"]. Geometry matches our app exactly (outer scroller scrollHeight≈13772/clientHeight=600, [data-code] display:grid, overflow: scroll hidden) — but it does not crash, and notably the scroller never auto-scrolls during the drag there.

So the missing ingredient is whatever makes the diff actually scroll under the live selection in our app. I'm now bisecting top-down from the app (stripping shell/theme/scroll-restore/props) to isolate it, and will post the standalone repro here as soon as it reproduces.

OLavrik20d ago

Confirmed the exact crash signature from a macOS crash report (~/Library/Logs/DiagnosticReports/com.apple.WebKit.WebContent.Development-*.ips), produced by a standalone repro (pure additions, enableGutterUtility on, native drag-select with auto-scroll) under Playwright WebKit 2311 / Desktop Safari.

Exception: EXC_BREAKPOINT (SIGTRAP) — "Trace/BPT trap: 5" (a WebKit RELEASE_ASSERT/CRASH(), i.e. a deliberate abort, not a segfault).

Crashing thread (top frames):

0  <deduplicated_symbol>                                     // the assert/trap
1  WebCore::EventHandler::handleMouseDraggedEvent(MouseEventWithHitTestResults const&, CheckDragHysteresis) +676
2  WebCore::EventHandler::handleMouseMoveEvent(PlatformMouseEvent const&, HitTestResult*, bool, ...) +1780
3  WebCore::EventHandler::mouseMoved(PlatformMouseEvent const&) +180
4  WebKit::WebFrame::handleMouseEvent(WebMouseEvent const&) +452
5  WebKit::WebPage::mouseEvent(...) +488
6  IPC::handleMessage<Messages::WebPage::MouseEvent, ...> +112
7  WebKit::WebPage::didReceiveMessage(IPC::Connection&, IPC::Decoder&) +3848
   ... (IPC dispatch / RunLoop)

This matches the signature reported here: the assert fires inside EventHandler::handleMouseDraggedEvent while WebKit extends the native selection, as the gutter utility node is re-parented into the hovered line's number cell on each pointermove.

Version note: reproduces on webkit 2311+ (Playwright 1.61 / recent Safari / Electrobun WKWebView), but not on webkit 2140 (Playwright 1.51).

Fix in #1185 — gate gutter-utility placement while a native text-selection drag is in progress (no DOM node is moved under the drag).

OLavrik20d ago

Resolved by #1185 — the cause is the gutter utility being re-parented on every pointermove, so a native selection drag mutated the DOM under the in-progress selection.

Verified against the original real-world reproduction on WebKit with no app-side mitigation: all previously crashing drags pass with the fix (details and the control metrics are in https://github.com/pierrecomputer/pierre/pull/1185#issuecomment-6097345436).

Note the two hypotheses raised earlier in this issue are wrong and should be ignored by anyone reading this later: it is neither row virtualization nor display:grid on the scroll container.

amadeus19d ago

yo, this is a ton of ai slop for me to sift through. please try to personally write stuff...

I am definitely not able to repro this... so i'll need more context on how to reproduce this before we go ahead with any PRs:

https://github.com/user-attachments/assets/f7f9c617-9188-4486-94fa-e7605fd4bb0a