1 /* 2 * Copyright (c) 2013, 2016, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. 8 * 9 * This code is distributed in the hope that it will be useful, but WITHOUT 10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 12 * version 2 for more details (a copy is included in the LICENSE file that 13 * accompanied this code). 14 * 15 * You should have received a copy of the GNU General Public License version 16 * 2 along with this work; if not, write to the Free Software Foundation, 17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 18 * 19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 20 * or visit www.oracle.com if you need additional information or have any 21 * questions. 22 */ 23 24 /* 25 @test 26 @key headful 27 @bug 8007220 8039081 28 @summary Reference to the popup leaks after the TrayIcon is removed. 29 @requires os.family != "windows" 30 @library ../../../../lib/client/ 31 @build ExtendedRobot 32 @run main/othervm -Xmx50m PopupMenuLeakTest 33 */ 34 35 import java.awt.AWTException; 36 import java.awt.Color; 37 import java.awt.Graphics2D; 38 import java.awt.Image; 39 import java.awt.MenuItem; 40 import java.awt.PopupMenu; 41 import java.awt.RenderingHints; 42 import java.awt.SystemTray; 43 import java.awt.TrayIcon; 44 import javax.swing.SwingUtilities; 45 46 import java.awt.image.BufferedImage; 47 import java.lang.ref.WeakReference; 48 import java.util.ArrayList; 49 import java.util.concurrent.atomic.AtomicReference; 50 51 public class PopupMenuLeakTest { 52 53 static final AtomicReference<WeakReference<TrayIcon>> iconWeakReference = new AtomicReference<>(); 54 static final AtomicReference<WeakReference<PopupMenu>> popupWeakReference = new AtomicReference<>(); 55 static ExtendedRobot robot; 56 public static void main(String[] args) throws Exception { 57 robot = new ExtendedRobot(); 58 SwingUtilities.invokeAndWait(PopupMenuLeakTest::createSystemTrayIcon); 59 sleep(); 60 // To make the test automatic we explicitly call addNotify on a popup to create the peer 61 SwingUtilities.invokeAndWait(PopupMenuLeakTest::addNotifyPopup); 62 sleep(); 63 SwingUtilities.invokeAndWait(PopupMenuLeakTest::removeIcon); 64 sleep(); 65 assertCollected(iconWeakReference.get(), "Failed, reference to tray icon not collected"); 66 assertCollected(popupWeakReference.get(), "Failed, reference to popup not collected"); 67 } 68 69 private static void addNotifyPopup() { 70 PopupMenu menu = popupWeakReference.get().get(); 71 if (menu == null) { 72 throw new RuntimeException("Failed: popup collected too early"); 73 } 74 menu.addNotify(); 75 } 76 77 private static void removeIcon() { 78 TrayIcon icon = iconWeakReference.get().get(); 79 if (icon == null) { 80 throw new RuntimeException("Failed: TrayIcon collected too early"); 81 } 82 SystemTray.getSystemTray().remove(icon); 83 } 84 85 private static void assertCollected(WeakReference<?> reference, String message) { 86 java.util.List<byte[]> bytes = new ArrayList<>(); 87 for (int i = 0; i < 5; i ++) { 88 if (reference.get() == null) { 89 // reference is collected, avoid OOMs. 90 break; 91 } 92 try { 93 while (true) { 94 bytes.add(new byte[4096]); 95 } 96 } catch (OutOfMemoryError err) { 97 bytes.clear(); 98 causeGC(); 99 } 100 } 101 if (reference.get() != null) { 102 throw new RuntimeException(message); 103 } 104 } 105 106 private static void causeGC() { 107 System.gc(); 108 System.runFinalization(); 109 robot.delay(1000); 110 } 111 112 113 private static void createSystemTrayIcon() { 114 final TrayIcon trayIcon = new TrayIcon(createTrayIconImage()); 115 trayIcon.setImageAutoSize(true); 116 117 try { 118 // Add tray icon to system tray *before* adding popup menu to demonstrate buggy behaviour 119 trayIcon.setPopupMenu(createTrayIconPopupMenu()); 120 SystemTray.getSystemTray().add(trayIcon); 121 iconWeakReference.set(new WeakReference<>(trayIcon)); 122 popupWeakReference.set(new WeakReference<>(trayIcon.getPopupMenu())); 123 } catch (final AWTException awte) { 124 awte.printStackTrace(); 125 } 126 } 127 128 private static Image createTrayIconImage() { 129 /** 130 * Create a small image of a red circle to use as the icon for the tray icon 131 */ 132 int trayIconImageSize = 32; 133 final BufferedImage trayImage = new BufferedImage(trayIconImageSize, trayIconImageSize, BufferedImage.TYPE_INT_ARGB); 134 final Graphics2D trayImageGraphics = (Graphics2D) trayImage.getGraphics(); 135 136 trayImageGraphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); 137 138 trayImageGraphics.setColor(new Color(255, 255, 255, 0)); 139 trayImageGraphics.fillRect(0, 0, trayImage.getWidth(), trayImage.getHeight()); 140 141 trayImageGraphics.setColor(Color.red); 142 143 int trayIconImageInset = 4; 144 trayImageGraphics.fillOval(trayIconImageInset, 145 trayIconImageInset, 146 trayImage.getWidth() - 2 * trayIconImageInset, 147 trayImage.getHeight() - 2 * trayIconImageInset); 148 149 trayImageGraphics.setColor(Color.darkGray); 150 151 trayImageGraphics.drawOval(trayIconImageInset, 152 trayIconImageInset, 153 trayImage.getWidth() - 2 * trayIconImageInset, 154 trayImage.getHeight() - 2 * trayIconImageInset); 155 156 return trayImage; 157 } 158 159 private static PopupMenu createTrayIconPopupMenu() { 160 final PopupMenu trayIconPopupMenu = new PopupMenu(); 161 final MenuItem popupMenuItem = new MenuItem("TEST!"); 162 trayIconPopupMenu.add(popupMenuItem); 163 return trayIconPopupMenu; 164 } 165 166 private static void sleep() { 167 robot.waitForIdle(100); 168 } 169 }