On Fri, 24 Apr 2026 21:46:26 GMT, Alexander Zuev <[email protected]> wrote:
>> Jeremy Wood has updated the pull request incrementally with one additional
>> commit since the last revision:
>>
>> 8381236: refactor proposed solution
>>
>> I still don't understand the life cycle of a CAccessible, but I'm starting
>> to think this simpler approach may be better.
>>
>> It seems like:
>> A. We never discard a CAccessible. Once CAccessible 'Y' is constructed, it
>> will forever be paired with (and referenced by) its AccessibleContext 'X'.
>> (Therefore the listeners that 'Y' installs never need to be uninstalled.)
>> B. Some external entity is defining `CAccessible#ptr`. We don't receive a
>> call to `setPtr`, and it's not defined during construction. But at some
>> point as VoiceOver interacts with a Swing app the `ptr` field is defined.
>> C. It appears safe to call `dispose()` repeatedly on the same CAccessible.
>> So in this way `dispose()` is like `Window.dispose()`: it releases
>> resources, but it can be reallocated later (by some external agent). (By
>> contrast it is NOT like `Graphics2D.dispose()`; subsequent attempts to use a
>> Graphics2D will fail after it has been disposed.)
>
> src/java.desktop/macosx/classes/sun/lwawt/macosx/CAccessible.java line 93:
>
>> 91: if (accessible instanceof Component component) {
>> 92: addNotificationListeners(component);
>> 93: component.addPropertyChangeListener("ancestor", (e) -> {
>
> My only question is: you are registering a new listener but when this
> listener is being removed? I understand that this happens in the CAccessible
> constructor and under normal circumstances component has only one CAccessible
> but that can change and in this case wouldn't it be a potential memory leak?
That is a great question, and it's the heart of what I don't understand well
here. (It's also why my original draft (which was much more invasive) moved a
lot more logic into the `dispose` method.)
Further testing/exploration led me to believe the CAccessible object is never
intended to be discarded. It is created once and permanently linked to its
analog AccessibleContext in `getCAccessible`. (This is the only place where
`setNativeAXResource` is ever called in the codebase; it is _never_ nullified
to release the CAccessible for GC.)
> under normal circumstances component has only one CAccessible but that can
> change and in this case wouldn't it be a potential memory leak
Is this a hypothetical, or is there a specific usage pattern you're thinking of
(that I'd like to learn about)?
What event could lead us to uninstall this new "ancestor" PCL? I can't think of
a universal signal that tells us "we're ready to uninstall now", given what I
(think I) understand about how CAccessible's work.
## Alternative Proposal
How about this alternative (borrowed from
[here](https://github.com/mickleness/swing-accessibility/blob/main/src/main/java/com/pump/ax/mac/FixChangingAncestor.java)):
We never add a PCL to any Component. Instead we listen to _all_
COMPONENT_REMOVED events:
static {
AWTEventListener componentRemovedListener = new AWTEventListener() {
@Override
public void eventDispatched(AWTEvent event) {
if (event.getID() == ContainerEvent.COMPONENT_REMOVED) {
ContainerEvent containerEvent = (ContainerEvent) event;
CAccessible ca =
getCAccessibleOnlyIfOneAlreadyExists(containerEvent.getChild());
if (ca != null)
ca.dispose();
}
}
};
Toolkit.getDefaultToolkit().addAWTEventListener(componentRemovedListener,
ContainerEvent.CONTAINER_EVENT_MASK);
}
Would this bypass concerns about memory leaks?
-------------
PR Review Comment: https://git.openjdk.org/jdk/pull/30578#discussion_r3141678212