JDK-8013611 : Modal dialog fails to obtain keyboard focus
  • Type: Bug
  • Component: client-libs
  • Sub-Component: javax.swing
  • Affected Version: 7u13
  • Priority: P3
  • Status: Closed
  • Resolution: Fixed
  • OS: windows
  • CPU: generic
  • Submitted: 2013-02-07
  • Updated: 2014-11-17
  • Resolved: 2013-08-08
The Version table provides details related to the release that this issue/RFE will be addressed.

Unresolved : Release in which this issue/RFE will be addressed.
Resolved: Release in which this issue/RFE has been resolved.
Fixed : Release in which this issue/RFE has been fixed. The release containing this fix may be available for download as an Early Access Release or a General Availability Release.

To download the current JDK release, click here.
JDK 7 JDK 8
7u55Fixed 8 b105Fixed
Description
FULL PRODUCT VERSION :
java version  " 1.7.0_13 " 
Java(TM) SE Runtime Environment (build 1.7.0_13-b20)
Java HotSpot(TM) 64-Bit Server VM (build 23.7-b01, mixed mode)

ADDITIONAL OS VERSION INFORMATION :
Windows 7 Enterprise Service Pack 1

A DESCRIPTION OF THE PROBLEM :
If a modal dialog is displayed from a focus listener and at the same time some modifications to focus are done in the frame in background then the dialog will fail to obtain focus. Both calls to requestFocusInWindow and use of FocusTraversalPolicy seems to provoke this bug.

Stepping in the debugger it looks like the dialog is processing focusing events not related to the dialog but instead related to the frame in background which makes the focus state broken.

REGRESSION.  Last worked in version 6u31

STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
The following steps will produce the problem

- Add a focus listener to a JTextField
- In the focusLost() method display an modal dialog
- In the next component that will receive focus add a focus listener and in the focusGained() method call requestFocusInWindow() for any other component in the same frame

When focus is lost on the text field the dialog fails to gain focus and the focus is instead given to the component called for requestFocusInWindow()

EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
Focus placed on the default component inside the modal dialog
ACTUAL -
Focus is placed in the frame in the background, from the keyboard it's possible to tab around in the frame in the background and also to activate buttons by pressing enter on keyboard.

REPRODUCIBILITY :
This bug can be reproduced always.

---------- BEGIN SOURCE ----------
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.FocusAdapter;
import java.awt.event.FocusEvent;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;

/**
 *
 * @author jonas
 */
public class SwingFocusBug extends JFrame {

    JTextField textField = new JTextField( " Press <tab> on keyboard " );
    JButton button1 = new JButton( " 1 " );
    JButton button2 = new JButton( " 2 " );

    public SwingFocusBug() {
        initComponents();

        textField.addFocusListener(new FocusAdapter() {
            @Override
            public void focusLost(FocusEvent e) {
                JOptionPane.showMessageDialog(SwingFocusBug.this,
                         " Focus is now in frame behind and not in the dialog! " );
            }
        });

        button1.addFocusListener(new FocusAdapter() {
            @Override
            public void focusGained(FocusEvent e) {
                button2.requestFocusInWindow();
            }
        });

        button2.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                button2.setText( " Button 2 pressed! " );
            }
        });
    }

    private void initComponents() {
        JPanel panel = new JPanel();
        panel.setLayout(new BorderLayout());
        panel.add(textField, BorderLayout.CENTER);
        panel.add(button1, BorderLayout.EAST);
        panel.add(button2, BorderLayout.SOUTH);

        add(panel);

        setSize(new Dimension(500, 200));

    }

    public static void main(String[] args) throws ClassNotFoundException,
            InstantiationException, IllegalAccessException,
            UnsupportedLookAndFeelException {

        UIManager.setLookAndFeel( " com.sun.java.swing.plaf.windows.WindowsLookAndFeel " );

        SwingFocusBug frame = new SwingFocusBug();
        frame.setVisible(true);


    }
}

---------- END SOURCE ----------

CUSTOMER SUBMITTED WORKAROUND :
In the focus listener displaying the dialog, make sure all events related to focus changes in the parent frame are dispatch by the event queue by wrapping the code in at least two SwingUtils.invokeLater blocks:

    private FocusListener fieldFocusListener = new FocusAdapter() {
        @Override
        public void focusLost(FocusEvent e) {
            SwingUtilities.invokeLater(new Runnable() {
                @Override
                public void run() {
                    SwingUtilities.invokeLater(new Runnable() {
                        @Override
                        public void run() {
                            // code that will display a dialog
                        }
                    });
                }
            });
        }
    };
Comments
SQE tested the fix in 7u55 b32. Swing results are ok. So, SQE OK to take the fix into CPU14_03.
14-04-2014

Verified using regression test test/java/awt/Focus/8013611/JDK8013611.java jdk8u100: Test failed: dialog didn't get focus! jdk8b132: Passed. Execution successfull
14-04-2014

webrev: http://cr.openjdk.java.net/~ant/JDK-8013611/webrev.0/
03-07-2013

There're two method KeyboardFocusManagerPeer.getCurrentFocusOwner() & KeyboardFocusManagerPeer.getCurrentFocusedWindow() which return values of current native focus owner and current native focused window. The problem is that the value of a native focused window changes synchronously with the native message receiving. Whereas, the value of current native focus owner is set later after it is calculated on java side and is reported to peers. In the testcase, showing the dialog and requesting focus in frame just happens b/w the two events mentioned above. So, the native focused window has already changed but the native focus owner has not yet. At that moment, KFM.shouldNativelyFocusHeavyweight() method is called in response to requestFocusInWindow(). (The method checks for the focus subsystem current state (pending focus requests, current native focus owner, native focused window etc.) and decides how the request should be processed.) It gets native focus owner and checks if it equals the heavyweight container of the component requesting focus. If it does equal, it just adds a lightweight component to the focus requests queue and posts corresponding focus events. The problem is that the current native focus owner is outdated at the moment. It doesn't correspond to the current native focused window. And so, the requestFocusInWindow() is satisfied, focus events are posted. In order to avoid that, I suggest to additionally check if the parent window of the component requesting focus equals the current native focused window. In case they aren't, requestFocusInWindow() should be rejected.
03-07-2013

The bug was reproduced with JDK 7u21 on MS Windows 7 OS.
30-04-2013