From 5c5c0d80814740c7ec211efe85664162f966332c Mon Sep 17 00:00:00 2001 From: GyDi Date: Mon, 23 Mar 2026 22:38:20 +0800 Subject: [PATCH] fix: enhance target tracking and update page information handling (#969) --- cli/src/native/actions.rs | 29 +++++++++++---- cli/src/native/browser.rs | 76 ++++++++++++++++++++++++++++++++++++--- 2 files changed, 94 insertions(+), 11 deletions(-) diff --git a/cli/src/native/actions.rs b/cli/src/native/actions.rs index c5a751e..4c29404 100644 --- a/cli/src/native/actions.rs +++ b/cli/src/native/actions.rs @@ -9,13 +9,13 @@ use time::{format_description::well_known::Rfc3339, OffsetDateTime}; use tokio::sync::{broadcast, oneshot, RwLock}; use super::auth; -use super::browser::{BrowserManager, WaitUntil}; +use super::browser::{should_track_target, BrowserManager, WaitUntil}; use super::cdp::chrome::LaunchOptions; use super::cdp::client::CdpClient; use super::cdp::types::{ AttachToTargetParams, AttachToTargetResult, CdpEvent, ConsoleApiCalledEvent, CreateTargetResult, DispatchMouseEventParams, ExceptionThrownEvent, TargetCreatedEvent, - TargetDestroyedEvent, + TargetDestroyedEvent, TargetInfoChangedEvent, }; use super::cookies; use super::diff; @@ -137,6 +137,7 @@ pub struct MouseState { struct DrainedEvents { pending_acks: Vec, new_targets: Vec, + changed_targets: Vec, destroyed_targets: Vec, /// Cross-origin iframe (frame_id, session_id) pairs from Target.attachedToTarget. attached_iframe_sessions: Vec<(String, String)>, @@ -379,6 +380,7 @@ impl DaemonState { let mut pending_acks: Vec = Vec::new(); let mut new_targets: Vec = Vec::new(); + let mut changed_targets: Vec = Vec::new(); let mut destroyed_targets: Vec = Vec::new(); let mut attached_iframe_sessions: Vec<(String, String)> = Vec::new(); let mut detached_iframe_sessions: Vec = Vec::new(); @@ -392,10 +394,7 @@ impl DaemonState { if let Ok(te) = serde_json::from_value::(event.params.clone()) { - if (te.target_info.target_type == "page" - || te.target_info.target_type == "webview") - && !te.target_info.url.is_empty() - { + if should_track_target(&te.target_info) { let already_tracked = self .browser .as_ref() @@ -407,6 +406,16 @@ impl DaemonState { } continue; } + "Target.targetInfoChanged" => { + if let Ok(te) = serde_json::from_value::( + event.params.clone(), + ) { + if should_track_target(&te.target_info) { + changed_targets.push(te); + } + } + continue; + } "Target.targetDestroyed" => { if let Ok(te) = serde_json::from_value::(event.params.clone()) @@ -687,6 +696,7 @@ impl DaemonState { DrainedEvents { pending_acks, new_targets, + changed_targets, destroyed_targets, attached_iframe_sessions, detached_iframe_sessions, @@ -716,6 +726,7 @@ pub async fn execute_command(cmd: &Value, state: &mut DaemonState) -> Value { let DrainedEvents { pending_acks, new_targets, + changed_targets, destroyed_targets, attached_iframe_sessions, detached_iframe_sessions, @@ -795,6 +806,12 @@ pub async fn execute_command(cmd: &Value, state: &mut DaemonState) -> Value { } } + for te in &changed_targets { + if let Some(ref mut mgr) = state.browser { + mgr.update_page_target_info(&te.target_info); + } + } + // Hot-reload and check action policy if let Some(ref mut policy) = state.policy { let _ = policy.reload(); diff --git a/cli/src/native/browser.rs b/cli/src/native/browser.rs index 661e5d2..c6eae36 100644 --- a/cli/src/native/browser.rs +++ b/cli/src/native/browser.rs @@ -95,6 +95,21 @@ fn is_internal_chrome_target(url: &str) -> bool { || url.starts_with("devtools://") } +pub(crate) fn should_track_target(target: &TargetInfo) -> bool { + (target.target_type == "page" || target.target_type == "webview") + && (target.url.is_empty() || !is_internal_chrome_target(&target.url)) +} + +fn update_page_target_info_in_pages(pages: &mut [PageInfo], target: &TargetInfo) -> bool { + if let Some(page) = pages.iter_mut().find(|p| p.target_id == target.target_id) { + page.url = target.url.clone(); + page.title = target.title.clone(); + page.target_type = target.target_type.clone(); + return true; + } + false +} + /// Converts common error messages into AI-friendly, actionable descriptions. pub fn to_ai_friendly_error(error: &str) -> String { let lower = error.to_lowercase(); @@ -334,11 +349,7 @@ impl BrowserManager { let page_targets: Vec = result .target_infos .into_iter() - .filter(|t| { - (t.target_type == "page" || t.target_type == "webview") - && !t.url.is_empty() - && !is_internal_chrome_target(&t.url) - }) + .filter(should_track_target) .collect(); if page_targets.is_empty() { @@ -1072,6 +1083,10 @@ impl BrowserManager { self.active_page_index = index; } + pub fn update_page_target_info(&mut self, target: &TargetInfo) -> bool { + update_page_target_info_in_pages(&mut self.pages, target) + } + pub fn remove_page_by_target_id(&mut self, target_id: &str) { if let Some(pos) = self.pages.iter().position(|p| p.target_id == target_id) { self.pages.remove(pos); @@ -1328,6 +1343,57 @@ mod tests { use super::*; use tokio::time::sleep; + #[test] + fn test_should_track_popup_target_with_empty_url() { + let target = TargetInfo { + target_id: "popup-1".to_string(), + target_type: "page".to_string(), + title: String::new(), + url: String::new(), + attached: None, + browser_context_id: None, + }; + + assert!(should_track_target(&target)); + } + + #[test] + fn test_should_not_track_internal_chrome_target() { + let target = TargetInfo { + target_id: "chrome-tab".to_string(), + target_type: "page".to_string(), + title: "New Tab".to_string(), + url: "chrome://newtab/".to_string(), + attached: None, + browser_context_id: None, + }; + + assert!(!should_track_target(&target)); + } + + #[test] + fn test_update_page_target_info_in_pages_updates_existing_page() { + let mut pages = vec![PageInfo { + target_id: "popup-1".to_string(), + session_id: "session-1".to_string(), + url: String::new(), + title: String::new(), + target_type: "page".to_string(), + }]; + let target = TargetInfo { + target_id: "popup-1".to_string(), + target_type: "page".to_string(), + title: "Popup".to_string(), + url: "https://example.com/popup".to_string(), + attached: None, + browser_context_id: None, + }; + + assert!(update_page_target_info_in_pages(&mut pages, &target)); + assert_eq!(pages[0].url, "https://example.com/popup"); + assert_eq!(pages[0].title, "Popup"); + } + #[test] fn test_validate_launch_options_extensions_and_cdp() { let ext = vec!["/path/to/ext".to_string()];