diff --git a/cli/src/commands.rs b/cli/src/commands.rs index bfa68e7..fae0468 100644 --- a/cli/src/commands.rs +++ b/cli/src/commands.rs @@ -2455,6 +2455,15 @@ fn parse_get(rest: &[&str], id: &str) -> Result { if all_frames { return Ok(json!({ "id": id, "action": "gettext", "allFrames": true })); } + // `get text --pierce` reads through CLOSED shadow DOM / child docs + // via the CDP DOM tree — content eval/innerText can't reach, e.g. an + // extension's injected panel in a closed shadow root (issue #30). + let pierce = rest[1..] + .iter() + .any(|a| matches!(*a, "--pierce" | "--shadow" | "--deep")); + if pierce { + return Ok(json!({ "id": id, "action": "gettext", "pierce": true })); + } // `get text --main` returns the main-content region (readability), // skipping header/nav/footer/sidebar boilerplate (issue #27). let main = rest[1..] @@ -4897,6 +4906,16 @@ mod tests { } } + #[test] + fn test_get_text_pierce() { + for variant in ["get text --pierce", "get text --shadow", "text --deep"] { + let cmd = parse_command(&args(variant), &default_flags()).unwrap(); + assert_eq!(cmd["action"], "gettext", "{variant}"); + assert_eq!(cmd["pierce"], true, "{variant}"); + assert!(cmd.get("selector").is_none(), "{variant}"); + } + } + #[test] fn test_tab_activate_flag() { let plain = parse_command(&args("tab t3"), &default_flags()).unwrap(); diff --git a/cli/src/native/actions.rs b/cli/src/native/actions.rs index eb436c6..02f770a 100644 --- a/cli/src/native/actions.rs +++ b/cli/src/native/actions.rs @@ -3628,6 +3628,15 @@ async fn handle_gettext(cmd: &Value, state: &mut DaemonState) -> Result Resu .to_string()) } +// Text nodes whose parent is one of these carry no visible content. +fn is_noise_tag(name: &str) -> bool { + matches!(name, "SCRIPT" | "STYLE" | "NOSCRIPT" | "TEMPLATE" | "HEAD") +} + +// Walk a CDP DOM.Node tree, collecting text-node values. Unlike `innerText` +// (JS, blocked by CLOSED shadow roots), the CDP DOM tree from +// `DOM.getDocument(pierce:true)` includes closed shadow roots and child +// documents — so this reaches text JS can't. `parent_noise` carries whether an +// ancestor was