use std::io::{Read, Write};
use std::net::TcpListener;
use std::time::Duration;
use runtime_foxdriver::{launch_firefox, FoxBrowserConfig, FrameId, Page};
const IFRAME_LEFT: f64 = 60.0;
const IFRAME_TOP: f64 = 80.0;
const CB_LOCAL_X: f64 = 30.0;
const CB_LOCAL_Y: f64 = 34.0;
const INNER_LEFT_IN_MID: f64 = 20.0;
const INNER_TOP_IN_MID: f64 = 20.0;
const NESTED_TOP_X: f64 = IFRAME_LEFT + INNER_LEFT_IN_MID + CB_LOCAL_X;
const NESTED_TOP_Y: f64 = IFRAME_TOP + INNER_TOP_IN_MID + CB_LOCAL_Y;
fn parent_html(box_port: u16, mid_port: u16) -> String {
format!(
r#"<!doctype html><html><head><meta charset="utf-8"></head>
<body style="margin:0;padding:0">
<iframe id="cap" src="http://127.0.0.1:{box_port}/box"
style="position:absolute;left:{IFRAME_LEFT}px;top:{IFRAME_TOP}px;width:300px;height:65px;border:0"></iframe>
<iframe id="mid" src="http://127.0.0.1:{mid_port}/mid"
style="position:absolute;left:{IFRAME_LEFT}px;top:{IFRAME_TOP}px;width:300px;height:200px;border:0;display:none"></iframe>
<script>
window.__delivered=false; window.__trusted=null; window.__moveTrusted=null;
window.addEventListener('mousemove', function(e){{ window.__moveTrusted=e.isTrusted; }}, true);
window.addEventListener('message', function(e){{
if(e.data&&e.data.t==='clicked'){{ window.__delivered=true; window.__trusted=!!e.data.trusted; }}
if(e.data&&e.data.t==='show_nested'){{ document.getElementById('cap').style.display='none'; document.getElementById('mid').style.display='block'; }}
}});
</script></body></html>"#
)
}
const CHECKBOX_HTML: &str = r#"<!doctype html><html><head><meta charset="utf-8"></head>
<body style="margin:0;padding:0">
<div id="cb" style="position:absolute;left:18px;top:22px;width:24px;height:24px;background:#3a7"></div>
<script>document.getElementById('cb').addEventListener('click',function(e){window.top.postMessage({t:'clicked',trusted:e.isTrusted},'*');});</script>
</body></html>"#;
fn mid_html(inner_port: u16) -> String {
format!(
r#"<!doctype html><html><head><meta charset="utf-8"></head>
<body style="margin:0;padding:0">
<iframe id="inner" src="http://127.0.0.1:{inner_port}/inner"
style="position:absolute;left:{INNER_LEFT_IN_MID}px;top:{INNER_TOP_IN_MID}px;width:250px;height:120px;border:0"></iframe>
</body></html>"#
)
}
fn serve(listener: TcpListener, route: impl Fn(&str) -> Option<String> + Send + 'static) {
for stream in listener.incoming() {
let Ok(mut s) = stream else { continue };
let mut buf = [0u8; 2048];
let n = s.read(&mut buf).unwrap_or(0);
let req = String::from_utf8_lossy(&buf[..n]);
let path = req.lines().next().and_then(|l| l.split_whitespace().nth(1)).unwrap_or("/");
let body = route(path);
let (status, body) = match body {
Some(b) => ("200 OK", b),
None => ("404 Not Found", "no".to_string()),
};
let resp = format!(
"HTTP/1.1 {status}\r\nContent-Type: text/html; charset=utf-8\r\nContent-Length: {}\r\nConnection: close\r\n\r\n{body}",
body.len()
);
let _ = s.write_all(resp.as_bytes());
let _ = s.flush();
}
}
fn firefox_present() -> bool {
std::process::Command::new("firefox")
.arg("--version")
.output()
.map(|o| o.status.success())
.unwrap_or(false)
}
async fn delivered(page: &Page) -> (bool, Option<bool>) {
let v = page
.evaluate("JSON.stringify([window.__delivered===true, window.__trusted])")
.await
.ok()
.and_then(|e| e.into_value::<String>().ok())
.unwrap_or_default();
let d = v.starts_with("[true");
let t = if v.contains("true,true") { Some(true) } else if v.contains("true,false") { Some(false) } else { None };
(d, t)
}
async fn reset(page: &Page) {
let _ = page.evaluate("window.__delivered=false;window.__trusted=null;void 0").await;
}
async fn nonmain_ctx(page: &Page) -> Option<FrameId> {
let frames = page.frames().await.ok()?;
let main = page.mainframe().await.ok().flatten();
frames.into_iter().find(|c| Some(c) != main.as_ref())
}
async fn ctx_with_path(page: &Page, needle: &str) -> Option<FrameId> {
for c in page.frames().await.ok()? {
if let Ok(r) = page.evaluate_in_context("location.pathname", &c).await {
if r.into_value::<String>().map(|p| p.contains(needle)).unwrap_or(false) {
return Some(c);
}
}
}
None
}
async fn run() {
let l_parent = TcpListener::bind("127.0.0.1:0").unwrap();
let l_box = TcpListener::bind("127.0.0.1:0").unwrap();
let l_mid = TcpListener::bind("127.0.0.1:0").unwrap();
let l_inner = TcpListener::bind("127.0.0.1:0").unwrap();
let p_parent = l_parent.local_addr().unwrap().port();
let p_box = l_box.local_addr().unwrap().port();
let p_mid = l_mid.local_addr().unwrap().port();
let p_inner = l_inner.local_addr().unwrap().port();
let phtml = parent_html(p_box, p_mid);
std::thread::spawn(move || serve(l_parent, move |p| (p == "/").then(|| phtml.clone())));
std::thread::spawn(move || serve(l_box, |p| p.starts_with("/box").then(|| CHECKBOX_HTML.to_string())));
let midhtml = mid_html(p_inner);
std::thread::spawn(move || serve(l_mid, move |p| p.starts_with("/mid").then(|| midhtml.clone())));
std::thread::spawn(move || serve(l_inner, |p| p.starts_with("/inner").then(|| CHECKBOX_HTML.to_string())));
let page = launch_firefox(FoxBrowserConfig {
executable_path: None,
profile_dir: None,
headless: true,
viewport_width: 1280,
viewport_height: 800,
user_agent: None,
user_js_content: None,
proxy: None,
})
.await
.expect("launch firefox");
page.goto(&format!("http://127.0.0.1:{p_parent}/")).await.expect("goto");
for _ in 0..50 {
if page.frames().await.unwrap().len() >= 2 { break; }
tokio::time::sleep(Duration::from_millis(100)).await;
}
let box_ctx = nonmain_ctx(&page).await.expect("cross-origin iframe context tracked");
let top_x = IFRAME_LEFT + CB_LOCAL_X;
let top_y = IFRAME_TOP + CB_LOCAL_Y;
reset(&page).await;
page.click_at(top_x, top_y).await.expect("top-context click");
tokio::time::sleep(Duration::from_millis(400)).await;
assert_eq!(delivered(&page).await, (true, Some(true)), "top-context click must deliver a TRUSTED event into the cross-origin iframe");
reset(&page).await;
page.click_at_in(&box_ctx, CB_LOCAL_X, CB_LOCAL_Y).await.expect("iframe-context click");
tokio::time::sleep(Duration::from_millis(400)).await;
assert_eq!(delivered(&page).await, (true, Some(true)), "click_at_in must deliver a TRUSTED event in the iframe's own context");
reset(&page).await;
page.evaluate_in_context("document.getElementById('cb').click()", &box_ctx).await.expect("synthetic click");
tokio::time::sleep(Duration::from_millis(400)).await;
assert_eq!(delivered(&page).await, (true, Some(false)), "synthetic JS click must arrive UNtrusted (the reason JS clicks never solve a real captcha)");
let _ = page.evaluate("window.postMessage({t:'show_nested'},'*')").await;
for _ in 0..60 {
if page.frames().await.unwrap().len() >= 3 { break; }
tokio::time::sleep(Duration::from_millis(100)).await;
}
reset(&page).await;
page.click_at(NESTED_TOP_X, NESTED_TOP_Y).await.expect("nested top-context click");
tokio::time::sleep(Duration::from_millis(400)).await;
assert_eq!(delivered(&page).await, (true, Some(true)), "top-context click must route into a doubly-nested cross-origin frame, trusted");
let inner = ctx_with_path(&page, "inner").await.expect("inner context resolved");
reset(&page).await;
page.click_at_in(&inner, CB_LOCAL_X, CB_LOCAL_Y).await.expect("inner-context click");
tokio::time::sleep(Duration::from_millis(400)).await;
assert_eq!(delivered(&page).await, (true, Some(true)), "click_at_in must deliver a TRUSTED event into the innermost nested frame");
let _ = page.evaluate("window.__moveTrusted=null; void 0").await;
page.move_mouse_to(500.0, 400.0).await.expect("trusted move");
tokio::time::sleep(Duration::from_millis(200)).await;
let move_trusted = page
.evaluate("JSON.stringify(window.__moveTrusted)")
.await
.ok()
.and_then(|e| e.into_value::<String>().ok())
.unwrap_or_default();
assert_eq!(move_trusted, "true", "move_mouse_to must dispatch a TRUSTED mousemove (isTrusted=true), not a synthetic JS event");
}
#[test]
fn cross_origin_trusted_click_delivery() {
if !firefox_present() {
eprintln!("SKIP cross_origin_trusted_click_delivery: firefox not on PATH");
return;
}
let rt = tokio::runtime::Builder::new_multi_thread().enable_all().build().unwrap();
rt.block_on(run());
}