feat: support cross-origin iframe snapshots and interactions via Target.setAutoAttach (#949)

* 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

* fix

* fix

* fix

---------

Co-authored-by: hyunjinee <leehj0110@kakao.com>
This commit is contained in:
jin.2
2026-03-20 16:37:33 -05:00
committed by GitHub
co-authored by hyunjinee
parent 39e54113e6
commit 59b6e5a034
6 changed files with 919 additions and 168 deletions
+366 -65
View File
@@ -133,6 +133,17 @@ pub struct MouseState {
pub buttons: i32,
}
#[derive(Default)]
struct DrainedEvents {
pending_acks: Vec<i64>,
new_targets: Vec<TargetCreatedEvent>,
destroyed_targets: Vec<String>,
/// 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<String>,
}
pub struct DaemonState {
pub browser: Option<BrowserManager>,
pub appium: Option<AppiumManager>,
@@ -158,6 +169,9 @@ pub struct DaemonState {
pub tracked_requests: Vec<TrackedRequest>,
pub request_tracking: bool,
pub active_frame_id: Option<String>,
/// Cross-origin iframe frame_id → dedicated CDP session_id.
/// Populated by Target.attachedToTarget events from Target.setAutoAttach.
pub iframe_sessions: HashMap<String, String>,
/// 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<RwLock<>> 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<i64>, Vec<TargetCreatedEvent>, Vec<String>) {
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<i64> = Vec::new();
let mut new_targets: Vec<TargetCreatedEvent> = Vec::new();
let mut destroyed_targets: Vec<String> = Vec::new();
let mut attached_iframe_sessions: Vec<(String, String)> = Vec::new();
let mut detached_iframe_sessions: Vec<String> = Vec::new();
loop {
match rx.try_recv() {
@@ -397,6 +414,36 @@ 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" {
// 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())
{
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 +683,13 @@ impl DaemonState {
}
}
(pending_acks, new_targets, destroyed_targets)
DrainedEvents {
pending_acks,
new_targets,
destroyed_targets,
attached_iframe_sessions,
detached_iframe_sessions,
}
}
}
@@ -659,7 +712,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 +736,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<AttachToTargetResult, String> = mgr
@@ -1483,6 +1562,8 @@ async fn handle_navigate(cmd: &Value, state: &mut DaemonState) -> Result<Value,
}
state.ref_map.clear();
state.iframe_sessions.clear();
state.active_frame_id = None;
mgr.navigate(url, wait_until).await
}
@@ -1680,6 +1761,7 @@ async fn handle_snapshot(cmd: &Value, state: &mut DaemonState) -> Result<Value,
&options,
&mut state.ref_map,
state.active_frame_id.as_deref(),
&state.iframe_sessions,
)
.await?;
@@ -1786,12 +1868,19 @@ async fn handle_screenshot(cmd: &Value, state: &mut DaemonState) -> Result<Value
},
&mut state.ref_map,
state.active_frame_id.as_deref(),
&state.iframe_sessions,
)
.await?;
}
let result =
screenshot::take_screenshot(&mgr.client, &session_id, &state.ref_map, &options).await?;
let result = screenshot::take_screenshot(
&mgr.client,
&session_id,
&state.ref_map,
&options,
&state.iframe_sessions,
)
.await?;
let mut response = json!({ "path": result.path });
if !result.annotations.is_empty() {
@@ -1822,8 +1911,14 @@ async fn handle_click(cmd: &Value, state: &mut DaemonState) -> Result<Value, Str
if new_tab {
use super::element::resolve_element_object_id;
let object_id =
resolve_element_object_id(&mgr.client, &session_id, &state.ref_map, selector).await?;
let (object_id, effective_session_id) = resolve_element_object_id(
&mgr.client,
&session_id,
&state.ref_map,
selector,
&state.iframe_sessions,
)
.await?;
let call_params = json!({
"objectId": object_id,
"functionDeclaration": "function() { var h = this.getAttribute('href'); if (!h) return null; try { return new URL(h, document.baseURI).toString(); } catch(e) { return null; } }",
@@ -1834,7 +1929,7 @@ async fn handle_click(cmd: &Value, state: &mut DaemonState) -> Result<Value, Str
.send_command(
"Runtime.callFunctionOn",
Some(call_params),
Some(&session_id),
Some(&effective_session_id),
)
.await?;
let href = call_result
@@ -1866,6 +1961,7 @@ async fn handle_click(cmd: &Value, state: &mut DaemonState) -> Result<Value, Str
selector,
button,
click_count,
&state.iframe_sessions,
)
.await?;
@@ -1880,7 +1976,14 @@ async fn handle_dblclick(cmd: &Value, state: &mut DaemonState) -> Result<Value,
.and_then(|v| v.as_str())
.ok_or("Missing 'selector' parameter")?;
interaction::dblclick(&mgr.client, &session_id, &state.ref_map, selector).await?;
interaction::dblclick(
&mgr.client,
&session_id,
&state.ref_map,
selector,
&state.iframe_sessions,
)
.await?;
Ok(json!({ "clicked": selector }))
}
@@ -1904,7 +2007,15 @@ async fn handle_fill(cmd: &Value, state: &mut DaemonState) -> Result<Value, Stri
let mgr = state.browser.as_ref().ok_or("Browser not launched")?;
let session_id = mgr.active_session_id()?.to_string();
interaction::fill(&mgr.client, &session_id, &state.ref_map, selector, value).await?;
interaction::fill(
&mgr.client,
&session_id,
&state.ref_map,
selector,
value,
&state.iframe_sessions,
)
.await?;
Ok(json!({ "filled": selector }))
}
@@ -1930,6 +2041,7 @@ async fn handle_type(cmd: &Value, state: &mut DaemonState) -> Result<Value, Stri
text,
clear,
delay,
&state.iframe_sessions,
)
.await?;
Ok(json!({ "typed": text }))
@@ -1955,7 +2067,14 @@ async fn handle_hover(cmd: &Value, state: &mut DaemonState) -> Result<Value, Str
.and_then(|v| v.as_str())
.ok_or("Missing 'selector' parameter")?;
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 }))
}
@@ -1980,7 +2099,16 @@ async fn handle_scroll(cmd: &Value, state: &mut DaemonState) -> Result<Value, St
}
}
interaction::scroll(&mgr.client, &session_id, &state.ref_map, selector, dx, dy).await?;
interaction::scroll(
&mgr.client,
&session_id,
&state.ref_map,
selector,
dx,
dy,
&state.iframe_sessions,
)
.await?;
Ok(json!({ "scrolled": true }))
}
@@ -2005,7 +2133,15 @@ async fn handle_select(cmd: &Value, state: &mut DaemonState) -> Result<Value, St
.unwrap_or_default(),
};
interaction::select_option(&mgr.client, &session_id, &state.ref_map, selector, &values).await?;
interaction::select_option(
&mgr.client,
&session_id,
&state.ref_map,
selector,
&values,
&state.iframe_sessions,
)
.await?;
Ok(json!({ "selected": values }))
}
@@ -2017,7 +2153,14 @@ async fn handle_check(cmd: &Value, state: &mut DaemonState) -> Result<Value, Str
.and_then(|v| v.as_str())
.ok_or("Missing 'selector' parameter")?;
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 }))
}
@@ -2029,7 +2172,14 @@ async fn handle_uncheck(cmd: &Value, state: &mut DaemonState) -> Result<Value, S
.and_then(|v| v.as_str())
.ok_or("Missing 'selector' parameter")?;
interaction::uncheck(&mgr.client, &session_id, &state.ref_map, selector).await?;
interaction::uncheck(
&mgr.client,
&session_id,
&state.ref_map,
selector,
&state.iframe_sessions,
)
.await?;
Ok(json!({ "unchecked": selector }))
}
@@ -2082,8 +2232,14 @@ async fn handle_gettext(cmd: &Value, state: &mut DaemonState) -> Result<Value, S
.and_then(|v| v.as_str())
.ok_or("Missing 'selector' parameter")?;
let text = super::element::get_element_text(&mgr.client, &session_id, &state.ref_map, selector)
.await?;
let text = super::element::get_element_text(
&mgr.client,
&session_id,
&state.ref_map,
selector,
&state.iframe_sessions,
)
.await?;
let url = mgr.get_url().await.unwrap_or_default();
Ok(json!({ "text": text, "origin": url }))
}
@@ -2106,6 +2262,7 @@ async fn handle_getattribute(cmd: &Value, state: &mut DaemonState) -> Result<Val
&state.ref_map,
selector,
attribute,
&state.iframe_sessions,
)
.await?;
let url = mgr.get_url().await.unwrap_or_default();
@@ -2120,9 +2277,14 @@ async fn handle_isvisible(cmd: &Value, state: &mut DaemonState) -> Result<Value,
.and_then(|v| v.as_str())
.ok_or("Missing 'selector' parameter")?;
let visible =
super::element::is_element_visible(&mgr.client, &session_id, &state.ref_map, selector)
.await?;
let visible = super::element::is_element_visible(
&mgr.client,
&session_id,
&state.ref_map,
selector,
&state.iframe_sessions,
)
.await?;
let url = mgr.get_url().await.unwrap_or_default();
Ok(json!({ "visible": visible, "origin": url }))
}
@@ -2135,9 +2297,14 @@ async fn handle_isenabled(cmd: &Value, state: &mut DaemonState) -> Result<Value,
.and_then(|v| v.as_str())
.ok_or("Missing 'selector' parameter")?;
let enabled =
super::element::is_element_enabled(&mgr.client, &session_id, &state.ref_map, selector)
.await?;
let enabled = super::element::is_element_enabled(
&mgr.client,
&session_id,
&state.ref_map,
selector,
&state.iframe_sessions,
)
.await?;
let url = mgr.get_url().await.unwrap_or_default();
Ok(json!({ "enabled": enabled, "origin": url }))
}
@@ -2150,9 +2317,14 @@ async fn handle_ischecked(cmd: &Value, state: &mut DaemonState) -> Result<Value,
.and_then(|v| v.as_str())
.ok_or("Missing 'selector' parameter")?;
let checked =
super::element::is_element_checked(&mgr.client, &session_id, &state.ref_map, selector)
.await?;
let checked = super::element::is_element_checked(
&mgr.client,
&session_id,
&state.ref_map,
selector,
&state.iframe_sessions,
)
.await?;
let url = mgr.get_url().await.unwrap_or_default();
Ok(json!({ "checked": checked, "origin": url }))
}
@@ -2582,6 +2754,7 @@ async fn handle_diff_snapshot(cmd: &Value, state: &mut DaemonState) -> Result<Va
&options,
&mut state.ref_map,
state.active_frame_id.as_deref(),
&state.iframe_sessions,
)
.await?;
@@ -2627,16 +2800,28 @@ async fn handle_diff_url(cmd: &Value, state: &mut DaemonState) -> Result<Value,
mgr.navigate(url1, wait_until).await?;
let session_id = mgr.active_session_id()?.to_string();
let options = SnapshotOptions::default();
let snap1 =
snapshot::take_snapshot(&mgr.client, &session_id, &options, &mut state.ref_map, None)
.await?;
let snap1 = snapshot::take_snapshot(
&mgr.client,
&session_id,
&options,
&mut state.ref_map,
None,
&state.iframe_sessions,
)
.await?;
// Navigate to URL2 and snapshot
mgr.navigate(url2, wait_until).await?;
state.ref_map.clear();
let snap2 =
snapshot::take_snapshot(&mgr.client, &session_id, &options, &mut state.ref_map, None)
.await?;
let snap2 = snapshot::take_snapshot(
&mgr.client,
&session_id,
&options,
&mut state.ref_map,
None,
&state.iframe_sessions,
)
.await?;
let result = diff::diff_text(&snap1, &snap2);
Ok(json!({
@@ -2767,6 +2952,8 @@ async fn handle_tab_new(cmd: &Value, state: &mut DaemonState) -> Result<Value, S
let mgr = state.browser.as_mut().ok_or("Browser not launched")?;
let url = cmd.get("url").and_then(|v| v.as_str());
state.ref_map.clear();
state.iframe_sessions.clear();
state.active_frame_id = None;
mgr.tab_new(url).await
}
@@ -2777,6 +2964,8 @@ async fn handle_tab_switch(cmd: &Value, state: &mut DaemonState) -> Result<Value
.and_then(|v| v.as_u64())
.ok_or("Missing 'index' parameter")? as usize;
state.ref_map.clear();
state.iframe_sessions.clear();
state.active_frame_id = None;
mgr.tab_switch(index).await
}
@@ -2787,6 +2976,8 @@ async fn handle_tab_close(cmd: &Value, state: &mut DaemonState) -> Result<Value,
.and_then(|v| v.as_u64())
.map(|i| i as usize);
state.ref_map.clear();
state.iframe_sessions.clear();
state.active_frame_id = None;
mgr.tab_close(index).await
}
@@ -3076,7 +3267,14 @@ async fn handle_focus(cmd: &Value, state: &mut DaemonState) -> Result<Value, Str
.and_then(|v| v.as_str())
.ok_or("Missing 'selector' parameter")?;
interaction::focus(&mgr.client, &session_id, &state.ref_map, selector).await?;
interaction::focus(
&mgr.client,
&session_id,
&state.ref_map,
selector,
&state.iframe_sessions,
)
.await?;
Ok(json!({ "focused": selector }))
}
@@ -3088,7 +3286,14 @@ async fn handle_clear(cmd: &Value, state: &mut DaemonState) -> Result<Value, Str
.and_then(|v| v.as_str())
.ok_or("Missing 'selector' parameter")?;
interaction::clear(&mgr.client, &session_id, &state.ref_map, selector).await?;
interaction::clear(
&mgr.client,
&session_id,
&state.ref_map,
selector,
&state.iframe_sessions,
)
.await?;
Ok(json!({ "cleared": selector }))
}
@@ -3100,7 +3305,14 @@ async fn handle_selectall(cmd: &Value, state: &mut DaemonState) -> Result<Value,
.and_then(|v| v.as_str())
.ok_or("Missing 'selector' parameter")?;
interaction::select_all(&mgr.client, &session_id, &state.ref_map, selector).await?;
interaction::select_all(
&mgr.client,
&session_id,
&state.ref_map,
selector,
&state.iframe_sessions,
)
.await?;
Ok(json!({ "selected": selector }))
}
@@ -3112,7 +3324,14 @@ async fn handle_scrollintoview(cmd: &Value, state: &mut DaemonState) -> Result<V
.and_then(|v| v.as_str())
.ok_or("Missing 'selector' parameter")?;
interaction::scroll_into_view(&mgr.client, &session_id, &state.ref_map, selector).await?;
interaction::scroll_into_view(
&mgr.client,
&session_id,
&state.ref_map,
selector,
&state.iframe_sessions,
)
.await?;
Ok(json!({ "scrolled": selector }))
}
@@ -3137,6 +3356,7 @@ async fn handle_dispatch(cmd: &Value, state: &mut DaemonState) -> Result<Value,
selector,
event_type,
event_init,
&state.iframe_sessions,
)
.await?;
Ok(json!({ "dispatched": event_type, "selector": selector }))
@@ -3150,7 +3370,14 @@ async fn handle_highlight(cmd: &Value, state: &mut DaemonState) -> Result<Value,
.and_then(|v| v.as_str())
.ok_or("Missing 'selector' parameter")?;
interaction::highlight(&mgr.client, &session_id, &state.ref_map, selector).await?;
interaction::highlight(
&mgr.client,
&session_id,
&state.ref_map,
selector,
&state.iframe_sessions,
)
.await?;
Ok(json!({ "highlighted": selector }))
}
@@ -3171,7 +3398,14 @@ async fn handle_tap(cmd: &Value, state: &mut DaemonState) -> Result<Value, Strin
let mgr = state.browser.as_ref().ok_or("Browser not launched")?;
let session_id = mgr.active_session_id()?.to_string();
interaction::tap_touch(&mgr.client, &session_id, &state.ref_map, sel).await?;
interaction::tap_touch(
&mgr.client,
&session_id,
&state.ref_map,
sel,
&state.iframe_sessions,
)
.await?;
Ok(json!({ "tapped": sel }))
}
@@ -3188,6 +3422,7 @@ async fn handle_boundingbox(cmd: &Value, state: &mut DaemonState) -> Result<Valu
&session_id,
&state.ref_map,
selector,
&state.iframe_sessions,
)
.await?;
Ok(bbox)
@@ -3201,9 +3436,14 @@ async fn handle_innertext(cmd: &Value, state: &mut DaemonState) -> Result<Value,
.and_then(|v| v.as_str())
.ok_or("Missing 'selector' parameter")?;
let text =
super::element::get_element_inner_text(&mgr.client, &session_id, &state.ref_map, selector)
.await?;
let text = super::element::get_element_inner_text(
&mgr.client,
&session_id,
&state.ref_map,
selector,
&state.iframe_sessions,
)
.await?;
Ok(json!({ "text": text }))
}
@@ -3215,9 +3455,14 @@ async fn handle_innerhtml(cmd: &Value, state: &mut DaemonState) -> Result<Value,
.and_then(|v| v.as_str())
.ok_or("Missing 'selector' parameter")?;
let html =
super::element::get_element_inner_html(&mgr.client, &session_id, &state.ref_map, selector)
.await?;
let html = super::element::get_element_inner_html(
&mgr.client,
&session_id,
&state.ref_map,
selector,
&state.iframe_sessions,
)
.await?;
Ok(json!({ "html": html }))
}
@@ -3229,9 +3474,14 @@ async fn handle_inputvalue(cmd: &Value, state: &mut DaemonState) -> Result<Value
.and_then(|v| v.as_str())
.ok_or("Missing 'selector' parameter")?;
let value =
super::element::get_element_input_value(&mgr.client, &session_id, &state.ref_map, selector)
.await?;
let value = super::element::get_element_input_value(
&mgr.client,
&session_id,
&state.ref_map,
selector,
&state.iframe_sessions,
)
.await?;
Ok(json!({ "value": value }))
}
@@ -3247,8 +3497,15 @@ async fn handle_setvalue(cmd: &Value, state: &mut DaemonState) -> Result<Value,
.and_then(|v| v.as_str())
.ok_or("Missing 'value' parameter")?;
super::element::set_element_value(&mgr.client, &session_id, &state.ref_map, selector, value)
.await?;
super::element::set_element_value(
&mgr.client,
&session_id,
&state.ref_map,
selector,
value,
&state.iframe_sessions,
)
.await?;
Ok(json!({ "set": selector, "value": value }))
}
@@ -3284,6 +3541,7 @@ async fn handle_styles(cmd: &Value, state: &mut DaemonState) -> Result<Value, St
&state.ref_map,
selector,
properties,
&state.iframe_sessions,
)
.await?;
Ok(json!({ "styles": styles }))
@@ -3872,6 +4130,7 @@ async fn execute_subaction(
selector,
"left",
1,
&state.iframe_sessions,
)
.await?;
Ok(json!({ "clicked": selector }))
@@ -3881,15 +4140,37 @@ async fn execute_subaction(
.get("value")
.and_then(|v| v.as_str())
.ok_or("Missing 'value' for fill subaction")?;
interaction::fill(&mgr.client, &session_id, &state.ref_map, selector, value).await?;
interaction::fill(
&mgr.client,
&session_id,
&state.ref_map,
selector,
value,
&state.iframe_sessions,
)
.await?;
Ok(json!({ "filled": selector }))
}
"check" => {
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 +4179,7 @@ async fn execute_subaction(
&session_id,
&state.ref_map,
selector,
&state.iframe_sessions,
)
.await?;
Ok(json!({ "text": text }))
@@ -4351,26 +4633,36 @@ async fn handle_drag(cmd: &Value, state: &mut DaemonState) -> Result<Value, Stri
.and_then(|v| v.as_str())
.ok_or("Missing 'target' parameter")?;
let (sx, sy) =
super::element::resolve_element_center(&mgr.client, &session_id, &state.ref_map, source)
.await?;
let (tx, ty) =
super::element::resolve_element_center(&mgr.client, &session_id, &state.ref_map, target)
.await?;
let (sx, sy, source_session_id) = super::element::resolve_element_center(
&mgr.client,
&session_id,
&state.ref_map,
source,
&state.iframe_sessions,
)
.await?;
let (tx, ty, target_session_id) = super::element::resolve_element_center(
&mgr.client,
&session_id,
&state.ref_map,
target,
&state.iframe_sessions,
)
.await?;
// Mouse down at source
mgr.client
.send_command(
"Input.dispatchMouseEvent",
Some(json!({ "type": "mouseMoved", "x": sx, "y": sy })),
Some(&session_id),
Some(&source_session_id),
)
.await?;
mgr.client
.send_command(
"Input.dispatchMouseEvent",
Some(json!({ "type": "mousePressed", "x": sx, "y": sy, "button": "left", "clickCount": 1 })),
Some(&session_id),
Some(&source_session_id),
)
.await?;
@@ -4383,7 +4675,7 @@ async fn handle_drag(cmd: &Value, state: &mut DaemonState) -> Result<Value, Stri
.send_command(
"Input.dispatchMouseEvent",
Some(json!({ "type": "mouseMoved", "x": cx, "y": cy })),
Some(&session_id),
Some(&target_session_id),
)
.await?;
tokio::time::sleep(tokio::time::Duration::from_millis(10)).await;
@@ -4394,7 +4686,7 @@ async fn handle_drag(cmd: &Value, state: &mut DaemonState) -> Result<Value, Stri
.send_command(
"Input.dispatchMouseEvent",
Some(json!({ "type": "mouseReleased", "x": tx, "y": ty, "button": "left", "clickCount": 1 })),
Some(&session_id),
Some(&target_session_id),
)
.await?;
@@ -4665,8 +4957,14 @@ async fn handle_diff_screenshot(cmd: &Value, state: &DaemonState) -> Result<Valu
output_dir: None,
};
let result =
screenshot::take_screenshot(&mgr.client, &session_id, &state.ref_map, &options).await?;
let result = screenshot::take_screenshot(
&mgr.client,
&session_id,
&state.ref_map,
&options,
&state.iframe_sessions,
)
.await?;
let current_bytes =
base64::Engine::decode(&base64::engine::general_purpose::STANDARD, &result.base64)
@@ -5670,6 +5968,7 @@ async fn handle_auth_login(cmd: &Value, state: &mut DaemonState) -> Result<Value
&state.ref_map,
&user_sel,
&username,
&state.iframe_sessions,
)
.await?;
@@ -5690,6 +5989,7 @@ async fn handle_auth_login(cmd: &Value, state: &mut DaemonState) -> Result<Value
&state.ref_map,
&pass_sel,
&password,
&state.iframe_sessions,
)
.await?;
@@ -5721,6 +6021,7 @@ async fn handle_auth_login(cmd: &Value, state: &mut DaemonState) -> Result<Value
&sub_sel,
"left",
1,
&state.iframe_sessions,
)
.await?;