I don't normally post stuff like this, but I haven't been able to find any mention of this issue in Google. It's a problem for any Java Applet that needs to respond to the tab keystroke, or keystrokes in general.
If the Applet contains multiple components (text fields, buttons, etc), you can "tab" through those components but when you "tab" off the last one, the Applet loses the focus. Or if you "shift-tab" to go backwards from the first component, the Applet loses the focus. Prior to the Java 7 Plugin, the focus would have cycled among the Applet components.
sun.plugin2.main.client.PluginEmbeddedFramePluginEmbeddedFrame extends
sun.awt.EmbeddedFrameI could not find the source for PluginEmbeddedFrame, but source for EmbeddedFrame is here. EmbeddedFrame implements java.awt.KeyEventDispatcher. When its dispatchKeyEvent() function is called, it calls its traverseOut() function, which, in EmbeddedFrame, is an empty stub. But in PluginEmbeddedFrame, traverseOut is not empty -- it apparently sets the focus to something outside of the Applet, which is the offending behavior that I am writing about here.
In the Java Plugin (Java 1.4 and above), all KeyEvents pass through DefaultKeyboardFocusManager's dispatchEvent() function, and from there to the typeAheadAssertions() function, then to preDispatchKeyEvent(), where it looks at its linked list of KeyEventDispatchers and calls their dispatchKeyEvent() functions. If any of them returns true, the process stops and the KeyEvent never makes it to your Applet's listeners. In a running Applet, I find that DefaultKeyboardFocusManager has two KeyEventDispatchers installed. They are both PluginEmbeddedFrame. I think these KeyEventDispatchers are added to DefaultKeyboardFocusManager as a result of a PropertyChangeEvent that is fired when the KeyboardFocusManager is installed. Why is it in there twice? Probably that's not intentional.
The offending behavior does not look like a bug or an unintended side effect. It looks like Oracle's Java developers went to some trouble to achieve this behavior. Sun's developers had their faults, but we would not have seen this absence of good judgement from them.
It looks like the KeyEventDispatchers are called in the order in which they were added to DefaultKeyboardFocusManager, so it wouldn't do any good to add your own KeyEventDispatcher in the hopes of grabbing the tab KeyEvent early.
Anyway, to summarize, when a tab KeyEvent passes through DefaultKeyboardFocusManager, the PluginEmbeddedFrame is given the opportunity to steal it and take away the Applet's focus as a result.
I put my the workaround in the Applet's init() function so it would be called after the Applet has a parent component:
public void init() { Container topParent = null; Container parent = this; // The natural thing would be to call getParent() until it returns // null, but then you would be looping for a long time, since // PluginEmbeddedFrame's getParent() returns itself. for (int k=0; k < 10; k++) { topParent = parent; parent = parent.getParent(); if (parent == null) break; } // If topParent isn't a KeyEventDispatcher then we must be in some // Plugin version that doesn't need the workaround. try { KeyEventDispatcher ked = (KeyEventDispatcher)topParent; KeyboardFocusManager kfm = KeyboardFocusManager.getCurrentKeyboardFocusManager(); // You have to remove it twice, otherwise the problem isn't fixed kfm.removeKeyEventDispatcher(ked); kfm.removeKeyEventDispatcher(ked); } catch (ClassCastException e) {} }The above code is compatible with Java 1.4 and above.
I probably wasted 12 hours figuring this out. I hope this saves somebody else from wasting their time.
Note added November 14, 2011: There's another focus issue. If you ALT-TAB to another window, then back to your applet, your applet may not have the focus until you press the tab key once. Apparently PluginEmbeddedFrame has the focus. The workaround for this is to add a FocusListener to the PluginEmbeddedFrame (topParent, above). In the focusGained() method, put the focus where you want it.
Note added April 25, 2012: Thanks to Robin Bankhead for letting me know that the Linux plugin has the same problem. The issue was encountered with the TightVNC+SSH Java Viewer.