Skip to content

fix(window): per-instance WebView2 user data folders so new launchers boot alongside old ones#342

Merged
lshw54 merged 2 commits into
codefrom
fix/per-instance-webview-profile
Jul 12, 2026
Merged

fix(window): per-instance WebView2 user data folders so new launchers boot alongside old ones#342
lshw54 merged 2 commits into
codefrom
fix/per-instance-webview-profile

Conversation

@lshw54

@lshw54 lshw54 commented Jul 12, 2026

Copy link
Copy Markdown
Collaborator

What

Multi-instance users report (#340) that after leaving several launchers running for a while (8 in the report), newly started launchers show a process in Task Manager but never display any UI.

Root cause chain:

  1. Every instance shares Tauri's default WebView2 user data folder (%LOCALAPPDATA%\tw.beanfun.app\EBWebView), and all WebView2s sharing a folder share one browser process.
  2. CoreWebView2Environment creation fails with HRESULT_FROM_WIN32(ERROR_INVALID_STATE) when the new environment doesn't exactly match the already-running browser process — a different WebView2 Runtime version or different environment options. The Evergreen WebView2 Runtime auto-updates in the background, which is the "left idle for a while" trigger: instances started before the update keep the old browser process alive, and every launcher started after it fails webview creation. Mixing Beanfun versions (whose browser args differ) trips the same check, and cross-process sharing is fragile even without version skew (WebView2Feedback#4751, closed "not planned").
  3. The main window is transparent: true + decorations: false — with no webview there is nothing to paint, so the failed instance is an invisible process. Exactly as reported.

Fix

Give each instance its own throwaway profile: %LOCALAPPDATA%\tw.beanfun.app\webview-instances\<pid>. No two Beanfun processes ever negotiate over a shared browser process, so runtime auto-updates and version mixes can no longer strand new instances. Nothing in this app needs webview persistence — settings live in Config.xml via the backend (pinia-plugin-persistedstate deliberately not installed) and login cookies live in the backend HTTP client's cookie jar — so isolation costs nothing functionally.

  • Cleanup protocol: folders are reclaimed on the next boot, and liveness is detected with the filesystem instead of the PID table (PIDs get reused): a folder is only deleted after being renamed away, and Windows refuses to rename a directory while any file inside is open. Each instance holds an open .instance-lock handle in its folder for its whole lifetime, so a live instance can never be swept. Interrupted deletes keep the .stale name and are retried next boot. The sweep runs on a background thread off the boot path.
  • The main window is now built in setup() via WebviewWindowBuilder (identical properties to the old tauri.conf.json entry, label stays "main") because a static config can't carry a PID-dependent path.
  • 5 new unit tests cover the sweep protocol (removes unlocked, keeps own, keeps open-handle, retries .stale, ignores plain files).

Verified on-device

  • Two simultaneous instances: both show a real window, each with its own webview-instances\<pid> folder.
  • After killing both and launching a third: the boot sweep removed both dead folders.
  • Rust 771 unit + integration tests, clippy, fmt; frontend 639 tests (unchanged) all pass.

Trade-off notes

Closes #340

… boot alongside old ones

Multi-instance users (issue #340) found that after leaving several
launchers running for a while, newly started launchers showed a process
in Task Manager but never any UI.

Root cause: every instance shared Tauri's default WebView2 user data
folder, and all WebView2s sharing a folder share one browser process.
CoreWebView2Environment creation fails with ERROR_INVALID_STATE when it
does not exactly match the running browser process — a different
WebView2 Runtime version (the Evergreen runtime auto-updates in the
background: the 'left idle for a while' trigger) or different browser
args (mixing app versions). And because the main window is transparent
+ undecorated, a window whose webview failed paints nothing: invisible
process, exactly as reported. Cross-process sharing is fragile even
without version skew (MicrosoftEdge/WebView2Feedback#4751).

Fix: give each instance its own throwaway profile at
%LOCALAPPDATA%\tw.beanfun.app\webview-instances\<pid>. Nothing in
this app needs webview persistence (settings live in Config.xml via
the backend; login cookies live in the backend HTTP client's jar), so
isolation costs nothing functionally and removes the shared browser
process entirely.

- lib.rs: prepare_webview_data_dir() creates the folder and holds an
  open .instance-lock handle for the process lifetime;
  sweep_stale_webview_dirs() reclaims dead instances' folders on boot
  (rename-then-delete: Windows refuses to rename a directory while any
  file inside is open, so live instances can never be swept — no PID
  table races)
- main window is now built in setup() via WebviewWindowBuilder (same
  properties as the old tauri.conf.json entry, label stays 'main') so
  the PID-dependent data_directory can be injected
- 5 new unit tests for the sweep protocol

Verified on-device: two simultaneous instances each get their own
folder and visible window; a later boot sweeps both dead folders.
@lshw54 lshw54 self-assigned this Jul 12, 2026
@lshw54
lshw54 merged commit 13b9c91 into code Jul 12, 2026
3 checks passed
@lshw54
lshw54 deleted the fix/per-instance-webview-profile branch July 12, 2026 19:08
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.

同時啟動多個登入器經過一段時間後,會無法開啟新的登入器

1 participant