Skip chromium restart on headful display resize#262
Conversation
Headful PATCH /display previously restarted chromium to re-apply
--start-maximized against the new X root. The restart costs ~9s and
also wipes browser-side state (Emulation.* overrides, devtools
sessions). It turns out the restart isn't load-bearing: mutter reflows
a window in windowState=maximized (or fullscreen) onto the new root
whenever the X root resizes via xrandr/Neko. The server just has to
make sure the window is in that state.
This replaces the restart on the Xorg branch of PatchDisplay with a
single Browser.setWindowBounds{windowState:"maximized"} call. The
helper is idempotent: it early-returns when the window is already
maximized or fullscreen, so kiosk windows stay in fullscreen. Callers
that still want the legacy restart can pass restart_chromium=true.
The explicit-bounds form of setWindowBounds (windowState:"normal" with
{left,top,width,height}) is intentionally NOT used: once a window is
in normal state mutter stops auto-tracking RANDR events, which would
silently break subsequent resizes.
New e2e test e2e_display_resize_window_test.go covers headless fast
path, --start-maximized + neko, kiosk + fullscreen, and a
force-maximized-via-CDP scenario. Each subtest asserts:
- X root reaches the requested size (xrandr Screen 0: current WxH)
- renderer screen.* and outer{Width,Height} converge
- CDP windowState is preserved across the resize
- CDP windowId is stable (proves no chromium restart)
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Follow-up to the prior commit: remove the restart_chromium=true escape hatch from the Xorg branch entirely. The CDP re-assert-maximized path is strictly better (faster, preserves browser state, stable windowId) and the e2e tests exercise it exclusively. Existing callers in kernel/kernel (kraft.go, chromium_configure.go) hardcode restart_chromium=true today and now transparently get the faster behaviour. The restart_chromium field stays in the request schema for API compatibility. On the Xorg branch it is now a no-op. The Xvfb-with- recordings branch (orthogonal — headless chromium has no OS window) still honours the flag. Also drop the unused restartChrome parameter from setResolutionXorgViaXrandr and its one caller in chromium_configure.go. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
After the legacy restart_chromium path was removed, two of the five
TestDisplayResizeChromiumWindow scenarios became strictly redundant:
- headful_kiosk_no_restart: passes restart_chromium=false explicitly,
but the field is a no-op on the Xorg branch — functionally
identical to headful_kiosk.
- headful_force_maximized_no_restart: forces windowState=maximized
via CDP at baseline, then resizes. Was useful while investigating
whether mutter's "maximized window reflows on RANDR" invariant
holds; headful_start_maximized now covers the same hot path with
production-equivalent CHROMIUM_FLAGS.
Both also flaked in CI when running later in a heavy e2e suite —
neko/mutter occasionally fails to apply its 1920x1080 desktop.screen
startup mode under load, leaving the X root at the dummy DDX's
3840x2160 default and breaking the resize assertions. Running them
locally or earlier in the suite passes consistently, so it's an
environmental flake, not a code regression.
Also remove the now-unused helpers setChromiumWindowStateCDP,
waitForWindowState, baselineOrForcedState, and the
forceMaximizeAtBaseline / restartChromium fields on resizeScenario.
The remaining three subtests cover every production path: headless
CDP fast path, headful --start-maximized + neko, headful kiosk +
fullscreen.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Monitoring Plan: Display Resize Skips Chromium Restart, Uses CDP InsteadWhat this PR does: Headful browser display resizes are now ~9 seconds faster and no longer wipe browser-side state (Emulation.* overrides, open devtools sessions). Instead of restarting Chromium after an xrandr resize, the server re-asserts the window's maximized state via a single CDP call — Mutter then automatically reflows the window to fill the new screen. Intended effect:
Risks:
Status updates will be posted automatically on this PR as monitoring progresses. |
Three follow-ups addressing review feedback: 1. xrandr targets DUMMY0 (or KERNEL_IMAGES_XRANDR_OUTPUT env override), not "default". The headful Xorg dummy driver does not expose a "default" output, so `xrandr --output default --mode ...` exits 0 without applying anything. This affects only the non-Neko Xorg path (production runs with Neko enabled, which doesn't touch xrandr), but left the path silently broken. 2. CDP maximize re-assert failure is now fatal. Previously logged a warning and returned 200; if the window happened to be in normal state and the CDP call failed, the server returned success with the browser window stuck at the old size. Now the resize fails closed with a 500 so the caller sees the mismatch. 3. TestDisplayResizeChromiumWindow now asserts windowID stability and windowState preservation across both resizes. Previously logged but never asserted; a silent chromium restart regression would have slipped through. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The else branch for refreshRate <= 0 built `xrandr --output DUMMY0 --size WxH`. `--size` is xrandr's legacy global screen option (long form of `-s`) and cannot be combined with `--output`; per-output resizing requires `--mode <name>`. The mismatched flag combination either silently no-ops or errors depending on the xrandr version. The branch was effectively dead — the schema enum (PatchDisplayRequestRefreshRate) only permits 10/25/30/60 and getCurrentResolution synthesizes 60 when xrandr is silent — but the footgun should not stay in the tree. Normalize refreshRate to 60 when non-positive and always go through the named modeline. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Two follow-ups against Raf's review: 1. Synchronous post-condition. setWindowMaximizedViaCDP now takes the requested width/height and polls Browser.getWindowForTarget until the live OS-window bounds match. PATCH /display only returns after mutter's RANDR reflow has settled, so callers see a synchronous contract: the response means the window is at the new size, not just that the resize was kicked off. Typical convergence on docker + mutter is 20-50ms; deadline is 3s. Adds cdpclient.GetWindowBounds() returning windowID/width/height/ windowState, plus the waitForWindowSize polling loop in display.go. 2. Non-Neko Xorg coverage. Adds headful_xorg_no_neko subtest with ENABLE_WEBRTC unset, so the server falls through to setResolutionXorgViaXrandr instead of nekoAuthClient.Screen- ConfigurationChange. This actually exercises the `xrandr --output DUMMY0 --mode WxH_RR.00` path the prior commit landed but left untested. Mode targets (1920x1080 / 1280x720) are chosen because they're attached at 60Hz on DUMMY0; 2560x1440 isn't, per `xrandr -q` on this image. Confirms mutter reflows the chromium window onto the new active mode size even though the dummy DDX virtual framebuffer stays at its boot-time maximum. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
cdpclient had three near-identical copies of "Target.getTargets → unmarshal → loop for type=page → extract targetId" — SetWindowBoundsMaximized, GetWindowBounds, and SetDeviceMetricsOverride each carried ~20 lines of the same routine. Pulls them into a private firstPageTargetID(ctx) helper. DispatchStartURL has a different shape (walks all page targets, closes extras, falls back to creating one) so it stays as-is. Net 65 lines removed, no behaviour change. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
test comment is backwards. Medium — poll the X-root, not the window's self-report. Medium — two simplifications.
Medium — PR body out of sync. It claims five subtests (incl. |
Four follow-ups against the PR #262 review: 1. Poll the X root, not the chromium window. waitForWindowSize used CDP Browser.getWindowForTarget to wait for the live OS window's width/height to match the request, which only worked because there are no panels/struts in this mutter config (maximized == full root). If mutter ever gained a taskbar, maximized windows would be smaller than the root by the panel's reserved space and the poll would 3s-timeout forever. waitForXRootSize polls s.getCurrentResolution (xrandr Screen 0: current WxH) instead — the value the server actually controls. setWindowMaximizedViaCDP no longer takes width/height and does only window-state assertion. 2. SetWindowBoundsMaximized now calls GetWindowBounds. Dropped the duplicate Browser.getWindowForTarget call + struct, ~14 lines. 3. Extract withCDPClient(ctx, fn) for the dial + 10s-timeout + defer-close scaffolding shared between setWindowMaximizedViaCDP and setViewportViaCDP. 4. Flip the chromiumWindowBounds doc comment in the e2e test. The prior version described maximized as saved-restore and normal as live size; cdpclient.WindowBounds has it correctly (and the test should match): maximized/fullscreen → live size that the WM aligns with the X root; normal → saved-restore bounds. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
Thanks — all four valid, addressed in 2f81c56: 1. Test comment flipped. 2. Poll the X-root. Dropped 3a. 3b. Extracted 4. PR body updated to reflect the four-subtest reality. Dropped the stale claims about Numbers updated in a separate comment. |
Benchmark: PATCH /display before vs afterSame docker host, same image base (
~118× faster at the median. ~8.9s saved per The AFTER number includes the full server post-condition: CDP kernel/kernel#2264 drops |
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 2f81c56. Configure here.
| case <-ctx.Done(): | ||
| return ctx.Err() | ||
| case <-time.After(50 * time.Millisecond): | ||
| } |
There was a problem hiding this comment.
X root poll uses unreliable grep '*' after Neko resize
Medium Severity
waitForXRootSize calls s.getCurrentResolution(), which internally runs xrandr | grep -E '\\*' to find the active mode. However, the new test helper getXRootResolution in the same PR explicitly avoids the *-grep approach, with a comment explaining that "Xorg-with-Neko surfaces a synthetic active mode that xrandr does not mark with an asterisk." If this is accurate, waitForXRootSize will always time out on the Neko path after a resize because the new mode won't have a * marker, causing PATCH /display to return a 500 even though the resize succeeded.
Additional Locations (1)
Reviewed by Cursor Bugbot for commit 2f81c56. Configure here.
There was a problem hiding this comment.
checking on this. I did not see this issue when I was testing
There was a problem hiding this comment.
Right — the bot was working off a wrong claim in my test-helper doc, not a real bug. Verified in a running container on this branch + Neko enabled:
BEFORE PATCH: xrandr | grep '*' | awk '{print $1}' → 1920x1080_25.00
PATCH 2560x1440 → 200 OK
AFTER PATCH: xrandr | grep '*' | awk '{print $1}' → 2560x1440_60
Neko's active mode IS marked with * after a resize, so the server's getCurrentResolutionFromXrandr works fine. The benchmark above (~76ms median over 8 iterations) is the empirical confirmation that the polling loop converges quickly.
Fixed the stale doc claim in 0bad77e — no server code change needed.
There was a problem hiding this comment.
Good catch — fixed in 7f4d47d. Now calls getCurrentResolutionFromXrandr directly, so the post-condition is bound to the X root regardless of whether anyone ever sets a viewportOverride on the Xorg path. Doc comment updated to call out why the higher-level wrapper is the wrong abstraction here.
The getXRootResolution doc said `xrandr` does not mark Neko's synthetic active mode with an asterisk, which would imply the server's getCurrentResolutionFromXrandr (which greps for *) wouldn't work on the Neko path. Empirical check on the chromium-headful:11e1b04 image shows both before and after a Neko-driven resize the active mode IS marked with *, so the * grep works fine — the benchmark (~76ms per PATCH /display) is consistent with that. The Screen 0: current form is still the most direct read, so the test helper keeps using it; just removing the wrong justification. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The doc comment that claim was based on is stale — verified empirically in the running container ( The active mode IS marked with The wrong claim was in |
rgarcia
left a comment
There was a problem hiding this comment.
LGTM — all four findings addressed cleanly (test comment flipped, X-root poll, withCDPClient / GetWindowBounds simplifications, PR body). Build + vet green. One non-blocking nit:
nit — have waitForXRootSize read the root directly. It polls getCurrentResolution (display.go:468), which returns viewportOverride when one is set, not the X root. Safe today only because the override is set on a single path — the headless fast path (display.go:186) — that an Xorg container never reaches. That's a non-local invariant sitting far from the poll site: if a viewport override is ever set on the Xorg path, this "panel-robust" check silently validates against the CDP viewport instead of the root. Calling getCurrentResolutionFromXrandr (display.go:619) directly makes the post-condition read the root unconditionally.
s.getCurrentResolution prefers a cached viewportOverride when one is set, which would silently validate this post-condition against the CDP viewport instead of the X root. The override is only written on the headless Xvfb fast path today, so the Xorg branch never reaches that case — but that's a non-local invariant sitting far from the poll site. Anyone adding a viewport override on the Xorg path would silently break this check without touching it. Read xrandr unconditionally via getCurrentResolutionFromXrandr so the post-condition stays bound to the value it's named for. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
rgarcia
left a comment
There was a problem hiding this comment.
Re-reviewed at 7f4d47d — the X-root-reader nit is resolved (waitForXRootSize now calls getCurrentResolutionFromXrandr directly, decoupled from viewportOverride) and the Screen 0: current vs per-output grep '*' discrepancy is documented. Build + vet green. LGTM.


Summary
Headful
PATCH /displaypreviously restarted chromium after thexrandr/Neko screen resize so chromium could re-apply
--start-maximizedagainst the new X root. The restart costs ~9s and wipes browser-side
state (Emulation.* overrides, devtools sessions).
The restart turns out not to be load-bearing. Mutter reflows a window in
windowState=maximized(orfullscreen) onto the new root whenever theX root resizes via xrandr/Neko. The server just has to make sure the
window is in that state, then verify the X root reached the requested
size before returning.
This PR replaces the restart on the Xorg branch with a single
Browser.setWindowBounds{windowState:"maximized"}CDP call plus anxrandr poll for the X root. The
restart_chromiumrequest field staysin the API schema for compatibility but is now a no-op on the Xorg
path. Existing callers that hardcode
restart_chromium=true(kraft.go, chromium_configure.go in kernel/kernel) silently get the
faster path.
The trap we avoid
The explicit-bounds form of
setWindowBounds—windowState:"normal"with
{left, top, width, height}— is intentionally not used. Oncea window is in normal state, mutter stops auto-tracking RANDR events,
which silently breaks subsequent resizes.
Why poll the X root, not the chromium window
The post-condition is verified by polling
getCurrentResolution(xrandr's
Screen 0: ... current WxH), not the chromium window's ownCDP bounds. The X root is the value the server actually controls;
polling it is also panel-robust — if mutter ever gains a taskbar/dock,
a maximized chromium window would be smaller than the root by the
panel's reserved space, and a window-bounds poll would silently time
out.
Changes
server/lib/cdpclient/cdpclient.go—SetWindowBoundsMaximized(ctx).Idempotent: early-returns on
maximizedorfullscreenso kioskwindows aren't demoted. Plus a
firstPageTargetIDhelper todeduplicate the Target.getTargets → page-target boilerplate that was
spread across three methods, and a
GetWindowBounds(ctx)readerreused by
SetWindowBoundsMaximized.server/cmd/api/api/display.go— Xorg branch: alwayssetWindowMaximizedViaCDP+waitForXRootSizeafter a successfulresize. The legacy restart branch is gone. A small
withCDPClienthelper hides the dial/timeout/close scaffolding shared by the two
per-call CDP helpers.
server/cmd/api/api/chromium_configure.go— drop the now-unusedrestartChromeargument from thesetResolutionXorgViaXrandrcall.server/e2e/e2e_display_resize_window_test.go— new test exercisingfour scenarios: headless CDP fast path, headful
--start-maximized--kiosk+ fullscreen, and headful non-Neko Xorg(
xrandr --output DUMMY0 --mode WxH_RR.00). Each subtest assertsX root convergence, renderer
screen.*andouter{Width,Height}convergence,
WindowStatepreservation, and a stablewindowId(proving no chromium restart).
The Xvfb-with-recordings branch in
display.gois orthogonal (headlesschromium has no OS window) and still honours
restart_chromium=true.Numbers
headful_start_maximizedheadful_kioskheadful_xorg_no_nekoTwo resizes per scenario; ~17s saved per PATCH
/displaycall againstthe kernel/kernel callers that all hardcode
restart_chromium=truetoday.
Test plan
cd server && go vet ./...cd server && go test ./lib/cdpclient/... ./cmd/api/...— unittests for cdpclient and the api package
TestDisplayResizeChromiumWindow— 4 subtests, all passagainst an overlay image carrying this branch's
kernel-images-apiTestDisplayResolutionChangestill passes against themodified server
the unikernel runtime as in the docker e2e
🤖 Generated with Claude Code
Note
Medium Risk
Changes core display/WM integration (xrandr output, CDP window bounds, stricter 500s) and leaves chromium_configure’s stop-cycle Xorg resize without the new CDP/X-root post-steps.
Overview
Headful
PATCH /displayno longer restarts Chromium after an Xorg resize. After Neko or xrandr applies the new mode, the server re-asserts a maximized/fullscreen OS window viaBrowser.setWindowBounds(CDP) and treats success only when xrandr reports the X root at the requested size—failures on either step return 500 instead of 200 with a mismatched display.restart_chromiumremains in the API but is a no-op on this Xorg path; the Xvfb/recording path can still restart when requested.xrandr on the dummy driver now targets
DUMMY0(orKERNEL_IMAGES_XRANDR_OUTPUT) with a namedWxH_RR.00modeline instead of--output default, which could exit 0 without resizing.cdpclientaddsSetWindowBoundsMaximized/GetWindowBoundsand sharedfirstPageTargetID;display.goaddswithCDPClient,setWindowMaximizedViaCDP, andwaitForXRootSize. New e2eTestDisplayResizeChromiumWindowcovers headless, Neko maximized, kiosk fullscreen, and non-Neko Xorg.Reviewed by Cursor Bugbot for commit 7f4d47d. Bugbot is set up for automated code reviews on this repo. Configure here.