changeset 841:d11be4170079

6607170: Focus not set by requestFocus Summary: fixing/refactoring focus auto-transfer mechanism. Reviewed-by: son
author ant
date Mon, 24 Jan 2011 20:20:34 +0000
parents 818359ecd50d
children 26559bff34ef
files src/share/classes/java/awt/Component.java src/share/classes/java/awt/Container.java src/share/classes/java/awt/DefaultKeyboardFocusManager.java src/share/classes/java/awt/KeyboardFocusManager.java src/solaris/classes/sun/awt/X11/XKeyboardFocusManagerPeer.java src/windows/native/sun/windows/awt_Component.cpp test/java/awt/Focus/ContainerFocusAutoTransferTest/ContainerFocusAutoTransferTest.java
diffstat 7 files changed, 396 insertions(+), 130 deletions(-) [+]
line wrap: on
line diff
--- a/src/share/classes/java/awt/Component.java	Tue Mar 18 14:10:28 2008 +0300
+++ b/src/share/classes/java/awt/Component.java	Mon Jan 24 20:20:34 2011 +0000
@@ -1370,12 +1370,15 @@
             KeyboardFocusManager.clearMostRecentFocusOwner(this);
             synchronized (getTreeLock()) {
                 enabled = false;
-                if (isFocusOwner()) {
+                // A disabled lw container is allowed to contain a focus owner.
+                if ((isFocusOwner() || (containsFocus() && !isLightweight())) &&
+                    KeyboardFocusManager.isAutoFocusTransferEnabled())
+                {
                     // Don't clear the global focus owner. If transferFocus
                     // fails, we want the focus to stay on the disabled
                     // Component so that keyboard traversal, et. al. still
                     // makes sense to the user.
-                    autoTransferFocus(false);
+                    transferFocus(false);
                 }
                 ComponentPeer peer = this.peer;
                 if (peer != null) {
@@ -1536,8 +1539,8 @@
             synchronized (getTreeLock()) {
                 visible = false;
                 mixOnHiding(isLightweight());
-                if (containsFocus()) {
-                    autoTransferFocus(true);
+                if (containsFocus() && KeyboardFocusManager.isAutoFocusTransferEnabled()) {
+                    transferFocus(true);
                 }
                 ComponentPeer peer = this.peer;
                 if (peer != null) {
@@ -6620,12 +6623,8 @@
         }
 
         synchronized (getTreeLock()) {
-            if (isFocusOwner()
-                && KeyboardFocusManager.isAutoFocusTransferEnabled()
-                && !nextFocusHelper())
-            {
-                KeyboardFocusManager.getCurrentKeyboardFocusManager().
-                    clearGlobalFocusOwner();
+            if (isFocusOwner() && KeyboardFocusManager.isAutoFocusTransferEnabledFor(this)) {
+                transferFocus(true);
             }
 
             if (getContainer() != null && isAddNotifyComplete) {
@@ -6760,8 +6759,8 @@
 
         firePropertyChange("focusable", oldFocusable, focusable);
         if (oldFocusable && !focusable) {
-            if (isFocusOwner()) {
-                autoTransferFocus(true);
+            if (isFocusOwner() && KeyboardFocusManager.isAutoFocusTransferEnabled()) {
+                transferFocus(true);
             }
             KeyboardFocusManager.clearMostRecentFocusOwner(this);
         }
@@ -7415,69 +7414,6 @@
         }
     }
 
-    private void autoTransferFocus(boolean clearOnFailure) {
-        Component toTest = KeyboardFocusManager.
-            getCurrentKeyboardFocusManager().getFocusOwner();
-        if (toTest != this) {
-            if (toTest != null) {
-                toTest.autoTransferFocus(clearOnFailure);
-            }
-            return;
-        }
-
-        // Check if there are pending focus requests.  We shouldn't do
-        // auto-transfer if user has already took care of this
-        // component becoming ineligible to hold focus.
-        if (!KeyboardFocusManager.isAutoFocusTransferEnabled()) {
-            return;
-        }
-
-        // the following code will execute only if this Component is the focus
-        // owner
-
-        if (!(isDisplayable() && isVisible() && isEnabled() && isFocusable())) {
-            doAutoTransfer(clearOnFailure);
-            return;
-        }
-
-        toTest = getParent();
-
-        while (toTest != null && !(toTest instanceof Window)) {
-            if (!(toTest.isDisplayable() && toTest.isVisible() &&
-                  (toTest.isEnabled() || toTest.isLightweight()))) {
-                doAutoTransfer(clearOnFailure);
-                return;
-            }
-            toTest = toTest.getParent();
-        }
-    }
-    private void doAutoTransfer(boolean clearOnFailure) {
-        if (focusLog.isLoggable(Level.FINER)) {
-            focusLog.log(Level.FINER, "this = " + this + ", clearOnFailure = " + clearOnFailure);
-        }
-        if (clearOnFailure) {
-            if (!nextFocusHelper()) {
-                if (focusLog.isLoggable(Level.FINER)) {
-                    focusLog.log(Level.FINER, "clear global focus owner");
-                }
-                KeyboardFocusManager.getCurrentKeyboardFocusManager().
-                    clearGlobalFocusOwner();
-            }
-        } else {
-            transferFocus();
-        }
-    }
-
-    /**
-     * Transfers the focus to the next component, as though this Component were
-     * the focus owner.
-     * @see       #requestFocus()
-     * @since     JDK1.1
-     */
-    public void transferFocus() {
-        nextFocus();
-    }
-
     /**
      * Returns the Container which is the focus cycle root of this Component's
      * focus traversal cycle. Each focus traversal cycle has only a single
@@ -7517,31 +7453,51 @@
         return (rootAncestor == container);
     }
 
+    Container getTraversalRoot() {
+        return getFocusCycleRootAncestor();
+    }
+
+    /**
+     * Transfers the focus to the next component, as though this Component were
+     * the focus owner.
+     * @see       #requestFocus()
+     * @since     JDK1.1
+     */
+    public void transferFocus() {
+        nextFocus();
+    }
+
     /**
      * @deprecated As of JDK version 1.1,
      * replaced by transferFocus().
      */
     @Deprecated
     public void nextFocus() {
-        nextFocusHelper();
-    }
-
-    private boolean nextFocusHelper() {
-        Component toFocus = preNextFocusHelper();
+        transferFocus(false);
+    }
+
+    boolean transferFocus(boolean clearOnFailure) {
         if (focusLog.isLoggable(Level.FINER)) {
-            focusLog.log(Level.FINER, "toFocus = " + toFocus);
-        }
-        if (isFocusOwner() && toFocus == this) {
-            return false;
-        }
-        return postNextFocusHelper(toFocus, CausedFocusEvent.Cause.TRAVERSAL_FORWARD);
-    }
-
-    Container getTraversalRoot() {
-        return getFocusCycleRootAncestor();
-    }
-
-    final Component preNextFocusHelper() {
+            focusLog.finer("clearOnFailure = " + clearOnFailure);
+        }
+        Component toFocus = getNextFocusCandidate();
+        boolean res = false;
+        if (toFocus != null && !toFocus.isFocusOwner() && toFocus != this) {
+            res = toFocus.requestFocusInWindow(CausedFocusEvent.Cause.TRAVERSAL_FORWARD);
+        }
+        if (clearOnFailure && !res) {
+            if (focusLog.isLoggable(Level.FINER)) {
+                focusLog.finer("clear global focus owner");
+            }
+            KeyboardFocusManager.getCurrentKeyboardFocusManager().clearGlobalFocusOwner();
+        }
+        if (focusLog.isLoggable(Level.FINER)) {
+            focusLog.finer("returning result: " + res);
+        }
+        return res;
+    }
+
+    final Component getNextFocusCandidate() {
         Container rootAncestor = getTraversalRoot();
         Component comp = this;
         while (rootAncestor != null &&
@@ -7553,18 +7509,19 @@
             rootAncestor = comp.getFocusCycleRootAncestor();
         }
         if (focusLog.isLoggable(Level.FINER)) {
-            focusLog.log(Level.FINER, "comp = " + comp + ", root = " + rootAncestor);
-        }
+            focusLog.finer("comp = " + comp + ", root = " + rootAncestor);
+        }
+        Component candidate = null;
         if (rootAncestor != null) {
             FocusTraversalPolicy policy = rootAncestor.getFocusTraversalPolicy();
             Component toFocus = policy.getComponentAfter(rootAncestor, comp);
             if (focusLog.isLoggable(Level.FINER)) {
-                focusLog.log(Level.FINER, "component after is " + toFocus);
+                focusLog.finer("component after is " + toFocus);
             }
             if (toFocus == null) {
                 toFocus = policy.getDefaultComponent(rootAncestor);
                 if (focusLog.isLoggable(Level.FINER)) {
-                    focusLog.log(Level.FINER, "default component is " + toFocus);
+                    focusLog.finer("default component is " + toFocus);
                 }
             }
             if (toFocus == null) {
@@ -7573,23 +7530,12 @@
                     toFocus = applet;
                 }
             }
-            return toFocus;
-        }
-        return null;
-    }
-
-    static boolean postNextFocusHelper(Component toFocus, CausedFocusEvent.Cause cause) {
-        if (toFocus != null) {
-            if (focusLog.isLoggable(Level.FINER)) {
-                focusLog.log(Level.FINER, "Next component " + toFocus);
-            }
-            boolean res = toFocus.requestFocusInWindow(cause);
-            if (focusLog.isLoggable(Level.FINER)) {
-                focusLog.log(Level.FINER, "Request focus returned " + res);
-            }
-            return res;
-        }
-        return false;
+            candidate = toFocus;
+        }
+        if (focusLog.isLoggable(Level.FINER)) {
+            focusLog.finer("Focus transfer candidate: " + candidate);
+        }
+        return candidate;
     }
 
     /**
@@ -7599,6 +7545,10 @@
      * @since     1.4
      */
     public void transferFocusBackward() {
+        transferFocusBackward(false);
+    }
+
+    boolean transferFocusBackward(boolean clearOnFailure) {
         Container rootAncestor = getTraversalRoot();
         Component comp = this;
         while (rootAncestor != null &&
@@ -7609,6 +7559,7 @@
             comp = rootAncestor;
             rootAncestor = comp.getFocusCycleRootAncestor();
         }
+        boolean res = false;
         if (rootAncestor != null) {
             FocusTraversalPolicy policy = rootAncestor.getFocusTraversalPolicy();
             Component toFocus = policy.getComponentBefore(rootAncestor, comp);
@@ -7616,9 +7567,19 @@
                 toFocus = policy.getDefaultComponent(rootAncestor);
             }
             if (toFocus != null) {
-                toFocus.requestFocusInWindow(CausedFocusEvent.Cause.TRAVERSAL_BACKWARD);
-            }
-        }
+                res = toFocus.requestFocusInWindow(CausedFocusEvent.Cause.TRAVERSAL_BACKWARD);
+            }
+        }
+        if (!res) {
+            if (focusLog.isLoggable(Level.FINER)) {
+                focusLog.finer("clear global focus owner");
+            }
+            KeyboardFocusManager.getCurrentKeyboardFocusManager().clearGlobalFocusOwner();
+        }
+        if (focusLog.isLoggable(Level.FINER)) {
+            focusLog.finer("returning result: " + res);
+        }
+        return res;
     }
 
     /**
@@ -7693,6 +7654,20 @@
         return hasFocus();
     }
 
+    /*
+     * Used to disallow auto-focus-transfer on disposal of the focus owner
+     * in the process of disposing its parent container.
+     */
+    private boolean autoFocusTransferOnDisposal = true;
+
+    void setAutoFocusTransferOnDisposal(boolean value) {
+        autoFocusTransferOnDisposal = value;
+    }
+
+    boolean isAutoFocusTransferOnDisposal() {
+        return autoFocusTransferOnDisposal;
+    }
+
     /**
      * Adds the specified popup menu to the component.
      * @param     popup the popup menu to be added to the component.
--- a/src/share/classes/java/awt/Container.java	Tue Mar 18 14:10:28 2008 +0300
+++ b/src/share/classes/java/awt/Container.java	Mon Jan 24 20:20:34 2011 +0000
@@ -2656,9 +2656,26 @@
         synchronized (getTreeLock()) {
             int ncomponents = this.ncomponents;
             Component component[] = this.component;
-            for (int i = ncomponents-1 ; i >= 0 ; i--) {
-                if( component[i] != null )
-                component[i].removeNotify();
+            for (int i = ncomponents - 1; i >= 0; i--) {
+                if( component[i] != null ) {
+                    // Fix for 6607170.
+                    // We want to suppress focus change on disposal
+                    // of the focused component. But because of focus
+                    // is asynchronous, we should suppress focus change
+                    // on every component in case it receives native focus
+                    // in the process of disposal.
+                    component[i].setAutoFocusTransferOnDisposal(false);
+                    component[i].removeNotify();
+                    component[i].setAutoFocusTransferOnDisposal(true);
+                }
+            }
+            // If some of the children had focus before disposal then it still has.
+            // Auto-transfer focus to the next (or previous) component if auto-transfer
+            // is enabled.
+            if (containsFocus() && KeyboardFocusManager.isAutoFocusTransferEnabledFor(this)) {
+                if (!transferFocus(false)) {
+                    transferFocusBackward(true);
+                }
             }
             if ( dispatcher != null ) {
                 dispatcher.dispose();
--- a/src/share/classes/java/awt/DefaultKeyboardFocusManager.java	Tue Mar 18 14:10:28 2008 +0300
+++ b/src/share/classes/java/awt/DefaultKeyboardFocusManager.java	Mon Jan 24 20:20:34 2011 +0000
@@ -166,12 +166,13 @@
                                    boolean clearOnFailure)
     {
         if (toFocus != vetoedComponent && toFocus.isShowing() && toFocus.isFocusable() &&
-            toFocus.requestFocus(false, CausedFocusEvent.Cause.ROLLBACK)) {
+            toFocus.requestFocus(false, CausedFocusEvent.Cause.ROLLBACK))
+        {
             return true;
         } else {
-            Component nextFocus = toFocus.preNextFocusHelper();
-            if (nextFocus != vetoedComponent
-                && Component.postNextFocusHelper(nextFocus, CausedFocusEvent.Cause.ROLLBACK))
+            Component nextFocus = toFocus.getNextFocusCandidate();
+            if (nextFocus != null && nextFocus != vetoedComponent &&
+                nextFocus.requestFocusInWindow(CausedFocusEvent.Cause.ROLLBACK))
             {
                 return true;
             } else if (clearOnFailure) {
@@ -516,9 +517,16 @@
                 {
                     // we should not accept focus on such component, so reject it.
                     dequeueKeyEvents(-1, newFocusOwner);
-                    if (KeyboardFocusManager.isAutoFocusTransferEnabled())
-                    {
-                        restoreFocus(fe, newFocusedWindow);
+                    if (KeyboardFocusManager.isAutoFocusTransferEnabled()) {
+                        // If FOCUS_GAINED is for a disposed component (however
+                        // it shouldn't happen) its toplevel parent is null. In this
+                        // case we have to try to restore focus in the current focused
+                        // window (for the details: 6607170).
+                        if (newFocusedWindow == null) {
+                            restoreFocus(fe, currentFocusedWindow);
+                        } else {
+                            restoreFocus(fe, newFocusedWindow);
+                        }
                     }
                     break;
                 }
--- a/src/share/classes/java/awt/KeyboardFocusManager.java	Tue Mar 18 14:10:28 2008 +0300
+++ b/src/share/classes/java/awt/KeyboardFocusManager.java	Mon Jan 24 20:20:34 2011 +0000
@@ -2578,6 +2578,10 @@
         }
     }
 
+    static boolean isAutoFocusTransferEnabledFor(Component comp) {
+        return isAutoFocusTransferEnabled() && comp.isAutoFocusTransferOnDisposal();
+    }
+
     /*
      * Used to process exceptions in dispatching focus event (in focusLost/focusGained callbacks).
      * @param ex previously caught exception that may be processed right here, or null
--- a/src/solaris/classes/sun/awt/X11/XKeyboardFocusManagerPeer.java	Tue Mar 18 14:10:28 2008 +0300
+++ b/src/solaris/classes/sun/awt/X11/XKeyboardFocusManagerPeer.java	Mon Jan 24 20:20:34 2011 +0000
@@ -93,12 +93,12 @@
             Component focusOwner = activeWindow.getFocusOwner();
             if (focusLog.isLoggable(Level.FINE)) focusLog.fine("Clearing global focus owner " + focusOwner);
             if (focusOwner != null) {
-                XComponentPeer nativePeer = XComponentPeer.getNativeContainer(focusOwner);
-                if (nativePeer != null) {
+//                XComponentPeer nativePeer = XComponentPeer.getNativeContainer(focusOwner);
+//                if (nativePeer != null) {
                     FocusEvent fl = new CausedFocusEvent(focusOwner, FocusEvent.FOCUS_LOST, false, null,
                                                          CausedFocusEvent.Cause.CLEAR_GLOBAL_FOCUS_OWNER);
                     XWindow.sendEvent(fl);
-                }
+//                }
             }
         }
    }
--- a/src/windows/native/sun/windows/awt_Component.cpp	Tue Mar 18 14:10:28 2008 +0300
+++ b/src/windows/native/sun/windows/awt_Component.cpp	Mon Jan 24 20:20:34 2011 +0000
@@ -901,8 +901,27 @@
 
 void AwtComponent::Hide()
 {
+    JNIEnv *env = (JNIEnv *)JNU_GetEnv(jvm, JNI_VERSION_1_2);
+    jobject peer = GetPeer(env);
+    BOOL oldValue = sm_suppressFocusAndActivation;
     m_visible = false;
+
+    // On disposal the focus owner actually loses focus at the moment of hiding.
+    // So, focus change suppression (if requested) should be made here.
+    if (GetHWnd() == sm_focusOwner &&
+        !JNU_CallMethodByName(env, NULL, peer, "isAutoFocusTransferOnDisposal", "()Z").z)
+   {
+        sm_suppressFocusAndActivation = TRUE;
+        // The native system may autotransfer focus on hiding to the parent
+        // of the component. Nevertheless this focus change won't be posted
+        // to the Java level, we're better to avoid this. Anyway, after
+        // the disposal focus should be requested to the right component.
+        ::SetFocus(NULL);
+        sm_focusOwner = NULL;
+    }
     ::ShowWindow(GetHWnd(), SW_HIDE);
+
+    sm_suppressFocusAndActivation = oldValue;
 }
 
 BOOL
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/java/awt/Focus/ContainerFocusAutoTransferTest/ContainerFocusAutoTransferTest.java	Mon Jan 24 20:20:34 2011 +0000
@@ -0,0 +1,243 @@
+/*
+ * Copyright (c) 2008, Oracle and/or its affiliates. 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/*
+  @test
+  @bug       6607170
+  @summary   Tests for focus-auto-transfer.
+  @author    Anton Tarasov: area=awt-focus
+  @library   ../../regtesthelpers
+  @build     Util
+  @run       main ContainerFocusAutoTransferTest
+*/
+
+import java.applet.Applet;
+import java.awt.AWTEvent;
+import java.awt.Component;
+import java.awt.ComponentOrientation;
+import java.awt.DefaultKeyboardFocusManager;
+import java.awt.KeyboardFocusManager;
+import java.awt.Robot;
+import java.awt.Color;
+import java.awt.FlowLayout;
+import java.awt.Toolkit;
+import java.awt.event.AWTEventListener;
+import java.awt.event.FocusEvent;
+import java.awt.event.WindowEvent;
+import javax.swing.JButton;
+import javax.swing.JFrame;
+import javax.swing.JPanel;
+import test.java.awt.regtesthelpers.Util;
+
+public class ContainerFocusAutoTransferTest extends Applet {
+    Robot robot;
+    TestFrame frame;
+    KeyboardFocusManager kfm;
+    enum TestCase {
+        REMOVAL { public String toString() { return "removal"; } },
+        HIDING { public String toString() { return "hiding"; } },
+        DISABLING { public String toString() { return "disabling"; } },
+        DEFOCUSING { public String toString() { return "defocusing"; } };
+        public abstract String toString();
+    };
+
+    public static void main(String[] args) {
+        ContainerFocusAutoTransferTest app = new ContainerFocusAutoTransferTest();
+        app.init();
+        app.start();
+    }
+
+    public void init() {
+        robot = Util.createRobot();
+        kfm = KeyboardFocusManager.getCurrentKeyboardFocusManager();
+        Toolkit.getDefaultToolkit().addAWTEventListener(new AWTEventListener() {
+            public void eventDispatched(AWTEvent event) {
+                System.out.println("--> " + event);
+            }
+        }, FocusEvent.FOCUS_EVENT_MASK | WindowEvent.WINDOW_FOCUS_EVENT_MASK);
+    }
+
+    public void start() {
+        System.out.println("*** TEST #1 ***");
+        test(TestCase.HIDING);
+
+        System.out.println("*** TEST #2 ***");
+        test(TestCase.REMOVAL);
+
+        System.out.println("*** TEST #3 ***");
+        test3(TestCase.DISABLING);
+
+        System.out.println("*** TEST #4 ***");
+        test3(TestCase.DEFOCUSING);
+
+        System.out.println("*** TEST #5 ***");
+        test4();
+
+        System.out.println("Test passed.");
+    }
+
+    void test(final TestCase t) {
+        showFrame();
+        test1(t); // Test for correct auto-transfer
+        test2(t); // Test for clearing focus
+    }
+
+    void test1(final TestCase t) {
+        Runnable action = new Runnable() {
+            public void run() {
+                KeyboardFocusManager.setCurrentKeyboardFocusManager(new TestKFM());
+                if (t == TestCase.REMOVAL) {
+                    frame.remove(frame.panel0);
+
+                } else if (t == TestCase.HIDING) {
+                    frame.panel0.setVisible(false);
+                }
+                frame.repaint();
+            }
+        };
+        if (!Util.trackFocusGained(frame.b3, action, 2000, false)) {
+            throw new TestFailedException(t + ": focus wasn't transfered as expected!");
+        }
+        KeyboardFocusManager.setCurrentKeyboardFocusManager(kfm);
+    }
+
+    void test2(TestCase t) {
+        frame.setFocusable(false); // exclude it from the focus cycle
+        if (t == TestCase.REMOVAL) {
+            frame.remove(frame.panel1);
+
+        } else if (t == TestCase.HIDING) {
+            frame.panel1.setVisible(false);
+        }
+        frame.repaint();
+        Util.waitForIdle(robot);
+        if (kfm.getFocusOwner() != null) {
+            throw new TestFailedException(t + ": focus wasn't cleared!");
+        }
+    }
+
+    void test3(final TestCase t) {
+        showFrame();
+        Runnable action = new Runnable() {
+            public void run() {
+                if (t == TestCase.DISABLING) {
+                    frame.b0.setEnabled(false);
+
+                } else if (t == TestCase.DEFOCUSING) {
+                    frame.b0.setFocusable(false);
+                }
+            }};
+        if (!Util.trackFocusGained(frame.b1, action, 2000, false)) {
+            throw new TestFailedException(t + ": focus wasn't transfered as expected!");
+        }
+    }
+
+    void test4() {
+        showFrame();
+        frame.setFocusableWindowState(false);
+        Util.waitForIdle(robot);
+        if (kfm.getFocusOwner() != null) {
+            throw new TestFailedException("defocusing the frame: focus wasn't cleared!");
+        }
+    }
+
+    void showFrame() {
+        if (frame != null) {
+            frame.dispose();
+            Util.waitForIdle(robot);
+        }
+        frame = new TestFrame();
+        frame.setVisible(true);
+        Util.waitTillShown(frame);
+
+        if (!frame.b0.hasFocus()) {
+            Util.clickOnComp(frame.b0, robot);
+            Util.waitForIdle(robot);
+            if (!frame.b0.hasFocus()) {
+                throw new TestErrorException("couldn't set focus on " + frame.b2);
+            }
+        }
+    }
+
+    class TestKFM extends DefaultKeyboardFocusManager {
+        public boolean dispatchEvent(AWTEvent e) {
+            if (e.getID() == FocusEvent.FOCUS_GAINED) {
+                System.out.println(e);
+                Component src = (Component)e.getSource();
+                if (src == frame.b1 || src == frame.b2) {
+                    throw new TestFailedException("wrong focus transfer on removal!");
+                }
+            }
+            return super.dispatchEvent(e);
+        }
+    }
+}
+
+class TestFrame extends JFrame {
+    public JPanel panel0 = new JPanel();
+    public JPanel panel1 = new JPanel();
+    public JButton b0 = new JButton("b0");
+    public JButton b1 = new JButton("b1");
+    public JButton b2 = new JButton("b2");
+    public JButton b3 = new JButton("b3");
+    public JButton b4 = new JButton("b4");
+
+    public TestFrame() {
+        super("TestFrame");
+
+        // The change of the orientation and the reverse order of
+        // adding the buttons to the panel is because in Container.removeNotify()
+        // the child components are removed in the reverse order.
+        // We want that the focus owner (b0) would be removed first and
+        // that the next traversable component would be b1.
+        panel0.setComponentOrientation(ComponentOrientation.RIGHT_TO_LEFT);
+        panel0.add(b2);
+        panel0.add(b1);
+        panel0.add(b0);
+
+        panel1.add(b3);
+        panel1.add(b4);
+
+        setLayout(new FlowLayout());
+        add(panel0);
+        add(panel1);
+        pack();
+
+        panel0.setBackground(Color.red);
+        panel1.setBackground(Color.blue);
+    }
+}
+
+// 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);
+    }
+}