Fix browser canvas startup viewport settle
Wait for the right-canvas browser surface to finish its opening transition before using its dimensions as the Playwright viewport. Measure raw stage dimensions for stability, then apply the existing clamped viewport values so initial screencasts do not render into a stretched canvas. Add a browser regression guard for the raw viewport settle path.
Alessandro committed
Apr 27, 2026 at 17:35 UTC
e412f5faf71820da97d6691f7fab96cc4e08d9fc
2 files changed
+41
-8
plugins/_browser/webui/browser-store.js
+28
-8
@@ -13,6 +13,8 @@ const BROWSER_FIRST_INSTALL_TIMEOUT_MS = 300000;
13
const BROWSER_CONFIG_REFRESH_MS = 15000;
14
const VIEWPORT_SYNC_DEBOUNCE_MS = 220;
15
const VIEWPORT_SYNC_SIZE_TOLERANCE = 4;
16
+const CANVAS_VIEWPORT_SETTLE_MS = 260;
17
+const SURFACE_VIEWPORT_STABLE_FRAMES = 2;
18
const ANNOTATION_DRAG_THRESHOLD = 6;
19
const ANNOTATION_MAX_COMMENTS = 24;
20
const ANNOTATION_DOM_LIMIT = 1200;
@@ -91,6 +93,7 @@ const model = {
93
_mode: "",
94
_surfaceMounted: false,
95
_surfaceSwitching: false,
96
+ _surfaceOpenedAt: 0,
97
_connectSequence: 0,
98
_viewerToken: "",
99
extensionMenuOpen: false,
@@ -403,6 +406,7 @@ const model = {
406
const targetBrowserId = requestedBrowserId || this.activeBrowserId || this.firstBrowserId();
407
this._mode = nextMode;
408
this._surfaceMounted = true;
409
+ this._surfaceOpenedAt = Date.now();
410
this._lastViewportKey = "";
411
if (!modeChanged && (this.frameSrc || !targetBrowserId)) return;
412
@@ -442,12 +446,17 @@ const model = {
446
let stableCount = 0;
447
for (let index = 0; index < 24; index += 1) {
448
await nextAnimationFrame();
445
- const viewport = this.currentViewportSize();
449
+ const viewport = this.surfaceViewportMeasurement();
450
if (!viewport) continue;
447
- const key = `${viewport.width}x${viewport.height}`;
451
+ const key = `${viewport.rawWidth}x${viewport.rawHeight}`;
452
if (key === lastKey) {
453
stableCount += 1;
450
- if (stableCount >= 2) return viewport;
454
+ const canvasSettled = this._mode !== "canvas"
455
+ || !this._surfaceOpenedAt
456
+ || Date.now() - this._surfaceOpenedAt >= CANVAS_VIEWPORT_SETTLE_MS;
457
+ if (canvasSettled && stableCount >= SURFACE_VIEWPORT_STABLE_FRAMES) {
458
+ return { width: viewport.width, height: viewport.height };
459
+ }
460
} else {
461
stableCount = 0;
462
lastKey = key;
@@ -1286,15 +1295,26 @@ const model = {
1295
},
1296
1297
currentViewportSize() {
1298
+ const measurement = this.surfaceViewportMeasurement();
1299
+ if (!measurement) return null;
1300
+ return {
1301
+ width: measurement.width,
1302
+ height: measurement.height,
1303
+ };
1304
+ },
1305
+
1306
+ surfaceViewportMeasurement() {
1307
const stage = this._stageElement;
1308
if (!stage) return null;
1309
const rect = stage.getBoundingClientRect?.();
1292
- const width = Math.round(rect?.width || stage.clientWidth || 0);
1293
- const height = Math.round(rect?.height || stage.clientHeight || 0);
1294
- if (width < 80 || height < 80) return null;
1310
+ const rawWidth = Math.round(rect?.width || stage.clientWidth || 0);
1311
+ const rawHeight = Math.round(rect?.height || stage.clientHeight || 0);
1312
+ if (rawWidth < 80 || rawHeight < 80) return null;
1313
return {
1296
- width: Math.max(320, width),
1297
- height: Math.max(200, height),
1314
+ rawWidth,
1315
+ rawHeight,
1316
+ width: Math.max(320, rawWidth),
1317
+ height: Math.max(200, rawHeight),
1318
};
1319
},
1320
tests/test_browser_agent_regressions.py
+13
@@ -347,6 +347,19 @@ def test_browser_viewer_allows_slow_extension_startup():
347
assert "Installing Chromium for the first Browser run" in js
348
349
350
+def test_browser_canvas_startup_waits_for_raw_viewport_settle():
351
+ js = (PROJECT_ROOT / "plugins" / "_browser" / "webui" / "browser-store.js").read_text(
352
+ encoding="utf-8"
353
+ )
354
+
355
+ assert "const CANVAS_VIEWPORT_SETTLE_MS = 260;" in js
356
+ assert "surfaceViewportMeasurement()" in js
357
+ assert "rawWidth" in js
358
+ assert "rawHeight" in js
359
+ assert "const key = `${viewport.rawWidth}x${viewport.rawHeight}`;" in js
360
+ assert "Date.now() - this._surfaceOpenedAt >= CANVAS_VIEWPORT_SETTLE_MS" in js
361
+
362
+
363
def test_browser_ui_spinners_have_browser_local_animation():
364
main_html = (PROJECT_ROOT / "plugins" / "_browser" / "webui" / "browser-panel.html").read_text(
365
encoding="utf-8"