diff --git a/plugins/_browser/webui/browser-store.js b/plugins/_browser/webui/browser-store.js index 1bc5bb337..a848a4313 100644 --- a/plugins/_browser/webui/browser-store.js +++ b/plugins/_browser/webui/browser-store.js @@ -13,6 +13,8 @@ const BROWSER_FIRST_INSTALL_TIMEOUT_MS = 300000; const BROWSER_CONFIG_REFRESH_MS = 15000; const VIEWPORT_SYNC_DEBOUNCE_MS = 220; const VIEWPORT_SYNC_SIZE_TOLERANCE = 4; +const CANVAS_VIEWPORT_SETTLE_MS = 260; +const SURFACE_VIEWPORT_STABLE_FRAMES = 2; const ANNOTATION_DRAG_THRESHOLD = 6; const ANNOTATION_MAX_COMMENTS = 24; const ANNOTATION_DOM_LIMIT = 1200; @@ -91,6 +93,7 @@ const model = { _mode: "", _surfaceMounted: false, _surfaceSwitching: false, + _surfaceOpenedAt: 0, _connectSequence: 0, _viewerToken: "", extensionMenuOpen: false, @@ -403,6 +406,7 @@ const model = { const targetBrowserId = requestedBrowserId || this.activeBrowserId || this.firstBrowserId(); this._mode = nextMode; this._surfaceMounted = true; + this._surfaceOpenedAt = Date.now(); this._lastViewportKey = ""; if (!modeChanged && (this.frameSrc || !targetBrowserId)) return; @@ -442,12 +446,17 @@ const model = { let stableCount = 0; for (let index = 0; index < 24; index += 1) { await nextAnimationFrame(); - const viewport = this.currentViewportSize(); + const viewport = this.surfaceViewportMeasurement(); if (!viewport) continue; - const key = `${viewport.width}x${viewport.height}`; + const key = `${viewport.rawWidth}x${viewport.rawHeight}`; if (key === lastKey) { stableCount += 1; - if (stableCount >= 2) return viewport; + const canvasSettled = this._mode !== "canvas" + || !this._surfaceOpenedAt + || Date.now() - this._surfaceOpenedAt >= CANVAS_VIEWPORT_SETTLE_MS; + if (canvasSettled && stableCount >= SURFACE_VIEWPORT_STABLE_FRAMES) { + return { width: viewport.width, height: viewport.height }; + } } else { stableCount = 0; lastKey = key; @@ -1286,15 +1295,26 @@ const model = { }, currentViewportSize() { + const measurement = this.surfaceViewportMeasurement(); + if (!measurement) return null; + return { + width: measurement.width, + height: measurement.height, + }; + }, + + surfaceViewportMeasurement() { const stage = this._stageElement; if (!stage) return null; const rect = stage.getBoundingClientRect?.(); - const width = Math.round(rect?.width || stage.clientWidth || 0); - const height = Math.round(rect?.height || stage.clientHeight || 0); - if (width < 80 || height < 80) return null; + const rawWidth = Math.round(rect?.width || stage.clientWidth || 0); + const rawHeight = Math.round(rect?.height || stage.clientHeight || 0); + if (rawWidth < 80 || rawHeight < 80) return null; return { - width: Math.max(320, width), - height: Math.max(200, height), + rawWidth, + rawHeight, + width: Math.max(320, rawWidth), + height: Math.max(200, rawHeight), }; }, diff --git a/tests/test_browser_agent_regressions.py b/tests/test_browser_agent_regressions.py index 9e4c40c8e..fea453201 100644 --- a/tests/test_browser_agent_regressions.py +++ b/tests/test_browser_agent_regressions.py @@ -347,6 +347,19 @@ def test_browser_viewer_allows_slow_extension_startup(): assert "Installing Chromium for the first Browser run" in js +def test_browser_canvas_startup_waits_for_raw_viewport_settle(): + js = (PROJECT_ROOT / "plugins" / "_browser" / "webui" / "browser-store.js").read_text( + encoding="utf-8" + ) + + assert "const CANVAS_VIEWPORT_SETTLE_MS = 260;" in js + assert "surfaceViewportMeasurement()" in js + assert "rawWidth" in js + assert "rawHeight" in js + assert "const key = `${viewport.rawWidth}x${viewport.rawHeight}`;" in js + assert "Date.now() - this._surfaceOpenedAt >= CANVAS_VIEWPORT_SETTLE_MS" in js + + def test_browser_ui_spinners_have_browser_local_animation(): main_html = (PROJECT_ROOT / "plugins" / "_browser" / "webui" / "browser-panel.html").read_text( encoding="utf-8"