chromiumembedded / java-cef

Java Chromium Embedded Framework (JCEF). A simple framework for embedding Chromium-based browsers in other applications using the Java programming language.
https://bitbucket.org/chromiumembedded/java-cef
Other
641 stars 139 forks source link

Linux: Window is deactivated when CEF browser gains focus #329

Open magreenblatt opened 5 years ago

magreenblatt commented 5 years ago

Original report by me.


What steps will reproduce the problem?

Add the following code in the simple application:

        addWindowListener(new WindowAdapter() {
            @Override
            public void windowActivated(WindowEvent e) {
                System.out.println("windowActivated");
            }

            @Override
            public void windowDeactivated(WindowEvent e) {
                System.out.println("windowDeactivated");
            }
        });

Notice that the windowActivated/windowDeactivated messages are printed when clicking between the URL bar and the CEF browser window.

What is the expected output?

Window activation should not change.

What version of the product are you using? On what operating system?

Current JCEF master on Ubuntu 14.04.

The windowDeactivated event is originating from the WINDOW_LOST_FOCUS case in DefaultKeyboardFocusManager.dispatchEvent. Java appears to be synthesizing the windowDeactivated event because WindowEvent.getOppositeWindow is returning null. From the documentation, the opposite window should be "the Window that gained activation or focus", which in this case would be the MainFrame window.

The WINDOW_LOST_FOCUS event is originating from XWindowPeer.handleWindowFocusOut, which is called from the native event queue [1]. The oppositeWindow value is determined in XWindowPeer.handleFocusEvent by calling getNativeFocusedWindowPeer, which in this case returns null (XToolkit.windowToXWindow(xGetInputFocus()) returns null).

So, it looks like the root problem is that there's no XBaseWindow mapped to CEF's X11 Window handle. I suspect that we need something similar to XEmbedCanvasPeer, but to wrap a Window handle that already exists. Unfortunately the parent class (XCanvasPeer) is package scope restricted, so we can't just implement one outside of sun.awt.X11.

[1] Call stack:

at sun.awt.X11.XWindowPeer.handleWindowFocusOut(XWindowPeer.java:646)
at sun.awt.X11.XDecoratedPeer.handleWindowFocusOut(XDecoratedPeer.java:1232)
at sun.awt.X11.XWindowPeer.handleFocusEvent(XWindowPeer.java:913)
at sun.awt.X11.XDecoratedPeer.handleFocusEvent(XDecoratedPeer.java:230)
at sun.awt.X11.XFocusProxyWindow.handleFocusEvent(XFocusProxyWindow.java:77)
at sun.awt.X11.XFocusProxyWindow.dispatchEvent(XFocusProxyWindow.java:70)
at sun.awt.X11.XBaseWindow.dispatchToWindow(XBaseWindow.java:1090)
at sun.awt.X11.XToolkit.dispatchEvent(XToolkit.java:512)
at sun.awt.X11.XToolkit.run(XToolkit.java:621)
at sun.awt.X11.XToolkit.run(XToolkit.java:542)
at java.lang.Thread.run(Thread.java:744)
magreenblatt commented 5 years ago

This issue was diagnosed using a local debug build of OpenJDK which is documented here.

magreenblatt commented 5 years ago

A simple fix (requiring a custom OpenJDK build) could patch XToolkit.windowToXWindow to walk the X11 parent hierarchy (using XlibWrapper.XQueryTree), and return the XBaseWindow for the parent, if any. For example, in C++ code this might look like:

#!c++
::Window FindXBaseWindowParent(::Display* display, ::Window window) {
  ::Window root = x11::None;
  ::Window parent = x11::None;
  ::Window* children = NULL;
  unsigned int nchildren = 0;
  while (XQueryTree(display, window, &root, &parent, &children, &nchildren)) {
    if (children) {
      XFree(children);
    }

    if (HasXBaseWindow(parent)) {
      return parent;
    }

    window = parent;
  }
  return x11::None;
}
magreenblatt commented 5 years ago