Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Windowed Browser Game Resolution: CDP Outer Size vs Content Area Size

Date: 2026-08-02

Trigger Scenario

When you use Playwright/CDP to control a browser running a windowed game (this article uses OpenYRWeb, the Red Alert 2 browser engine, as an example), you hit a sneaky resolution trap: once the game is windowed (not fullscreen), the main menu and freshly-entered gameplay look fine, but as soon as you click on the scene or UI in-game, the bottom command bar and menu buttons end up beyond the browser’s visible area and are almost unclickable; screenshots show the rendered region is noticeably larger than the visible area.

After debugging, the root cause turns out not to be in the game’s render layer but in the resolution conversion of the external control script: the script uses CDP Browser.getWindowBounds to get the window outer size (including title bar/borders) and directly calls page.setViewportSize to set the page viewport, making the page viewport larger than the browser’s visible area (content area) — the main menu is DOM-adaptive so it works fine, but the in-game HUD is pixel-positioned (the bottom UI computes coordinates from viewport.height - command bar height) and naturally gets pushed outside the visible area.

A second compounding factor: the engine clamps the viewport to a minimum of 800×600, so when the window is dragged too small the two conflict. This article gives a complete fix: measure and cache the browser border delta (title bar height), convert the CDP outer size into the content area size, and only then sync it to the page.

Tracing the Misconceptions: Three Assumptions Taken for Granted

Assumption 1: “Browser.getWindowBounds returns the size of the page’s visible area”

In fact it returns the bounds of the entire browser window, including the title bar, tabs (in vertical-tab mode), address bar, and borders — in practice one window measured 1440×846 outer, while the page visible area (content area) was 1440×759, a difference of about 87px (title bar + tab bar). Using the outer size directly in setViewportSize makes the page viewport 87px larger than the visible area.

Assumption 2: “Dynamically computing the delta outer - inner is enough”

In practice setViewportSize forces the page viewport to equal the value you set; after that, window.innerWidth/innerHeight read back exactly what you set, so the outer - inner delta becomes distorted or even negative, and can’t be used as a conversion basis.

The correct approach is to measure and cache the border delta once on first run: chrome = {dx: window.outerWidth - window.innerWidth, dy: window.outerHeight - window.innerHeight} — the title bar height is constant over the window’s lifetime, so measure it only once.

Assumption 3: “The game engine automatically adapts to any window size”

Actually, the non-fullscreen branch of OpenYRWeb’s Application.updateViewportSize() has Math.max(800, ...)/Math.max(600, ...) minimum clamps — when the content area is smaller than 800×600, the engine viewport is clamped to 800×600 and the canvas necessarily overflows the visible area; the script side also needs a minimum-window guard to pull the window back to a safe size.

Source-Code Verification and Actionable Conclusions

The verification points are all in the engine source and the control script:

  • updateViewportSize() in Application.ts.js (non-fullscreen branch Math.min(window.innerWidth, config.viewport.width) + the 800×600 clamp)
  • Gui.handleViewportChange (syncs the viewport to the renderer and UiScene)
  • HudFactory.create() in gui/screen/game/component/Hud.ts (lays out the sidebar and command bar from uiScene.viewport, bottom coordinates computed from viewport height)

The fix lands in three steps:

  1. Measure and cache the border delta on script startup chrome = {dx, dy} (title bar height is constant; measured dy=87);
  2. On every poll, take the outer size via CDP Browser.getWindowBounds and convert it to the content area: cw = w - chrome.dx, ch = h - chrome.dy, with a Math.max(800, ...)/Math.max(600, ...) lower-bound guard (matching the engine’s clamp), then page.setViewportSize({width: cw, height: ch});
  3. Make “fit to window” the default behavior — the engine’s non-fullscreen updateViewportSize branch should use window.innerWidth/innerHeight (content area) directly, no longer clamped by config.viewport.width (e.g. 1024), so users don’t have to manually pick “Resolution → Fit to Window” in the graphics settings.

After the fix, the log confirms the conversion is correct (window 1440x846 → content 1440x759); no matter how you stretch the window or drag it to the minimum size, the in-game bottom UI stays fully clickable.

Lessons learned: the size chain for a windowed browser game is “CDP outer → border delta → content area → engine viewport → HUD pixel positioning” — getting any link wrong makes the UI overflow; measuring the title-bar delta only once and aligning the 800×600 clamps on both sides are the two key points of this approach.


Human Developers Quick Reference

Four-step self-check:

  1. CDP getWindowBounds returns the outer size (including title bar/tabs); it can’t be used directly as the page viewport;
  2. Measure and cache the border delta on first run (outer - inner, title bar constant, measured dy≈87);
  3. Convert to the content area cw=w-dx, ch=h-dy, add an 800×600 lower bound, then setViewportSize;
  4. Make the engine’s non-fullscreen branch default to window.innerWidth/innerHeight for “Fit to Window”, avoiding the graphics settings.

Conversational AI Quick Reference

Question template: “playwright/CDP windowed game UI overflows the visible area / resolution is wrong — how to fix?”

The answer should be: getWindowBounds returns the outer size including the title bar; cache the border delta, convert it to the content area, then setViewportSize; a dynamic outer-inner is distorted, don’t use it; the engine has an 800×600 minimum viewport clamp, the script must mirror the lower bound; to make the default resolution fit the window, change updateViewportSize to use innerWidth/innerHeight.

Code Agents Quick Reference

Key anchors:

  • updateViewportSize in Application.ts.js (800×600 clamp, non-fullscreen branch)
  • gui/screen/game/component/Hud.ts (pixel positioning)
  • gui/GameGui.ts.js (handleViewportChange)

Script side: Browser.getWindowBounds outer size → cache chrome={dx,dy} → setViewportSize(cw, ch); when changing the engine’s default resolution, remove the Math.min(innerWidth, config.viewport.width) clamp.

This article is licensed under Mulan Open Works License Attribution-PatentLicensed, Version 1 (MulanOWL BY-PL v1), or alternatively CC0-1.0 (public domain dedication). Copyright reserved. Attribution required. For enterprise compliance, please retain the original license notice. Original version: https://k4m7v2pz.github.io/en/blog/games/browser-windowed-game-resolution-cdp.html