JBR-2533 Popup is not focused on click when switching from another application on macOS

(cherry picked from commits d9ff151211, 67b174dc8c, 72b0add80c, 21af1eba85, 2f1d317d87, 6dd334f9f0, cd863bac0d, 010f6fc951, 25e087d269, parts of 7d5ac56b6c, cd6dd5c3cf, e8bbd8ffdd)

with fix for JBR-3640 (java/awt/Modal/ModalFocusTransferTests/FocusTransferDWFAppModalTest.java: window Open button lost focus when it should not) and JBR-3979 (Focus is not transferred to parent window)

with fix for JBR-5300 Change source code and test files to use GPL license
This commit is contained in:
Dmitry Batrak
2020-06-30 12:36:27 +03:00
committed by jbrbot
parent 65954ef015
commit fb71e8d2aa
9 changed files with 447 additions and 110 deletions

View File

@@ -955,18 +955,13 @@ public abstract class LWComponentPeer<T extends Component, D extends JComponent>
// however that is the shared code and this particular problem's reproducibility has
// platform specifics. So, it was decided to narrow down the fix to lwawt (OSX) in
// current release. TODO: consider fixing it in the shared code.
if (!focusedWindowChangeAllowed) {
LWWindowPeer decoratedPeer = parentPeer.isSimpleWindow() ?
LWWindowPeer.getOwnerFrameDialog(parentPeer) : parentPeer;
if (decoratedPeer == null || !decoratedPeer.getPlatformWindow().isActive()) {
if (focusLog.isLoggable(PlatformLogger.Level.FINE)) {
focusLog.fine("request rejected, focusedWindowChangeAllowed==false, " +
"decoratedPeer is inactive: " + decoratedPeer);
}
LWKeyboardFocusManagerPeer.removeLastFocusRequest(getTarget());
return false;
if (!focusedWindowChangeAllowed && !parentPeer.getPlatformWindow().isActive()) {
if (focusLog.isLoggable(PlatformLogger.Level.FINE)) {
focusLog.fine("request rejected, focusedWindowChangeAllowed==false, " +
"parentPeer is inactive: " + parentPeer);
}
LWKeyboardFocusManagerPeer.removeLastFocusRequest(getTarget());
return false;
}
boolean res = parentPeer.requestWindowFocus(cause);

View File

@@ -1272,53 +1272,6 @@ public class LWWindowPeer
return false;
}
AppContext targetAppContext = AWTAccessor.getComponentAccessor().getAppContext(getTarget());
KeyboardFocusManager kfm = AWTAccessor.getKeyboardFocusManagerAccessor()
.getCurrentKeyboardFocusManager(targetAppContext);
Window currentActive = kfm.getActiveWindow();
Window opposite = LWKeyboardFocusManagerPeer.getInstance().
getCurrentFocusedWindow();
// Make the owner active window.
if (isSimpleWindow()) {
LWWindowPeer owner = getOwnerFrameDialog(this);
// If owner is not natively active, request native
// activation on it w/o sending events up to java.
if (owner != null && !owner.platformWindow.isActive()) {
if (focusLog.isLoggable(PlatformLogger.Level.FINE)) {
focusLog.fine("requesting native focus to the owner " + owner);
}
LWWindowPeer currentActivePeer = currentActive == null ? null :
(LWWindowPeer) AWTAccessor.getComponentAccessor().getPeer(
currentActive);
// Ensure the opposite is natively active and suppress sending events.
if (currentActivePeer != null && currentActivePeer.platformWindow.isActive()) {
if (focusLog.isLoggable(PlatformLogger.Level.FINE)) {
focusLog.fine("the opposite is " + currentActivePeer);
}
currentActivePeer.skipNextFocusChange = true;
}
owner.skipNextFocusChange = true;
owner.platformWindow.requestWindowFocus();
}
// DKFM will synthesize all the focus/activation events correctly.
changeFocusedWindow(true, opposite);
return true;
// In case the toplevel is active but not focused, change focus directly,
// as requesting native focus on it will not have effect.
} else if (getTarget() == currentActive && !getTarget().hasFocus()) {
changeFocusedWindow(true, opposite);
return true;
}
return platformWindow.requestWindowFocus();
}
@@ -1394,7 +1347,8 @@ public class LWWindowPeer
// - when the opposite (gaining focus) window is an owned/owner window.
// - for a simple window in any case.
if (!becomesFocused &&
(isGrabbing() || this.isOneOfOwnersOf(grabbingWindow)))
(isGrabbing() || this.isOneOfOwnersOf(grabbingWindow)) &&
(opposite == null || getOwnerFrameDialog(AWTAccessor.getComponentAccessor().getPeer(opposite)) != this))
{
if (focusLog.isLoggable(PlatformLogger.Level.FINE)) {
focusLog.fine("ungrabbing on " + grabbingWindow);

View File

@@ -297,18 +297,6 @@ public class CInputMethod extends InputMethodAdapter {
if (component.getInputMethodRequests() == null) {
imInstance = null;
}
LWWindowPeer windowPeer = peer.getPlatformWindow().getPeer();
if (windowPeer.isSimpleWindow()) {
// A simple window gains focus. Cocoa won't dispatch IME events into the simple window, but into its owner.
// This IM represents the focused component in the simple window. We will use the owner as IME proxy.
// For that, this IM is set for the owner and is dropped for the simple window.
Window owner = windowPeer.getTarget().getOwner();
assert owner != null && owner.isActive();
long ownerPtr = getNativeViewPtr((LWComponentPeer)AWTAccessor.getComponentAccessor().getPeer(owner));
nativeNotifyPeer(ownerPtr, this);
imInstance = null;
}
}
if (peer != null) {

View File

@@ -99,6 +99,7 @@ public class CPlatformWindow extends CFRetainedResource implements PlatformWindo
private static native void nativeEnterFullScreenMode(long nsWindowPtr);
private static native void nativeExitFullScreenMode(long nsWindowPtr);
static native CPlatformWindow nativeGetTopmostPlatformWindowUnderMouse();
private static native void nativeRaiseLevel(long nsWindowPtr, boolean popup, boolean onlyIfParentIsActive);
// Logger to report issues happened during execution but that do not affect functionality
private static final PlatformLogger logger = PlatformLogger.getLogger("sun.lwawt.macosx.CPlatformWindow");
@@ -159,7 +160,6 @@ public class CPlatformWindow extends CFRetainedResource implements PlatformWindo
static final int MINIMIZABLE = 1 << 8;
static final int RESIZABLE = 1 << 9; // both a style bit and prop bit
static final int NONACTIVATING = 1 << 24;
static final int IS_DIALOG = 1 << 25;
static final int IS_MODAL = 1 << 26;
static final int IS_POPUP = 1 << 27;
@@ -421,10 +421,7 @@ public class CPlatformWindow extends CFRetainedResource implements PlatformWindo
// defaults style bits
int styleBits = DECORATED | HAS_SHADOW | CLOSEABLE | ZOOMABLE | RESIZABLE | TITLE_VISIBLE;
if (isNativelyFocusableWindow()) {
styleBits = SET(styleBits, SHOULD_BECOME_KEY, true);
styleBits = SET(styleBits, SHOULD_BECOME_MAIN, true);
}
styleBits |= getFocusableStyleBits();
final boolean isFrame = (target instanceof Frame);
final boolean isDialog = (target instanceof Dialog);
@@ -460,7 +457,6 @@ public class CPlatformWindow extends CFRetainedResource implements PlatformWindo
if (isPopup) {
styleBits = SET(styleBits, TEXTURED, false);
// Popups in applets don't activate applet's process
styleBits = SET(styleBits, NONACTIVATING, true);
styleBits = SET(styleBits, IS_POPUP, true);
}
@@ -558,9 +554,13 @@ public class CPlatformWindow extends CFRetainedResource implements PlatformWindo
return styleBits;
}
// this is the counter-point to -[CWindow _nativeSetStyleBit:]
private void setStyleBits(final int mask, final boolean value) {
execute(ptr -> nativeSetNSWindowStyleBits(ptr, mask, value ? mask : 0));
setStyleBits(mask, value ? mask : 0);
}
// this is the counter-point to -[CWindow _nativeSetStyleBit:]
private void setStyleBits(final int mask, final int value) {
execute(ptr -> nativeSetNSWindowStyleBits(ptr, mask, value));
}
private native void _toggleFullScreenMode(final long model);
@@ -936,8 +936,7 @@ public class CPlatformWindow extends CFRetainedResource implements PlatformWindo
@Override
public void updateFocusableWindowState() {
final boolean isFocusable = isNativelyFocusableWindow();
setStyleBits(SHOULD_BECOME_KEY | SHOULD_BECOME_MAIN, isFocusable); // set both bits at once
setStyleBits(SHOULD_BECOME_KEY | SHOULD_BECOME_MAIN, getFocusableStyleBits()); // set both bits at once
}
@Override
@@ -1050,7 +1049,6 @@ public class CPlatformWindow extends CFRetainedResource implements PlatformWindo
}
execute(ptr -> nativeSetEnabled(ptr, !blocked));
checkBlockingAndOrder();
}
public final void invalidateShadow() {
@@ -1199,16 +1197,13 @@ public class CPlatformWindow extends CFRetainedResource implements PlatformWindo
}
}
/*
* Our focus model is synthetic and only non-simple window
* may become natively focusable window.
*/
private boolean isNativelyFocusableWindow() {
if (peer == null) {
return false;
}
return !peer.isSimpleWindow() && target.getFocusableWindowState();
// returns a combination of SHOULD_BECOME_KEY/SHOULD_BECOME_MAIN relevant for the current window
private int getFocusableStyleBits() {
return (peer == null || target == null || !target.isFocusableWindow())
? 0
: peer.isSimpleWindow()
? SHOULD_BECOME_KEY
: SHOULD_BECOME_KEY | SHOULD_BECOME_MAIN;
}
/*
@@ -1217,8 +1212,11 @@ public class CPlatformWindow extends CFRetainedResource implements PlatformWindo
* circumstances.
*/
private void updateFocusabilityForAutoRequestFocus(boolean isFocusable) {
if (target.isAutoRequestFocus() || !isNativelyFocusableWindow()) return;
setStyleBits(SHOULD_BECOME_KEY | SHOULD_BECOME_MAIN, isFocusable); // set both bits at once
if (target.isAutoRequestFocus()) return;
int focusableStyleBits = getFocusableStyleBits();
if (focusableStyleBits == 0) return;
setStyleBits(SHOULD_BECOME_KEY | SHOULD_BECOME_MAIN,
isFocusable ? focusableStyleBits : 0); // set both bits at once
}
private boolean checkBlockingAndOrder() {
@@ -1338,10 +1336,10 @@ public class CPlatformWindow extends CFRetainedResource implements PlatformWindo
}
protected void applyWindowLevel(Window target) {
if (target.isAlwaysOnTop() && target.getType() != Window.Type.POPUP) {
execute(ptr->CWrapper.NSWindow.setLevel(ptr, CWrapper.NSWindow.NSFloatingWindowLevel));
} else if (target.getType() == Window.Type.POPUP) {
execute(ptr->CWrapper.NSWindow.setLevel(ptr, CWrapper.NSWindow.NSPopUpMenuWindowLevel));
boolean popup = target.getType() == Window.Type.POPUP;
boolean alwaysOnTop = target.isAlwaysOnTop();
if (popup || alwaysOnTop || owner != null) {
execute(ptr -> nativeRaiseLevel(ptr, popup, !popup && !alwaysOnTop));
}
}

View File

@@ -281,7 +281,6 @@ AWT_NS_WINDOW_IMPLEMENTATION
if (IS(styleBits, UTILITY)) type |= NSWindowStyleMaskUtilityWindow;
if (IS(styleBits, HUD)) type |= NSWindowStyleMaskHUDWindow;
if (IS(styleBits, SHEET)) type |= NSWindowStyleMaskDocModalWindow;
if (IS(styleBits, NONACTIVATING)) type |= NSWindowStyleMaskNonactivatingPanel;
return type;
}
@@ -367,7 +366,6 @@ AWT_ASSERT_APPKIT_THREAD;
if (self == nil) return nil; // no hope
if (IS(bits, UTILITY) ||
IS(bits, NONACTIVATING) ||
IS(bits, HUD) ||
IS(bits, HIDES_ON_DEACTIVATE) ||
IS(bits, SHEET))
@@ -777,9 +775,7 @@ AWT_ASSERT_APPKIT_THREAD;
NSLog(@"became main: %d %@ %@", [self.nsWindow isKeyWindow], [self.nsWindow title], [self menuBarForWindow]);
#endif
if (![self.nsWindow isKeyWindow]) {
[self activateWindowMenuBar];
}
[self activateWindowMenuBar];
JNIEnv *env = [ThreadUtilities getJNIEnv];
jobject platformWindow = (*env)->NewLocalRef(env, self.javaPlatformWindow);
@@ -790,6 +786,8 @@ AWT_ASSERT_APPKIT_THREAD;
CHECK_EXCEPTION();
(*env)->DeleteLocalRef(env, platformWindow);
}
[self orderChildWindows:YES];
}
- (void) windowDidBecomeKey: (NSNotification *) notification {
@@ -801,13 +799,26 @@ AWT_ASSERT_APPKIT_THREAD;
AWTWindow *opposite = [AWTWindow lastKeyWindow];
if (![self.nsWindow isMainWindow]) {
[self activateWindowMenuBar];
[self makeRelevantAncestorMain];
}
[AWTWindow setLastKeyWindow:nil];
[self _deliverWindowFocusEvent:YES oppositeWindow: opposite];
[self orderChildWindows:YES];
}
- (void) makeRelevantAncestorMain {
NSWindow *nativeWindow;
AWTWindow *awtWindow = self;
do {
nativeWindow = awtWindow.nsWindow;
if ([nativeWindow canBecomeMainWindow]) {
[nativeWindow makeMainWindow];
break;
}
awtWindow = awtWindow.ownerWindow;
} while (awtWindow);
}
- (void) activateWindowMenuBar {
@@ -865,6 +876,9 @@ AWT_ASSERT_APPKIT_THREAD;
if (![self.nsWindow isKeyWindow]) {
[self deactivateWindow];
}
[self.javaMenuBar deactivate];
[self orderChildWindows:NO];
}
- (void) deactivateWindow {
@@ -872,7 +886,6 @@ AWT_ASSERT_APPKIT_THREAD;
#ifdef DEBUG
NSLog(@"deactivating window: %@", [self.nsWindow title]);
#endif
[self.javaMenuBar deactivate];
// the new key window
NSWindow *keyWindow = [NSApp keyWindow];
@@ -885,7 +898,6 @@ AWT_ASSERT_APPKIT_THREAD;
}
[self _deliverWindowFocusEvent:NO oppositeWindow: opposite];
[self orderChildWindows:NO];
}
- (BOOL)windowShouldClose:(id)sender {
@@ -1241,7 +1253,7 @@ JNI_COCOA_ENTER(env);
AWTWindow *window = (AWTWindow*)[nsWindow delegate];
if ([nsWindow isKeyWindow] || [nsWindow isMainWindow]) {
if ([nsWindow isMainWindow]) {
[window.javaMenuBar deactivate];
}
@@ -1252,7 +1264,7 @@ JNI_COCOA_ENTER(env);
actualMenuBar = [[ApplicationDelegate sharedDelegate] defaultMenuBar];
}
if ([nsWindow isKeyWindow] || [nsWindow isMainWindow]) {
if ([nsWindow isMainWindow]) {
[CMenuBar activate:actualMenuBar modallyDisabled:NO];
}
}];
@@ -1774,3 +1786,25 @@ JNI_COCOA_ENTER(env);
JNI_COCOA_EXIT(env);
}
JNIEXPORT void JNICALL Java_sun_lwawt_macosx_CPlatformWindow_nativeRaiseLevel
(JNIEnv *env, jclass clazz, jlong windowPtr, jboolean popup, jboolean onlyIfParentIsActive)
{
JNI_COCOA_ENTER(env);
NSWindow *nsWindow = OBJC(windowPtr);
[ThreadUtilities performOnMainThreadWaiting:NO block:^(){
AWTWindow *window = (AWTWindow*)[nsWindow delegate];
if (onlyIfParentIsActive) {
AWTWindow *parent = window;
do {
parent = parent.ownerWindow;
} while (parent != nil && !parent.nsWindow.isMainWindow);
if (parent == nil) {
return;
}
}
[nsWindow setLevel: popup ? NSPopUpMenuWindowLevel : NSFloatingWindowLevel];
}];
JNI_COCOA_EXIT(env);
}

View File

@@ -1081,7 +1081,13 @@ public class Window extends Container implements Accessible {
} else {
// fix for 6532736: after this window is shown, its blocker
// should be raised to front
modalBlocker.toFront_NoClientCode();
boolean storedValue = modalBlocker.isAutoRequestFocus();
modalBlocker.setAutoRequestFocus(false);
try {
modalBlocker.toFront_NoClientCode();
} finally {
modalBlocker.setAutoRequestFocus(storedValue);
}
}
if (this instanceof Frame || this instanceof Dialog) {
updateChildFocusableWindowState(this);

View File

@@ -0,0 +1,152 @@
/*
* Copyright 2000-2023 JetBrains s.r.o.
* 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.
*/
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.io.File;
import java.nio.file.Files;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
/**
* @test
* @summary Regression test for JBR-2533 Popup is not focused on click when switching from another application on macOS
* @key headful
*/
public class PopupIncomingFocusTest {
private static final CompletableFuture<Boolean> windowOpened = new CompletableFuture<>();
private static final CompletableFuture<Boolean> popupOpened = new CompletableFuture<>();
private static final CompletableFuture<Boolean> result = new CompletableFuture<>();
private static Robot robot;
private static Process otherProcess;
private static JFrame frame;
private static JButton button;
private static JWindow popup;
private static JTextField field;
public static void main(String[] args) throws Exception {
robot = new Robot();
robot.setAutoWaitForIdle(true);
try {
SwingUtilities.invokeAndWait(PopupIncomingFocusTest::init);
windowOpened.get(10, TimeUnit.SECONDS);
launchProcessWithWindow();
clickAt(button);
popupOpened.get(10, TimeUnit.SECONDS);
clickAt(400, 100); // other process' window
clickAt(field);
pressEnter();
result.get(10, TimeUnit.SECONDS);
}
finally {
SwingUtilities.invokeAndWait(PopupIncomingFocusTest::shutdown);
}
}
private static void init() {
button = new JButton("Open popup");
button.addActionListener(e -> {
popup.setVisible(true);
});
frame = new JFrame();
frame.add(button);
frame.setBounds(50, 50, 200, 100);
frame.addWindowFocusListener(new WindowAdapter() {
@Override
public void windowGainedFocus(WindowEvent e) {
windowOpened.complete(Boolean.TRUE);
}
});
field = new JTextField(10);
field.getCaret().setBlinkRate(0); // prevent caret blink timer from keeping event thread running
field.addActionListener(e -> result.complete(Boolean.TRUE));
popup = new JWindow(frame);
popup.setType(Window.Type.POPUP);
popup.add(field);
popup.pack();
popup.setLocation(50, 200);
popup.addWindowFocusListener(new WindowAdapter() {
@Override
public void windowGainedFocus(WindowEvent e) {
popupOpened.complete(Boolean.TRUE);
}
});
frame.setVisible(true);
}
private static void shutdown() {
if (frame != null) frame.dispose();
if (otherProcess != null) otherProcess.destroyForcibly();
}
private static void clickAt(int x, int y) {
robot.delay(1000); // needed for GNOME, to give it some time to update internal state after window showing
robot.mouseMove(x, y);
robot.mousePress(InputEvent.BUTTON1_DOWN_MASK);
robot.mouseRelease(InputEvent.BUTTON1_DOWN_MASK);
}
private static void clickAt(Component component) {
Point location = component.getLocationOnScreen();
clickAt(location.x + component.getWidth() / 2, location.y + component.getHeight() / 2);
}
private static void pressEnter() {
robot.keyPress(KeyEvent.VK_ENTER);
robot.keyRelease(KeyEvent.VK_ENTER);
}
private static void launchProcessWithWindow() throws Exception {
String javaPath = System.getProperty("java.home") + File.separator + "bin" + File.separator + "java";
File tmpFile = File.createTempFile("PopupIncomingFocusTest", ".java");
tmpFile.deleteOnExit();
Files.writeString(tmpFile.toPath(), "import javax.swing.*;\n" +
"import java.awt.event.*;\n" +
"\n" +
"public class TestWindow {\n" +
" public static void main(String[] args) {\n" +
" SwingUtilities.invokeLater(() -> {\n" +
" JFrame f = new JFrame();\n" +
" f.addWindowFocusListener(new WindowAdapter() {\n" +
" @Override\n" +
" public void windowGainedFocus(WindowEvent e) {\n" +
" System.out.println();\n" +
" }\n" +
" });\n" +
" f.setBounds(300, 50, 200, 100);\n" +
" f.setVisible(true);\n" +
" });\n" +
" }\n" +
"}\n");
otherProcess = Runtime.getRuntime().exec(new String[]{javaPath, tmpFile.getAbsolutePath()});
if (otherProcess.getInputStream().read() == -1) {
throw new RuntimeException("Error starting process");
}
}
}

View File

@@ -0,0 +1,97 @@
/*
* Copyright 2021 JetBrains s.r.o.
* 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.
*/
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
/**
* @test
* @summary Regression test for JBR-3979 Focus is not transferred to parent window
* @key headful
*/
public class RequestFocusInParent {
private static CompletableFuture<Boolean> result;
private static Robot robot;
private static JFrame frame;
private static JButton button1;
private static JButton button2;
public static void main(String[] args) throws Exception {
robot = new Robot();
try {
SwingUtilities.invokeAndWait(RequestFocusInParent::initUI);
robot.delay(1000); // wait for frame to appear
clickOn(button1);
robot.delay(1000); // wait for popup to appear
SwingUtilities.invokeAndWait(() -> result = new CompletableFuture<>());
clickOn(button2);
result.get(5, TimeUnit.SECONDS);
} finally {
SwingUtilities.invokeAndWait(RequestFocusInParent::disposeUI);
}
}
private static void initUI() {
frame = new JFrame("RequestFocusInParent");
button1 = new JButton("Open popup");
button1.addActionListener(e -> {
JWindow popup = new JWindow(frame);
button2 = new JButton("Return focus");
button2.addActionListener(ee -> button1.requestFocus());
popup.add(button2);
popup.setSize(100, 100);
popup.setLocation(100, 400);
popup.setVisible(true);
});
button1.addFocusListener(new FocusAdapter() {
@Override
public void focusGained(FocusEvent e) {
if (result != null) result.complete(true);
}
});
frame.add(button1);
frame.setSize(100, 100);
frame.setLocation(100, 100);
frame.setVisible(true);
}
private static void disposeUI() {
if (frame != null) frame.dispose();
}
private static void clickAt(int x, int y) {
robot.mouseMove(x, y);
robot.mousePress(InputEvent.BUTTON1_DOWN_MASK);
robot.mouseRelease(InputEvent.BUTTON1_DOWN_MASK);
}
private static void clickOn(Component component) {
Point location = component.getLocationOnScreen();
clickAt(location.x + component.getWidth() / 2, location.y + component.getHeight() / 2);
}
}

View File

@@ -0,0 +1,113 @@
/*
* Copyright 2000-2023 JetBrains s.r.o.
* 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.
*/
import javax.swing.*;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import java.awt.*;
import java.awt.event.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
/**
* @test
* @summary Regression test for JBR-2854 [macOS] Undecorated window without parent steals focus on showing
* @key headful
*/
public class WindowWithoutParentTest {
private static final CompletableFuture<Boolean> initFinished = new CompletableFuture<>();
private static final CompletableFuture<Boolean> typedInField = new CompletableFuture<>();
private static Robot robot;
private static JFrame frame;
private static JTextField field;
private static JWindow window;
public static void main(String[] args) throws Exception {
robot = new Robot();
robot.setAutoDelay(50); // ensure different timestamps for key events (can impact typeahead logic)
try {
SwingUtilities.invokeAndWait(WindowWithoutParentTest::initUI);
initFinished.get(10, TimeUnit.SECONDS);
clickOn(field);
pressAndRelease(KeyEvent.VK_ENTER);
pressAndRelease(KeyEvent.VK_A);
typedInField.get(10, TimeUnit.SECONDS);
} finally {
SwingUtilities.invokeAndWait(WindowWithoutParentTest::disposeUI);
}
}
private static void initUI() {
frame = new JFrame("WindowWithoutParentTest");
field = new JTextField(20);
field.addFocusListener(new FocusAdapter() {
@Override
public void focusGained(FocusEvent e) {
initFinished.complete(true);
}
});
field.addActionListener(e -> {
window = new JWindow();
window.setSize(50, 50);
window.setVisible(true);
});
field.getDocument().addDocumentListener(new DocumentListener() {
@Override
public void insertUpdate(DocumentEvent e) {
typedInField.complete(true);
}
@Override
public void removeUpdate(DocumentEvent e) {}
@Override
public void changedUpdate(DocumentEvent e) {}
});
frame.add(field);
frame.pack();
frame.setVisible(true);
}
private static void disposeUI() {
if (window != null) window.dispose();
if (frame != null) frame.dispose();
}
private static void pressAndRelease(int keyCode) {
robot.keyPress(keyCode);
robot.keyRelease(keyCode);
}
private static void clickAt(int x, int y) {
robot.delay(1000); // needed for GNOME, to give it some time to update internal state after window showing
robot.mouseMove(x, y);
robot.mousePress(InputEvent.BUTTON1_DOWN_MASK);
robot.mouseRelease(InputEvent.BUTTON1_DOWN_MASK);
}
private static void clickOn(Component component) {
Point location = component.getLocationOnScreen();
clickAt(location.x + component.getWidth() / 2, location.y + component.getHeight() / 2);
}
}