Skip to content

Seed headful window restore bounds and keep the window maximized - #503

Merged
ulziibay-kernel merged 2 commits into
mainfrom
hypeship/seed-headful-restore-bounds
Nov 6, 2026
Merged

ulziibay-kernel merged 2 commits into
mainfrom
hypeship/seed-headful-restore-bounds

Conversation

@ulziibay-kernel

@ulziibay-kernel ulziibay-kernel commented Nov 5, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Fixes the "browser opens halfway on the desktop" headful regression: on a 1920x1080 root, any transition of the main window out of maximized state (CDP Browser.setWindowBounds, the viewport-resize demote, a WM restore) landed on chromium's default initial geometry — a ~1050x1080 window at +10+0 — leaving most of the desktop background visible beside a portrait sliver of browser. Reproduced on the current image; mutter never showed this because its constraint logic clamped the restore geometry.

Root cause: a --start-maximized window never records a normal-state geometry of its own, so the WM has nothing sane to restore to.

Changes

  • chromium-launcher: seeds --window-size=W,H --window-position=0,0 from the live X root (x11.RootSize, new helper) when the merged flags contain --start-maximized. Skipped when the caller passed explicit --window-size/--window-position, or --kiosk (kiosk must stay fullscreen), or the root size can't be read.
  • api: StartWindowPromotion re-asserts the maximized state once per chromium start, driven by devtools upstream discovery (the upstream URL changes on every restart). This is needed because chromium 155 ships a --window-size window in normal state — the size flag overrides the maximized startup state. SetWindowBoundsMaximized is idempotent and leaves fullscreen (kiosk) windows alone, so the promotion is safe on every variant. No-op on headless (Xvfb).
  • e2e: new TestHeadfulUnmaximizeRestoreGeometry boots production-equivalent flags, demotes the window via CDP, and asserts the restored window covers the root. Fails on the current image (restores 1050px wide); passes with this change. The existing resize test's baseline-state assertion now polls briefly since the maximized state settles asynchronously.

Validation (built image, docker backend)

  • boot: window maximized, same as before (seeded normal bounds + promotion)
  • chromium restart: window re-promoted to maximized (per-restart subscription)
  • un-maximize: restores to root-sized at +0+0 instead of the 1050px sliver; re-maximize still works
  • kiosk (--kiosk): fullscreen untouched through resizes
  • go vet ./..., go test -race (non-e2e): pass
  • e2e headful scenarios (headful_start_maximized, headful_kiosk, headful_xorg_no_neko, TestHeadfulUnmaximizeRestoreGeometry): pass against a locally built image with this change

Known limitation

After a display resize, the seeded restore bounds are stale (last boot's root size), so an un-maximize after a resize restores a window larger than the new root — it still covers the desktop. Re-seeding on resize can be a follow-up if it matters in practice.


Note

Medium Risk
Changes headful Chromium flags and adds background CDP window control on every browser restart; scoped to xorg and reuses existing SetWindowBoundsMaximized behavior (kiosk/fullscreen left alone).

Overview
Fixes headful regression where un-maximizing left a narrow default window and visible desktop by giving Chromium a root-sized normal-state geometry at launch and re-applying maximized state after each browser start.

chromium-launcher (non-headless only) reads the X root via new x11.RootSize and, when flags include --start-maximized without explicit --window-size/--window-position or --kiosk, appends --window-size=W,H and --window-position=0,0 so restore/un-maximize lands on full-screen bounds instead of Chromium’s default sliver.

API adds StartWindowPromotion (wired from main.go): on xorg headful only, it subscribes to devtools upstream changes and calls SetWindowBoundsMaximized once per Chromium restart, with retries for slow targets, refused dials, and dropped CDP connections. Seeded --window-size starts the window in normal state (Chromium 155), so promotion restores the expected maximized boot behavior.

Tests: unit coverage for bound seeding, RootSize parsing, and promotion retry/redial paths; e2e TestHeadfulUnmaximizeRestoreGeometry and display-resize baseline now poll until the window reaches maximized because promotion is asynchronous.

Reviewed by Cursor Bugbot for commit 07b8d7d. Bugbot is set up for automated code reviews on this repo. Configure here.

A --start-maximized headful window never records a normal-state geometry
of its own. The first transition out of maximized — a CDP
Browser.setWindowBounds demote, the viewport-resize restore step, a WM
un-maximize — falls back to chromium's default initial bounds (~1050x1080
at +10+0 on a 1920x1080 root), leaving most of the desktop background
visible beside a portrait sliver of browser.

Two changes fix that:

- chromium-launcher seeds --window-size/--window-position from the live X
  root when the caller passes --start-maximized, so the window manager and
  chromium both carry a sane geometry to restore to. Chromium 155 ships a
  --window-size window in normal state (it overrides the maximized startup
  state), so:

- the api promotes the window back to maximized once per chromium start,
  driven by devtools upstream discovery (the upstream URL changes on every
  restart). SetWindowBoundsMaximized is idempotent and leaves fullscreen
  kiosk windows alone.

Verified on a built image: boot and chromium restart end maximized as
before; un-maximizing now restores to a root-sized window at +0+0 instead
of the 1050px sliver; kiosk and headless paths are untouched.
@ulziibay-kernel
ulziibay-kernel marked this pull request as ready for review November 5, 2026 22:23
@ulziibay-kernel

Copy link
Copy Markdown
Contributor Author

Picked chruffins to review: touched the changed code in e2e, lighter queue than hiroTamada; nothing pending

@chruffins chruffins left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

requesting changes on three bugs. all three were reproduced with smoke tests against a fake cdp endpoint on head 6a91d20. the smoke tests are not committed; happy to share the harness.

Bugs

  • server/cmd/api/api/window_promotion.go:53,66 — seen is set before the promotion attempt, so a failed initial dial is never retried for that url. the dial failure path at :85 returns straight away. reproduced: the endpoint returned 503 once, then accepted; there was no further upgrade attempt over 3s, and the window stayed in normal state until the next chromium restart. consider marking seen only after a successful promotion, or retrying the dial within the deadline.
  • server/cmd/api/api/window_promotion.go:93-104 — after the connection drops mid-retry, the loop keeps issuing commands on the closed client and never redials, even once a page target exists. reproduced: one upgrade attempt in total; every retry failed with "CDP command outcome is unknown" until the 30s deadline. reconnect after a transport error.
  • server/e2e/e2e_unmaximize_restore_test.go:92-95 — the restore assertions only bound width and height from below and left/top from above. a window at left=-500 with full root width passes all four, leaving 500px of desktop exposed on the right. reproduced by evaluating these exact expressions on that geometry; the docker e2e suite was not run. consider asserting that Left+Width and Top+Height also reach the root edges.

status: review complete; no tests run on the PR branch; smoke results above are from a scratch copy

promoteWindowMaximized retried only the maximize command: a failed
initial dial abandoned the url for good, and after a mid-attempt
transport drop the loop kept issuing commands on the closed client.
Dial failures and dropped transports now retry within the deadline,
redialing on the next pass. New unit tests cover both paths against a
fake devtools endpoint; they fail on the previous code.

The unmaximize-restore e2e assertions bounded width/height from below
and left/top from above, so a large window shifted off-screen (e.g.
Left=-500) passed while exposing the desktop. Assert the window also
reaches the right and bottom root edges.
@ulziibay-kernel

Copy link
Copy Markdown
Contributor Author

Addressed all three in 07b8d7d — thanks for the smoke repros, they translated directly into committed regression coverage.

Dial failure never retried — promoteWindowMaximized now retries dial failures within the 30s deadline (same promoteRetryDelay spacing as the command retries), instead of abandoning the url on the first refusal.

Closed client never redials — after a command failure the loop checks client.IsClosed(); a dropped transport is closed and the next pass redials. Live transports (the usual no-page-target-yet retry) keep the connection.

Both paths are covered by unit tests against a fake devtools endpoint (window_promotion_test.go): one serves a 503 on the first dial then accepts, one accepts the first websocket, reads a command, and drops without responding. Both fail on the previous code (0.1s and 30s respectively — the 30s one is your exact repro) and pass now, so no need to dig up your harness.

Off-screen restore passes the assertions — added right/bottom-edge coverage: Left+Width >= rootW-40 and Top+Height >= rootH-40, so a Left=-500 window now fails. Ran the full headful e2e set against a rebuilt image: unmaximize-restore, all resize scenarios (start_maximized, kiosk, xorg_no_neko), chromium-restart, screenshot, plus the headless counterparts — all green.

Validation summary: new unit tests + full api package with -race, e2e on the rebuilt image (commit includes both).

@chruffins chruffins left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

re-reviewed 07b8d7d — the three reproduced bugs are fixed, approving.

  • dial failures are retried within the deadline, and a dropped transport is redialed (window_promotion.go). reran the repro scenarios on 07b8d7d: the endpoint now gets a second upgrade attempt and the window is promoted; the dropped-connection case redials and promotes.
  • the e2e restore assertions now check the far edges (Left+Width, Top+Height). the new expressions reject the left=-500 geometry and accept a full-root window.
  • your three new tests pass, including under -race (3 runs).

not blocking, unchanged in this pass, for a separate decision: promotion still maximizes a window that was launched with explicit --window-size/--window-position, and a fresh api loop still re-maximizes an already-running browser that a client un-maximized.

status: review complete; no tests run on the PR branch beyond the targeted runs above in a scratch copy; no GitHub comment beyond this review

@ulziibay-kernel

Copy link
Copy Markdown
Contributor Author

Thanks — approval noted, and thanks for re-running the repro scenarios on 07b8d7d.

On the two deferred items: both confirmed real against the code, and tracked for a decision on KERNEL-2781 rather than changed here —

  • explicit geometry launch gets maximized: the launcher skips seeding for explicit --window-size/--window-position (chromium-launcher/main.go:217), but the promotion fires unconditionally in xorg mode — it has no knowledge of the launch flags. Skipping it would need a launcher→api handoff (marker file or shared flag parsing).
  • api restart re-maximizes a client-un-maximized browser: the initial Current() read in promoteWindowsLoop promotes the already-discovered URL, so an api restart into a live chromium overrides the client's chosen state. Distinguishing that from a fresh chromium start needs start-time comparison.

Both are edge cases (explicit geometry on headful, api-crash-restart coinciding with a deliberate un-maximize) and fixable later without migration, so this PR stays scoped to the restore-geometry regression.

@ulziibay-kernel
ulziibay-kernel merged commit dc64016 into main Nov 6, 2026
12 checks passed
@ulziibay-kernel
ulziibay-kernel deleted the hypeship/seed-headful-restore-bounds branch November 6, 2026 04:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants