feat(text): frame-aware text extraction — get text --all-frames / --main + frames (#27)
On listing/marketplace pages (Yahoo Auctions, Rakuten, Mercari shops) the seller's description lives in a child frame or under a related-items sidebar, so 'get text body' returned only header/nav boilerplate. - get text --all-frames: aggregate visible text across every reachable frame. Same-process child frames are read via Page.createIsolatedWorld; OOPIFs via their auto-attached debugger session (iframe_sessions). Each non-top frame is labelled with a '----- frame [kind] url -----' separator. - get text --main: readability-lite — prefer the densest <main>/<article> region over the whole body, dropping global header/nav/footer chrome. - frames: enumerate frames (kind + url + per-frame text length) so an agent can see where a page's text actually lives and pick the right read. Verified live: inline srcdoc frame text aggregated through --all-frames; Yahoo Auctions <main> (2881 chars) extracted via --main, stripping the Yahoo header.
This commit is contained in:
@@ -1332,6 +1332,7 @@ pub async fn execute_command(cmd: &Value, state: &mut DaemonState) -> Value {
|
||||
"uncheck" => handle_uncheck(cmd, state).await,
|
||||
"wait" => handle_wait(cmd, state).await,
|
||||
"gettext" => handle_gettext(cmd, state).await,
|
||||
"frames" => handle_frames(cmd, state).await,
|
||||
"getattribute" => handle_getattribute(cmd, state).await,
|
||||
"isvisible" => handle_isvisible(cmd, state).await,
|
||||
"isenabled" => handle_isenabled(cmd, state).await,
|
||||
@@ -3594,6 +3595,47 @@ async fn handle_wait(cmd: &Value, state: &mut DaemonState) -> Result<Value, Stri
|
||||
async fn handle_gettext(cmd: &Value, state: &mut DaemonState) -> Result<Value, String> {
|
||||
let mgr = state.browser.as_ref().ok_or("Browser not launched")?;
|
||||
let session_id = mgr.active_session_id()?.to_string();
|
||||
|
||||
// `get text --all-frames` aggregates visible text across every frame the
|
||||
// session can reach — including out-of-process iframes that never show up
|
||||
// in the top document (#27: Yahoo/Rakuten/Mercari listing descriptions).
|
||||
if cmd.get("allFrames").and_then(|v| v.as_bool()) == Some(true) {
|
||||
let frames = super::element::collect_all_frames_text(
|
||||
&mgr.client,
|
||||
&session_id,
|
||||
&state.iframe_sessions,
|
||||
)
|
||||
.await?;
|
||||
let mut combined = String::new();
|
||||
let mut frame_count = 0usize;
|
||||
for f in &frames {
|
||||
let t = f.text.trim();
|
||||
if t.is_empty() {
|
||||
continue;
|
||||
}
|
||||
frame_count += 1;
|
||||
if f.kind != "top" {
|
||||
combined.push_str(&format!("\n\n----- frame [{}] {} -----\n", f.kind, f.url));
|
||||
}
|
||||
combined.push_str(t);
|
||||
}
|
||||
let url = mgr.get_url().await.unwrap_or_default();
|
||||
return Ok(json!({
|
||||
"text": combined,
|
||||
"origin": url,
|
||||
"frames": frame_count,
|
||||
"allFrames": true,
|
||||
}));
|
||||
}
|
||||
|
||||
// `get text --main` returns the page's main-content region (readability-lite),
|
||||
// skipping global header/nav/footer/sidebar boilerplate (#27).
|
||||
if cmd.get("main").and_then(|v| v.as_bool()) == Some(true) {
|
||||
let text = super::element::get_main_content_text(&mgr.client, &session_id).await?;
|
||||
let url = mgr.get_url().await.unwrap_or_default();
|
||||
return Ok(json!({ "text": text, "origin": url, "main": true }));
|
||||
}
|
||||
|
||||
let selector = cmd
|
||||
.get("selector")
|
||||
.and_then(|v| v.as_str())
|
||||
@@ -3611,6 +3653,32 @@ async fn handle_gettext(cmd: &Value, state: &mut DaemonState) -> Result<Value, S
|
||||
Ok(json!({ "text": text, "origin": url }))
|
||||
}
|
||||
|
||||
async fn handle_frames(_cmd: &Value, state: &mut DaemonState) -> Result<Value, String> {
|
||||
let mgr = state.browser.as_ref().ok_or("Browser not launched")?;
|
||||
let session_id = mgr.active_session_id()?.to_string();
|
||||
let frames = super::element::collect_all_frames_text(
|
||||
&mgr.client,
|
||||
&session_id,
|
||||
&state.iframe_sessions,
|
||||
)
|
||||
.await?;
|
||||
let list: Vec<Value> = frames
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(i, f)| {
|
||||
json!({
|
||||
"index": i,
|
||||
"kind": f.kind,
|
||||
"url": f.url,
|
||||
"frameId": f.frame_id,
|
||||
"textLen": f.text.trim().chars().count(),
|
||||
})
|
||||
})
|
||||
.collect();
|
||||
let url = mgr.get_url().await.unwrap_or_default();
|
||||
Ok(json!({ "frames": list, "count": list.len(), "origin": url }))
|
||||
}
|
||||
|
||||
async fn handle_getattribute(cmd: &Value, state: &mut DaemonState) -> Result<Value, String> {
|
||||
let mgr = state.browser.as_ref().ok_or("Browser not launched")?;
|
||||
let session_id = mgr.active_session_id()?.to_string();
|
||||
|
||||
Reference in New Issue
Block a user