Skip to content

Resize the headless viewport with Browser.setContentsSize - #458

Merged
sjmiller609 merged 2 commits into
mainfrom
hypeship/headless-resize-setcontentssize
Oct 21, 2026
Merged

sjmiller609 merged 2 commits into
mainfrom
hypeship/headless-resize-setcontentssize

Conversation

@tnsardesai

@tnsardesai tnsardesai commented Oct 14, 2026 •

Copy link
Copy Markdown
Contributor

Summary

This fixes two things that break once the images move to Chromium 154. The images here are still pinned to Chrome for Testing 152, so nothing fails on main today. I'm landing this first so the version bump doesn't regress headless display resizing. Wherever Chromium declines the new approach, it falls back to today's behaviour, so no request that succeeds on main fails with this change.

Headless PATCH /display stops resizing the page on Chromium 154

On the headless fast path, setViewportViaCDP called cdpclient.SetDeviceMetricsOverride. That attaches to the first page target, sends Emulation.setDeviceMetricsOverride, and detaches. This only worked because Chromium used to keep the emulated view size after the session detached. In Chromium 154, EmulationHandler::Disable() (content/browser/devtools/protocol/emulation_handler.cc) calls WebContentsImpl::ClearDeviceEmulationSize() when the session is torn down. The override is undone as soon as the server disconnects: PATCH /display still returns 200, but innerWidth/innerHeight never change.

Fix: a new cdpclient.SetContentsSize looks up the window for the first page target and sends Browser.setContentsSize, so the window's web contents are exactly WxH. The size belongs to the window, not to a CDP session, so it holds after the server disconnects. Browser.setContentsSize is available in 152 and is already listed in cdp_protocol_pinned.json.

Chromium rejects setContentsSize with Restore window to normal state before setting content size unless the window is normal. Each window state is handled like this:

window state behaviour
normal setContentsSize
maximized / minimized Browser.setWindowBounds {windowState: normal}, then setContentsSize. Headless browsers launched with --start-maximized are maximized, and in headless that only means "fill the 800x600 headless screen", so nothing is lost. After a resize the window reports normal.
fullscreen (--kiosk, HTML5 fullscreen) left alone, because leaving fullscreen would exit kiosk mode or the page's fullscreen. Falls back to SetDeviceMetricsOverride, exactly as on main: the viewport changes on 152 and stays as it was on 154+.
setContentsSize refused with a CDP error same fallback, plus a Warn log

No page target, context and connection errors are returned as they are today, without a second attempt. SetDeviceMetricsOverride is unchanged.

TestMV3ServiceWorkerRegistration can't find the Developer mode toggle on Chromium 154

Chromium 154 changed the WebUI cr-toggle element (ui/webui/resources/cr_elements/cr_toggle/cr_toggle.ts) from role="button" + aria-pressed to role="switch" + aria-checked, so getByRole('button', { name: 'Developer mode' }) stops matching. The locator now accepts either role.

Tests

  • TestSetContentsSize (cdpclient) covers the call order for normal, maximized and minimized windows, a fullscreen window returning ErrWindowFullscreen without touching the window, a CDP refusal, and a missing page target.
  • TestContentsSizeRefused (api) checks which errors fall back.
  • TestDisplayResizeChromiumWindow gains two scenarios:
    • headless_start_maximized: --start-maximized. It asserts the exact inner size and the window ending up normal.
    • headless_kiosk: --kiosk. It asserts that PATCH /display returns 200 and that the window stays fullscreen, with the same window ID. The viewport isn't asserted, because on 154+ the fallback leaves it unchanged, as on main.

Validation

  • go vet is clean on the touched packages; go test -race ./lib/cdpclient/ and go test ./cmd/api/api/ pass.
  • Local e2e: I swapped this branch's kernel-images-api into three headless images, on Chrome 152.0.7977.42 (onkernel/chromium-headless:70d2dba), 154.0.8037.97 and 155.0.8059.79. headless_default, headless_start_maximized, headless_kiosk and TestMV3ServiceWorkerRegistration passed on all three. Inner size was exactly 1920x1080 and then 1280x720 for the default and maximized scenarios.
  • The new scenarios fail without the fallback: with the previous head's binary (f108be4) on 154, headless_start_maximized and headless_kiosk both fail with PATCH /display: 500 ... Restore window to normal state before setting content size.
  • Wider headless set on 154 with -parallel=4: 53 passed, 0 failed. That covers TestPlaywrightExecuteAPI (WebMCP), BiDi, the CDP proxy tests, TestChromiumConfigureMultipartPowerset, TestDisplayResolutionChange, TestExtensionUploadAndActivation and TestScreenshotHeadless.
  • Startup flake: in an earlier parallel run, headless_default failed once at 34 s with navigateBlank's page.goto('about:blank') timing out, before any PATCH /display was sent. It's a startup timing flake under load, not related to this change.
  • I didn't run the headful scenarios locally; the headful path doesn't use this code, and CI covers it.

Note

Medium Risk
Changes headless display resize CDP behavior on a user-facing API path; fallback preserves prior semantics for kiosk/fullscreen, but maximized headless windows end in normal state after resize.

Overview
Prepares headless PATCH /display for Chromium 154 by resizing the viewport with Browser.setContentsSize (via new cdpclient.SetContentsSize) instead of relying only on Emulation.setDeviceMetricsOverride, which 154+ clears when the CDP session ends.

setViewportViaCDP tries SetContentsSize first: maximized/minimized windows are restored to normal before sizing; fullscreen windows return ErrWindowFullscreen and are not demoted. On fullscreen or other Chromium refusals, it logs and falls back to SetDeviceMetricsOverride (kiosk/154+ behavior unchanged from main). Connection and missing-page-target errors do not trigger fallback.

E2E adds headless_start_maximized and headless_kiosk scenarios with optional viewport assertions and expected windowState after resize. Playwright MV3 test accepts Developer mode as switch or button for Chromium 154 WebUI.

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

Chromium 154 clears Emulation.setDeviceMetricsOverride when the session
that set it detaches, so the override PATCH /display applies on the
headless path is undone as soon as the server disconnects. Size the
window around the page instead, which persists independently of any CDP
session.

Also match the chrome://extensions Developer mode toggle by either role:
Chromium 154 changed cr-toggle from role=button to role=switch.
…efused

Chromium rejects Browser.setContentsSize for a maximized, minimized or
fullscreen window. Production headless launches maximized, so restore
maximized and minimized windows to normal first. Leave fullscreen windows
alone, since leaving fullscreen would exit kiosk mode, and fall back to
the previous device metrics override whenever Chromium refuses the
resize, so no request fails that succeeded before.

Keep SetDeviceMetricsOverride for that fallback, and cover maximized and
kiosk headless browsers in the resize e2e.
@sjmiller609
sjmiller609 marked this pull request as ready for review October 21, 2026 13:34
@sjmiller609

Copy link
Copy Markdown
Contributor

Taking liberty to merge this one, and will QA the path before launching:

  • private images already use Chromium 155, so this fixes an existing resize regression.
  • tested on Chrome 152, 154 and 155; existing CI passed.
  • resizing maximized/minimized headless windows restores them to normal; fullscreen/kiosk behavior is unchanged.

@sjmiller609
sjmiller609 merged commit 1b98da0 into main Oct 21, 2026
12 checks passed
@sjmiller609
sjmiller609 deleted the hypeship/headless-resize-setcontentssize branch October 21, 2026 13:37
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