-
Notifications
You must be signed in to change notification settings - Fork 583
8389092: PopupWindow.show() unconditionally overwrites user-set scene stylesheets with the owner's stylesheets #2236
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,5 +1,5 @@ | ||
| /* | ||
| * Copyright (c) 2010, 2025, Oracle and/or its affiliates. All rights reserved. | ||
| * Copyright (c) 2010, 2026, 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 | ||
|
|
@@ -458,31 +458,34 @@ public void show(Window ownerWindow, double anchorX, double anchorY) { | |
| } | ||
|
|
||
| private void showImpl(final Window owner) { | ||
| Window rootWindow = getRootWindow(owner); | ||
| if (rootWindow == null) { | ||
| return; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I am just a bit confused here. Referring to old version' line numbers, it looks like we should not have a null value returned from |
||
| } | ||
|
|
||
| // Update the owner field | ||
| this.ownerWindow.set(owner); | ||
| if (owner instanceof PopupWindow) { | ||
| ((PopupWindow)owner).children.add(this); | ||
| } | ||
|
|
||
| // PopupWindow should disappear when owner node is not visible | ||
| if (owner != null) { | ||
| owner.showingProperty().addListener(weakOwnerNodeListener); | ||
| } | ||
| owner.showingProperty().addListener(weakOwnerNodeListener); | ||
|
|
||
| final Scene sceneValue = getScene(); | ||
| SceneHelper.parentEffectiveOrientationInvalidated(sceneValue); | ||
|
|
||
| // JDK-8116444 | ||
| applyStylesheetFromOwner(owner); | ||
|
|
||
| final Scene ownerScene = getRootWindow(owner).getScene(); | ||
| final Scene ownerScene = rootWindow.getScene(); | ||
| if (ownerScene != null) { | ||
| copyStylesheetFromOwnerScene(ownerScene); | ||
|
|
||
| if (sceneValue.getCursor() == null) { | ||
| sceneValue.setCursor(ownerScene.getCursor()); | ||
| } | ||
| } | ||
|
|
||
| // It is required that the root window exist and be visible to show the popup. | ||
| if (getRootWindow(owner).isShowing()) { | ||
| if (rootWindow.isShowing()) { | ||
| // We do show() first so that the width and height of the | ||
| // popup window are initialized. This way the x,y location of the | ||
| // popup calculated below uses the right width and height values for | ||
|
|
@@ -498,14 +501,34 @@ private void showImpl(final Window owner) { | |
| * @param owner the owner {@link Window} | ||
| */ | ||
| void applyStylesheetFromOwner(Window owner) { | ||
| // JDK-8116444 | ||
| Window rootWindow = getRootWindow(owner); | ||
| if (rootWindow == null) { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. same thing - is it even possible for the
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, as you can see in the tests I wrote.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is not a question about the tests you wrote.
you see that |
||
| return; | ||
| } | ||
|
|
||
| final Scene ownerScene = rootWindow.getScene(); | ||
| if (ownerScene == null) { | ||
| return; | ||
| } | ||
|
|
||
| copyStylesheetFromOwnerScene(ownerScene); | ||
| } | ||
|
|
||
| private void copyStylesheetFromOwnerScene(Scene ownerScene) { | ||
| Scene scene = getScene(); | ||
| final Scene ownerScene = getRootWindow(owner).getScene(); | ||
| if (ownerScene != null) { | ||
| if (ownerScene.getUserAgentStylesheet() != null) { | ||
| scene.setUserAgentStylesheet(ownerScene.getUserAgentStylesheet()); | ||
| if (scene.getUserAgentStylesheet() == null && ownerScene.getUserAgentStylesheet() != null) { | ||
| scene.setUserAgentStylesheet(ownerScene.getUserAgentStylesheet()); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. there might be another issue:
since I know reusing the popup (context menu, etc.) is a bad idea, but surprisingly, I saw this happen many times in the past.
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. AFAIK, there is no way that you can actually change the user agent stylesheet. It is read once and then cached. So I think we are actually good here
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Here is the case for the |
||
| } | ||
|
|
||
| List<String> newStylesheets = new ArrayList<>(); | ||
| for (String stylesheet : ownerScene.getStylesheets()) { | ||
| if (!scene.getStylesheets().contains(stylesheet)) { | ||
| newStylesheets.add(stylesheet); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. can a PopupWindow be reused? if so, the stylesheets might accumulate from the earlier cycles. is this ok? should we keep track of the stylesheets added here and remove them on
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Does not happen as we will check with
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I suppose I need to spell this out. L527 adds new stylesheets, but there is not removing operation. So if you keep adding stylesheets and not removing the old stylesheets, they just get accumulated. Think of a scenario when a stylesheet is not sourced from a file/resource, but generated programmatically via a data url. |
||
| } | ||
| scene.getStylesheets().setAll(ownerScene.getStylesheets()); | ||
| } | ||
|
|
||
| scene.getStylesheets().addAll(newStylesheets); | ||
| } | ||
|
|
||
| /** | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,5 +1,5 @@ | ||
| /* | ||
| * Copyright (c) 2010, 2024, Oracle and/or its affiliates. All rights reserved. | ||
| * Copyright (c) 2010, 2026, 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 | ||
|
|
@@ -48,6 +48,9 @@ | |
| import javafx.scene.input.MouseEvent; | ||
| import javafx.scene.shape.Rectangle; | ||
|
|
||
| import java.nio.charset.StandardCharsets; | ||
| import java.util.Base64; | ||
| import java.util.List; | ||
| import java.util.concurrent.atomic.AtomicBoolean; | ||
| import javafx.scene.Node; | ||
| import javafx.scene.ParentShim; | ||
|
|
@@ -63,6 +66,7 @@ | |
| import org.junit.jupiter.api.Test; | ||
| import static org.junit.jupiter.api.Assertions.assertEquals; | ||
| import static org.junit.jupiter.api.Assertions.assertFalse; | ||
| import static org.junit.jupiter.api.Assertions.assertNull; | ||
| import static org.junit.jupiter.api.Assertions.assertTrue; | ||
| import static org.junit.jupiter.api.Assertions.assertThrows; | ||
| import test.com.sun.javafx.stage.PopupRootHelper; | ||
|
|
@@ -108,10 +112,8 @@ public void testShow() { | |
| assertFalse(p2.isShowing()); | ||
|
|
||
| // test showing popup without parent | ||
| // TODO should result in an exception | ||
| // Popup p3 = new Popup(); | ||
| // p3.show(null); | ||
| // assertFalse(p3.isVisible()); | ||
| Popup p3 = new Popup(); | ||
| assertThrows(NullPointerException.class, () -> p3.show(null)); | ||
| } | ||
|
|
||
| @Test | ||
|
|
@@ -498,7 +500,7 @@ public void testPeerListener() { | |
| } | ||
|
|
||
| @Test | ||
| public void testDefautValueOfAutofix() { | ||
| public void testDefaultValueOfAutofix() { | ||
| Popup p = new Popup(); | ||
| assertTrue(p.isAutoFix()); | ||
| assertTrue(p.autoFixProperty().get()); | ||
|
|
@@ -802,6 +804,104 @@ public void testCursorInheritance() { | |
|
|
||
| } | ||
|
|
||
| @Test | ||
| public void testShowWithOwnerWithoutRootWindow() { | ||
| final Popup ownerPopup = new Popup(); | ||
| final Popup popup = new Popup(); | ||
|
|
||
| popup.show(ownerPopup); | ||
|
|
||
| assertFalse(popup.isShowing()); | ||
| } | ||
|
|
||
| @Test | ||
| public void testShowWithOwnerWithoutScene() { | ||
| final Stage ownerWithoutScene = new Stage(); | ||
|
|
||
| final Popup popup = new Popup(); | ||
|
|
||
| popup.show(ownerWithoutScene); | ||
|
|
||
| assertFalse(popup.isShowing()); | ||
| } | ||
|
|
||
| @Test | ||
| public void testShowKeepsCursorOfPopup() { | ||
| stage.getScene().setCursor(Cursor.CLOSED_HAND); | ||
|
|
||
| final Popup popup = new Popup(); | ||
| popup.getScene().setCursor(Cursor.TEXT); | ||
|
|
||
| popup.show(stage); | ||
|
|
||
| assertEquals(Cursor.TEXT, popup.getScene().getCursor()); | ||
| } | ||
|
|
||
| @Test | ||
| public void testShowAppliesUserAgentStylesheetOfOwnerWhenPopupHasNone() { | ||
| final String ownerUserAgentStylesheet = toBase64(".owner-ua { -fx-fill: red; }"); | ||
| scene.setUserAgentStylesheet(ownerUserAgentStylesheet); | ||
|
|
||
| final Popup popup = new Popup(); | ||
| assertNull(popup.getScene().getUserAgentStylesheet()); | ||
|
|
||
| popup.show(stage); | ||
|
|
||
| assertEquals(ownerUserAgentStylesheet, popup.getScene().getUserAgentStylesheet()); | ||
| } | ||
|
|
||
| @Test | ||
| public void testShowKeepsStylesheetsOfPopupAndAddsStylesheetsOfOwner() { | ||
| final String ownerUserAgentStylesheet = toBase64(".owner-ua { -fx-fill: red; }"); | ||
| final String ownerStylesheet = toBase64(".owner { -fx-fill: green; }"); | ||
| final String popupUserAgentStylesheet = toBase64(".popup-ua { -fx-fill: blue; }"); | ||
| final String popupStylesheet = toBase64(".popup { -fx-fill: yellow; }"); | ||
|
|
||
| scene.setUserAgentStylesheet(ownerUserAgentStylesheet); | ||
| scene.getStylesheets().add(ownerStylesheet); | ||
|
|
||
| final Popup popup = new Popup(); | ||
| popup.getScene().setUserAgentStylesheet(popupUserAgentStylesheet); | ||
| popup.getScene().getStylesheets().add(popupStylesheet); | ||
|
|
||
| popup.show(stage); | ||
|
|
||
| assertEquals(popupUserAgentStylesheet, popup.getScene().getUserAgentStylesheet()); | ||
| assertEquals(List.of(popupStylesheet, ownerStylesheet), popup.getScene().getStylesheets()); | ||
| } | ||
|
|
||
| @Test | ||
| public void testShowTwiceAddsStylesheetOfOwnerOnlyOnce() { | ||
| final String ownerStylesheet = toBase64(".owner { -fx-fill: green; }"); | ||
| scene.getStylesheets().add(ownerStylesheet); | ||
|
|
||
| final Popup popup = new Popup(); | ||
|
|
||
| popup.show(stage); | ||
| popup.hide(); | ||
| popup.show(stage); | ||
|
|
||
| assertEquals(List.of(ownerStylesheet), popup.getScene().getStylesheets()); | ||
| } | ||
|
|
||
| @Test | ||
| public void testShowDoesNotAddStylesheetTwiceWhenPopupAndOwnerShareIt() { | ||
| final String sharedStylesheet = toBase64(".owner { -fx-fill: green; }"); | ||
|
|
||
| scene.getStylesheets().addAll(sharedStylesheet); | ||
|
|
||
| final Popup popup = new Popup(); | ||
| popup.getScene().getStylesheets().add(sharedStylesheet); | ||
|
|
||
| popup.show(stage); | ||
|
|
||
| assertEquals(List.of(sharedStylesheet), popup.getScene().getStylesheets()); | ||
| } | ||
|
|
||
| private String toBase64(String stylesheet) { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. should probably be static... |
||
| return "data:base64," + Base64.getEncoder().encodeToString(stylesheet.getBytes(StandardCharsets.UTF_8)); | ||
| } | ||
|
|
||
| private static final class EventCounter implements EventHandler<Event> { | ||
| private int counter; | ||
|
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
not equivalent change: the old code set this.ownerWindow (to null in this case, see L467)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You have a point but I don't see how this could be a problem. I can't imagine a usecase where you want to call
showjust to reset the ownerThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was pointing out that the change is not equivalent - in the old code, a
nullowner causesthis.ownerWindowbe set to null, in the new code it does not.