[Bug]: Embedded browser view renders a duplicated page and drifts over the chat pane at fractional DPI

未關閉
#3,700 1 則留言 0 個 reaction 已指派 0 人 在 GitHub 檢視

維護者通常 1 天內回覆

還沒有人認領這個 Issue。

評估

難度
4/5
預估耗時
3-5 天
新手友好度
40/100
Issue 類型
缺陷
描述清晰度
描述清楚
活躍度
活躍
技術堆疊
rust, typescript

研究方向

Start in src/web-ui/src/app/scenes/browser/useEmbeddedBrowserWebview.ts, where syncWebviewBounds and the browser_webview_create and browser_webview_set_bounds calls pass CSS-pixel bounds straight to the native host, then check the presentation.isCurrent() guard and the BrowserPreview frozen-frame overlay. Reproduce by setting devicePixelRatio to 1.21 and confirming the child view rect differs from the host rect. Done when the native view matches the host rect at fractional scale and geometry keeps syncing after a lost resize event.

由索引模型根據 Issue 內容生成。

描述

Summary

At a fractional Windows display scale (121%, devicePixelRatio = 1.21), the built-in browser panel renders the page twice: a live interactive view plus a second, magnified and blurred copy to its right. The live view itself is also mispositioned — it is smaller than its host and shifted toward the window origin, so it visually covers the chat pane next to the panel.

Area

Desktop app

Reproduction or evidence

Steps:

  1. Set the Windows display scale to a fractional value (here: 121%).
  2. Open the built-in browser panel — either let the agent open any URL, or use the Browser tab.
  3. Observe that the page is drawn twice, and that the interactive copy does not sit inside the panel.

Measurements (Win32 GetWindowRect on the child view vs. the rectangle the frontend computes for the panel host):

host rect (CSS px)   1325.6,  89.8, 397.5 x 986.2
devicePixelRatio     1.21
child view before     1044,   ...,  398  x ...     <- CSS numbers applied 1:1 as device pixels
child view after      1604,   109,  481  x 1193    <- expected: rect x 1.21

So the child view was rendered at 1 / 1.21 of its host and pulled toward the window origin by the same factor. At 100% scale the bug is invisible, which is why it only shows up on scaled displays.

Root cause 1 — units. getBoundingClientRect() reports CSS pixels, while browser_webview_create / browser_webview_set_bounds hand those values straight to the native host, which applies them 1:1 without multiplying by the window scale factor. At fractional DPI the child view therefore shrinks and offsets.

src/web-ui/src/app/scenes/browser/useEmbeddedBrowserWebview.ts:

await api.invoke('browser_webview_set_bounds', {
  request: { label, x: bounds.left, y: bounds.top, width: bounds.width, height: bounds.height },
});

Root cause 2 — geometry sync can be blocked forever. syncWebviewBounds() returns early when presentationRef.current.isCurrent() is false. A panel that loses the presentation race still renders the visible native view, but it never syncs its geometry again, so the view stays at its creation-time size:

if (!isTauri || !target || !viewportRef.current || !mountedRef.current
  || (targetId && !presentationRef.current?.isCurrent())) return;

The presentation.run() wrapper used for the actual call has the same owner check, so it silently skips too.

Root cause 3 — the frozen frame shows through. BrowserPreview is a decorative JPEG of the native view pinned below it (captured every 5s, object-fit: fill, pointer-events: none). It is only supposed to be visible while a DOM overlay covers the native view. When the geometry above drifts, the stale frame is stretched across the gap and produces exactly the "second, blurred browser" appearance. Additionally the ResizeObserver that drives syncWebviewBounds only reports layout changes it actually witnessed — a panel that mounts at its final size never receives one.

Proposed fix (I have a patch and updated unit tests ready):

  1. Convert CSS → device pixels once, at the create / set_bounds boundary:
    const dpr = window.devicePixelRatio || 1;
    x: bounds.left * dpr, y: bounds.top * dpr,
    width: bounds.width * dpr, height: bounds.height * dpr,
    
  2. Do not gate geometry sync on presentation ownership, and add a periodic reconcile (e.g. 400 ms) so a lost resize event cannot leave the view at its creation size.
  3. Render the frozen frame only on the active panel, and hide inactive panel content (visibility: hidden).

Happy to open a PR with the patch if that is useful.

Environment, if relevant
OpenBitFun version: 1.0.8 (frontend revision bundled-b5489663e559ac91)
OS: Windows 11 家庭中文版, build 22631
Display: 2560x1440 @ 121% scale
Mode: Desktop app, built-in browser panel (not an external browser)
主要語言
Rust
星號
2.3k
分支
236
平均合併
12 小時 24 分鐘
30 天內合併 PR
370

環境準備

從這裡開始

  1. 先讀完整個 Issue,再讀專案的貢獻指南。
  2. 在 Issue 下留言說明你要接手 —— 這能避免兩個人做同樣的事。
  3. Fork 儲存庫,在一個分支上完成修改。
  4. 送出 Pull Request,並在描述裡引用這個 Issue 編號。

GCWing/OpenBitFun 的其他 Issue

查看 GCWing/OpenBitFun 的全部 Issue

相似的 Issue

更多 Rust Issue

把新 issue 寄到你的電子郵件信箱

精選適合新手參與的 GitHub issue 摘要。