feat(text): 'get text --pierce' reads through CLOSED shadow DOM (#30)

Some injected UI (browser-extension debug panels, web components) renders into a
CLOSED shadow root that eval/innerText cannot read. --pierce walks the CDP DOM
tree (DOM.getDocument depth:-1 pierce:true), which includes closed shadow roots
and child documents, and collects text nodes (skipping script/style/etc).

Review-safe: rides the per-tab debugger session already attached, no new Chrome
permission and no ab-connect/extension change — so it works in extension-relay
mode without touching the published extension. Verified live: a closed-shadow
panel that main-world eval reports HIDDEN is read in full via --pierce.

First slice of #30 (read extension/injected-panel content). Deeper extension
introspection (background SW / chrome.storage) stays a launch-mode / raw-CDP
concern, deliberately NOT done by expanding ab-connect's debugger powers.
This commit is contained in:
leeguooooo
2026-06-15 15:46:23 +09:00
parent c85e3faa82
commit af8823b27b
5 changed files with 124 additions and 0 deletions
+19
View File
@@ -2455,6 +2455,15 @@ fn parse_get(rest: &[&str], id: &str) -> Result<Value, ParseError> {
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();