use crate::accessibility::AXTree;
use super::{JourneyCandidate, JourneyKind, PatternAnalysis, PatternConfidence, PatternKind};
const SUBMIT_HINTS: &[&str] = &[
"submit",
"send",
"senden",
"absenden",
"anmelden",
"registrieren",
"register",
"login",
"sign in",
"log in",
"suchen",
"search",
"apply",
"bewerben",
"bestellen",
"order",
"buchen",
"book",
"kontakt",
"contact",
];
pub fn detect(tree: &AXTree, out: &mut PatternAnalysis) {
let required_controls: Vec<_> = tree
.iter()
.filter(|n| {
matches!(
n.role.as_deref(),
Some("textbox")
| Some("searchbox")
| Some("combobox")
| Some("listbox")
| Some("radio")
| Some("checkbox")
| Some("spinbutton")
) && n.get_property_bool("required").unwrap_or(false)
})
.collect();
if required_controls.is_empty() {
return;
}
let buttons: Vec<_> = tree
.iter()
.filter(|n| matches!(n.role.as_deref(), Some("button")))
.collect();
if buttons.is_empty() {
return;
}
let best = buttons
.iter()
.find(|b| {
let name = b.name.as_deref().unwrap_or("").to_lowercase();
SUBMIT_HINTS.iter().any(|h| name.contains(h))
})
.or_else(|| buttons.first())
.copied();
let Some(trigger) = best else {
return;
};
let backend_id = trigger.backend_dom_node_id;
out.add_recognized(
"Form",
format!(
"{} required field(s) detected; submit trigger: {}",
required_controls.len(),
trigger.name.as_deref().unwrap_or("(unnamed button)")
),
PatternConfidence::Partial,
);
out.journey_candidates.push(JourneyCandidate {
pattern_kind: PatternKind::Form,
trigger_backend_id: backend_id,
controlled_backend_id: None,
confidence: 0.75,
required_journey: JourneyKind::FormErrorSubmit,
});
}