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:
+366
-65
@@ -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?;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user