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/testlibrary/
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;
main(String[] args)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 
addNotifyPopup()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 
removeIcon()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 
assertCollected(WeakReference<?> reference, String message)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 
causeGC()106     private static void causeGC() {
107         System.gc();
108         System.runFinalization();
109         robot.delay(1000);
110     }
111 
112 
createSystemTrayIcon()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 
createTrayIconImage()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 
createTrayIconPopupMenu()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 
sleep()166     private static void sleep() {
167         robot.waitForIdle(100);
168     }
169 }
170