Java 7 Plugin for Windows (and Linux) Tab Key Problems and Workaround

November 10, 2011

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.

Description of the problem

With the Java 7 plugin installed, a Java Applet loses the focus when the tab key is pressed. The focus goes to some other item on the web page (e.g. a link) or to the browser toolbar. There is no way to return the focus to the Applet using the tab key. After the Applet has lost the focus, it no longer responds to keyboard events (unless you click back into the Applet with the mouse).

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.

A Note About Java 1.4

In Java 1.4, a new focus-cycle mechanism was introduced. At that point, Applets stopped being able to respond to the tab key because it was sucked into the KeyboardFocusManager. There was a workaround for that -- you could call the Component's setFocusTraversalKeysEnabled(boolean), which was introduced in Java 1.4. Setting to false allowed the tab key events to go to your own KeyListener, as they did prior to Java 1.4.

What's Going On in Java 7

There's a container at the top of the containment hierarchy:
sun.plugin2.main.client.PluginEmbeddedFrame
PluginEmbeddedFrame extends
sun.awt.EmbeddedFrame
I 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.

The Workaround

Luckily Oracle left us a way to remove these KeyEventDispatchers from DefaultKeyboardFocusManager. We have to do it blindly, since there's no way to see what KeyEventDispatchers are actually installed. We have to have the actual KeyEventDispatcher object in hand before we can remove it. Luckily we can get a reference to the PluginEmbeddedFrame by calling getParent() from the Applet.

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.


<Dogfeathers Home Page>   <Mark's Home Page>

Email: Mark Newbold
This page URL: http://dogfeathers.com/mark/java7issue.html