Skip to content

Finger drags on GTK4 WebKit ride the scrolling thread, and r61's two patches cut the Wikipedia drag from 289 layouts per 10 s to 10

scope: device:google-taimen · severity: finding · confidence: proven · subsystem: browser

The question – the 2026-09-05/06 handoffs concluded that the scroll stall is the page’s JavaScript forcing ~290 layouts per drag, and left open whether the GTK port scrolls a finger drag off the main thread at all.

The answer – it does, and the layout storm was WebKit’s, not the page’s. The touch drag becomes wheel events with phases (WebKitWebViewBase.cpp), EventDispatcher routes them to ThreadedScrollingTree::handleWheelEvent on the Core: Scrolling thread (272 calls, and zero on the main-thread EventHandler::handleWheelEvent), display-link ticks reach that thread (441) and it repositions the scrolling layers itself (205). On r61 the same drag that cost 289 layouts on r60 costs 10, because r61 carries two patches: dont-relayout-fixed-objects-on-async-scroll.patch (LocalFrameView:: updateLayoutViewport() re-laid-out every fixed/sticky renderer on every async scroll step, dirtying the RenderView so the page’s next forced layout was document-wide) and no-fake-mouse-moves-for-touch-scrolling.patch (75 hit tests, each forcing a layout, for a hover state no finger has). The page’s scripts still read layout in their scroll handlers; with nothing dirty, those reads are cheap.

What this rules out – “the scroll stall is the page’s JavaScript” as a verdict that closes the campaign (the JS only paid for what WebKit dirtied); “async scrolling is off on GTK” (both AsyncFrameScrollingEnabled and the scrolling thread are live); and “r61 was a regression” – what invalidated r61 was the-webkit-snapshot-crash-is-epiphanys-full-document-thumbnail.

How it was established – the same uprobe arm as the 2026-09-05 finding, with the routing functions added and the trace split by thread (the summary table of ph-wkphase.sh measure only knows the fixed phase list; the raw /tmp/wk-drag.trace carries every probe as name: / name_ret: with the thread’s comm in front). Still open: the residual 34 janks per 10 s (max 138 ms) with the main thread at 27 % and the compositor at 14.5 %, and the un-measured gaps in ThreadedScrollingTree’s desync state machine (layer positions applied only on display-link ticks; DisplayLink sends nothing to a process without an observer). What would overturn it: the same probes on r63 showing hundreds of layouts again. Related: the-scroll-stall-is-the-pages-own-javascript.