8194327: [macos] AWT windows have incorrect main/key window behaviors
authorserb
Wed, 16 May 2018 16:46:51 -0700
changeset 50341 b933614e855e
parent 50151 e2eff4dcebbd
child 50342 ddbd2037f9ef
8194327: [macos] AWT windows have incorrect main/key window behaviors Reviewed-by: serb, erikj Contributed-by: Alan Snyder <fishgarage@cbfiddle.com>
make/test/JtregNativeJdk.gmk
src/java.desktop/macosx/native/libawt_lwawt/awt/AWTWindow.m
src/java.desktop/macosx/native/libawt_lwawt/awt/CMenuBar.m
src/java.desktop/macosx/native/libawt_lwawt/awt/CMenuItem.m
src/java.desktop/macosx/native/libawt_lwawt/awt/LWCToolkit.h
test/jdk/java/awt/Window/MainKeyWindowTest/TestMainKeyWindow.java
test/jdk/java/awt/Window/MainKeyWindowTest/libTestMainKeyWindow.c
--- a/make/test/JtregNativeJdk.gmk	Wed May 16 10:27:25 2018 -0700
+++ b/make/test/JtregNativeJdk.gmk	Wed May 16 16:46:51 2018 -0700
@@ -64,6 +64,14 @@
   endif
 endif
 
+ifeq ($(OPENJDK_TARGET_OS), macosx)
+  BUILD_JDK_JTREG_LIBRARIES_CFLAGS_libTestMainKeyWindow := -ObjC
+  BUILD_JDK_JTREG_LIBRARIES_LIBS_libTestMainKeyWindow := -framework JavaVM \
+      -framework Cocoa -framework JavaNativeFoundation
+else
+  BUILD_JDK_JTREG_EXCLUDE += libTestMainKeyWindow.c
+endif
+
 $(eval $(call SetupTestFilesCompilation, BUILD_JDK_JTREG_LIBRARIES, \
     TYPE := LIBRARY, \
     SOURCE_DIRS := $(BUILD_JDK_JTREG_NATIVE_SRC), \
--- a/src/java.desktop/macosx/native/libawt_lwawt/awt/AWTWindow.m	Wed May 16 10:27:25 2018 -0700
+++ b/src/java.desktop/macosx/native/libawt_lwawt/awt/AWTWindow.m	Wed May 16 16:46:51 2018 -0700
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2011, 2016, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2011, 2018, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -262,7 +262,7 @@
         } else {
             [self.nsWindow setCollectionBehavior:NSWindowCollectionBehaviorDefault];
         }
-    } 
+    }
 }
 
 - (id) initWithPlatformWindow:(JNFWeakJObjectWrapper *)platformWindow
@@ -465,7 +465,7 @@
 // Tests whether window is blocked by modal dialog/window
 - (BOOL) isBlocked {
     BOOL isBlocked = NO;
-    
+
     JNIEnv *env = [ThreadUtilities getJNIEnv];
     jobject platformWindow = [self.javaPlatformWindow jObjectWithEnv:env];
     if (platformWindow != NULL) {
@@ -473,7 +473,7 @@
         isBlocked = JNFCallBooleanMethod(env, platformWindow, jm_isBlocked) == JNI_TRUE ? YES : NO;
         (*env)->DeleteLocalRef(env, platformWindow);
     }
-    
+
     return isBlocked;
 }
 
@@ -679,7 +679,7 @@
         JNFCallVoidMethod(env, platformWindow, jm_windowWillMiniaturize);
         (*env)->DeleteLocalRef(env, platformWindow);
     }
-    // Excplicitly make myself a key window to avoid possible
+    // Explicitly make myself a key window to avoid possible
     // negative visual effects during iconify operation
     [self.nsWindow makeKeyAndOrderFront:self.nsWindow];
     [self iconifyChildWindows:YES];
@@ -713,13 +713,47 @@
     }
 }
 
+- (void) windowDidBecomeMain: (NSNotification *) notification {
+AWT_ASSERT_APPKIT_THREAD;
+    [AWTToolkit eventCountPlusPlus];
+#ifdef DEBUG
+    NSLog(@"became main: %d %@ %@", [self.nsWindow isKeyWindow], [self.nsWindow title], [self menuBarForWindow]);
+#endif
+
+    if (![self.nsWindow isKeyWindow]) {
+        [self activateWindowMenuBar];
+    }
+
+    JNIEnv *env = [ThreadUtilities getJNIEnv];
+    jobject platformWindow = [self.javaPlatformWindow jObjectWithEnv:env];
+    if (platformWindow != NULL) {
+        static JNF_MEMBER_CACHE(jm_windowDidBecomeMain, jc_CPlatformWindow, "windowDidBecomeMain", "()V");
+        JNFCallVoidMethod(env, platformWindow, jm_windowDidBecomeMain);
+        (*env)->DeleteLocalRef(env, platformWindow);
+    }
+}
 
 - (void) windowDidBecomeKey: (NSNotification *) notification {
 AWT_ASSERT_APPKIT_THREAD;
     [AWTToolkit eventCountPlusPlus];
+#ifdef DEBUG
+    NSLog(@"became key: %d %@ %@", [self.nsWindow isMainWindow], [self.nsWindow title], [self menuBarForWindow]);
+#endif
     AWTWindow *opposite = [AWTWindow lastKeyWindow];
 
-    // Finds appropriate menubar in our hierarchy,
+    if (![self.nsWindow isMainWindow]) {
+        [self activateWindowMenuBar];
+    }
+
+    [AWTWindow setLastKeyWindow:nil];
+
+    [self _deliverWindowFocusEvent:YES oppositeWindow: opposite];
+    [self orderChildWindows:YES];
+}
+
+- (void) activateWindowMenuBar {
+AWT_ASSERT_APPKIT_THREAD;
+    // Finds appropriate menubar in our hierarchy
     AWTWindow *awtWindow = self;
     while (awtWindow.ownerWindow != nil) {
         awtWindow = awtWindow.ownerWindow;
@@ -738,26 +772,48 @@
     }
 
     [CMenuBar activate:menuBar modallyDisabled:isDisabled];
-
-    [AWTWindow setLastKeyWindow:nil];
+}
 
-    [self _deliverWindowFocusEvent:YES oppositeWindow: opposite];
-    [self orderChildWindows:YES];
+#ifdef DEBUG
+- (CMenuBar *) menuBarForWindow {
+AWT_ASSERT_APPKIT_THREAD;
+    AWTWindow *awtWindow = self;
+    while (awtWindow.ownerWindow != nil) {
+        awtWindow = awtWindow.ownerWindow;
+    }
+    return awtWindow.javaMenuBar;
 }
+#endif
 
 - (void) windowDidResignKey: (NSNotification *) notification {
     // TODO: check why sometimes at start is invoked *not* on AppKit main thread.
 AWT_ASSERT_APPKIT_THREAD;
     [AWTToolkit eventCountPlusPlus];
-    [self.javaMenuBar deactivate];
+#ifdef DEBUG
+    NSLog(@"resigned key: %d %@ %@", [self.nsWindow isMainWindow], [self.nsWindow title], [self menuBarForWindow]);
+#endif
+    if (![self.nsWindow isMainWindow]) {
+        [self deactivateWindow];
+    }
+}
 
-    // In theory, this might cause flickering if the window gaining focus
-    // has its own menu. However, I couldn't reproduce it on practice, so
-    // perhaps this is a non issue.
-    CMenuBar* defaultMenu = [[ApplicationDelegate sharedDelegate] defaultMenuBar];
-    if (defaultMenu != nil) {
-        [CMenuBar activate:defaultMenu modallyDisabled:NO];
+- (void) windowDidResignMain: (NSNotification *) notification {
+AWT_ASSERT_APPKIT_THREAD;
+    [AWTToolkit eventCountPlusPlus];
+#ifdef DEBUG
+    NSLog(@"resigned main: %d %@ %@", [self.nsWindow isKeyWindow], [self.nsWindow title], [self menuBarForWindow]);
+#endif
+    if (![self.nsWindow isKeyWindow]) {
+        [self deactivateWindow];
     }
+}
+
+- (void) deactivateWindow {
+AWT_ASSERT_APPKIT_THREAD;
+#ifdef DEBUG
+    NSLog(@"deactivating window: %@", [self.nsWindow title]);
+#endif
+    [self.javaMenuBar deactivate];
 
     // the new key window
     NSWindow *keyWindow = [NSApp keyWindow];
@@ -773,19 +829,6 @@
     [self orderChildWindows:NO];
 }
 
-- (void) windowDidBecomeMain: (NSNotification *) notification {
-AWT_ASSERT_APPKIT_THREAD;
-    [AWTToolkit eventCountPlusPlus];
-
-    JNIEnv *env = [ThreadUtilities getJNIEnv];
-    jobject platformWindow = [self.javaPlatformWindow jObjectWithEnv:env];
-    if (platformWindow != NULL) {
-        static JNF_MEMBER_CACHE(jm_windowDidBecomeMain, jc_CPlatformWindow, "windowDidBecomeMain", "()V");
-        JNFCallVoidMethod(env, platformWindow, jm_windowDidBecomeMain);
-        (*env)->DeleteLocalRef(env, platformWindow);
-    }
-}
-
 - (BOOL)windowShouldClose:(id)sender {
 AWT_ASSERT_APPKIT_THREAD;
     [AWTToolkit eventCountPlusPlus];
@@ -1040,7 +1083,7 @@
 
         AWTWindow *window = (AWTWindow*)[nsWindow delegate];
 
-        if ([nsWindow isKeyWindow]) {
+        if ([nsWindow isKeyWindow] || [nsWindow isMainWindow]) {
             [window.javaMenuBar deactivate];
         }
 
@@ -1051,7 +1094,7 @@
             actualMenuBar = [[ApplicationDelegate sharedDelegate] defaultMenuBar];
         }
 
-        if ([nsWindow isKeyWindow]) {
+        if ([nsWindow isKeyWindow] || [nsWindow isMainWindow]) {
             [CMenuBar activate:actualMenuBar modallyDisabled:NO];
         }
     }];
--- a/src/java.desktop/macosx/native/libawt_lwawt/awt/CMenuBar.m	Wed May 16 10:27:25 2018 -0700
+++ b/src/java.desktop/macosx/native/libawt_lwawt/awt/CMenuBar.m	Wed May 16 16:46:51 2018 -0700
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2011, 2016, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2011, 2018, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -31,6 +31,7 @@
 #import "CMenuBar.h"
 #import "CMenu.h"
 #import "ThreadUtilities.h"
+#import "ApplicationDelegate.h"
 
 #import "sun_lwawt_macosx_CMenuBar.h"
 
@@ -101,6 +102,10 @@
         return;
     }
 
+#ifdef DEBUG
+    NSLog(@"activating menu bar: %@", menubar);
+#endif
+
     @synchronized([CMenuBar class]) {
         sActiveMenuBar = menubar;
     }
@@ -184,12 +189,30 @@
 -(void) deactivate {
     AWT_ASSERT_APPKIT_THREAD;
 
+    BOOL isDeactivated = NO;
     @synchronized([CMenuBar class]) {
-        sActiveMenuBar = nil;
+        if (sActiveMenuBar == self) {
+            sActiveMenuBar = nil;
+            isDeactivated = YES;
+        }
     }
 
-    @synchronized(self) {
-        fModallyDisabled = NO;
+    if (isDeactivated) {
+#ifdef DEBUG
+        NSLog(@"deactivating menu bar: %@", self);
+#endif
+
+        @synchronized(self) {
+            fModallyDisabled = NO;
+        }
+
+        // In theory, this might cause flickering if the window gaining focus
+        // has its own menu. However, I couldn't reproduce it on practice, so
+        // perhaps this is a non issue.
+        CMenuBar* defaultMenu = [[ApplicationDelegate sharedDelegate] defaultMenuBar];
+        if (defaultMenu != nil) {
+            [CMenuBar activate:defaultMenu modallyDisabled:NO];
+        }
     }
 }
 
--- a/src/java.desktop/macosx/native/libawt_lwawt/awt/CMenuItem.m	Wed May 16 10:27:25 2018 -0700
+++ b/src/java.desktop/macosx/native/libawt_lwawt/awt/CMenuItem.m	Wed May 16 16:46:51 2018 -0700
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2011, 2016, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2011, 2018, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -28,6 +28,7 @@
 #import "CMenuItem.h"
 #import "CMenu.h"
 #import "AWTEvent.h"
+#import "AWTWindow.h"
 #import "ThreadUtilities.h"
 
 #import "java_awt_Event.h"
@@ -111,12 +112,14 @@
                 }
                 eventKey = [NSString stringWithCharacters: &newChar length: 1];
             }
+            // The action event can be ignored only if the key window is an AWT window.
+            // Otherwise, the action event is the only notification and must be processed.
             NSWindow *keyWindow = [NSApp keyWindow];
-            if (keyWindow != nil) {
+            if (keyWindow != nil && [AWTWindow isAWTWindow: keyWindow]) {
                 return;
             }
-		}
-		
+        }
+        
         static JNF_CLASS_CACHE(jc_CMenuItem, "sun/lwawt/macosx/CMenuItem");
         static JNF_MEMBER_CACHE(jm_handleAction, jc_CMenuItem, "handleAction", "(JI)V"); // AWT_THREADING Safe (event)
 
@@ -126,7 +129,7 @@
         JNFCallVoidMethod(env, fPeer, jm_handleAction, UTC(currEvent), javaModifiers); // AWT_THREADING Safe (event)
     }
     JNF_COCOA_EXIT(env);
-	
+    
 }
 
 - (void) setJavaLabel:(NSString *)theLabel shortcut:(NSString *)theKeyEquivalent modifierMask:(jint)modifiers {
--- a/src/java.desktop/macosx/native/libawt_lwawt/awt/LWCToolkit.h	Wed May 16 10:27:25 2018 -0700
+++ b/src/java.desktop/macosx/native/libawt_lwawt/awt/LWCToolkit.h	Wed May 16 16:46:51 2018 -0700
@@ -30,7 +30,7 @@
 
 #import <Cocoa/Cocoa.h>
 
-#define DEBUG 1
+//#define DEBUG 1
 
 // number of mouse buttons supported
 extern int gNumberOfButtons;
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/awt/Window/MainKeyWindowTest/TestMainKeyWindow.java	Wed May 16 16:46:51 2018 -0700
@@ -0,0 +1,409 @@
+/*
+ * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved.
+ * 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.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * 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.
+ */
+
+/**
+ * @test
+ * @key headful
+ * @bug 8194327
+ * @summary [macosx] AWT windows have incorrect main/key window behaviors
+ * @author Alan Snyder
+ * @run main/othervm/native TestMainKeyWindow
+ * @requires (os.family == "mac")
+ */
+
+import java.awt.*;
+import java.awt.event.ActionEvent;
+import java.awt.event.InputEvent;
+import java.awt.event.KeyEvent;
+import java.io.IOException;
+import java.lang.reflect.InvocationTargetException;
+import java.util.Objects;
+import javax.swing.*;
+
+public class TestMainKeyWindow
+{
+    static TestMainKeyWindow theTest;
+
+    KeyStroke commandT = KeyStroke.getKeyStroke(KeyEvent.VK_T, KeyEvent.META_DOWN_MASK);
+
+    int nextX = 130;
+
+    private final MyFrame frame1;
+    private final MyFrame frame2;
+    private final Object COLOR_PANEL = "Color Panel";
+    private final Object NATIVE_WINDOW = "Native Window";
+
+    // these bounds must agree with the native code that creates the windows
+    private Rectangle colorPanelBounds = new Rectangle(130, 300, 225, 400);  // approximate is OK
+    private Rectangle nativeWindowBounds = new Rectangle(130, 200, 200, 100);
+
+    private Robot robot;
+
+    private int actionCounter;
+    private Object actionTarget;
+
+    private int failureCount;
+    private boolean isApplicationOpened;
+
+    public TestMainKeyWindow()
+    {
+        System.loadLibrary("testMainKeyWindow");
+
+        JMenuBar defaultMenuBar = createMenuBar("Application", true);
+        Desktop.getDesktop().setDefaultMenuBar(defaultMenuBar);
+
+        setup();
+
+        frame1 = new MyFrame("Frame 1");
+        frame2 = new MyFrame("Frame 2");
+        frame1.setVisible(true);
+        frame2.setVisible(true);
+
+        try {
+            robot = new Robot();
+            robot.setAutoDelay(50);
+        } catch (AWTException ex) {
+            throw new RuntimeException(ex);
+        }
+    }
+
+    class MyFrame
+        extends JFrame
+    {
+        public MyFrame(String title)
+            throws HeadlessException
+        {
+            super(title);
+
+            JMenuBar mainMenuBar = createMenuBar(title, true);
+            setJMenuBar(mainMenuBar);
+            setBounds(nextX, 60, 200, 90);
+            nextX += 250;
+            JComponent contentPane = new JPanel();
+            setContentPane(contentPane);
+            contentPane.setLayout(new FlowLayout());
+            contentPane.add(new JCheckBox("foo", true));
+            InputMap inputMap = contentPane.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
+            inputMap.put(commandT, "test");
+            ActionMap actionMap = contentPane.getActionMap();
+            actionMap.put("test", new MyAction(title + " Key"));
+        }
+    }
+
+    private void runTest()
+    {
+        failureCount = 0;
+        robot.waitForIdle();
+        performTest(frame1, false);
+        performTest(frame1, true);
+        performTest(frame2, false);
+        performTest(frame2, true);
+        performTest(NATIVE_WINDOW, false);
+        performTest(NATIVE_WINDOW, true);
+        performTest(COLOR_PANEL, false);
+        if (failureCount > 0) {
+            throw new RuntimeException("Test failed: " + failureCount + " failure(s)");
+        }
+    }
+
+    private void performTest(Object windowIdentification, boolean selectColorPanel)
+    {
+        setupWindows(windowIdentification, selectColorPanel);
+
+        performMenuShortcutTest(windowIdentification, selectColorPanel);
+        performMenuItemTest(windowIdentification, selectColorPanel);
+
+        // test deactivating and reactivating the application
+        // the window state and behavior should be restored
+
+        openOtherApplication();
+        activateApplication();
+        robot.delay(1000);
+
+        performMenuShortcutTest(windowIdentification, selectColorPanel);
+        performMenuItemTest(windowIdentification, selectColorPanel);
+    }
+
+    private void openOtherApplication() {
+        try {
+            String[] cmd = { "/usr/bin/open", "/Applications/System Preferences.app" };
+            Runtime.getRuntime().exec(cmd);
+            if (!isApplicationOpened) {
+                String[] cmd2 = { "/usr/bin/osascript", "-e",
+                    "tell application \"System Preferences\" to set bounds of window 1 to {400, 180, 1068, 821}" };
+                Runtime.getRuntime().exec(cmd2);
+            }
+            isApplicationOpened = true;
+        } catch (IOException ex) {
+            throw new RuntimeException("Unable to deactivate test application");
+        }
+        robot.delay(1000);
+    }
+
+    private void performMenuShortcutTest(Object windowIdentification, boolean selectColorPanel)
+    {
+        int currentActionCount = actionCounter;
+
+        // Perform the menu shortcut
+        robot.keyPress(KeyEvent.VK_META);
+        robot.keyPress(KeyEvent.VK_T);
+        robot.keyRelease(KeyEvent.VK_T);
+        robot.keyRelease(KeyEvent.VK_META);
+        robot.waitForIdle();
+
+        Object target = waitForAction(currentActionCount + 1);
+        boolean isDirectKey = windowIdentification instanceof Window && !selectColorPanel;
+        Object expectedTarget = getExpectedTarget(windowIdentification, isDirectKey);
+        if (!Objects.equals(target, expectedTarget)) {
+            failureCount++;
+            String configuration = getConfigurationName(windowIdentification, selectColorPanel);
+            System.err.println("***** Menu shortcut test failed for " + configuration + ". Expected: " + expectedTarget + ", Actual: " + target);
+        }
+    }
+
+    private void performMenuItemTest(Object windowIdentification, boolean selectColorPanel)
+    {
+        int currentActionCount = actionCounter;
+
+        // Find the menu on the screen menu bar
+        // The location depends upon the application name which is the name of the first menu.
+        // Unfortunately, the application name can vary based on how the application is run.
+        // The work around is to make the menu and the menu item names very long.
+
+        int menuBarX = 250;
+        int menuBarY = 11;
+        int menuItemX = menuBarX;
+        int menuItemY = 34;
+
+        robot.mouseMove(menuBarX, menuBarY);
+        robot.delay(100);
+        robot.mousePress(InputEvent.BUTTON1_DOWN_MASK);
+        robot.delay(100);
+        robot.mouseMove(menuItemX, menuItemY);
+        robot.delay(100);
+        robot.mouseRelease(InputEvent.BUTTON1_DOWN_MASK);
+        robot.waitForIdle();
+
+        Object target = waitForAction(currentActionCount + 1);
+        Object expectedTarget = getExpectedTarget(windowIdentification, false);
+        if (!Objects.equals(target, expectedTarget)) {
+            failureCount++;
+            String configuration = getConfigurationName(windowIdentification, selectColorPanel);
+            System.err.println("***** Menu item test failed for " + configuration + ". Expected: " + expectedTarget + ", Actual: " + target);
+        }
+    }
+
+    private String getConfigurationName(Object windowIdentification, boolean selectColorPanel)
+    {
+        String name = "Unknown";
+        if (windowIdentification instanceof Window) {
+            Window w = (Window) windowIdentification;
+            name = getWindowTitle(w);
+        } else if (windowIdentification == NATIVE_WINDOW) {
+            name = "Native Window";
+        } else if (windowIdentification == COLOR_PANEL) {
+            name = "Color Panel";
+        }
+        if (selectColorPanel) {
+            return name + " with color panel";
+        } else {
+            return name;
+        }
+    }
+
+    private Object getExpectedTarget(Object windowIdentification, boolean isDirectKey)
+    {
+        if (windowIdentification instanceof Window) {
+            Window w = (Window) windowIdentification;
+            String title = getWindowTitle(w);
+            if (isDirectKey) {
+                title = title + " Key";
+            }
+            return title;
+        }
+        return "Application";
+    }
+
+    private String getWindowTitle(Window w)
+    {
+        if (w instanceof Frame) {
+            Frame f = (Frame) w;
+            return f.getTitle();
+        }
+        if (w instanceof Dialog) {
+            Dialog d = (Dialog) w;
+            return d.getTitle();
+        }
+        throw new IllegalStateException();
+    }
+
+    private synchronized void registerAction(Object target)
+    {
+        actionCounter++;
+        actionTarget = target;
+    }
+
+    private synchronized Object waitForAction(int count)
+    {
+        try {
+            for (int i = 0; i < 10; i++) {
+                if (actionCounter == count) {
+                    return actionTarget;
+                }
+                if (actionCounter > count) {
+                    throw new IllegalStateException();
+                }
+                wait(100);
+            }
+        } catch (InterruptedException ex) {
+        }
+        return "No Action";
+    }
+
+    private void setupWindows(Object windowIdentification, boolean selectColorPanel)
+    {
+        clickOnWindowTitleBar(windowIdentification);
+        if (selectColorPanel) {
+            clickOnWindowTitleBar(COLOR_PANEL);
+        }
+    }
+
+    private void clickOnWindowTitleBar(Object windowIdentification)
+    {
+        Rectangle bounds = getWindowBounds(windowIdentification);
+        int x = bounds.x + 70;  // to the right of the stoplight buttons
+        int y = bounds.y + 12;  // in the title bar
+        robot.mouseMove(x, y);
+        robot.mousePress(InputEvent.BUTTON1_DOWN_MASK);
+        robot.waitForIdle();
+        robot.mouseRelease(InputEvent.BUTTON1_DOWN_MASK);
+        robot.waitForIdle();
+    }
+
+    private Rectangle getWindowBounds(Object windowIdentification)
+    {
+        if (windowIdentification instanceof Window) {
+            Window w = (Window) windowIdentification;
+            return w.getBounds();
+        }
+        if (windowIdentification == COLOR_PANEL) {
+            return colorPanelBounds;
+        }
+        if (windowIdentification == NATIVE_WINDOW) {
+            return nativeWindowBounds;
+        }
+        throw new IllegalArgumentException();
+    }
+
+    JMenuBar createMenuBar(String text, boolean isEnabled)
+    {
+        JMenuBar mb = new JMenuBar();
+        // A very long name makes it more likely that the robot will hit the menu
+        JMenu menu = new JMenu("TestTestTestTestTestTestTestTestTestTest");
+        mb.add(menu);
+        JMenuItem item = new JMenuItem("TestTestTestTestTestTestTestTestTestTest");
+        item.setAccelerator(commandT);
+        item.setEnabled(isEnabled);
+        item.addActionListener(ev -> {
+            registerAction(text);
+        });
+        menu.add(item);
+        return mb;
+    }
+
+    void dispose()
+    {
+        frame1.setVisible(false);
+        frame2.setVisible(false);
+        frame1.dispose();
+        frame2.dispose();
+        takedown();
+        Desktop.getDesktop().setDefaultMenuBar(null);
+        if (isApplicationOpened) {
+            try {
+                String[] cmd = { "/usr/bin/osascript", "-e", "tell application \"System Preferences\" to close window 1" };
+                Process p = Runtime.getRuntime().exec(cmd);
+                p.waitFor();
+            } catch (IOException | InterruptedException ex) {
+            }
+        }
+    }
+
+    class MyAction
+        extends AbstractAction
+    {
+        String text;
+
+        public MyAction(String text)
+        {
+            super("Test");
+
+            this.text = text;
+        }
+
+        @Override
+        public void actionPerformed(ActionEvent e)
+        {
+            registerAction(text);
+        }
+    }
+
+    private static native void setup();
+    private static native void takedown();
+    private static native void activateApplication();
+
+    public static void main(String[] args)
+    {
+        if (!System.getProperty("os.name").contains("OS X")) {
+            System.out.println("This test is for MacOS only. Automatically passed on other platforms.");
+            return;
+        }
+
+        System.setProperty("apple.laf.useScreenMenuBar", "true");
+
+        try {
+            runSwing(() -> {
+                theTest = new TestMainKeyWindow();
+            });
+            theTest.runTest();
+        } finally {
+            if (theTest != null) {
+                runSwing(() -> {
+                    theTest.dispose();
+                });
+            }
+        }
+    }
+
+    private static void runSwing(Runnable r)
+    {
+        try {
+            SwingUtilities.invokeAndWait(r);
+        } catch (InterruptedException e) {
+        } catch (InvocationTargetException e) {
+            throw new RuntimeException(e);
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/awt/Window/MainKeyWindowTest/libTestMainKeyWindow.c	Wed May 16 16:46:51 2018 -0700
@@ -0,0 +1,123 @@
+/*
+ * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved.
+ * 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.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * 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 <Cocoa/Cocoa.h>
+#import <JavaNativeFoundation/JavaNativeFoundation.h>
+
+static NSWindow *testWindow;
+static NSColorPanel *colorPanel;
+
+/*
+ * Class:     TestMainKeyWindow
+ * Method:    setup
+ * Signature: ()V
+ */
+JNIEXPORT void JNICALL Java_TestMainKeyWindow_setup(JNIEnv *env, jclass cl)
+{
+    JNF_COCOA_ENTER(env);
+
+    void (^block)() = ^(){
+        NSScreen *mainScreen = [NSScreen mainScreen];
+        NSRect screenFrame = [mainScreen frame];
+        NSRect frame = NSMakeRect(130, screenFrame.size.height - 280, 200, 100);
+
+#if __MAC_OS_X_VERSION_MAX_ALLOWED >= 101200
+        NSWindowStyleMask style = NSWindowStyleMaskTitled;
+#else
+        NSInteger style = NSTitledWindowMask;
+#endif
+
+        NSRect rect = [NSWindow contentRectForFrameRect:frame styleMask:style];
+        NSBackingStoreType bt = NSBackingStoreBuffered;
+        testWindow = [[[NSWindow alloc] initWithContentRect:rect styleMask:style backing:bt defer:NO] retain];
+        testWindow.title = @"NSWindow";
+        [testWindow setBackgroundColor:[NSColor blueColor]];
+        [testWindow makeKeyAndOrderFront:nil];
+        // Java coordinates are 100, 200
+
+        colorPanel = [[NSColorPanel sharedColorPanel] retain];
+        [colorPanel setReleasedWhenClosed: YES];
+        colorPanel.restorable = NO;
+        [colorPanel setFrameTopLeftPoint: NSMakePoint(130, screenFrame.size.height - 300)];
+        // Java coordinates are 100, 400
+        [colorPanel makeKeyAndOrderFront: nil];
+    };
+
+    if ([NSThread isMainThread]) {
+        block();
+    } else {
+        [JNFRunLoop performOnMainThreadWaiting:YES withBlock:block];
+    }
+
+    JNF_COCOA_EXIT(env);
+}
+
+/*
+ * Class:     TestMainKeyWindow
+ * Method:    takedown
+ * Signature: ()V
+ */
+JNIEXPORT void JNICALL Java_TestMainKeyWindow_takedown(JNIEnv *env, jclass cl)
+{
+    JNF_COCOA_ENTER(env);
+
+    void (^block)() = ^(){
+        if (testWindow != nil) {
+            [testWindow close];
+            testWindow = nil;
+        }
+        if (colorPanel != nil) {
+            [colorPanel orderOut:nil];
+            colorPanel = nil;
+        }
+    };
+
+    if ([NSThread isMainThread]) {
+        block();
+    } else {
+        [JNFRunLoop performOnMainThreadWaiting:YES withBlock:block];
+    }
+
+    JNF_COCOA_EXIT(env);
+}
+
+/*
+ * Class:     TestMainKeyWindow
+ * Method:    activateApplication
+ * Signature: ()V
+ */
+JNIEXPORT void JNICALL Java_TestMainKeyWindow_activateApplication
+  (JNIEnv *env, jclass cl)
+{
+    JNF_COCOA_ENTER(env);
+
+    void (^block)() = ^(){
+        [NSApp activateIgnoringOtherApps:YES];
+    };
+
+    [JNFRunLoop performOnMainThreadWaiting:YES withBlock:block];
+
+  JNF_COCOA_EXIT(env);
+}