8160893: [macosx] JMenuItems in JPopupMenu are not accessible
Summary: post events for MenuOpened/Closed/ItemSelected
Reviewed-by: ant, alexsch
--- a/jdk/src/java.desktop/macosx/classes/sun/lwawt/macosx/CAccessibility.java Thu Oct 06 11:39:20 2016 -0700
+++ b/jdk/src/java.desktop/macosx/classes/sun/lwawt/macosx/CAccessibility.java Thu Oct 06 20:31:59 2016 -0500
@@ -29,11 +29,9 @@
import java.awt.*;
import java.beans.*;
-import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.util.*;
import java.util.concurrent.Callable;
-import sun.awt.AWTAccessor;
import javax.accessibility.*;
import javax.swing.*;
@@ -73,8 +71,20 @@
}
public void propertyChange(final PropertyChangeEvent evt) {
- if (evt.getNewValue() == null) return;
- focusChanged();
+ Object newValue = evt.getNewValue();
+ if (newValue == null) return;
+ // Don't post focus on things that don't matter, i.e. alert, colorchooser,
+ // desktoppane, dialog, directorypane, filechooser, filler, fontchoose,
+ // frame, glasspane, layeredpane, optionpane, panel, rootpane, separator,
+ // tooltip, viewport, window.
+ // List taken from initializeRoles() in JavaComponentUtilities.m.
+ if (newValue instanceof Accessible) {
+ AccessibleContext nvAC = ((Accessible) newValue).getAccessibleContext();
+ AccessibleRole nvRole = nvAC.getAccessibleRole();
+ if (!ignoredRoles.contains(roleKey(nvRole))) {
+ focusChanged();
+ }
+ }
}
private native void focusChanged();
@@ -683,9 +693,15 @@
if (context == null) continue;
if (whichChildren == JAVA_AX_VISIBLE_CHILDREN) {
- if (!context.getAccessibleComponent().isVisible()) continue;
+ AccessibleComponent acomp = context.getAccessibleComponent();
+ if (acomp == null || !acomp.isVisible()) {
+ continue;
+ }
} else if (whichChildren == JAVA_AX_SELECTED_CHILDREN) {
- if (!ac.getAccessibleSelection().isAccessibleChildSelected(i)) continue;
+ AccessibleSelection sel = ac.getAccessibleSelection();
+ if (sel == null || !sel.isAccessibleChildSelected(i)) {
+ continue;
+ }
}
if (!allowIgnored) {
--- a/jdk/src/java.desktop/macosx/classes/sun/lwawt/macosx/CAccessible.java Thu Oct 06 11:39:20 2016 -0700
+++ b/jdk/src/java.desktop/macosx/classes/sun/lwawt/macosx/CAccessible.java Thu Oct 06 20:31:59 2016 -0500
@@ -39,7 +39,10 @@
import static javax.accessibility.AccessibleContext.ACCESSIBLE_ACTIVE_DESCENDANT_PROPERTY;
import static javax.accessibility.AccessibleContext.ACCESSIBLE_CARET_PROPERTY;
import static javax.accessibility.AccessibleContext.ACCESSIBLE_SELECTION_PROPERTY;
+import static javax.accessibility.AccessibleContext.ACCESSIBLE_STATE_PROPERTY;
import static javax.accessibility.AccessibleContext.ACCESSIBLE_TEXT_PROPERTY;
+import javax.accessibility.AccessibleRole;
+import javax.accessibility.AccessibleState;
import sun.awt.AWTAccessor;
@@ -63,6 +66,9 @@
private static native void valueChanged(long ptr);
private static native void selectedTextChanged(long ptr);
private static native void selectionChanged(long ptr);
+ private static native void menuOpened(long ptr);
+ private static native void menuClosed(long ptr);
+ private static native void menuItemSelected(long ptr);
private Accessible accessible;
@@ -111,16 +117,45 @@
public void propertyChange(PropertyChangeEvent e) {
String name = e.getPropertyName();
if ( ptr != 0 ) {
+ Object newValue = e.getNewValue();
+ Object oldValue = e.getOldValue();
if (name.compareTo(ACCESSIBLE_CARET_PROPERTY) == 0) {
selectedTextChanged(ptr);
} else if (name.compareTo(ACCESSIBLE_TEXT_PROPERTY) == 0 ) {
valueChanged(ptr);
} else if (name.compareTo(ACCESSIBLE_SELECTION_PROPERTY) == 0 ) {
selectionChanged(ptr);
- } else if (name.compareTo(ACCESSIBLE_ACTIVE_DESCENDANT_PROPERTY) == 0 ) {
- Object nv = e.getNewValue();
- if (nv instanceof AccessibleContext) {
- activeDescendant = (AccessibleContext)nv;
+ } else if (name.compareTo(ACCESSIBLE_ACTIVE_DESCENDANT_PROPERTY) == 0 ) {
+ if (newValue instanceof AccessibleContext) {
+ activeDescendant = (AccessibleContext)newValue;
+ }
+ } else if (name.compareTo(ACCESSIBLE_STATE_PROPERTY) == 0) {
+ AccessibleContext thisAC = accessible.getAccessibleContext();
+ AccessibleRole thisRole = thisAC.getAccessibleRole();
+ Accessible parentAccessible = thisAC.getAccessibleParent();
+ AccessibleRole parentRole = null;
+ if (parentAccessible != null) {
+ parentRole = parentAccessible.getAccessibleContext().getAccessibleRole();
+ }
+ // 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
+ // for JPopupMenus, not for combobox menus.
+ if (parentRole != AccessibleRole.COMBO_BOX) {
+ if (thisRole == AccessibleRole.POPUP_MENU) {
+ if ( newValue != null &&
+ ((AccessibleState)newValue) == AccessibleState.VISIBLE ) {
+ menuOpened(ptr);
+ } else if ( oldValue != null &&
+ ((AccessibleState)oldValue) == AccessibleState.VISIBLE ) {
+ menuClosed(ptr);
+ }
+ } else if (thisRole == AccessibleRole.MENU_ITEM) {
+ if ( newValue != null &&
+ ((AccessibleState)newValue) == AccessibleState.FOCUSED ) {
+ menuItemSelected(ptr);
+ }
+ }
}
}
}
--- a/jdk/src/java.desktop/macosx/native/libawt_lwawt/awt/JavaComponentAccessibility.m Thu Oct 06 11:39:20 2016 -0700
+++ b/jdk/src/java.desktop/macosx/native/libawt_lwawt/awt/JavaComponentAccessibility.m Thu Oct 06 20:31:59 2016 -0500
@@ -66,7 +66,6 @@
static JNF_MEMBER_CACHE(jf_ptr, sjc_CAccessible, "ptr", "J");
static JNF_STATIC_MEMBER_CACHE(sjm_getCAccessible, sjc_CAccessible, "getCAccessible", "(Ljavax/accessibility/Accessible;)Lsun/lwawt/macosx/CAccessible;");
-
static jobject sAccessibilityClass = NULL;
// sAttributeNamesForRoleCache holds the names of the attributes to which each java
@@ -213,6 +212,24 @@
NSAccessibilityPostNotification(self, NSAccessibilitySelectedChildrenChangedNotification);
}
+- (void)postMenuOpened
+{
+ AWT_ASSERT_APPKIT_THREAD;
+ NSAccessibilityPostNotification(self, (NSString *)kAXMenuOpenedNotification);
+}
+
+- (void)postMenuClosed
+{
+ AWT_ASSERT_APPKIT_THREAD;
+ NSAccessibilityPostNotification(self, (NSString *)kAXMenuClosedNotification);
+}
+
+- (void)postMenuItemSelected
+{
+ AWT_ASSERT_APPKIT_THREAD;
+ NSAccessibilityPostNotification(self, (NSString *)kAXMenuItemSelectedNotification);
+}
+
- (BOOL)isEqual:(id)anObject
{
if (![anObject isKindOfClass:[self class]]) return NO;
@@ -278,8 +295,7 @@
+ (jobject) getCAccessible:(jobject)jaccessible withEnv:(JNIEnv *)env {
if (JNFIsInstanceOf(env, jaccessible, &sjc_CAccessible)) {
return jaccessible;
- }
- else if (JNFIsInstanceOf(env, jaccessible, &sjc_Accessible)) {
+ } else if (JNFIsInstanceOf(env, jaccessible, &sjc_Accessible)) {
return JNFCallStaticObjectMethod(env, sjm_getCAccessible, jaccessible);
}
return NULL;
@@ -368,6 +384,14 @@
// must init freshly -alloc'd object
[newChild initWithParent:parent withEnv:env withAccessible:jCAX withIndex:index withView:view withJavaRole:javaRole]; // must init new instance
+ // If creating a JPopupMenu (not a combobox popup list) need to fire menuOpened.
+ // This is the only way to know if the menu is opening; visible state change
+ // can't be caught because the listeners are not set up in time.
+ if ( [javaRole isEqualToString:@"popupmenu"] &&
+ ![[parent javaRole] isEqualToString:@"combobox"] ) {
+ [newChild postMenuOpened];
+ }
+
// must hard retain pointer poked into Java object
[newChild retain];
JNFSetLongField(env, jCAX, jf_ptr, ptr_to_jlong(newChild));
@@ -634,6 +658,15 @@
return moreNames;
}
}
+ // popupmenu's return values not selected children
+ if ( [javaRole isEqualToString:@"popupmenu"] &&
+ ![[[self parent] javaRole] isEqualToString:@"combobox"] ) {
+ NSMutableArray *moreNames =
+ [[NSMutableArray alloc] initWithCapacity: [names count] + 1];
+ [moreNames addObjectsFromArray: names];
+ [moreNames addObject:NSAccessibilityValueAttribute];
+ return moreNames;
+ }
return names;
} // end @synchronized
@@ -707,6 +740,7 @@
return value;
}
+
- (BOOL)accessibilityIsChildrenAttributeSettable
{
return NO;
@@ -939,6 +973,13 @@
if (fNSRole == nil) {
NSString *javaRole = [self javaRole];
fNSRole = [sRoles objectForKey:javaRole];
+ // The sRoles NSMutableDictionary maps popupmenu to Mac's popup button.
+ // JComboBox behavior currently relies on this. However this is not the
+ // proper mapping for a JPopupMenu so fix that.
+ if ( [javaRole isEqualToString:@"popupmenu"] &&
+ ![[[self parent] javaRole] isEqualToString:@"combobox"] ) {
+ fNSRole = NSAccessibilityMenuRole;
+ }
if (fNSRole == nil) {
// this component has assigned itself a custom AccessibleRole not in the sRoles array
fNSRole = javaRole;
@@ -947,6 +988,7 @@
}
return fNSRole;
}
+
- (BOOL)accessibilityIsRoleAttributeSettable
{
return NO;
@@ -1046,8 +1088,7 @@
- (NSString *)accessibilitySubroleAttribute
{
NSString *value = nil;
- if ([[self javaRole] isEqualToString:@"passwordtext"])
- {
+ if ([[self javaRole] isEqualToString:@"passwordtext"]) {
value = NSAccessibilitySecureTextFieldSubrole;
}
/*
@@ -1123,6 +1164,45 @@
JNIEnv* env = [ThreadUtilities getJNIEnv];
+ // Need to handle popupmenus differently.
+ //
+ // At least for now don't handle combo box menus.
+ // This may change when later fixing issues which currently
+ // exist for combo boxes, but for now the following is only
+ // for JPopupMenus, not for combobox menus.
+ id parent = [self parent];
+ if ( [[self javaRole] isEqualToString:@"popupmenu"] &&
+ ![[parent javaRole] isEqualToString:@"combobox"] ) {
+ NSArray *children =
+ [JavaComponentAccessibility childrenOfParent:self
+ withEnv:env
+ withChildrenCode:JAVA_AX_ALL_CHILDREN
+ allowIgnored:YES];
+ if ([children count] > 0) {
+ // handle case of AXMenuItem
+ // need to ask menu what is selected
+ NSArray *selectedChildrenOfMenu =
+ [self accessibilitySelectedChildrenAttribute];
+ JavaComponentAccessibility *selectedMenuItem =
+ [selectedChildrenOfMenu objectAtIndex:0];
+ if (selectedMenuItem != nil) {
+ jobject itemValue =
+ JNFCallStaticObjectMethod( env,
+ sjm_getAccessibleName,
+ selectedMenuItem->fAccessible,
+ selectedMenuItem->fComponent ); // AWT_THREADING Safe (AWTRunLoop)
+ if (itemValue == NULL) {
+ return nil;
+ }
+ NSString* itemString = JNFJavaToNSString(env, itemValue);
+ (*env)->DeleteLocalRef(env, itemValue);
+ return itemString;
+ } else {
+ return nil;
+ }
+ }
+ }
+
// ask Java for the component's accessibleValue. In java, the "accessibleValue" just means a numerical value
// a text value is taken care of in JavaTextAccessibility
@@ -1345,6 +1425,54 @@
/*
* Class: sun_lwawt_macosx_CAccessible
+ * Method: menuOpened
+ * Signature: (I)V
+ */
+JNIEXPORT void JNICALL Java_sun_lwawt_macosx_CAccessible_menuOpened
+(JNIEnv *env, jclass jklass, jlong element)
+{
+JNF_COCOA_ENTER(env);
+ [ThreadUtilities performOnMainThread:@selector(postMenuOpened)
+ on:(JavaComponentAccessibility *)jlong_to_ptr(element)
+ withObject:nil
+ waitUntilDone:NO];
+JNF_COCOA_EXIT(env);
+}
+
+/*
+ * Class: sun_lwawt_macosx_CAccessible
+ * Method: menuClosed
+ * Signature: (I)V
+ */
+JNIEXPORT void JNICALL Java_sun_lwawt_macosx_CAccessible_menuClosed
+(JNIEnv *env, jclass jklass, jlong element)
+{
+JNF_COCOA_ENTER(env);
+ [ThreadUtilities performOnMainThread:@selector(postMenuClosed)
+ on:(JavaComponentAccessibility *)jlong_to_ptr(element)
+ withObject:nil
+ waitUntilDone:NO];
+JNF_COCOA_EXIT(env);
+}
+
+/*
+ * Class: sun_lwawt_macosx_CAccessible
+ * Method: menuItemSelected
+ * Signature: (I)V
+ */
+JNIEXPORT void JNICALL Java_sun_lwawt_macosx_CAccessible_menuItemSelected
+(JNIEnv *env, jclass jklass, jlong element)
+{
+JNF_COCOA_ENTER(env);
+ [ThreadUtilities performOnMainThread:@selector(postMenuItemSelected)
+ on:(JavaComponentAccessibility *)jlong_to_ptr(element)
+ withObject:nil
+ waitUntilDone:NO];
+JNF_COCOA_EXIT(env);
+}
+
+/*
+ * Class: sun_lwawt_macosx_CAccessible
* Method: unregisterFromCocoaAXSystem
* Signature: (I)V
*/