4.9 KiB
Helper Page Lifecycle Fix & Hidden Domain Support
Date: 2026-04-14 Status: Approved
Problem Statement
Two bugs in the browser-helper.html page management:
-
Duplicate helper pages: Every WebSocket client reconnection triggers a new
serve_client()call, which creates a newLiveBrowserCallbackHostand opens a new helper page viasgBrowerserOpenPage. The old helper page tab is never closed, causing accumulation of orphaned tabs. -
Helper page is visible: The bootstrap uses
sgBrowerserOpenPage(visible tab API) instead ofsgHideBrowerserOpenPage(hidden domain API). The helper page should not be visible to the user.
Root Cause Analysis
Duplicate pages
Call chain:
src/service/mod.rs:72— outerloopaccepts new WebSocket connectionssrc/service/mod.rs:79— each connection callsserve_client()src/service/server.rs:241—cached_hostdeclared as local variable, re-initialized toNoneeach callsrc/service/server.rs:288→callback_host.rs:241—bootstrap_helper_page()opens a new helper tab
Drop for LiveBrowserCallbackHost (callback_host.rs:321-328) only shuts down the HTTP server thread. It does not send a browser close command for the helper tab.
Visible page
callback_host.rs:28: HELPER_BOOTSTRAP_ACTION = "sgBrowerserOpenPage" — this is the visible-domain open API (API #7). The hidden-domain equivalent is sgHideBrowerserOpenPage (API #6).
Solution: Approach C — Incremental Fix
Step 1: Fix lifecycle (immediate, deterministic fix)
1a. Lift cached_host to outer loop
Move cached_host: Option<Arc<LiveBrowserCallbackHost>> from inside serve_client() to before the loop in run_service() (mod.rs). Change serve_client() signature to accept &mut Option<Arc<LiveBrowserCallbackHost>> instead of creating its own.
Effect: Multiple WebSocket reconnections share the same host. Helper page opens once per process lifetime.
1b. Close helper page on Drop
Enhance Drop for LiveBrowserCallbackHost:
- Add
browser_ws_url: Stringfield toLiveBrowserCallbackHost(stored at construction time) - Add
use_hidden_domain: boolfield (stored at construction time) - In
Drop::drop, before shutting down the server thread:- Connect to
browser_ws_urlwith 100ms connection timeout - Send register message
- Send close command:
[helper_url, close_api, helper_url]close_api="sgBrowserClosePage"whenuse_hidden_domain == falseclose_api="sgHideBrowerserClosePage"whenuse_hidden_domain == true
- All steps are best-effort: failures are silently ignored
- Total timeout cap: 500ms
- Connect to
Step 2: Hidden domain config switch (for testing/gradual rollout)
2a. Parameter plumbing
LiveBrowserCallbackHost::start_with_browser_ws_urlgains parameteruse_hidden_domain: boolbootstrap_helper_pageselects API based on this flag:true→"sgHideBrowerserOpenPage"false→"sgBrowerserOpenPage"(current behavior, default)
LiveBrowserCallbackHoststores the flag for Drop close-command selection
2b. Caller changes
mod.rs/server.rspassfalseas default- To enable hidden domain, change the call site to pass
true
What Does NOT Change
callback_backend.rsSHOW_AREA = "show"— JS injection targets visible business pages, not the helper itselfsgBrowserExcuteJsCodeByDomainarea parameter — stays"show"regardless of helper domain- Helper page HTML content — WebSocket connection and command polling JS remain the same
collect_lineloss.js— not affected
Affected Files
| File | Change |
|---|---|
src/browser/callback_host.rs |
New fields on LiveBrowserCallbackHost, start_with_browser_ws_url signature change, Drop enhancement, new close_helper_page helper fn |
src/service/mod.rs |
cached_host lifted to outer loop, passed to serve_client |
src/service/server.rs |
serve_client signature change to accept &mut Option<Arc<LiveBrowserCallbackHost>> |
| Existing test files | Adapt start_with_browser_ws_url calls with new use_hidden_domain parameter |
Testing
- Existing
callback_hosttests: adapt to new signature (addfalseparameter) - New unit test:
use_hidden_domain = true→ bootstrap sendssgHideBrowerserOpenPage - New unit test:
use_hidden_domain = false→ bootstrap sendssgBrowerserOpenPage(regression) cargo build+cargo testfull verification
Browser API Reference
| API | Wire format | Effect |
|---|---|---|
sgBrowerserOpenPage (API #7) |
[requesturl, "sgBrowerserOpenPage", url] |
Opens visible tab |
sgHideBrowerserOpenPage (API #6) |
[requesturl, "sgHideBrowerserOpenPage", url] |
Opens in hidden domain |
sgBrowserClosePage (API #64) |
[requesturl, "sgBrowserClosePage", url] |
Closes visible tab |
sgHideBrowerserClosePage (API #68) |
[requesturl, "sgHideBrowerserClosePage", url] |
Closes hidden domain page |