Skip to content

Commit 4a4272b

Browse files
author
Andy Goryachev
committed
8350976: MenuBarSkin: exception initializing in a background thread
Reviewed-by: lkostyra, jdv
1 parent 056c7f5 commit 4a4272b

File tree

2 files changed

+60
-11
lines changed

2 files changed

+60
-11
lines changed

modules/javafx.controls/src/main/java/javafx/scene/control/skin/MenuBarSkin.java

Lines changed: 38 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2010, 2024, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2010, 2025, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* This code is free software; you can redistribute it and/or modify it
@@ -35,7 +35,7 @@
3535
import java.util.Optional;
3636
import java.util.WeakHashMap;
3737
import java.util.stream.Collectors;
38-
38+
import javafx.application.Platform;
3939
import javafx.beans.InvalidationListener;
4040
import javafx.beans.property.DoubleProperty;
4141
import javafx.beans.property.ObjectProperty;
@@ -77,7 +77,7 @@
7777
import javafx.stage.Stage;
7878
import javafx.stage.Window;
7979
import javafx.util.Pair;
80-
80+
import javafx.util.Subscription;
8181
import com.sun.javafx.menu.MenuBase;
8282
import com.sun.javafx.scene.ParentHelper;
8383
import com.sun.javafx.scene.SceneHelper;
@@ -130,7 +130,7 @@ public class MenuBarSkin extends SkinBase<MenuBar> {
130130
private WeakChangeListener<Boolean> weakMenuVisibilityChangeListener;
131131
private ListenerHelper sceneListenerHelper;
132132
private IDisconnectable windowFocusHelper;
133-
133+
private volatile Subscription windowSubscription;
134134
private boolean pendingDismiss = false;
135135
private boolean altKeyPressed = false;
136136

@@ -229,17 +229,36 @@ public MenuBarSkin(final MenuBar control) {
229229

230230
ListenerHelper lh = ListenerHelper.get(this);
231231

232+
if (Platform.isFxApplicationThread()) {
233+
if (Toolkit.getToolkit().getSystemMenu().isSupported()) {
234+
lh.addInvalidationListener(control.useSystemMenuBarProperty(), (v) -> {
235+
rebuildUI();
236+
});
237+
}
238+
} else {
239+
// delay rebuildUI() until after MenuBar becomes a part of the scene graph
240+
// this subscription will be removed by cleanUpListeners()
241+
windowSubscription = getSkinnable()
242+
.sceneProperty()
243+
.flatMap(Scene::windowProperty)
244+
.subscribe(w -> {
245+
if (w != null) {
246+
if (Toolkit.getToolkit().getSystemMenu().isSupported()) {
247+
lh.addInvalidationListener(control.useSystemMenuBarProperty(), (v) -> {
248+
rebuildUI();
249+
});
250+
}
251+
// this method will unsubscribe on first run
252+
rebuildUI();
253+
}
254+
});
255+
}
256+
232257
rebuildUI();
233258
lh.addListChangeListener(control.getMenus(), (v) -> {
234259
rebuildUI();
235260
});
236261

237-
if (Toolkit.getToolkit().getSystemMenu().isSupported()) {
238-
lh.addInvalidationListener(control.useSystemMenuBarProperty(), (v) -> {
239-
rebuildUI();
240-
});
241-
}
242-
243262
// When the mouse leaves the menu, the last hovered item should lose
244263
// it's focus so that it is no longer selected. This code returns focus
245264
// to the MenuBar itself, such that keyboard navigation can continue.
@@ -783,6 +802,12 @@ private void updateActionListeners(MenuItem item, boolean add) {
783802
}
784803

785804
private void cleanUpListeners() {
805+
Subscription sub = windowSubscription;
806+
if (sub != null) {
807+
sub.unsubscribe();
808+
windowSubscription = null;
809+
}
810+
786811
getSkinnable().focusedProperty().removeListener(weakMenuBarFocusedPropertyListener);
787812

788813
for (Menu m : getSkinnable().getMenus()) {
@@ -817,6 +842,9 @@ private void cleanUpListeners() {
817842
}
818843

819844
private void rebuildUI() {
845+
if (!Platform.isFxApplicationThread()) {
846+
return;
847+
}
820848
cleanUpListeners();
821849

822850
if (Toolkit.getToolkit().getSystemMenu().isSupported()) {

tests/system/src/test/java/test/robot/javafx/scene/NodeInitializationStressTest.java

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,8 @@
8787
import javafx.scene.control.Hyperlink;
8888
import javafx.scene.control.Label;
8989
import javafx.scene.control.ListView;
90+
import javafx.scene.control.Menu;
91+
import javafx.scene.control.MenuBar;
9092
import javafx.scene.control.MenuButton;
9193
import javafx.scene.control.MenuItem;
9294
import javafx.scene.control.Pagination;
@@ -124,6 +126,7 @@
124126
import javafx.scene.control.skin.HyperlinkSkin;
125127
import javafx.scene.control.skin.LabelSkin;
126128
import javafx.scene.control.skin.ListViewSkin;
129+
import javafx.scene.control.skin.MenuBarSkin;
127130
import javafx.scene.control.skin.MenuButtonSkin;
128131
import javafx.scene.control.skin.PaginationSkin;
129132
import javafx.scene.control.skin.ProgressIndicatorSkin;
@@ -209,7 +212,6 @@
209212
*
210213
* Notable exceptions to this rule:
211214
* - HTMLEditor
212-
* - MenuBar
213215
* - WebView
214216
*
215217
* The test creates a visible node on the JavaFX application thread, and at the same time,
@@ -744,6 +746,25 @@ public void mediaView() {
744746
});
745747
}
746748

749+
@Test
750+
public void menuBar() {
751+
assumeFalse(SKIP_TEST);
752+
test(() -> {
753+
MenuBar c = new MenuBar();
754+
// exercise skin installation and disposal code paths
755+
c.setSkin(new MenuBarSkin(c));
756+
c.setSkin(new MenuBarSkin(c));
757+
c.setUseSystemMenuBar(nextBoolean());
758+
return c;
759+
}, (c) -> {
760+
c.getMenus().setAll(new Menu("MenuBar"));
761+
accessControl(c);
762+
if (Platform.isFxApplicationThread()) {
763+
c.setUseSystemMenuBar(nextBoolean());
764+
}
765+
});
766+
}
767+
747768
@Test
748769
public void menuButton() {
749770
assumeFalse(SKIP_TEST);

0 commit comments

Comments
 (0)