From adb64429ee1f76444bed293e6e10bc3e2bbd6519 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9C=A8=E7=82=8E?= <635735027@qq.com> Date: Tue, 14 Apr 2026 09:09:59 +0800 Subject: [PATCH] feat(callback_host): close helper page on Drop via browser WS MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🤖 Generated with [Qoder][https://qoder.com] --- src/browser/callback_host.rs | 47 ++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/src/browser/callback_host.rs b/src/browser/callback_host.rs index e39d21f..6d18b0f 100644 --- a/src/browser/callback_host.rs +++ b/src/browser/callback_host.rs @@ -324,6 +324,13 @@ impl BrowserCallbackExecutor for LiveBrowserCallbackHost { impl Drop for LiveBrowserCallbackHost { fn drop(&mut self) { + // Best-effort: tell the browser to close the helper page tab. + close_helper_page( + &self.browser_ws_url, + self.host.helper_url(), + self.use_hidden_domain, + ); + self.shutdown.store(true, Ordering::Relaxed); if let Some(handle) = self.server_thread.lock().unwrap().take() { let _ = handle.join(); @@ -373,6 +380,46 @@ fn bootstrap_helper_page( Ok(()) } +/// Best-effort attempt to close the helper page tab via browser WebSocket. +/// Silently ignores all errors — this runs during Drop and must not panic. +fn close_helper_page(browser_ws_url: &str, helper_url: &str, use_hidden_domain: bool) { + let close_action = if use_hidden_domain { + "sgHideBrowerserClosePage" + } else { + "sgBrowserClosePage" + }; + + let result: Result<(), Box> = (|| { + // Use a raw TcpStream with timeouts instead of tungstenite::connect + // which does not expose a connection timeout. + let addr = browser_ws_url + .trim_start_matches("ws://") + .trim_start_matches("wss://"); + let stream = TcpStream::connect_timeout( + &addr.parse().map_err(|e| format!("addr parse: {e}"))?, + Duration::from_millis(100), + )?; + stream.set_read_timeout(Some(Duration::from_millis(200)))?; + stream.set_write_timeout(Some(Duration::from_millis(200)))?; + let (mut websocket, _) = tungstenite::client( + browser_ws_url, + stream, + )?; + websocket.send(Message::Text( + r#"{"type":"register","role":"web"}"#.to_string().into(), + ))?; + // Drain the welcome prelude (best-effort, ignore timeout). + let _ = websocket.read(); + let payload = json!([helper_url, close_action, helper_url]).to_string(); + websocket.send(Message::Text(payload.into()))?; + Ok(()) + })(); + + if let Err(err) = result { + eprintln!("close_helper_page best-effort failed (harmless): {err}"); + } +} + fn recv_bootstrap_prelude( websocket: &mut tungstenite::WebSocket>, ) -> Result<(), PipeError> {