docs: add helper page lifecycle fix & hidden domain design spec
🤖 Generated with [Qoder][https://qoder.com]
This commit is contained in:
@@ -0,0 +1,99 @@
|
|||||||
|
# Helper Page Lifecycle Fix & Hidden Domain Support
|
||||||
|
|
||||||
|
**Date:** 2026-04-14
|
||||||
|
**Status:** Approved
|
||||||
|
|
||||||
|
## Problem Statement
|
||||||
|
|
||||||
|
Two bugs in the browser-helper.html page management:
|
||||||
|
|
||||||
|
1. **Duplicate helper pages**: Every WebSocket client reconnection triggers a new `serve_client()` call, which creates a new `LiveBrowserCallbackHost` and opens a new helper page via `sgBrowerserOpenPage`. The old helper page tab is never closed, causing accumulation of orphaned tabs.
|
||||||
|
|
||||||
|
2. **Helper page is visible**: The bootstrap uses `sgBrowerserOpenPage` (visible tab API) instead of `sgHideBrowerserOpenPage` (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` — outer `loop` accepts new WebSocket connections
|
||||||
|
- `src/service/mod.rs:79` — each connection calls `serve_client()`
|
||||||
|
- `src/service/server.rs:241` — `cached_host` declared as local variable, re-initialized to `None` each call
|
||||||
|
- `src/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: String` field to `LiveBrowserCallbackHost` (stored at construction time)
|
||||||
|
- Add `use_hidden_domain: bool` field (stored at construction time)
|
||||||
|
- In `Drop::drop`, before shutting down the server thread:
|
||||||
|
1. Connect to `browser_ws_url` with 100ms connection timeout
|
||||||
|
2. Send register message
|
||||||
|
3. Send close command: `[helper_url, close_api, helper_url]`
|
||||||
|
- `close_api` = `"sgBrowserClosePage"` when `use_hidden_domain == false`
|
||||||
|
- `close_api` = `"sgHideBrowerserClosePage"` when `use_hidden_domain == true`
|
||||||
|
4. All steps are best-effort: failures are silently ignored
|
||||||
|
5. Total timeout cap: 500ms
|
||||||
|
|
||||||
|
### Step 2: Hidden domain config switch (for testing/gradual rollout)
|
||||||
|
|
||||||
|
#### 2a. Parameter plumbing
|
||||||
|
|
||||||
|
- `LiveBrowserCallbackHost::start_with_browser_ws_url` gains parameter `use_hidden_domain: bool`
|
||||||
|
- `bootstrap_helper_page` selects API based on this flag:
|
||||||
|
- `true` → `"sgHideBrowerserOpenPage"`
|
||||||
|
- `false` → `"sgBrowerserOpenPage"` (current behavior, default)
|
||||||
|
- `LiveBrowserCallbackHost` stores the flag for Drop close-command selection
|
||||||
|
|
||||||
|
#### 2b. Caller changes
|
||||||
|
|
||||||
|
- `mod.rs` / `server.rs` pass `false` as default
|
||||||
|
- To enable hidden domain, change the call site to pass `true`
|
||||||
|
|
||||||
|
## What Does NOT Change
|
||||||
|
|
||||||
|
- `callback_backend.rs` `SHOW_AREA = "show"` — JS injection targets visible business pages, not the helper itself
|
||||||
|
- `sgBrowserExcuteJsCodeByDomain` area 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_host` tests: adapt to new signature (add `false` parameter)
|
||||||
|
- New unit test: `use_hidden_domain = true` → bootstrap sends `sgHideBrowerserOpenPage`
|
||||||
|
- New unit test: `use_hidden_domain = false` → bootstrap sends `sgBrowerserOpenPage` (regression)
|
||||||
|
- `cargo build` + `cargo test` full 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 |
|
||||||
Reference in New Issue
Block a user