mirror of
https://github.com/lahfir/agent-desktop.git
synced 2026-08-19 21:49:23 +00:00
perf: trim actionability preflight and resolve-search allocations
- action_list: gate the AXValue and AXExpanded `is_settable` probes on whether the role could plausibly carry that capability (unknown roles always probe), skipping up to two AX round trips per preflight on common click-only targets. No capability is lost — value/expandable roles still probe. - resolve_search: reuse one scratch FxHashSet across nodes instead of allocating a fresh dedup set per node during path and recursive search.
This commit is contained in:
parent
16351b05a2
commit
4784424fad
2 changed files with 50 additions and 5 deletions
|
|
@ -34,13 +34,16 @@ pub(crate) fn platform_available_actions(
|
|||
if has_scroll_mechanism(role, &has, has_scrollbars) {
|
||||
push_unique(&mut actions, capability::SCROLL);
|
||||
}
|
||||
if has("AXIncrement") || has("AXDecrement") || is_attr_settable(el, kAXValueAttribute) {
|
||||
if has("AXIncrement")
|
||||
|| has("AXDecrement")
|
||||
|| (role_may_bear_value(role) && is_attr_settable(el, kAXValueAttribute))
|
||||
{
|
||||
push_unique(&mut actions, capability::SET_VALUE);
|
||||
}
|
||||
if is_attr_settable(el, kAXFocusedAttribute) {
|
||||
push_unique(&mut actions, capability::SET_FOCUS);
|
||||
}
|
||||
if is_attr_settable(el, "AXExpanded")
|
||||
if (role_may_expand(role) && is_attr_settable(el, "AXExpanded"))
|
||||
|| (has("AXPress") && agent_desktop_core::roles::is_expandable_role(role))
|
||||
{
|
||||
push_unique(&mut actions, capability::EXPAND);
|
||||
|
|
@ -50,6 +53,38 @@ pub(crate) fn platform_available_actions(
|
|||
actions
|
||||
}
|
||||
|
||||
/// Whether a role could carry a settable `AXValue`, so the `is_settable` probe
|
||||
/// is worth an IPC. Click/navigation-only roles never do; `unknown` always
|
||||
/// probes so an unmapped role never loses a capability.
|
||||
fn role_may_bear_value(role: &str) -> bool {
|
||||
matches!(
|
||||
role,
|
||||
"textfield"
|
||||
| "combobox"
|
||||
| "slider"
|
||||
| "incrementor"
|
||||
| "stepper"
|
||||
| "spinbutton"
|
||||
| "checkbox"
|
||||
| "radiobutton"
|
||||
| "switch"
|
||||
| "colorwell"
|
||||
| "scrollbar"
|
||||
| "valueindicator"
|
||||
| "unknown"
|
||||
)
|
||||
}
|
||||
|
||||
/// Whether a role could expose a settable `AXExpanded`. Leaf/interactive roles
|
||||
/// never expand; `unknown` always probes.
|
||||
fn role_may_expand(role: &str) -> bool {
|
||||
agent_desktop_core::roles::is_expandable_role(role)
|
||||
|| matches!(
|
||||
role,
|
||||
"group" | "outline" | "row" | "browser" | "table" | "list" | "cell" | "unknown"
|
||||
)
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
pub(crate) fn platform_available_actions(
|
||||
_el: &AXElement,
|
||||
|
|
|
|||
|
|
@ -54,11 +54,12 @@ fn element_at_path(
|
|||
deadline: Instant,
|
||||
) -> Result<Option<AXElement>, AdapterError> {
|
||||
let mut current = root.clone();
|
||||
let mut seen = FxHashSet::default();
|
||||
for idx in path {
|
||||
ensure_before_deadline(deadline)?;
|
||||
set_messaging_timeout(¤t, remaining_before_deadline(deadline)?);
|
||||
let ax_role = copy_string_attr(¤t, accessibility_sys::kAXRoleAttribute);
|
||||
let children = resolve_children(¤t, ax_role.as_deref(), deadline)?;
|
||||
let children = resolve_children(¤t, ax_role.as_deref(), deadline, &mut seen)?;
|
||||
let Some(child) = children.get(*idx) else {
|
||||
return Ok(None);
|
||||
};
|
||||
|
|
@ -77,6 +78,7 @@ pub(super) fn find_entry_in_roots(
|
|||
) -> Result<NativeHandle, AdapterError> {
|
||||
let mut matches = Vec::new();
|
||||
let mut seen_matches = ElementDedupe;
|
||||
let mut child_scratch = FxHashSet::default();
|
||||
for root in roots {
|
||||
if should_stop_collecting(matches.len(), entry) {
|
||||
break;
|
||||
|
|
@ -88,6 +90,7 @@ pub(super) fn find_entry_in_roots(
|
|||
ancestors: &mut visited,
|
||||
seen_matches: &mut seen_matches,
|
||||
matches: &mut matches,
|
||||
child_scratch: &mut child_scratch,
|
||||
deadline,
|
||||
};
|
||||
collect_elements_recursive(root, 0, &mut context)?;
|
||||
|
|
@ -102,6 +105,7 @@ struct CollectContext<'a> {
|
|||
ancestors: &'a mut FxHashSet<usize>,
|
||||
seen_matches: &'a mut ElementDedupe,
|
||||
matches: &'a mut Vec<AXElement>,
|
||||
child_scratch: &'a mut FxHashSet<usize>,
|
||||
deadline: Instant,
|
||||
}
|
||||
|
||||
|
|
@ -136,7 +140,12 @@ fn collect_elements_recursive(
|
|||
}
|
||||
|
||||
if depth < context.max_depth && !should_prune_for_resolution(el, context.entry, depth) {
|
||||
let children = resolve_children(el, ax_role.as_deref(), context.deadline)?;
|
||||
let children = resolve_children(
|
||||
el,
|
||||
ax_role.as_deref(),
|
||||
context.deadline,
|
||||
context.child_scratch,
|
||||
)?;
|
||||
for child in &children {
|
||||
collect_elements_recursive(child, depth + 1, context)?;
|
||||
}
|
||||
|
|
@ -187,8 +196,9 @@ fn resolve_children(
|
|||
el: &AXElement,
|
||||
ax_role: Option<&str>,
|
||||
deadline: Instant,
|
||||
seen: &mut FxHashSet<usize>,
|
||||
) -> Result<Vec<AXElement>, AdapterError> {
|
||||
let mut seen = FxHashSet::default();
|
||||
seen.clear();
|
||||
let mut result = Vec::new();
|
||||
for attr in child_attributes(ax_role) {
|
||||
ensure_before_deadline(deadline)?;
|
||||
|
|
|
|||
Loading…
Reference in a new issue