From 0d91ef72edd554bb485df18f14e85e380371e9bb Mon Sep 17 00:00:00 2001 From: hyunjinee Date: Sat, 21 Mar 2026 01:19:33 +0900 Subject: [PATCH 1/4] feat: support cross-origin iframe snapshots and interactions via Target.setAutoAttach (#925) Enable Target.setAutoAttach with flatten: true on page sessions so Chrome auto-creates dedicated CDP sessions for cross-origin iframe targets. - Add DrainedEvents struct, iframe_sessions map, attach/detach event handling - Extract resolve_ax_session (shared) and resolve_frame_session helpers - resolve_element_object_id returns (object_id, effective_session) tuple - resolve_element_center returns (x, y, effective_session) tuple - Input dispatch (click/hover/tap) uses effective session for correct coordinates - Thread iframe_sessions through element.rs, interaction.rs, screenshot.rs - Clear iframe sessions on navigate, tab switch, tab new, tab close - Ignore Target.setAutoAttach failure for non-Chrome backends (Lightpanda) - Unit tests for session resolution logic --- cli/src/native/actions.rs | 421 +++++++++++++++++++++++++++++----- cli/src/native/browser.rs | 15 ++ cli/src/native/element.rs | 273 ++++++++++++++++++---- cli/src/native/interaction.rs | 265 +++++++++++++++++---- cli/src/native/screenshot.rs | 29 ++- cli/src/native/snapshot.rs | 73 +++++- 6 files changed, 912 insertions(+), 164 deletions(-) diff --git a/cli/src/native/actions.rs b/cli/src/native/actions.rs index a9453c505..138831663 100644 --- a/cli/src/native/actions.rs +++ b/cli/src/native/actions.rs @@ -133,6 +133,17 @@ pub struct MouseState { pub buttons: i32, } +#[derive(Default)] +struct DrainedEvents { + pending_acks: Vec, + new_targets: Vec, + destroyed_targets: Vec, + /// Cross-origin iframe (frame_id, session_id) pairs from Target.attachedToTarget. + attached_iframe_sessions: Vec<(String, String)>, + /// Session IDs from Target.detachedFromTarget. + detached_iframe_sessions: Vec, +} + pub struct DaemonState { pub browser: Option, pub appium: Option, @@ -158,6 +169,9 @@ pub struct DaemonState { pub tracked_requests: Vec, pub request_tracking: bool, pub active_frame_id: Option, + /// Cross-origin iframe frame_id → dedicated CDP session_id. + /// Populated by Target.attachedToTarget events from Target.setAutoAttach. + pub iframe_sessions: HashMap, /// Origin-scoped extra HTTP headers set via `--headers` on navigate. /// Key is the origin (scheme + host + port), value is the headers map. /// Wrapped in Arc> so the background Fetch handler can read it. @@ -205,6 +219,7 @@ impl DaemonState { tracked_requests: Vec::new(), request_tracking: false, active_frame_id: None, + iframe_sessions: HashMap::new(), origin_headers: Arc::new(RwLock::new(HashMap::new())), fetch_handler_task: None, mouse_state: MouseState::default(), @@ -355,15 +370,17 @@ impl DaemonState { recording::stop_recording_task(&mut self.recording_state).await } - fn drain_cdp_events(&mut self) -> (Vec, Vec, Vec) { + fn drain_cdp_events(&mut self) -> DrainedEvents { let rx = match self.event_rx.as_mut() { Some(rx) => rx, - None => return (Vec::new(), Vec::new(), Vec::new()), + None => return DrainedEvents::default(), }; let mut pending_acks: Vec = Vec::new(); let mut new_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(); loop { match rx.try_recv() { @@ -397,6 +414,34 @@ impl DaemonState { } continue; } + "Target.attachedToTarget" => { + if let (Some(sid), Some(target_info)) = ( + event.params.get("sessionId").and_then(|v| v.as_str()), + event.params.get("targetInfo"), + ) { + let target_type = target_info + .get("type") + .and_then(|v| v.as_str()) + .unwrap_or(""); + if target_type == "iframe" { + if let Some(target_id) = + target_info.get("targetId").and_then(|v| v.as_str()) + { + attached_iframe_sessions + .push((target_id.to_string(), sid.to_string())); + } + } + } + continue; + } + "Target.detachedFromTarget" => { + if let Some(sid) = + event.params.get("sessionId").and_then(|v| v.as_str()) + { + detached_iframe_sessions.push(sid.to_string()); + } + continue; + } _ => {} } @@ -636,7 +681,13 @@ impl DaemonState { } } - (pending_acks, new_targets, destroyed_targets) + DrainedEvents { + pending_acks, + new_targets, + destroyed_targets, + attached_iframe_sessions, + detached_iframe_sessions, + } } } @@ -659,7 +710,13 @@ pub async fn execute_command(cmd: &Value, state: &mut DaemonState) -> Value { .to_string(); // Drain pending CDP events (console, errors, screencast frames, target lifecycle) - let (pending_acks, new_targets, destroyed_targets) = state.drain_cdp_events(); + let DrainedEvents { + pending_acks, + new_targets, + destroyed_targets, + attached_iframe_sessions, + detached_iframe_sessions, + } = state.drain_cdp_events(); if !pending_acks.is_empty() { if let Some(ref browser) = state.browser { if let Ok(session_id) = browser.active_session_id() { @@ -677,6 +734,26 @@ pub async fn execute_command(cmd: &Value, state: &mut DaemonState) -> Value { } } + // Track cross-origin iframe sessions + for (frame_id, iframe_sid) in &attached_iframe_sessions { + state + .iframe_sessions + .insert(frame_id.clone(), iframe_sid.clone()); + if let Some(ref mgr) = state.browser { + let _ = mgr + .client + .send_command_no_params("DOM.enable", Some(iframe_sid.as_str())) + .await; + let _ = mgr + .client + .send_command_no_params("Accessibility.enable", Some(iframe_sid.as_str())) + .await; + } + } + for sid in &detached_iframe_sessions { + state.iframe_sessions.retain(|_, v| v != sid); + } + for te in &new_targets { if let Some(ref mut mgr) = state.browser { let attach_result: Result = mgr @@ -1483,6 +1560,8 @@ async fn handle_navigate(cmd: &Value, state: &mut DaemonState) -> Result Result Result Result Result Result Result Result Result Result Result Result Result Result Result Result Result Result Result Result Result Result Result Result Result Result Result Result Result Result Result Result Result Result Result Result Result { - interaction::check(&mgr.client, &session_id, &state.ref_map, selector).await?; + interaction::check( + &mgr.client, + &session_id, + &state.ref_map, + selector, + &state.iframe_sessions, + ) + .await?; Ok(json!({ "checked": selector })) } "hover" => { - interaction::hover(&mgr.client, &session_id, &state.ref_map, selector).await?; + interaction::hover( + &mgr.client, + &session_id, + &state.ref_map, + selector, + &state.iframe_sessions, + ) + .await?; Ok(json!({ "hovered": selector })) } "text" => { @@ -3898,6 +4177,7 @@ async fn execute_subaction( &session_id, &state.ref_map, selector, + &state.iframe_sessions, ) .await?; Ok(json!({ "text": text })) @@ -4351,12 +4631,22 @@ async fn handle_drag(cmd: &Value, state: &mut DaemonState) -> Result Result Result Result Result Result<(f64, f64), String> { + iframe_sessions: &HashMap, +) -> Result<(f64, f64, String), String> { if let Some(ref_id) = parse_ref(selector_or_ref) { let entry = ref_map .get(&ref_id) .ok_or_else(|| format!("Unknown ref: {}", ref_id))?; + let effective_session = + resolve_frame_session(entry.frame_id.as_deref(), session_id, iframe_sessions); + // Try cached backend_node_id first (fast path) if let Some(backend_node_id) = entry.backend_node_id { let result: Result = client @@ -163,25 +167,26 @@ pub async fn resolve_element_center( node_id: None, object_id: None, }, - Some(session_id), + Some(effective_session), ) .await; if let Ok(r) = result { - return Ok(box_model_center(&r.model)); + let (x, y) = box_model_center(&r.model); + return Ok((x, y, effective_session.to_string())); } // backend_node_id is stale; re-query the accessibility tree below } // Fallback: re-query the accessibility tree to find a fresh node by role/name - let ref_frame_id = entry.frame_id.clone(); let fresh_id = find_node_id_by_role_name( client, session_id, &entry.role, &entry.name, entry.nth, - ref_frame_id.as_deref(), + entry.frame_id.as_deref(), + iframe_sessions, ) .await?; let result: DomGetBoxModelResult = client @@ -192,14 +197,16 @@ pub async fn resolve_element_center( node_id: None, object_id: None, }, - Some(session_id), + Some(effective_session), ) .await?; - return Ok(box_model_center(&result.model)); + let (x, y) = box_model_center(&result.model); + return Ok((x, y, effective_session.to_string())); } // CSS selector - resolve_by_selector(client, session_id, selector_or_ref).await + let (x, y) = resolve_by_selector(client, session_id, selector_or_ref).await?; + Ok((x, y, session_id.to_string())) } pub async fn resolve_element_object_id( @@ -207,12 +214,16 @@ pub async fn resolve_element_object_id( session_id: &str, ref_map: &RefMap, selector_or_ref: &str, -) -> Result { + iframe_sessions: &HashMap, +) -> Result<(String, String), String> { if let Some(ref_id) = parse_ref(selector_or_ref) { let entry = ref_map .get(&ref_id) .ok_or_else(|| format!("Unknown ref: {}", ref_id))?; + let effective_session = + resolve_frame_session(entry.frame_id.as_deref(), session_id, iframe_sessions); + // Try cached backend_node_id first (fast path) if let Some(backend_node_id) = entry.backend_node_id { let result: Result = client @@ -223,27 +234,27 @@ pub async fn resolve_element_object_id( node_id: None, object_group: Some("agent-browser".to_string()), }, - Some(session_id), + Some(effective_session), ) .await; if let Ok(r) = result { - if let Some(oid) = r.object.object_id { - return Ok(oid); + if let Some(object_id) = r.object.object_id { + return Ok((object_id, effective_session.to_string())); } } // backend_node_id is stale; re-query the accessibility tree below } // Fallback: re-query the accessibility tree to find a fresh node by role/name - let ref_frame_id = entry.frame_id.clone(); let fresh_id = find_node_id_by_role_name( client, session_id, &entry.role, &entry.name, entry.nth, - ref_frame_id.as_deref(), + entry.frame_id.as_deref(), + iframe_sessions, ) .await?; let result: DomResolveNodeResult = client @@ -254,13 +265,14 @@ pub async fn resolve_element_object_id( node_id: None, object_group: Some("agent-browser".to_string()), }, - Some(session_id), + Some(effective_session), ) .await?; - return result + let object_id = result .object .object_id - .ok_or_else(|| format!("No objectId for ref {}", ref_id)); + .ok_or_else(|| format!("No objectId for ref {}", ref_id))?; + return Ok((object_id, effective_session.to_string())); } // Selector fallback (CSS or XPath) @@ -277,10 +289,44 @@ pub async fn resolve_element_object_id( ) .await?; - result + let object_id = result .result .object_id - .ok_or_else(|| format!("Element not found: {}", selector_or_ref)) + .ok_or_else(|| format!("Element not found: {}", selector_or_ref))?; + Ok((object_id, session_id.to_string())) +} + +/// Determine which CDP session and parameters to use for an AX tree query. +/// Cross-origin iframes have a dedicated session (no frameId needed); +/// same-origin iframes use the parent session with a frameId parameter. +pub(super) fn resolve_ax_session<'a>( + frame_id: Option<&str>, + session_id: &'a str, + iframe_sessions: &'a HashMap, +) -> (serde_json::Value, &'a str) { + if let Some(fid) = frame_id { + if let Some(iframe_sid) = iframe_sessions.get(fid) { + (serde_json::json!({}), iframe_sid.as_str()) + } else { + (serde_json::json!({ "frameId": fid }), session_id) + } + } else { + (serde_json::json!({}), session_id) + } +} + +/// Resolve the effective CDP session for an element's frame. +/// If the element's frame_id has a dedicated cross-origin iframe session, return it. +/// Otherwise, return the parent session. +fn resolve_frame_session<'a>( + frame_id: Option<&str>, + session_id: &'a str, + iframe_sessions: &'a HashMap, +) -> &'a str { + frame_id + .and_then(|fid| iframe_sessions.get(fid)) + .map(|s| s.as_str()) + .unwrap_or(session_id) } /// Re-query the accessibility tree to find a node matching role+name+nth, @@ -294,14 +340,15 @@ async fn find_node_id_by_role_name( name: &str, nth: Option, frame_id: Option<&str>, + iframe_sessions: &HashMap, ) -> Result { - let ax_params = if let Some(fid) = frame_id { - serde_json::json!({ "frameId": fid }) - } else { - serde_json::json!({}) - }; + let (ax_params, effective_session) = resolve_ax_session(frame_id, session_id, iframe_sessions); let ax_tree: GetFullAXTreeResult = client - .send_command_typed("Accessibility.getFullAXTree", &ax_params, Some(session_id)) + .send_command_typed( + "Accessibility.getFullAXTree", + &ax_params, + Some(effective_session), + ) .await?; let nth_index = nth.unwrap_or(0); @@ -431,8 +478,16 @@ pub async fn get_element_text( session_id: &str, ref_map: &RefMap, selector_or_ref: &str, + iframe_sessions: &HashMap, ) -> Result { - let object_id = resolve_element_object_id(client, session_id, ref_map, selector_or_ref).await?; + let (object_id, effective_session) = resolve_element_object_id( + client, + session_id, + ref_map, + selector_or_ref, + iframe_sessions, + ) + .await?; let result: EvaluateResult = client .send_command_typed( @@ -445,7 +500,7 @@ pub async fn get_element_text( return_by_value: Some(true), await_promise: Some(false), }, - Some(session_id), + Some(&effective_session), ) .await?; @@ -462,8 +517,16 @@ pub async fn get_element_attribute( ref_map: &RefMap, selector_or_ref: &str, attribute: &str, + iframe_sessions: &HashMap, ) -> Result { - let object_id = resolve_element_object_id(client, session_id, ref_map, selector_or_ref).await?; + let (object_id, effective_session) = resolve_element_object_id( + client, + session_id, + ref_map, + selector_or_ref, + iframe_sessions, + ) + .await?; let result: EvaluateResult = client .send_command_typed( @@ -478,7 +541,7 @@ pub async fn get_element_attribute( return_by_value: Some(true), await_promise: Some(false), }, - Some(session_id), + Some(&effective_session), ) .await?; @@ -490,8 +553,16 @@ pub async fn is_element_visible( session_id: &str, ref_map: &RefMap, selector_or_ref: &str, + iframe_sessions: &HashMap, ) -> Result { - let object_id = resolve_element_object_id(client, session_id, ref_map, selector_or_ref).await?; + let (object_id, effective_session) = resolve_element_object_id( + client, + session_id, + ref_map, + selector_or_ref, + iframe_sessions, + ) + .await?; let result: EvaluateResult = client .send_command_typed( @@ -511,7 +582,7 @@ pub async fn is_element_visible( return_by_value: Some(true), await_promise: Some(false), }, - Some(session_id), + Some(&effective_session), ) .await?; @@ -527,8 +598,16 @@ pub async fn is_element_enabled( session_id: &str, ref_map: &RefMap, selector_or_ref: &str, + iframe_sessions: &HashMap, ) -> Result { - let object_id = resolve_element_object_id(client, session_id, ref_map, selector_or_ref).await?; + let (object_id, effective_session) = resolve_element_object_id( + client, + session_id, + ref_map, + selector_or_ref, + iframe_sessions, + ) + .await?; let result: EvaluateResult = client .send_command_typed( @@ -540,7 +619,7 @@ pub async fn is_element_enabled( return_by_value: Some(true), await_promise: Some(false), }, - Some(session_id), + Some(&effective_session), ) .await?; @@ -556,8 +635,16 @@ pub async fn is_element_checked( session_id: &str, ref_map: &RefMap, selector_or_ref: &str, + iframe_sessions: &HashMap, ) -> Result { - let object_id = resolve_element_object_id(client, session_id, ref_map, selector_or_ref).await?; + let (object_id, effective_session) = resolve_element_object_id( + client, + session_id, + ref_map, + selector_or_ref, + iframe_sessions, + ) + .await?; // Mirrors Playwright's getChecked() with follow-label retargeting: // 1. If element is a native checkbox/radio input, return .checked @@ -602,7 +689,7 @@ pub async fn is_element_checked( return_by_value: Some(true), await_promise: Some(false), }, - Some(session_id), + Some(&effective_session), ) .await?; @@ -618,8 +705,16 @@ pub async fn get_element_inner_text( session_id: &str, ref_map: &RefMap, selector_or_ref: &str, + iframe_sessions: &HashMap, ) -> Result { - let object_id = resolve_element_object_id(client, session_id, ref_map, selector_or_ref).await?; + let (object_id, effective_session) = resolve_element_object_id( + client, + session_id, + ref_map, + selector_or_ref, + iframe_sessions, + ) + .await?; let result: EvaluateResult = client .send_command_typed( @@ -631,7 +726,7 @@ pub async fn get_element_inner_text( return_by_value: Some(true), await_promise: Some(false), }, - Some(session_id), + Some(&effective_session), ) .await?; @@ -647,8 +742,16 @@ pub async fn get_element_inner_html( session_id: &str, ref_map: &RefMap, selector_or_ref: &str, + iframe_sessions: &HashMap, ) -> Result { - let object_id = resolve_element_object_id(client, session_id, ref_map, selector_or_ref).await?; + let (object_id, effective_session) = resolve_element_object_id( + client, + session_id, + ref_map, + selector_or_ref, + iframe_sessions, + ) + .await?; let result: EvaluateResult = client .send_command_typed( @@ -660,7 +763,7 @@ pub async fn get_element_inner_html( return_by_value: Some(true), await_promise: Some(false), }, - Some(session_id), + Some(&effective_session), ) .await?; @@ -676,8 +779,16 @@ pub async fn get_element_input_value( session_id: &str, ref_map: &RefMap, selector_or_ref: &str, + iframe_sessions: &HashMap, ) -> Result { - let object_id = resolve_element_object_id(client, session_id, ref_map, selector_or_ref).await?; + let (object_id, effective_session) = resolve_element_object_id( + client, + session_id, + ref_map, + selector_or_ref, + iframe_sessions, + ) + .await?; let result: EvaluateResult = client .send_command_typed( @@ -691,7 +802,7 @@ pub async fn get_element_input_value( return_by_value: Some(true), await_promise: Some(false), }, - Some(session_id), + Some(&effective_session), ) .await?; @@ -708,8 +819,16 @@ pub async fn set_element_value( ref_map: &RefMap, selector_or_ref: &str, value: &str, + iframe_sessions: &HashMap, ) -> Result<(), String> { - let object_id = resolve_element_object_id(client, session_id, ref_map, selector_or_ref).await?; + let (object_id, effective_session) = resolve_element_object_id( + client, + session_id, + ref_map, + selector_or_ref, + iframe_sessions, + ) + .await?; let js = format!( "function() {{ this.value = {}; this.dispatchEvent(new Event('input', {{bubbles: true}})); this.dispatchEvent(new Event('change', {{bubbles: true}})); }}", @@ -726,7 +845,7 @@ pub async fn set_element_value( return_by_value: Some(true), await_promise: Some(false), }, - Some(session_id), + Some(&effective_session), ) .await?; @@ -738,8 +857,16 @@ pub async fn get_element_bounding_box( session_id: &str, ref_map: &RefMap, selector_or_ref: &str, + iframe_sessions: &HashMap, ) -> Result { - let object_id = resolve_element_object_id(client, session_id, ref_map, selector_or_ref).await?; + let (object_id, effective_session) = resolve_element_object_id( + client, + session_id, + ref_map, + selector_or_ref, + iframe_sessions, + ) + .await?; let result: EvaluateResult = client .send_command_typed( @@ -755,7 +882,7 @@ pub async fn get_element_bounding_box( return_by_value: Some(true), await_promise: Some(false), }, - Some(session_id), + Some(&effective_session), ) .await?; @@ -793,8 +920,16 @@ pub async fn get_element_styles( ref_map: &RefMap, selector_or_ref: &str, properties: Option>, + iframe_sessions: &HashMap, ) -> Result { - let object_id = resolve_element_object_id(client, session_id, ref_map, selector_or_ref).await?; + let (object_id, effective_session) = resolve_element_object_id( + client, + session_id, + ref_map, + selector_or_ref, + iframe_sessions, + ) + .await?; let js = match properties { Some(props) => { @@ -832,7 +967,7 @@ pub async fn get_element_styles( return_by_value: Some(true), await_promise: Some(false), }, - Some(session_id), + Some(&effective_session), ) .await?; @@ -932,4 +1067,48 @@ mod tests { assert!((x - 60.0).abs() < 0.01); assert!((y - 40.0).abs() < 0.01); } + + // ----------------------------------------------------------------------- + // resolve_frame_session tests (Issue #925) + // Cross-origin iframe elements must resolve to the dedicated session. + // ----------------------------------------------------------------------- + + #[test] + fn test_cross_origin_element_uses_dedicated_session() { + let mut iframe_sessions = HashMap::new(); + iframe_sessions.insert( + "cross-origin-frame".to_string(), + "iframe-session".to_string(), + ); + + let session = resolve_frame_session( + Some("cross-origin-frame"), + "parent-session", + &iframe_sessions, + ); + + assert_eq!(session, "iframe-session"); + } + + #[test] + fn test_same_origin_element_uses_parent_session() { + let iframe_sessions = HashMap::new(); + + let session = resolve_frame_session( + Some("same-origin-frame"), + "parent-session", + &iframe_sessions, + ); + + assert_eq!(session, "parent-session"); + } + + #[test] + fn test_main_frame_element_uses_parent_session() { + let iframe_sessions = HashMap::new(); + + let session = resolve_frame_session(None, "parent-session", &iframe_sessions); + + assert_eq!(session, "parent-session"); + } } diff --git a/cli/src/native/interaction.rs b/cli/src/native/interaction.rs index 1d9dc78c6..b951f3461 100644 --- a/cli/src/native/interaction.rs +++ b/cli/src/native/interaction.rs @@ -1,3 +1,5 @@ +use std::collections::HashMap; + use serde_json::Value; use super::cdp::client::CdpClient; @@ -11,9 +13,17 @@ pub async fn click( selector_or_ref: &str, button: &str, click_count: i32, + iframe_sessions: &HashMap, ) -> Result<(), String> { - let (x, y) = resolve_element_center(client, session_id, ref_map, selector_or_ref).await?; - dispatch_click(client, session_id, x, y, button, click_count).await + let (x, y, effective_session) = resolve_element_center( + client, + session_id, + ref_map, + selector_or_ref, + iframe_sessions, + ) + .await?; + dispatch_click(client, &effective_session, x, y, button, click_count).await } pub async fn dblclick( @@ -21,8 +31,18 @@ pub async fn dblclick( session_id: &str, ref_map: &RefMap, selector_or_ref: &str, + iframe_sessions: &HashMap, ) -> Result<(), String> { - click(client, session_id, ref_map, selector_or_ref, "left", 2).await + click( + client, + session_id, + ref_map, + selector_or_ref, + "left", + 2, + iframe_sessions, + ) + .await } pub async fn hover( @@ -30,8 +50,16 @@ pub async fn hover( session_id: &str, ref_map: &RefMap, selector_or_ref: &str, + iframe_sessions: &HashMap, ) -> Result<(), String> { - let (x, y) = resolve_element_center(client, session_id, ref_map, selector_or_ref).await?; + let (x, y, effective_session) = resolve_element_center( + client, + session_id, + ref_map, + selector_or_ref, + iframe_sessions, + ) + .await?; client .send_command_typed::<_, Value>( "Input.dispatchMouseEvent", @@ -46,7 +74,7 @@ pub async fn hover( delta_y: None, modifiers: None, }, - Some(session_id), + Some(&effective_session), ) .await?; Ok(()) @@ -58,8 +86,16 @@ pub async fn fill( ref_map: &RefMap, selector_or_ref: &str, value: &str, + iframe_sessions: &HashMap, ) -> Result<(), String> { - let object_id = resolve_element_object_id(client, session_id, ref_map, selector_or_ref).await?; + let (object_id, effective_session) = resolve_element_object_id( + client, + session_id, + ref_map, + selector_or_ref, + iframe_sessions, + ) + .await?; // Focus the element client @@ -72,7 +108,7 @@ pub async fn fill( return_by_value: Some(true), await_promise: Some(false), }, - Some(session_id), + Some(&effective_session), ) .await?; @@ -92,11 +128,11 @@ pub async fn fill( return_by_value: Some(true), await_promise: Some(false), }, - Some(session_id), + Some(&effective_session), ) .await?; - // Insert text + // Insert text (keyboard input dispatched at page level, use parent session_id) client .send_command_typed::<_, Value>( "Input.insertText", @@ -119,8 +155,16 @@ pub async fn type_text( text: &str, clear: bool, delay_ms: Option, + iframe_sessions: &HashMap, ) -> Result<(), String> { - let object_id = resolve_element_object_id(client, session_id, ref_map, selector_or_ref).await?; + let (object_id, effective_session) = resolve_element_object_id( + client, + session_id, + ref_map, + selector_or_ref, + iframe_sessions, + ) + .await?; // Focus client @@ -133,7 +177,7 @@ pub async fn type_text( return_by_value: Some(true), await_promise: Some(false), }, - Some(session_id), + Some(&effective_session), ) .await?; @@ -153,7 +197,7 @@ pub async fn type_text( return_by_value: Some(true), await_promise: Some(false), }, - Some(session_id), + Some(&effective_session), ) .await?; } @@ -283,9 +327,11 @@ pub async fn scroll( selector_or_ref: Option<&str>, delta_x: f64, delta_y: f64, + iframe_sessions: &HashMap, ) -> Result<(), String> { if let Some(sel) = selector_or_ref { - let object_id = resolve_element_object_id(client, session_id, ref_map, sel).await?; + let (object_id, effective_session) = + resolve_element_object_id(client, session_id, ref_map, sel, iframe_sessions).await?; let js = "function(dx, dy) { this.scrollBy(dx, dy); }".to_string(); client .send_command_typed::<_, Value>( @@ -306,7 +352,7 @@ pub async fn scroll( return_by_value: Some(true), await_promise: Some(false), }, - Some(session_id), + Some(&effective_session), ) .await?; } else { @@ -332,8 +378,16 @@ pub async fn select_option( ref_map: &RefMap, selector_or_ref: &str, values: &[String], + iframe_sessions: &HashMap, ) -> Result<(), String> { - let object_id = resolve_element_object_id(client, session_id, ref_map, selector_or_ref).await?; + let (object_id, effective_session) = resolve_element_object_id( + client, + session_id, + ref_map, + selector_or_ref, + iframe_sessions, + ) + .await?; let js = r#"function(vals) { const options = Array.from(this.options); @@ -357,7 +411,7 @@ pub async fn select_option( return_by_value: Some(true), await_promise: Some(false), }, - Some(session_id), + Some(&effective_session), ) .await?; @@ -369,18 +423,48 @@ pub async fn check( session_id: &str, ref_map: &RefMap, selector_or_ref: &str, + iframe_sessions: &HashMap, ) -> Result<(), String> { - let is_checked = - super::element::is_element_checked(client, session_id, ref_map, selector_or_ref).await?; + let is_checked = super::element::is_element_checked( + client, + session_id, + ref_map, + selector_or_ref, + iframe_sessions, + ) + .await?; if !is_checked { - click(client, session_id, ref_map, selector_or_ref, "left", 1).await?; + click( + client, + session_id, + ref_map, + selector_or_ref, + "left", + 1, + iframe_sessions, + ) + .await?; // Verify the click changed the state (Playwright parity: _setChecked re-checks). // If the coordinate-based click missed (e.g. hidden input, overlay), retry // with a JS .click() on the element and its associated input. - if !super::element::is_element_checked(client, session_id, ref_map, selector_or_ref).await? + if !super::element::is_element_checked( + client, + session_id, + ref_map, + selector_or_ref, + iframe_sessions, + ) + .await? { - js_click_checkbox(client, session_id, ref_map, selector_or_ref).await?; + js_click_checkbox( + client, + session_id, + ref_map, + selector_or_ref, + iframe_sessions, + ) + .await?; } } Ok(()) @@ -391,15 +475,46 @@ pub async fn uncheck( session_id: &str, ref_map: &RefMap, selector_or_ref: &str, + iframe_sessions: &HashMap, ) -> Result<(), String> { - let is_checked = - super::element::is_element_checked(client, session_id, ref_map, selector_or_ref).await?; + let is_checked = super::element::is_element_checked( + client, + session_id, + ref_map, + selector_or_ref, + iframe_sessions, + ) + .await?; if is_checked { - click(client, session_id, ref_map, selector_or_ref, "left", 1).await?; + click( + client, + session_id, + ref_map, + selector_or_ref, + "left", + 1, + iframe_sessions, + ) + .await?; // Same verify-and-retry as check(). - if super::element::is_element_checked(client, session_id, ref_map, selector_or_ref).await? { - js_click_checkbox(client, session_id, ref_map, selector_or_ref).await?; + if super::element::is_element_checked( + client, + session_id, + ref_map, + selector_or_ref, + iframe_sessions, + ) + .await? + { + js_click_checkbox( + client, + session_id, + ref_map, + selector_or_ref, + iframe_sessions, + ) + .await?; } } Ok(()) @@ -419,8 +534,16 @@ async fn js_click_checkbox( session_id: &str, ref_map: &RefMap, selector_or_ref: &str, + iframe_sessions: &HashMap, ) -> Result<(), String> { - let object_id = resolve_element_object_id(client, session_id, ref_map, selector_or_ref).await?; + let (object_id, effective_session) = resolve_element_object_id( + client, + session_id, + ref_map, + selector_or_ref, + iframe_sessions, + ) + .await?; let js = r#"function() { var el = this; @@ -456,7 +579,7 @@ async fn js_click_checkbox( return_by_value: Some(true), await_promise: Some(false), }, - Some(session_id), + Some(&effective_session), ) .await?; @@ -468,8 +591,16 @@ pub async fn focus( session_id: &str, ref_map: &RefMap, selector_or_ref: &str, + iframe_sessions: &HashMap, ) -> Result<(), String> { - let object_id = resolve_element_object_id(client, session_id, ref_map, selector_or_ref).await?; + let (object_id, effective_session) = resolve_element_object_id( + client, + session_id, + ref_map, + selector_or_ref, + iframe_sessions, + ) + .await?; client .send_command_typed::<_, Value>( @@ -481,7 +612,7 @@ pub async fn focus( return_by_value: Some(true), await_promise: Some(false), }, - Some(session_id), + Some(&effective_session), ) .await?; @@ -493,8 +624,16 @@ pub async fn clear( session_id: &str, ref_map: &RefMap, selector_or_ref: &str, + iframe_sessions: &HashMap, ) -> Result<(), String> { - let object_id = resolve_element_object_id(client, session_id, ref_map, selector_or_ref).await?; + let (object_id, effective_session) = resolve_element_object_id( + client, + session_id, + ref_map, + selector_or_ref, + iframe_sessions, + ) + .await?; client .send_command_typed::<_, Value>( @@ -512,7 +651,7 @@ pub async fn clear( return_by_value: Some(true), await_promise: Some(false), }, - Some(session_id), + Some(&effective_session), ) .await?; @@ -524,8 +663,16 @@ pub async fn select_all( session_id: &str, ref_map: &RefMap, selector_or_ref: &str, + iframe_sessions: &HashMap, ) -> Result<(), String> { - let object_id = resolve_element_object_id(client, session_id, ref_map, selector_or_ref).await?; + let (object_id, effective_session) = resolve_element_object_id( + client, + session_id, + ref_map, + selector_or_ref, + iframe_sessions, + ) + .await?; client .send_command_typed::<_, Value>( @@ -549,7 +696,7 @@ pub async fn select_all( return_by_value: Some(true), await_promise: Some(false), }, - Some(session_id), + Some(&effective_session), ) .await?; @@ -561,8 +708,16 @@ pub async fn scroll_into_view( session_id: &str, ref_map: &RefMap, selector_or_ref: &str, + iframe_sessions: &HashMap, ) -> Result<(), String> { - let object_id = resolve_element_object_id(client, session_id, ref_map, selector_or_ref).await?; + let (object_id, effective_session) = resolve_element_object_id( + client, + session_id, + ref_map, + selector_or_ref, + iframe_sessions, + ) + .await?; client .send_command_typed::<_, Value>( @@ -576,7 +731,7 @@ pub async fn scroll_into_view( return_by_value: Some(true), await_promise: Some(false), }, - Some(session_id), + Some(&effective_session), ) .await?; @@ -590,8 +745,16 @@ pub async fn dispatch_event( selector_or_ref: &str, event_type: &str, event_init: Option<&Value>, + iframe_sessions: &HashMap, ) -> Result<(), String> { - let object_id = resolve_element_object_id(client, session_id, ref_map, selector_or_ref).await?; + let (object_id, effective_session) = resolve_element_object_id( + client, + session_id, + ref_map, + selector_or_ref, + iframe_sessions, + ) + .await?; let init_json = event_init .map(|v| serde_json::to_string(v).unwrap_or("{}".to_string())) @@ -613,7 +776,7 @@ pub async fn dispatch_event( return_by_value: Some(true), await_promise: Some(false), }, - Some(session_id), + Some(&effective_session), ) .await?; @@ -625,8 +788,16 @@ pub async fn highlight( session_id: &str, ref_map: &RefMap, selector_or_ref: &str, + iframe_sessions: &HashMap, ) -> Result<(), String> { - let object_id = resolve_element_object_id(client, session_id, ref_map, selector_or_ref).await?; + let (object_id, effective_session) = resolve_element_object_id( + client, + session_id, + ref_map, + selector_or_ref, + iframe_sessions, + ) + .await?; client .send_command_typed::<_, Value>( @@ -647,7 +818,7 @@ pub async fn highlight( return_by_value: Some(true), await_promise: Some(false), }, - Some(session_id), + Some(&effective_session), ) .await?; @@ -659,8 +830,16 @@ pub async fn tap_touch( session_id: &str, ref_map: &RefMap, selector_or_ref: &str, + iframe_sessions: &HashMap, ) -> Result<(), String> { - let (x, y) = resolve_element_center(client, session_id, ref_map, selector_or_ref).await?; + let (x, y, effective_session) = resolve_element_center( + client, + session_id, + ref_map, + selector_or_ref, + iframe_sessions, + ) + .await?; client .send_command( @@ -669,7 +848,7 @@ pub async fn tap_touch( "type": "touchStart", "touchPoints": [{ "x": x, "y": y }], })), - Some(session_id), + Some(&effective_session), ) .await?; @@ -680,7 +859,7 @@ pub async fn tap_touch( "type": "touchEnd", "touchPoints": [], })), - Some(session_id), + Some(&effective_session), ) .await?; diff --git a/cli/src/native/screenshot.rs b/cli/src/native/screenshot.rs index f0848de1d..d5cf44f5c 100644 --- a/cli/src/native/screenshot.rs +++ b/cli/src/native/screenshot.rs @@ -2,6 +2,8 @@ use serde::Serialize; use serde_json::Value; use std::path::PathBuf; +use std::collections::HashMap; + use super::cdp::client::CdpClient; use super::cdp::types::*; use super::element::RefMap; @@ -100,10 +102,14 @@ pub async fn take_screenshot( session_id: &str, ref_map: &RefMap, options: &ScreenshotOptions, + iframe_sessions: &HashMap, ) -> Result { let target_rect = if options.annotate { match options.selector.as_deref() { - Some(selector) => get_rect_for_selector(client, session_id, ref_map, selector).await?, + Some(selector) => { + get_rect_for_selector(client, session_id, ref_map, selector, iframe_sessions) + .await? + } None => None, } } else { @@ -124,7 +130,8 @@ pub async fn take_screenshot( false }; - let base64 = capture_screenshot_base64(client, session_id, ref_map, options).await; + let base64 = + capture_screenshot_base64(client, session_id, ref_map, options, iframe_sessions).await; if overlay_injected { let _ = remove_annotation_overlay(client, session_id).await; @@ -166,6 +173,7 @@ async fn capture_screenshot_base64( session_id: &str, ref_map: &RefMap, options: &ScreenshotOptions, + iframe_sessions: &HashMap, ) -> Result { let mut params = CaptureScreenshotParams { format: Some(options.format.clone()), @@ -200,7 +208,9 @@ async fn capture_screenshot_base64( }); } } else if let Some(ref selector) = options.selector { - if let Some(rect) = get_rect_for_selector(client, session_id, ref_map, selector).await? { + if let Some(rect) = + get_rect_for_selector(client, session_id, ref_map, selector, iframe_sessions).await? + { params.clip = Some(Viewport { x: rect.x, y: rect.y, @@ -316,10 +326,17 @@ async fn get_rect_for_selector( session_id: &str, ref_map: &RefMap, selector: &str, + iframe_sessions: &HashMap, ) -> Result, String> { - let object_id = - super::element::resolve_element_object_id(client, session_id, ref_map, selector).await?; - get_rect_for_object(client, session_id, &object_id).await + let (object_id, effective_session) = super::element::resolve_element_object_id( + client, + session_id, + ref_map, + selector, + iframe_sessions, + ) + .await?; + get_rect_for_object(client, &effective_session, &object_id).await } async fn get_rect_for_object( diff --git a/cli/src/native/snapshot.rs b/cli/src/native/snapshot.rs index 8d170a45d..4a1e76bd6 100644 --- a/cli/src/native/snapshot.rs +++ b/cli/src/native/snapshot.rs @@ -6,7 +6,7 @@ use super::cdp::client::CdpClient; use super::cdp::types::{ AXNode, AXProperty, AXValue, EvaluateParams, EvaluateResult, GetFullAXTreeResult, }; -use super::element::RefMap; +use super::element::{resolve_ax_session, RefMap}; const INTERACTIVE_ROLES: &[&str] = &[ "button", @@ -139,6 +139,7 @@ pub async fn take_snapshot( options: &SnapshotOptions, ref_map: &mut RefMap, frame_id: Option<&str>, + iframe_sessions: &HashMap, ) -> Result { client .send_command_no_params("DOM.enable", Some(session_id)) @@ -202,13 +203,24 @@ pub async fn take_snapshot( None }; - let ax_params = if let Some(fid) = frame_id { - serde_json::json!({ "frameId": fid }) - } else { - serde_json::json!({}) - }; + let (ax_params, effective_session_id) = + resolve_ax_session(frame_id, session_id, iframe_sessions); + // Ensure domains are enabled on the iframe session (defensive fallback + // in case the attach-time enable in execute_command was missed). + if effective_session_id != session_id { + let _ = client + .send_command_no_params("DOM.enable", Some(effective_session_id)) + .await; + let _ = client + .send_command_no_params("Accessibility.enable", Some(effective_session_id)) + .await; + } let ax_tree: GetFullAXTreeResult = client - .send_command_typed("Accessibility.getFullAXTree", &ax_params, Some(session_id)) + .send_command_typed( + "Accessibility.getFullAXTree", + &ax_params, + Some(effective_session_id), + ) .await?; let (tree_nodes, root_indices) = build_tree(&ax_tree.nodes); @@ -350,6 +362,7 @@ pub async fn take_snapshot( options, ref_map, Some(&child_fid), + iframe_sessions, )) .await { @@ -1161,4 +1174,50 @@ mod tests { assert_eq!(set.len(), 1); assert!(set.contains("ok")); } + + // ----------------------------------------------------------------------- + // resolve_ax_session tests (Issue #925 regression guard) + // Cross-origin iframes must use a dedicated session without frameId. + // Same-origin iframes must use the parent session with frameId. + // ----------------------------------------------------------------------- + + #[test] + fn test_cross_origin_iframe_uses_dedicated_session() { + let parent_session = "parent-session"; + let iframe_frame_id = "cross-origin-iframe-frame"; + let iframe_session = "cross-origin-iframe-session"; + + let mut iframe_sessions = HashMap::new(); + iframe_sessions.insert(iframe_frame_id.to_string(), iframe_session.to_string()); + + let (params, session) = + resolve_ax_session(Some(iframe_frame_id), parent_session, &iframe_sessions); + + assert_eq!(session, iframe_session); + assert_eq!(params, serde_json::json!({})); + } + + #[test] + fn test_same_origin_iframe_uses_parent_session_with_frame_id() { + let parent_session = "parent-session"; + let iframe_frame_id = "same-origin-iframe-frame"; + let iframe_sessions = HashMap::new(); + + let (params, session) = + resolve_ax_session(Some(iframe_frame_id), parent_session, &iframe_sessions); + + assert_eq!(session, parent_session); + assert_eq!(params, serde_json::json!({ "frameId": iframe_frame_id })); + } + + #[test] + fn test_main_frame_uses_parent_session() { + let parent_session = "parent-session"; + let iframe_sessions = HashMap::new(); + + let (params, session) = resolve_ax_session(None, parent_session, &iframe_sessions); + + assert_eq!(session, parent_session); + assert_eq!(params, serde_json::json!({})); + } } From 603c888f5b3cac920a1e0ba9ea5fe20b5db51954 Mon Sep 17 00:00:00 2001 From: hyunjinee Date: Sat, 21 Mar 2026 01:21:27 +0900 Subject: [PATCH 2/4] fix --- cli/src/native/element.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cli/src/native/element.rs b/cli/src/native/element.rs index 3b66c79e8..9e5700c01 100644 --- a/cli/src/native/element.rs +++ b/cli/src/native/element.rs @@ -304,11 +304,11 @@ pub(super) fn resolve_ax_session<'a>( session_id: &'a str, iframe_sessions: &'a HashMap, ) -> (serde_json::Value, &'a str) { - if let Some(fid) = frame_id { - if let Some(iframe_sid) = iframe_sessions.get(fid) { + if let Some(frame_id) = frame_id { + if let Some(iframe_sid) = iframe_sessions.get(frame_id) { (serde_json::json!({}), iframe_sid.as_str()) } else { - (serde_json::json!({ "frameId": fid }), session_id) + (serde_json::json!({ "frameId": frame_id }), session_id) } } else { (serde_json::json!({}), session_id) From cca30a30a59c14a3d7314a7f6dcb4d6921721612 Mon Sep 17 00:00:00 2001 From: hyunjinee Date: Sat, 21 Mar 2026 01:35:58 +0900 Subject: [PATCH 3/4] fix --- cli/src/native/actions.rs | 18 +++++---- cli/src/native/element.rs | 69 ++++++++++++++++++----------------- cli/src/native/interaction.rs | 62 +++++++++++++++---------------- cli/src/native/screenshot.rs | 4 +- 4 files changed, 78 insertions(+), 75 deletions(-) diff --git a/cli/src/native/actions.rs b/cli/src/native/actions.rs index 138831663..78c1bdcd1 100644 --- a/cli/src/native/actions.rs +++ b/cli/src/native/actions.rs @@ -424,6 +424,8 @@ impl DaemonState { .and_then(|v| v.as_str()) .unwrap_or(""); if target_type == "iframe" { + // For OOPIF targets, Chrome uses the frameId as + // the targetId, so we can key iframe_sessions by it. if let Some(target_id) = target_info.get("targetId").and_then(|v| v.as_str()) { @@ -1909,7 +1911,7 @@ async fn handle_click(cmd: &Value, state: &mut DaemonState) -> Result Result Result Result Result Result Result, iframe_sessions: &HashMap, ) -> Result { - let (ax_params, effective_session) = resolve_ax_session(frame_id, session_id, iframe_sessions); + let (ax_params, effective_session_id) = + resolve_ax_session(frame_id, session_id, iframe_sessions); let ax_tree: GetFullAXTreeResult = client .send_command_typed( "Accessibility.getFullAXTree", &ax_params, - Some(effective_session), + Some(effective_session_id), ) .await?; @@ -480,7 +481,7 @@ pub async fn get_element_text( selector_or_ref: &str, iframe_sessions: &HashMap, ) -> Result { - let (object_id, effective_session) = resolve_element_object_id( + let (object_id, effective_session_id) = resolve_element_object_id( client, session_id, ref_map, @@ -500,7 +501,7 @@ pub async fn get_element_text( return_by_value: Some(true), await_promise: Some(false), }, - Some(&effective_session), + Some(&effective_session_id), ) .await?; @@ -519,7 +520,7 @@ pub async fn get_element_attribute( attribute: &str, iframe_sessions: &HashMap, ) -> Result { - let (object_id, effective_session) = resolve_element_object_id( + let (object_id, effective_session_id) = resolve_element_object_id( client, session_id, ref_map, @@ -541,7 +542,7 @@ pub async fn get_element_attribute( return_by_value: Some(true), await_promise: Some(false), }, - Some(&effective_session), + Some(&effective_session_id), ) .await?; @@ -555,7 +556,7 @@ pub async fn is_element_visible( selector_or_ref: &str, iframe_sessions: &HashMap, ) -> Result { - let (object_id, effective_session) = resolve_element_object_id( + let (object_id, effective_session_id) = resolve_element_object_id( client, session_id, ref_map, @@ -582,7 +583,7 @@ pub async fn is_element_visible( return_by_value: Some(true), await_promise: Some(false), }, - Some(&effective_session), + Some(&effective_session_id), ) .await?; @@ -600,7 +601,7 @@ pub async fn is_element_enabled( selector_or_ref: &str, iframe_sessions: &HashMap, ) -> Result { - let (object_id, effective_session) = resolve_element_object_id( + let (object_id, effective_session_id) = resolve_element_object_id( client, session_id, ref_map, @@ -619,7 +620,7 @@ pub async fn is_element_enabled( return_by_value: Some(true), await_promise: Some(false), }, - Some(&effective_session), + Some(&effective_session_id), ) .await?; @@ -637,7 +638,7 @@ pub async fn is_element_checked( selector_or_ref: &str, iframe_sessions: &HashMap, ) -> Result { - let (object_id, effective_session) = resolve_element_object_id( + let (object_id, effective_session_id) = resolve_element_object_id( client, session_id, ref_map, @@ -689,7 +690,7 @@ pub async fn is_element_checked( return_by_value: Some(true), await_promise: Some(false), }, - Some(&effective_session), + Some(&effective_session_id), ) .await?; @@ -707,7 +708,7 @@ pub async fn get_element_inner_text( selector_or_ref: &str, iframe_sessions: &HashMap, ) -> Result { - let (object_id, effective_session) = resolve_element_object_id( + let (object_id, effective_session_id) = resolve_element_object_id( client, session_id, ref_map, @@ -726,7 +727,7 @@ pub async fn get_element_inner_text( return_by_value: Some(true), await_promise: Some(false), }, - Some(&effective_session), + Some(&effective_session_id), ) .await?; @@ -744,7 +745,7 @@ pub async fn get_element_inner_html( selector_or_ref: &str, iframe_sessions: &HashMap, ) -> Result { - let (object_id, effective_session) = resolve_element_object_id( + let (object_id, effective_session_id) = resolve_element_object_id( client, session_id, ref_map, @@ -763,7 +764,7 @@ pub async fn get_element_inner_html( return_by_value: Some(true), await_promise: Some(false), }, - Some(&effective_session), + Some(&effective_session_id), ) .await?; @@ -781,7 +782,7 @@ pub async fn get_element_input_value( selector_or_ref: &str, iframe_sessions: &HashMap, ) -> Result { - let (object_id, effective_session) = resolve_element_object_id( + let (object_id, effective_session_id) = resolve_element_object_id( client, session_id, ref_map, @@ -802,7 +803,7 @@ pub async fn get_element_input_value( return_by_value: Some(true), await_promise: Some(false), }, - Some(&effective_session), + Some(&effective_session_id), ) .await?; @@ -821,7 +822,7 @@ pub async fn set_element_value( value: &str, iframe_sessions: &HashMap, ) -> Result<(), String> { - let (object_id, effective_session) = resolve_element_object_id( + let (object_id, effective_session_id) = resolve_element_object_id( client, session_id, ref_map, @@ -845,7 +846,7 @@ pub async fn set_element_value( return_by_value: Some(true), await_promise: Some(false), }, - Some(&effective_session), + Some(&effective_session_id), ) .await?; @@ -859,7 +860,7 @@ pub async fn get_element_bounding_box( selector_or_ref: &str, iframe_sessions: &HashMap, ) -> Result { - let (object_id, effective_session) = resolve_element_object_id( + let (object_id, effective_session_id) = resolve_element_object_id( client, session_id, ref_map, @@ -882,7 +883,7 @@ pub async fn get_element_bounding_box( return_by_value: Some(true), await_promise: Some(false), }, - Some(&effective_session), + Some(&effective_session_id), ) .await?; @@ -922,7 +923,7 @@ pub async fn get_element_styles( properties: Option>, iframe_sessions: &HashMap, ) -> Result { - let (object_id, effective_session) = resolve_element_object_id( + let (object_id, effective_session_id) = resolve_element_object_id( client, session_id, ref_map, @@ -967,7 +968,7 @@ pub async fn get_element_styles( return_by_value: Some(true), await_promise: Some(false), }, - Some(&effective_session), + Some(&effective_session_id), ) .await?; diff --git a/cli/src/native/interaction.rs b/cli/src/native/interaction.rs index b951f3461..c858316df 100644 --- a/cli/src/native/interaction.rs +++ b/cli/src/native/interaction.rs @@ -15,7 +15,7 @@ pub async fn click( click_count: i32, iframe_sessions: &HashMap, ) -> Result<(), String> { - let (x, y, effective_session) = resolve_element_center( + let (x, y, effective_session_id) = resolve_element_center( client, session_id, ref_map, @@ -23,7 +23,7 @@ pub async fn click( iframe_sessions, ) .await?; - dispatch_click(client, &effective_session, x, y, button, click_count).await + dispatch_click(client, &effective_session_id, x, y, button, click_count).await } pub async fn dblclick( @@ -52,7 +52,7 @@ pub async fn hover( selector_or_ref: &str, iframe_sessions: &HashMap, ) -> Result<(), String> { - let (x, y, effective_session) = resolve_element_center( + let (x, y, effective_session_id) = resolve_element_center( client, session_id, ref_map, @@ -74,7 +74,7 @@ pub async fn hover( delta_y: None, modifiers: None, }, - Some(&effective_session), + Some(&effective_session_id), ) .await?; Ok(()) @@ -88,7 +88,7 @@ pub async fn fill( value: &str, iframe_sessions: &HashMap, ) -> Result<(), String> { - let (object_id, effective_session) = resolve_element_object_id( + let (object_id, effective_session_id) = resolve_element_object_id( client, session_id, ref_map, @@ -108,7 +108,7 @@ pub async fn fill( return_by_value: Some(true), await_promise: Some(false), }, - Some(&effective_session), + Some(&effective_session_id), ) .await?; @@ -128,7 +128,7 @@ pub async fn fill( return_by_value: Some(true), await_promise: Some(false), }, - Some(&effective_session), + Some(&effective_session_id), ) .await?; @@ -157,7 +157,7 @@ pub async fn type_text( delay_ms: Option, iframe_sessions: &HashMap, ) -> Result<(), String> { - let (object_id, effective_session) = resolve_element_object_id( + let (object_id, effective_session_id) = resolve_element_object_id( client, session_id, ref_map, @@ -177,7 +177,7 @@ pub async fn type_text( return_by_value: Some(true), await_promise: Some(false), }, - Some(&effective_session), + Some(&effective_session_id), ) .await?; @@ -197,7 +197,7 @@ pub async fn type_text( return_by_value: Some(true), await_promise: Some(false), }, - Some(&effective_session), + Some(&effective_session_id), ) .await?; } @@ -330,7 +330,7 @@ pub async fn scroll( iframe_sessions: &HashMap, ) -> Result<(), String> { if let Some(sel) = selector_or_ref { - let (object_id, effective_session) = + let (object_id, effective_session_id) = resolve_element_object_id(client, session_id, ref_map, sel, iframe_sessions).await?; let js = "function(dx, dy) { this.scrollBy(dx, dy); }".to_string(); client @@ -352,7 +352,7 @@ pub async fn scroll( return_by_value: Some(true), await_promise: Some(false), }, - Some(&effective_session), + Some(&effective_session_id), ) .await?; } else { @@ -380,7 +380,7 @@ pub async fn select_option( values: &[String], iframe_sessions: &HashMap, ) -> Result<(), String> { - let (object_id, effective_session) = resolve_element_object_id( + let (object_id, effective_session_id) = resolve_element_object_id( client, session_id, ref_map, @@ -411,7 +411,7 @@ pub async fn select_option( return_by_value: Some(true), await_promise: Some(false), }, - Some(&effective_session), + Some(&effective_session_id), ) .await?; @@ -536,7 +536,7 @@ async fn js_click_checkbox( selector_or_ref: &str, iframe_sessions: &HashMap, ) -> Result<(), String> { - let (object_id, effective_session) = resolve_element_object_id( + let (object_id, effective_session_id) = resolve_element_object_id( client, session_id, ref_map, @@ -579,7 +579,7 @@ async fn js_click_checkbox( return_by_value: Some(true), await_promise: Some(false), }, - Some(&effective_session), + Some(&effective_session_id), ) .await?; @@ -593,7 +593,7 @@ pub async fn focus( selector_or_ref: &str, iframe_sessions: &HashMap, ) -> Result<(), String> { - let (object_id, effective_session) = resolve_element_object_id( + let (object_id, effective_session_id) = resolve_element_object_id( client, session_id, ref_map, @@ -612,7 +612,7 @@ pub async fn focus( return_by_value: Some(true), await_promise: Some(false), }, - Some(&effective_session), + Some(&effective_session_id), ) .await?; @@ -626,7 +626,7 @@ pub async fn clear( selector_or_ref: &str, iframe_sessions: &HashMap, ) -> Result<(), String> { - let (object_id, effective_session) = resolve_element_object_id( + let (object_id, effective_session_id) = resolve_element_object_id( client, session_id, ref_map, @@ -651,7 +651,7 @@ pub async fn clear( return_by_value: Some(true), await_promise: Some(false), }, - Some(&effective_session), + Some(&effective_session_id), ) .await?; @@ -665,7 +665,7 @@ pub async fn select_all( selector_or_ref: &str, iframe_sessions: &HashMap, ) -> Result<(), String> { - let (object_id, effective_session) = resolve_element_object_id( + let (object_id, effective_session_id) = resolve_element_object_id( client, session_id, ref_map, @@ -696,7 +696,7 @@ pub async fn select_all( return_by_value: Some(true), await_promise: Some(false), }, - Some(&effective_session), + Some(&effective_session_id), ) .await?; @@ -710,7 +710,7 @@ pub async fn scroll_into_view( selector_or_ref: &str, iframe_sessions: &HashMap, ) -> Result<(), String> { - let (object_id, effective_session) = resolve_element_object_id( + let (object_id, effective_session_id) = resolve_element_object_id( client, session_id, ref_map, @@ -731,7 +731,7 @@ pub async fn scroll_into_view( return_by_value: Some(true), await_promise: Some(false), }, - Some(&effective_session), + Some(&effective_session_id), ) .await?; @@ -747,7 +747,7 @@ pub async fn dispatch_event( event_init: Option<&Value>, iframe_sessions: &HashMap, ) -> Result<(), String> { - let (object_id, effective_session) = resolve_element_object_id( + let (object_id, effective_session_id) = resolve_element_object_id( client, session_id, ref_map, @@ -776,7 +776,7 @@ pub async fn dispatch_event( return_by_value: Some(true), await_promise: Some(false), }, - Some(&effective_session), + Some(&effective_session_id), ) .await?; @@ -790,7 +790,7 @@ pub async fn highlight( selector_or_ref: &str, iframe_sessions: &HashMap, ) -> Result<(), String> { - let (object_id, effective_session) = resolve_element_object_id( + let (object_id, effective_session_id) = resolve_element_object_id( client, session_id, ref_map, @@ -818,7 +818,7 @@ pub async fn highlight( return_by_value: Some(true), await_promise: Some(false), }, - Some(&effective_session), + Some(&effective_session_id), ) .await?; @@ -832,7 +832,7 @@ pub async fn tap_touch( selector_or_ref: &str, iframe_sessions: &HashMap, ) -> Result<(), String> { - let (x, y, effective_session) = resolve_element_center( + let (x, y, effective_session_id) = resolve_element_center( client, session_id, ref_map, @@ -848,7 +848,7 @@ pub async fn tap_touch( "type": "touchStart", "touchPoints": [{ "x": x, "y": y }], })), - Some(&effective_session), + Some(&effective_session_id), ) .await?; @@ -859,7 +859,7 @@ pub async fn tap_touch( "type": "touchEnd", "touchPoints": [], })), - Some(&effective_session), + Some(&effective_session_id), ) .await?; diff --git a/cli/src/native/screenshot.rs b/cli/src/native/screenshot.rs index d5cf44f5c..0736691f3 100644 --- a/cli/src/native/screenshot.rs +++ b/cli/src/native/screenshot.rs @@ -328,7 +328,7 @@ async fn get_rect_for_selector( selector: &str, iframe_sessions: &HashMap, ) -> Result, String> { - let (object_id, effective_session) = super::element::resolve_element_object_id( + let (object_id, effective_session_id) = super::element::resolve_element_object_id( client, session_id, ref_map, @@ -336,7 +336,7 @@ async fn get_rect_for_selector( iframe_sessions, ) .await?; - get_rect_for_object(client, &effective_session, &object_id).await + get_rect_for_object(client, &effective_session_id, &object_id).await } async fn get_rect_for_object( From 80f15a53bd732487bb298b9f6b4d8cf4b86fb0f2 Mon Sep 17 00:00:00 2001 From: hyunjinee Date: Sat, 21 Mar 2026 01:40:08 +0900 Subject: [PATCH 4/4] fix --- cli/src/native/actions.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/cli/src/native/actions.rs b/cli/src/native/actions.rs index 78c1bdcd1..9ba90882d 100644 --- a/cli/src/native/actions.rs +++ b/cli/src/native/actions.rs @@ -4633,7 +4633,7 @@ async fn handle_drag(cmd: &Value, state: &mut DaemonState) -> Result Result Result Result Result