Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

8267385: Create NSAccessibilityElement implementation for JavaComponentAccessibility #4412

Closed
wants to merge 15 commits into from
Closed
Show file tree
Hide file tree
Changes from 7 commits
Commits
Show all changes
15 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Expand Up @@ -32,13 +32,15 @@
import java.awt.Point;
import java.awt.Window;
import java.awt.event.KeyEvent;
import java.awt.EventQueue;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.Arrays;

import javax.accessibility.Accessible;
import javax.accessibility.AccessibleAction;
Expand All @@ -57,6 +59,8 @@
import javax.swing.JLabel;
import javax.swing.JMenuItem;
import javax.swing.JTextArea;
import javax.swing.JList;
import javax.swing.JTree;
import javax.swing.KeyStroke;

import sun.awt.AWTAccessor;
Expand Down Expand Up @@ -116,19 +120,14 @@ public void propertyChange(final PropertyChangeEvent evt) {
private native void focusChanged();

static <T> T invokeAndWait(final Callable<T> callable, final Component c) {
if (c != null) {
try {
return LWCToolkit.invokeAndWait(callable, c);
} catch (final Exception e) { e.printStackTrace(); }
}
return null;
return invokeAndWait(callable, c, null);
}

static <T> T invokeAndWait(final Callable<T> callable, final Component c, final T defValue) {
T value = null;
if (c != null) {
try {
value = LWCToolkit.invokeAndWait(callable, c);
value = EventQueue.isDispatchThread() ? callable.call() : LWCToolkit.invokeAndWait(callable, c);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When this method is called on EDT?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the same class there is a getChildrenAndRolesRecursive () method that calls invokeAndWait (), inside it it calls itself and getChildrenAndRoles (), which also call invokeAndWait ().

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok, so this is in the new code, how hard it will be to reimplement these methods to call invokeAndWait only once per callback from the native, as it usually was done before?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

} catch (final Exception e) { e.printStackTrace(); }
}

Expand Down Expand Up @@ -551,6 +550,10 @@ public void run() {
if (pac == null) return;
AccessibleSelection as = pac.getAccessibleSelection();
if (as == null) return;
if (parent instanceof JList) {
((JList) parent).setSelectedIndex(i);
return;
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like the a11y interface miss "setSelectedIndex" method? The code above will not work for the custom component which uses the "AccessibleJListChild" a11y object?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unfortunately, the current a11y interface does not allow this action.
As for the custom AccessibleJListChild, I prepared a sample project in which I implemented a custom child. The experiment showed that there are no negative consequences. The custom child works as intended.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the example, you use a JList class, and the checks in the code above works, but if some other "AccessibleJList" class will be used then it will not work, right? This is the reason why implementations of a11y classes usually use specific interfaces like "AccessibleJList".

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, this will not work with a custom list not inherited from JList. Unfortunately AccessibleJList is not perfect and the functionality you need is not there.
If you have a better solution, please share it. - Could you please advice on what we can do here?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is what I meant in the first message, "a11y interface miss setSelectedIndex method". I suggest filing a separate issue to accumulate such use cases and then fix them by adding additional methods to a11y interfaces. Probably something similar will be found in the Table/etc.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done. JDK-8271846

as.addAccessibleSelection(i);
}
}, c);
Expand Down Expand Up @@ -720,6 +723,32 @@ public Object[] call() throws Exception {
}, c);
}

// This method is called from the native
// Each child takes up three entries in the array: one for itself, one for its role, and one for the recursion level
private static Object[] getChildrenAndRolesRecursive(final Accessible a, final Component c, final int whichChildren, final boolean allowIgnored, final int level) {
if (a == null) return null;
return invokeAndWait(new Callable<Object[]>() {
public Object[] call() throws Exception {
ArrayList<Object> currentLevelChildren = new ArrayList<Object>();
currentLevelChildren.addAll(Arrays.asList(getChildrenAndRoles(a, c, JAVA_AX_ALL_CHILDREN, allowIgnored)));
ArrayList<Object> allChildren = new ArrayList<Object>();
for (int i = 0; i < currentLevelChildren.size(); i += 2) {
if ((((Accessible) currentLevelChildren.get(i)).getAccessibleContext().getAccessibleStateSet().contains(AccessibleState.SELECTED) && (whichChildren == JAVA_AX_SELECTED_CHILDREN)) ||
(((Accessible) currentLevelChildren.get(i)).getAccessibleContext().getAccessibleStateSet().contains(AccessibleState.VISIBLE) && (whichChildren == JAVA_AX_VISIBLE_CHILDREN)) ||
(whichChildren == JAVA_AX_ALL_CHILDREN)) {
allChildren.add(currentLevelChildren.get(i));
allChildren.add(currentLevelChildren.get(i + 1));
allChildren.add(String.valueOf(level));
}
if (getAccessibleStateSet(((Accessible) currentLevelChildren.get(i)).getAccessibleContext(), c).contains(AccessibleState.EXPANDED)) {
allChildren.addAll(Arrays.asList(getChildrenAndRolesRecursive(((Accessible) currentLevelChildren.get(i)), c, whichChildren, allowIgnored, level + 1)));
}
}
return allChildren.toArray();
}
}, c);
}

private static final int JAVA_AX_ROWS = 1;
private static final int JAVA_AX_COLS = 2;

Expand Down Expand Up @@ -858,4 +887,18 @@ public Long call() throws Exception {
}
}, (Component)ax);
}

private static boolean isTreeRootVisible(Accessible a, Component c) {
if (a == null) return false;

return invokeAndWait(new Callable<Boolean>() {
public Boolean call() throws Exception {
Accessible sa = CAccessible.getSwingAccessible(a);
if (sa instanceof JTree) {
return ((JTree) sa).isRootVisible();
}
return false;
}
}, c);
}
}
Expand Up @@ -31,11 +31,7 @@

import javax.accessibility.Accessible;
import javax.accessibility.AccessibleContext;
import javax.swing.JProgressBar;
import javax.swing.JTabbedPane;
import javax.swing.JSlider;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;

import static javax.accessibility.AccessibleContext.ACCESSIBLE_ACTIVE_DESCENDANT_PROPERTY;
import static javax.accessibility.AccessibleContext.ACCESSIBLE_CARET_PROPERTY;
Expand Down Expand Up @@ -75,6 +71,8 @@ public static CAccessible getCAccessible(final Accessible a) {
private static native void menuOpened(long ptr);
private static native void menuClosed(long ptr);
private static native void menuItemSelected(long ptr);
private static native void treeNodeExpanded(long ptr);
private static native void treeNodeCollapsed(long ptr);

private Accessible accessible;

Expand Down Expand Up @@ -137,6 +135,13 @@ public void propertyChange(PropertyChangeEvent e) {
if (parentAccessible != null) {
parentRole = parentAccessible.getAccessibleContext().getAccessibleRole();
}

if (newValue == AccessibleState.EXPANDED) {
treeNodeExpanded(ptr);
} else if (newValue == AccessibleState.COLLAPSED) {
treeNodeCollapsed(ptr);
}

// At least for now don't handle combo box menu state changes.
// This may change when later fixing issues which currently
// exist for combo boxes, but for now the following is only
Expand Down
61 changes: 27 additions & 34 deletions src/java.desktop/macosx/native/libawt_lwawt/awt/AWTView.m
Expand Up @@ -27,8 +27,7 @@
#import "CGLGraphicsConfig.h"
#import "AWTView.h"
#import "AWTWindow.h"
#import "JavaComponentAccessibility.h"
#import "JavaTextAccessibility.h"
#import "a11y/CommonComponentAccessibility.h"
#import "JavaAccessibilityUtilities.h"
#import "GeomUtilities.h"
#import "ThreadUtilities.h"
Expand Down Expand Up @@ -611,42 +610,29 @@ + (AWTView *) awtView:(JNIEnv*)env ofAccessible:(jobject)jaccessible
- (id)getAxData:(JNIEnv*)env
{
jobject jcomponent = [self awtComponent:env];
id ax = [[[JavaComponentAccessibility alloc] initWithParent:self withEnv:env withAccessible:jcomponent withIndex:-1 withView:self withJavaRole:nil] autorelease];
id ax = [[[CommonComponentAccessibility alloc] initWithParent:self withEnv:env withAccessible:jcomponent withIndex:-1 withView:self withJavaRole:nil] autorelease];
(*env)->DeleteLocalRef(env, jcomponent);
return ax;
}

- (NSArray *)accessibilityAttributeNames
{
return [[super accessibilityAttributeNames] arrayByAddingObject:NSAccessibilityChildrenAttribute];
}

// NSAccessibility messages
// attribute methods
- (id)accessibilityAttributeValue:(NSString *)attribute
- (id)accessibilityChildren
{
AWT_ASSERT_APPKIT_THREAD;
JNIEnv *env = [ThreadUtilities getJNIEnv];

if ([attribute isEqualToString:NSAccessibilityChildrenAttribute])
{
JNIEnv *env = [ThreadUtilities getJNIEnv];

(*env)->PushLocalFrame(env, 4);
(*env)->PushLocalFrame(env, 4);

id result = NSAccessibilityUnignoredChildrenForOnlyChild([self getAxData:env]);
id result = NSAccessibilityUnignoredChildrenForOnlyChild([self getAxData:env]);

(*env)->PopLocalFrame(env, NULL);
(*env)->PopLocalFrame(env, NULL);

return result;
}
else
{
return [super accessibilityAttributeValue:attribute];
}
return result;
}
- (BOOL)accessibilityIsIgnored

- (BOOL)isAccessibilityElement
{
return YES;
return NO;
}

- (id)accessibilityHitTest:(NSPoint)point
Expand All @@ -656,7 +642,7 @@ - (id)accessibilityHitTest:(NSPoint)point

(*env)->PushLocalFrame(env, 4);

id result = [[self getAxData:env] accessibilityHitTest:point withEnv:env];
id result = [[self getAxData:env] accessibilityHitTest:point];

(*env)->PopLocalFrame(env, NULL);

Expand All @@ -681,17 +667,24 @@ - (id)accessibilityFocusedUIElement
// --- Services menu support for lightweights ---

// finds the focused accessible element, and if it is a text element, obtains the text from it
- (NSString *)accessibleSelectedText
- (NSString *)accessibilitySelectedText
{
id focused = [self accessibilityFocusedUIElement];
if (![focused isKindOfClass:[JavaTextAccessibility class]]) return nil;
return [(JavaTextAccessibility *)focused accessibilitySelectedTextAttribute];
if (![focused respondsToSelector:@selector(accessibilitySelectedText)]) return nil;
return [focused accessibilitySelectedText];
}

- (void)setAccessibilitySelectedText:(NSString *)accessibilitySelectedText {
id focused = [self accessibilityFocusedUIElement];
if ([focused respondsToSelector:@selector(setAccessibilitySelectedText)]) {
[focused setAccessibilitySelectedText:accessibilitySelectedText];
}
}

// same as above, but converts to RTFD
- (NSData *)accessibleSelectedTextAsRTFD
{
NSString *selectedText = [self accessibleSelectedText];
NSString *selectedText = [self accessibilitySelectedText];
NSAttributedString *styledText = [[NSAttributedString alloc] initWithString:selectedText];
NSData *rtfdData = [styledText RTFDFromRange:NSMakeRange(0, [styledText length])
documentAttributes:
Expand All @@ -704,8 +697,8 @@ - (NSData *)accessibleSelectedTextAsRTFD
- (BOOL)replaceAccessibleTextSelection:(NSString *)text
{
id focused = [self accessibilityFocusedUIElement];
if (![focused isKindOfClass:[JavaTextAccessibility class]]) return NO;
[(JavaTextAccessibility *)focused accessibilitySetSelectedTextAttribute:text];
if (![focused respondsToSelector:@selector(setAccessibilitySelectedText)]) return NO;
[focused setAccessibilitySelectedText:text];
return YES;
}

Expand All @@ -715,7 +708,7 @@ - (id)validRequestorForSendType:(NSString *)sendType returnType:(NSString *)retu
if ([[self window] firstResponder] != self) return nil; // let AWT components handle themselves

if ([sendType isEqual:NSStringPboardType] || [returnType isEqual:NSStringPboardType]) {
NSString *selectedText = [self accessibleSelectedText];
NSString *selectedText = [self accessibilitySelectedText];
if (selectedText) return self;
}

Expand All @@ -728,7 +721,7 @@ - (BOOL)writeSelectionToPasteboard:(NSPasteboard *)pboard types:(NSArray *)types
if ([types containsObject:NSStringPboardType])
{
[pboard declareTypes:[NSArray arrayWithObject:NSStringPboardType] owner:nil];
return [pboard setString:[self accessibleSelectedText] forType:NSStringPboardType];
return [pboard setString:[self accessibilitySelectedText] forType:NSStringPboardType];
}

if ([types containsObject:NSRTFDPboardType])
Expand Down
Expand Up @@ -26,6 +26,10 @@
#import <AppKit/AppKit.h>
#import <jni.h>

extern NSMutableDictionary *sActions;
extern NSMutableDictionary *sActionSelectors;
extern NSMutableArray *sAllActionSelectors;
void initializeActions();

@protocol JavaAccessibilityAction

Expand Down
Expand Up @@ -29,6 +29,10 @@
#import "ThreadUtilities.h"
#import "JNIUtilities.h"

NSMutableDictionary *sActions = nil;
NSMutableDictionary *sActionSelectors = nil;
NSMutableArray *sAllActionSelectors = nil;
void initializeActions();

@implementation JavaAxAction

Expand Down Expand Up @@ -148,3 +152,31 @@ - (void)perform
}

@end

void initializeActions() {
int actionsCount = 5;

sActions = [[NSMutableDictionary alloc] initWithCapacity:actionsCount];

[sActions setObject:NSAccessibilityPressAction forKey:@"click"];
[sActions setObject:NSAccessibilityIncrementAction forKey:@"increment"];
[sActions setObject:NSAccessibilityDecrementAction forKey:@"decrement"];
[sActions setObject:NSAccessibilityShowMenuAction forKey:@"togglePopup"];
[sActions setObject:NSAccessibilityPressAction forKey:@"toggleExpand"];

sActionSelectors = [[NSMutableDictionary alloc] initWithCapacity:actionsCount];

[sActionSelectors setObject:NSStringFromSelector(@selector(accessibilityPerformPress)) forKey:NSAccessibilityPressAction];
[sActionSelectors setObject:NSStringFromSelector(@selector(accessibilityPerformShowMenu)) forKey:NSAccessibilityShowMenuAction];
[sActionSelectors setObject:NSStringFromSelector(@selector(accessibilityPerformDecrement)) forKey:NSAccessibilityDecrementAction];
[sActionSelectors setObject:NSStringFromSelector(@selector(accessibilityPerformIncrement)) forKey:NSAccessibilityIncrementAction];
[sActionSelectors setObject:NSStringFromSelector(@selector(accessibilityPerformPick)) forKey:NSAccessibilityPickAction];

sAllActionSelectors = [[NSMutableArray alloc] initWithCapacity:actionsCount];

[sAllActionSelectors addObject:NSStringFromSelector(@selector(accessibilityPerformPick))];
[sAllActionSelectors addObject:NSStringFromSelector(@selector(accessibilityPerformIncrement))];
[sAllActionSelectors addObject:NSStringFromSelector(@selector(accessibilityPerformDecrement))];
[sAllActionSelectors addObject:NSStringFromSelector(@selector(accessibilityPerformShowMenu))];
[sAllActionSelectors addObject:NSStringFromSelector(@selector(accessibilityPerformPress))];
}
Expand Up @@ -48,6 +48,7 @@ BOOL isVertical(JNIEnv *env, jobject axContext, jobject component);
BOOL isHorizontal(JNIEnv *env, jobject axContext, jobject component);
BOOL isShowing(JNIEnv *env, jobject axContext, jobject component);
BOOL isSelectable(JNIEnv *env, jobject axContext, jobject component);
BOOL isExpanded(JNIEnv *env, jobject axContext, jobject component);
NSPoint getAxComponentLocationOnScreen(JNIEnv *env, jobject axComponent, jobject component);
jint getAxTextCharCount(JNIEnv *env, jobject axText, jobject component);

Expand All @@ -60,3 +61,6 @@ void JavaAccessibilitySetAttributeValue(id element, NSString *attribute, id valu
void JavaAccessibilityRaiseSetAttributeToIllegalTypeException(const char *functionName, id element, NSString *attribute, id value);
void JavaAccessibilityRaiseUnimplementedAttributeException(const char *functionName, id element, NSString *attribute);
void JavaAccessibilityRaiseIllegalParameterTypeException(const char *functionName, id element, NSString *attribute, id parameter);
BOOL ObjectEquals(JNIEnv *env, jobject a, jobject b, jobject component);
NSNumber* JavaNumberToNSNumber(JNIEnv *env, jobject jnumber);
NSValue *javaIntArrayToNSRangeValue(JNIEnv* env, jintArray array);