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

Reply via email to