Skip to content

JBR-10147 Optimize performace of CAccessibility.getChildrenAndRolesRecursive for JTree#599

Open
dmitrii-drobotov wants to merge 1 commit intoJetBrains:jbr25from
dmitrii-drobotov:ddrobotov/JBR-10147
Open

JBR-10147 Optimize performace of CAccessibility.getChildrenAndRolesRecursive for JTree#599
dmitrii-drobotov wants to merge 1 commit intoJetBrains:jbr25from
dmitrii-drobotov:ddrobotov/JBR-10147

Conversation

@dmitrii-drobotov
Copy link
Copy Markdown
Member

@dmitrii-drobotov dmitrii-drobotov commented Apr 14, 2026

This PR speeds up the CAccessibility.getChildrenAndRolesRecursive method for JTrees.
From my testing, it may be called in the following cases:

  • With JAVA_AX_ALL_CHILDREN when VoiceOver or Full Keyboard Access are enabled and interacting with a tree, usually on only the first focus
  • With JAVA_AX_SELECTED_CHILDREN when one of interactive assistive tools or settings is enabled, such as VoiceOver, Zoom, VoiceControl, Hover Text, etc. It's called on focus of the tree and on every selection change. Also, for me when all known assistive tools were turned off, it was sometimes called on mouse clicks on nodes, on tree focus, and node expansion - it could be coming from a non-accessibility related OS tool or setting, or from a third-party app that uses accessibility APIs.
  • With JAVA_AX_VISIBLE_CHILDREN when Voice Control was enabled and listening.

The approach is based on ffa33d7 with the following changes:

  • Instead of using reflection to obtain AccessibleJTreeNode from TreePath (which had issues described in https://bugs.openjdk.org/browse/JDK-8329667?focusedId=14668819&page=com.atlassian.jira.plugin.system.issuetabpanels%3Acomment-tabpanel#comment-14668819), I used getPathBounds and getAccessibleAt.
  • Decreased the level of nodes by 1: from allChildren.add(String.valueOf((tree.isRootVisible() ? path.getPathCount() : path.getPathCount() - 1))); to allChildren.add(String.valueOf(tree.isRootVisible() ? path.getPathCount() - 1 : path.getPathCount() - 2));. This way it matches the level from the code path below. For example, if the root is visible and the path is [Root], its level should be 0 (== count - 1), for path is [Root, Node1] the level needs to be 1 and so on. If root is not visible, it's still included in the path, so levels are decreased by 2 from the count.
  • Used the same code for JAVA_AX_VISIBLE_CHILDREN because it's used by Voice Control.

The main improvement between the existing code is with JAVA_AX_SELECTED_CHILDREN argument. Previously, the code was iterating the whole tree to find selected nodes, now it gets them instantly. For JAVA_AX_ALL_CHILDREN and JAVA_AX_VISIBLE_CHILDREN there is also an improvement because we avoid a lot of clear, add, addAll operations with arrays. From my basic benchmark on 50k nodes tree, for JAVA_AX_SELECTED_CHILDREN the time went from 6s to <1ms, for JAVA_AX_ALL_CHILDREN from 6s to 1.5s and for JAVA_AX_VISIBLE_CHILDREN from 6s to 2s. If we still get reports of freezes after this fix, we can try adding a limit (e.g., 1000) to the the returned children count, but for now I think this is already a good improvement.

The allowIgnored argument is not handled because for trees (NSAccessibilityOutlineRole) it's always passed as true: https://github.com/JetBrains/JetBrainsRuntime/blob/jbr25/src/java.desktop/macosx/native/libawt_lwawt/awt/a11y/CommonComponentAccessibility.m#L686

Another change is flipping the order of comparison of accessible states in the code path below (for non-JTrees). cac.getAccessibleStateSet() can be expensive and this way we skip it if whichChildren == JAVA_AX_ALL_CHILDREN.


…cursive for JTree

* Use tree.getSelectionPaths() for JAVA_AX_SELECTED_CHILDREN
* For JAVA_AX_ALL_CHILDREN and JAVA_AX_VISIBLE_CHILDREN also use linear iteration to avoid expensive operations with arrays on each level
* Flip comparison order for accessible states to not call getAccessibleStateSet() when not needed because it can be expensive
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant