Skip to content

Commit c197b62

Browse files
committed
8242621: TabPane: Memory leak when switching skin
Reviewed-by: fastegal, kcr
1 parent e74f679 commit c197b62

File tree

4 files changed

+116
-25
lines changed

4 files changed

+116
-25
lines changed

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

Lines changed: 56 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -267,10 +267,22 @@ public TabPaneSkin(TabPane control) {
267267

268268
/** {@inheritDoc} */
269269
@Override public void dispose() {
270+
if (getSkinnable() == null) return;
271+
270272
if (selectionModel != null) {
271273
selectionModel.selectedItemProperty().removeListener(weakSelectionChangeListener);
272274
selectionModel = null;
273275
}
276+
getSkinnable().getTabs().removeListener(weakTabsListener);
277+
tabHeaderArea.dispose();
278+
279+
// Control and Skin share the list of children, so children that are
280+
// added by Skin are actually added to control's list of children,
281+
// so a skin should remove the children that it adds.
282+
getChildren().remove(tabHeaderArea);
283+
for (Tab tab : getSkinnable().getTabs()) {
284+
removeTabContent(tab);
285+
}
274286

275287
super.dispose();
276288

@@ -499,7 +511,7 @@ private void removeTabs(List<? extends Tab> removedList) {
499511
if (tabRegion != null) {
500512
tabRegion.isClosing = true;
501513

502-
tabRegion.removeListeners(tab);
514+
tabRegion.dispose();
503515
removeTabContent(tab);
504516

505517
EventHandler<ActionEvent> cleanup = ae -> {
@@ -579,8 +591,10 @@ private void addTabs(List<? extends Tab> addedList, int from) {
579591
}
580592
}
581593

594+
ListChangeListener<Tab> tabsListener;
595+
WeakListChangeListener<Tab> weakTabsListener;
582596
private void initializeTabListener() {
583-
getSkinnable().getTabs().addListener((ListChangeListener<Tab>) c -> {
597+
tabsListener = c -> {
584598
List<Tab> tabsToRemove = new ArrayList<>();
585599
List<Tab> tabsToAdd = new ArrayList<>();
586600

@@ -659,7 +673,9 @@ private void initializeTabListener() {
659673

660674
// Fix for RT-34692
661675
getSkinnable().requestLayout();
662-
});
676+
};
677+
weakTabsListener = new WeakListChangeListener<>(tabsListener);
678+
getSkinnable().getTabs().addListener(weakTabsListener);
663679
}
664680

665681
private void addTabContent(Tab tab) {
@@ -673,14 +689,18 @@ private void addTabContent(Tab tab) {
673689
private void removeTabContent(Tab tab) {
674690
for (TabContentRegion contentRegion : tabContentRegions) {
675691
if (contentRegion.getTab().equals(tab)) {
676-
contentRegion.removeListeners(tab);
677-
getChildren().remove(contentRegion);
678-
tabContentRegions.remove(contentRegion);
692+
removeTabContent(contentRegion);
679693
break;
680694
}
681695
}
682696
}
683697

698+
private void removeTabContent(TabContentRegion contentRegion) {
699+
contentRegion.dispose();
700+
tabContentRegions.remove(contentRegion);
701+
getChildren().remove(contentRegion);
702+
}
703+
684704
private void updateTabPosition() {
685705
tabHeaderArea.invalidateScrollOffset();
686706
getSkinnable().applyCss();
@@ -1208,6 +1228,14 @@ private double firstTabIndent() {
12081228
positionInArea(controlButtons, controlStartX, controlStartY, btnWidth, btnHeight,
12091229
/*baseline ignored*/0, HPos.CENTER, VPos.CENTER);
12101230
}
1231+
1232+
void dispose() {
1233+
for (Node child : headersRegion.getChildren()) {
1234+
TabHeaderSkin header = (TabHeaderSkin) child;
1235+
header.dispose();
1236+
}
1237+
controlButtons.dispose();
1238+
}
12111239
} /* End TabHeaderArea */
12121240

12131241

@@ -1495,7 +1523,7 @@ public void handle(MouseEvent me) {
14951523
if (me.getButton().equals(MouseButton.MIDDLE)) {
14961524
if (showCloseButton()) {
14971525
if (behavior.canCloseTab(tab)) {
1498-
removeListeners(tab);
1526+
dispose();
14991527
behavior.closeTab(tab);
15001528
}
15011529
}
@@ -1541,10 +1569,9 @@ private boolean showCloseButton() {
15411569
}
15421570
};
15431571

1544-
private void removeListeners(Tab tab) {
1572+
private void dispose() {
1573+
tab.getStyleClass().removeListener(weakStyleClassListener);
15451574
listener.dispose();
1546-
inner.getChildren().clear();
1547-
getChildren().clear();
15481575
setOnContextMenuRequested(null);
15491576
setOnMousePressed(null);
15501577
}
@@ -1693,7 +1720,7 @@ private void updateContent() {
16931720
}
16941721
}
16951722

1696-
private void removeListeners(Tab tab) {
1723+
public void dispose() {
16971724
tab.selectedProperty().removeListener(weakTabSelectedListener);
16981725
tab.contentProperty().removeListener(weakTabContentListener);
16991726
}
@@ -1785,11 +1812,8 @@ private void positionArrow(Pane btn, StackPane arrow, double x, double y, double
17851812

17861813
getChildren().add(inner);
17871814

1788-
tabPane.sideProperty().addListener(valueModel -> {
1789-
Side tabPosition = getSkinnable().getSide();
1790-
downArrow.setRotate(tabPosition.equals(Side.BOTTOM)? 180.0F : 0.0F);
1791-
});
1792-
tabPane.getTabs().addListener((ListChangeListener<Tab>) c -> setupPopupMenu());
1815+
tabPane.sideProperty().addListener(weakSidePropListener);
1816+
tabPane.getTabs().addListener(weakTabsListenerForPopup);
17931817
showControlButtons = false;
17941818
if (isShowTabsMenu()) {
17951819
showControlButtons = true;
@@ -1798,6 +1822,21 @@ private void positionArrow(Pane btn, StackPane arrow, double x, double y, double
17981822
getProperties().put(ContextMenu.class, popup);
17991823
}
18001824

1825+
InvalidationListener sidePropListener = e -> {
1826+
Side tabPosition = getSkinnable().getSide();
1827+
downArrow.setRotate(tabPosition.equals(Side.BOTTOM)? 180.0F : 0.0F);
1828+
};
1829+
ListChangeListener<Tab> tabsListenerForPopup = e -> setupPopupMenu();
1830+
WeakInvalidationListener weakSidePropListener =
1831+
new WeakInvalidationListener(sidePropListener);
1832+
WeakListChangeListener weakTabsListenerForPopup =
1833+
new WeakListChangeListener<>(tabsListenerForPopup);
1834+
1835+
void dispose() {
1836+
getSkinnable().sideProperty().removeListener(weakSidePropListener);
1837+
getSkinnable().getTabs().removeListener(weakTabsListenerForPopup);
1838+
}
1839+
18011840
private boolean showTabsMenu = false;
18021841

18031842
private void showTabsMenu(boolean value) {
@@ -2058,11 +2097,7 @@ private void setupReordering(StackPane headersRegion) {
20582097
dragState = DragState.NONE;
20592098
this.headersRegion = headersRegion;
20602099
updateListeners();
2061-
getSkinnable().tabDragPolicyProperty().addListener((observable, oldValue, newValue) -> {
2062-
if (oldValue != newValue) {
2063-
updateListeners();
2064-
}
2065-
});
2100+
registerChangeListener(getSkinnable().tabDragPolicyProperty(), inv -> updateListeners());
20662101
}
20672102

20682103
private void handleHeaderMousePressed(MouseEvent event) {

modules/javafx.controls/src/test/java/test/javafx/scene/control/TabPaneTest.java

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1213,10 +1213,8 @@ class TabPaneSkin1 extends TabPaneSkin {
12131213
}
12141214
}
12151215

1216-
@Ignore("JDK-8242621")
12171216
@Test
12181217
public void testNPEOnSwitchSkinAndChangeSelection() {
1219-
// Because of JDK-8242621, this test fails with NPE.
12201218
tabPane.getTabs().addAll(tab1, tab2);
12211219
root.getChildren().add(tabPane);
12221220
stage.show();

modules/javafx.controls/src/test/java/test/javafx/scene/control/skin/SkinCleanupTest.java

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,11 +37,14 @@
3737
import static test.com.sun.javafx.scene.control.infrastructure.ControlSkinFactory.*;
3838

3939
import javafx.scene.Scene;
40+
import javafx.scene.control.skin.TabPaneSkin;
4041
import javafx.scene.control.Button;
4142
import javafx.scene.control.ChoiceBox;
4243
import javafx.scene.control.Control;
4344
import javafx.scene.control.ListCell;
4445
import javafx.scene.control.ListView;
46+
import javafx.scene.control.Tab;
47+
import javafx.scene.control.TabPane;
4548
import javafx.scene.control.ToolBar;
4649
import javafx.scene.control.TreeCell;
4750
import javafx.scene.control.TreeItem;
@@ -257,6 +260,63 @@ public void testToolBarFocus() {
257260
assertEquals("first item in toolbar must be focused", bar.getItems().get(0), scene.getFocusOwner());
258261
}
259262

263+
//-------- TabPane
264+
@Test
265+
public void testChildrenCountAfterSkinIsReplaced() {
266+
TabPane tabPane = new TabPane();
267+
tabPane.getTabs().addAll(new Tab("0"), new Tab("1"));
268+
installDefaultSkin(tabPane);
269+
int childrenCount = tabPane.getChildrenUnmodifiable().size();
270+
replaceSkin(tabPane);
271+
assertEquals(childrenCount, tabPane.getChildrenUnmodifiable().size());
272+
}
273+
274+
@Test
275+
public void testChildrenCountAfterSkinIsRemoved() {
276+
TabPane tabPane = new TabPane();
277+
assertEquals(0, tabPane.getChildrenUnmodifiable().size());
278+
tabPane.getTabs().addAll(new Tab("0"), new Tab("1"));
279+
installDefaultSkin(tabPane);
280+
assertEquals(3, tabPane.getChildrenUnmodifiable().size());
281+
tabPane.setSkin(null);
282+
assertNull(tabPane.getSkin());
283+
assertEquals(0, tabPane.getChildrenUnmodifiable().size());
284+
}
285+
286+
@Test
287+
public void testNPEWhenTabsAddedAfterSkinIsReplaced() {
288+
TabPane tabPane = new TabPane();
289+
tabPane.getTabs().addAll(new Tab("0"), new Tab("1"));
290+
installDefaultSkin(tabPane);
291+
replaceSkin(tabPane);
292+
tabPane.getTabs().addAll(new Tab("2"), new Tab("3"));
293+
}
294+
295+
@Test
296+
public void testNPEWhenTabRemovedAfterSkinIsReplaced() {
297+
TabPane tabPane = new TabPane();
298+
tabPane.getTabs().addAll(new Tab("0"), new Tab("1"));
299+
installDefaultSkin(tabPane);
300+
replaceSkin(tabPane);
301+
tabPane.getTabs().remove(0);
302+
}
303+
304+
@Test
305+
public void testAddRemoveTabsAfterSkinIsReplaced() {
306+
TabPane tabPane = new TabPane();
307+
tabPane.getTabs().addAll(new Tab("0"), new Tab("1"));
308+
installDefaultSkin(tabPane);
309+
assertEquals(2, tabPane.getTabs().size());
310+
assertEquals(3, tabPane.getChildrenUnmodifiable().size());
311+
replaceSkin(tabPane);
312+
tabPane.getTabs().addAll(new Tab("2"), new Tab("3"));
313+
assertEquals(4, tabPane.getTabs().size());
314+
assertEquals(5, tabPane.getChildrenUnmodifiable().size());
315+
tabPane.getTabs().clear();
316+
assertEquals(0, tabPane.getTabs().size());
317+
assertEquals(1, tabPane.getChildrenUnmodifiable().size());
318+
}
319+
260320
//---------------- setup and initial
261321

262322
/**

modules/javafx.controls/src/test/java/test/javafx/scene/control/skin/SkinMemoryLeakTest.java

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -115,8 +115,6 @@ public static Collection<Object[]> data() {
115115
SplitPane.class,
116116
TableRow.class,
117117
TableView.class,
118-
// @Ignore("8242621")
119-
TabPane.class,
120118
// @Ignore("8244419")
121119
TextArea.class,
122120
// @Ignore("8240506")

0 commit comments

Comments
 (0)