4685768: A11y issue - Focus set to disabled component, can't Tab/Shift-Tab
authorant
Tue, 17 Jun 2008 13:37:28 +0400
changeset 1171 a2782dd9f312
parent 1170 4e03057627b7
child 1172 a1d23c450f84
child 1173 96c2af2cb574
4685768: A11y issue - Focus set to disabled component, can't Tab/Shift-Tab Summary: The restore-focus procedure should skip disabled components. Reviewed-by: art, dcherepanov
jdk/src/share/classes/java/awt/Component.java
jdk/src/share/classes/java/awt/Container.java
jdk/src/share/classes/java/awt/ContainerOrderFocusTraversalPolicy.java
jdk/src/share/classes/java/awt/DefaultKeyboardFocusManager.java
jdk/src/share/classes/java/awt/Window.java
jdk/test/java/awt/Focus/NoAutotransferToDisabledCompTest/NoAutotransferToDisabledCompTest.java
jdk/test/java/awt/Focus/RequestFocusToDisabledCompTest/RequestFocusToDisabledCompTest.java
jdk/test/java/awt/regtesthelpers/Util.java
--- a/jdk/src/share/classes/java/awt/Component.java	Wed Jun 11 01:31:42 2008 -0700
+++ b/jdk/src/share/classes/java/awt/Component.java	Tue Jun 17 13:37:28 2008 +0400
@@ -7488,9 +7488,7 @@
         Container rootAncestor = getTraversalRoot();
         Component comp = this;
         while (rootAncestor != null &&
-               !(rootAncestor.isShowing() &&
-                 rootAncestor.isFocusable() &&
-                 rootAncestor.isEnabled()))
+               !(rootAncestor.isShowing() && rootAncestor.canBeFocusOwner()))
         {
             comp = rootAncestor;
             rootAncestor = comp.getFocusCycleRootAncestor();
@@ -7539,9 +7537,7 @@
         Container rootAncestor = getTraversalRoot();
         Component comp = this;
         while (rootAncestor != null &&
-               !(rootAncestor.isShowing() &&
-                 rootAncestor.isFocusable() &&
-                 rootAncestor.isEnabled()))
+               !(rootAncestor.isShowing() && rootAncestor.canBeFocusOwner()))
         {
             comp = rootAncestor;
             rootAncestor = comp.getFocusCycleRootAncestor();
@@ -8518,6 +8514,14 @@
         setComponentOrientation(orientation);
     }
 
+    final boolean canBeFocusOwner() {
+        // It is enabled, visible, focusable.
+        if (isEnabled() && isDisplayable() && isVisible() && isFocusable()) {
+            return true;
+        }
+        return false;
+    }
+
     /**
      * Checks that this component meets the prerequesites to be focus owner:
      * - it is enabled, visible, focusable
@@ -8527,9 +8531,9 @@
      * this component as focus owner
      * @since 1.5
      */
-    final boolean canBeFocusOwner() {
+    final boolean canBeFocusOwnerRecursively() {
         // - it is enabled, visible, focusable
-        if (!(isEnabled() && isDisplayable() && isVisible() && isFocusable())) {
+        if (!canBeFocusOwner()) {
             return false;
         }
 
--- a/jdk/src/share/classes/java/awt/Container.java	Wed Jun 11 01:31:42 2008 -0700
+++ b/jdk/src/share/classes/java/awt/Container.java	Tue Jun 17 13:37:28 2008 +0400
@@ -860,11 +860,11 @@
 
             // If component is focus owner or parent container of focus owner check that after reparenting
             // focus owner moved out if new container prohibit this kind of focus owner.
-            if (comp.isFocusOwner() && !comp.canBeFocusOwner()) {
+            if (comp.isFocusOwner() && !comp.canBeFocusOwnerRecursively()) {
                 comp.transferFocus();
             } else if (comp instanceof Container) {
                 Component focusOwner = KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner();
-                if (focusOwner != null && isParentOf(focusOwner) && !focusOwner.canBeFocusOwner()) {
+                if (focusOwner != null && isParentOf(focusOwner) && !focusOwner.canBeFocusOwnerRecursively()) {
                     focusOwner.transferFocus();
                 }
             }
--- a/jdk/src/share/classes/java/awt/ContainerOrderFocusTraversalPolicy.java	Wed Jun 11 01:31:42 2008 -0700
+++ b/jdk/src/share/classes/java/awt/ContainerOrderFocusTraversalPolicy.java	Tue Jun 17 13:37:28 2008 +0400
@@ -556,8 +556,7 @@
      *         enabled, and focusable; <code>false</code> otherwise
      */
     protected boolean accept(Component aComponent) {
-        if (!(aComponent.isVisible() && aComponent.isDisplayable() &&
-              aComponent.isFocusable() && aComponent.isEnabled())) {
+        if (!aComponent.canBeFocusOwner()) {
             return false;
         }
 
--- a/jdk/src/share/classes/java/awt/DefaultKeyboardFocusManager.java	Wed Jun 11 01:31:42 2008 -0700
+++ b/jdk/src/share/classes/java/awt/DefaultKeyboardFocusManager.java	Tue Jun 17 13:37:28 2008 +0400
@@ -154,7 +154,7 @@
     private boolean doRestoreFocus(Component toFocus, Component vetoedComponent,
                                    boolean clearOnFailure)
     {
-        if (toFocus != vetoedComponent && toFocus.isShowing() && toFocus.isFocusable() &&
+        if (toFocus != vetoedComponent && toFocus.isShowing() && toFocus.canBeFocusOwner() &&
             toFocus.requestFocus(false, CausedFocusEvent.Cause.ROLLBACK))
         {
             return true;
@@ -500,8 +500,11 @@
                     }
                 }
 
-                if (!(newFocusOwner.isFocusable() && newFocusOwner.isEnabled()
-                      && newFocusOwner.isShowing()))
+                if (!(newFocusOwner.isFocusable() && newFocusOwner.isShowing() &&
+                    // Refuse focus on a disabled component if the focus event
+                    // isn't of UNKNOWN reason (i.e. not a result of a direct request
+                    // but traversal, activation or system generated).
+                    (newFocusOwner.isEnabled() || cause.equals(CausedFocusEvent.Cause.UNKNOWN))))
                 {
                     // we should not accept focus on such component, so reject it.
                     dequeueKeyEvents(-1, newFocusOwner);
@@ -742,8 +745,7 @@
     public boolean dispatchKeyEvent(KeyEvent e) {
         Component focusOwner = (((AWTEvent)e).isPosted) ? getFocusOwner() : e.getComponent();
 
-        if (focusOwner != null && focusOwner.isShowing() &&
-            focusOwner.isFocusable() && focusOwner.isEnabled()) {
+        if (focusOwner != null && focusOwner.isShowing() && focusOwner.canBeFocusOwner()) {
             if (!e.isConsumed()) {
                 Component comp = e.getComponent();
                 if (comp != null && comp.isEnabled()) {
--- a/jdk/src/share/classes/java/awt/Window.java	Wed Jun 11 01:31:42 2008 -0700
+++ b/jdk/src/share/classes/java/awt/Window.java	Tue Jun 17 13:37:28 2008 +0400
@@ -3145,9 +3145,7 @@
         Component previousComp = temporaryLostComponent;
         // Check that "component" is an acceptable focus owner and don't store it otherwise
         // - or later we will have problems with opposite while handling  WINDOW_GAINED_FOCUS
-        if (component == null
-            || (component.isDisplayable() && component.isVisible() && component.isEnabled() && component.isFocusable()))
-        {
+        if (component == null || component.canBeFocusOwner()) {
             temporaryLostComponent = component;
         } else {
             temporaryLostComponent = null;
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/java/awt/Focus/NoAutotransferToDisabledCompTest/NoAutotransferToDisabledCompTest.java	Tue Jun 17 13:37:28 2008 +0400
@@ -0,0 +1,109 @@
+/*
+ * Copyright 2008 Sun Microsystems, Inc.  All Rights Reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+
+/*
+  @test
+  @bug       4685768
+  @summary   Tests that auto-transfering focus doesn't stuck on a disabled component.
+  @author    Anton Tarasov: area=awt.focus
+  @library   ../../regtesthelpers
+  @build     Util
+  @run       main NoAutotransferToDisabledCompTest
+*/
+
+import java.awt.Robot;
+import javax.swing.*;
+import java.awt.*;
+import java.awt.event.*;
+import java.applet.Applet;
+import test.java.awt.regtesthelpers.Util;
+
+public class NoAutotransferToDisabledCompTest extends Applet {
+    Robot robot;
+    JFrame frame = new JFrame("Frame");
+    JButton b0 = new JButton("b0");
+    JButton b1 = new JButton("b1");
+    JButton b2 = new JButton("b2");
+
+    public static void main(String[] args) {
+        NoAutotransferToDisabledCompTest app = new NoAutotransferToDisabledCompTest();
+        app.init();
+        app.start();
+    }
+
+    public void init() {
+        robot = Util.createRobot();
+        frame.add(b0);
+        frame.add(b1);
+        frame.add(b2);
+        frame.setLayout(new FlowLayout());
+        frame.pack();
+
+        b1.addActionListener(new ActionListener() {
+            public void actionPerformed(ActionEvent e) {
+                b1.setEnabled(false);
+                b2.setEnabled(false);
+            }
+        });
+    }
+
+    public void start() {
+        Util.showWindowWait(frame);
+
+        // Request focus on b1.
+        if (!Util.focusComponent(b1, 2000)) {
+            throw new TestErrorException("couldn't focus " + b1);
+        }
+
+        // Activate b1.
+        robot.keyPress(KeyEvent.VK_SPACE);
+        robot.delay(50);
+        robot.keyRelease(KeyEvent.VK_SPACE);
+        Util.waitForIdle(robot);
+
+        // Check that focus has been transfered to b0.
+        if (!b0.hasFocus()) {
+            throw new TestFailedException("focus wasn't auto-transfered properly!");
+        }
+        System.out.println("Test passed.");
+    }
+}
+
+/**
+ * Thrown when the behavior being verified is found wrong.
+ */
+class TestFailedException extends RuntimeException {
+    TestFailedException(String msg) {
+        super("Test failed: " + msg);
+    }
+}
+
+/**
+ * Thrown when an error not related to the behavior being verified is encountered.
+ */
+class TestErrorException extends RuntimeException {
+    TestErrorException(String msg) {
+        super("Unexpected error: " + msg);
+    }
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/java/awt/Focus/RequestFocusToDisabledCompTest/RequestFocusToDisabledCompTest.java	Tue Jun 17 13:37:28 2008 +0400
@@ -0,0 +1,97 @@
+/*
+ * Copyright 2008 Sun Microsystems, Inc.  All Rights Reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+
+/*
+  @test
+  @bug       4685768
+  @summary   Tests that it's possible to manually request focus on a disabled component.
+  @author    Anton Tarasov: area=awt.focus
+  @library   ../../regtesthelpers
+  @build     Util
+  @run       main RequestFocusToDisabledCompTest
+*/
+
+import java.awt.Robot;
+import javax.swing.*;
+import java.awt.*;
+import java.awt.event.*;
+import java.applet.Applet;
+import test.java.awt.regtesthelpers.Util;
+
+public class RequestFocusToDisabledCompTest extends Applet {
+    Robot robot;
+    JFrame frame = new JFrame("Frame");
+    JButton b0 = new JButton("b0");
+    JButton b1 = new JButton("b1");
+
+    public static void main(String[] args) {
+        RequestFocusToDisabledCompTest app = new RequestFocusToDisabledCompTest();
+        app.init();
+        app.start();
+    }
+
+    public void init() {
+        robot = Util.createRobot();
+        frame.add(b0);
+        frame.add(b1);
+        frame.setLayout(new FlowLayout());
+        frame.pack();
+
+        b1.setEnabled(false);
+    }
+
+    public void start() {
+        Util.showWindowWait(frame);
+
+        if (!b0.hasFocus()) {
+            // Request focus on b0.
+            if (!Util.focusComponent(b0, 2000)) {
+                throw new TestErrorException("couldn't focus " + b0);
+            }
+        }
+
+        // Try to request focus on b1.
+        if (!Util.focusComponent(b1, 2000)) {
+            throw new TestFailedException("focus wasn't requested on disabled " + b1);
+        }
+        System.out.println("Test passed.");
+    }
+}
+
+/**
+ * Thrown when the behavior being verified is found wrong.
+ */
+class TestFailedException extends RuntimeException {
+    TestFailedException(String msg) {
+        super("Test failed: " + msg);
+    }
+}
+
+/**
+ * Thrown when an error not related to the behavior being verified is encountered.
+ */
+class TestErrorException extends RuntimeException {
+    TestErrorException(String msg) {
+        super("Unexpected error: " + msg);
+    }
+}
--- a/jdk/test/java/awt/regtesthelpers/Util.java	Wed Jun 11 01:31:42 2008 -0700
+++ b/jdk/test/java/awt/regtesthelpers/Util.java	Tue Jun 17 13:37:28 2008 +0400
@@ -124,6 +124,14 @@
     }
 
     /**
+     * Makes the window visible and waits until it's shown.
+     */
+    public static void showWindowWait(Window win) {
+        win.setVisible(true);
+        waitTillShown(win);
+    }
+
+    /**
      * Moves mouse pointer in the center of given {@code comp} component
      * using {@code robot} parameter.
      */
@@ -574,4 +582,22 @@
     public static boolean trackActionPerformed(Button button, Runnable action, int time, boolean printEvent) {
         return trackEvent(ActionEvent.ACTION_PERFORMED, button, action, time, printEvent);
     }
+
+    /*
+     * Requests focus on the component provided and waits for the result.
+     * @return true if the component has been focused, false otherwise.
+     */
+    public static boolean focusComponent(Component comp, int time) {
+        return focusComponent(comp, time, false);
+    }
+    public static boolean focusComponent(final Component comp, int time, boolean printEvent) {
+        return trackFocusGained(comp,
+                                new Runnable() {
+                                    public void run() {
+                                        comp.requestFocus();
+                                    }
+                                },
+                                time, printEvent);
+
+    }
 }