8389092: PopupWindow.show() unconditionally overwrites user-set scene stylesheets with the owner's stylesheets - #2236
Conversation
… stylesheets with the owner's stylesheets
|
👋 Welcome back mhanl! A progress list of the required criteria for merging this PR into |
|
❗ This change is not yet ready to be integrated. |
|
The total number of required reviews for this PR has been set to 2 based on the presence of this label: |
|
|
||
| private void showImpl(final Window owner) { | ||
| Window rootWindow = getRootWindow(owner); | ||
| if (rootWindow == null) { |
There was a problem hiding this comment.
not equivalent change: the old code set this.ownerWindow (to null in this case, see L467)
There was a problem hiding this comment.
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 show just to reset the owner
There was a problem hiding this comment.
I was pointing out that the change is not equivalent - in the old code, a null owner causes this.ownerWindow be set to null, in the new code it does not.
| private void showImpl(final Window owner) { | ||
| Window rootWindow = getRootWindow(owner); | ||
| if (rootWindow == null) { | ||
| return; |
There was a problem hiding this comment.
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 getRootWindow(owner): it's being dereferenced in L477 and also L485.
| void applyStylesheetFromOwner(Window owner) { | ||
| // JDK-8116444 | ||
| Window rootWindow = getRootWindow(owner); | ||
| if (rootWindow == null) { |
There was a problem hiding this comment.
same thing - is it even possible for the rootWindow to be null?
There was a problem hiding this comment.
Yes, as you can see in the tests I wrote.
There was a problem hiding this comment.
This is not a question about the tests you wrote.
Sorry, I need to spell my question out. Looking at (old) L502:
final Scene ownerScene = getRootWindow(owner).getScene();
you see that getScene() dereferences the root window, so it can't be null there, I guess, otherwise we had seen an NPE.
| List<String> newStylesheets = new ArrayList<>(); | ||
| for (String stylesheet : ownerScene.getStylesheets()) { | ||
| if (!scene.getStylesheets().contains(stylesheet)) { | ||
| newStylesheets.add(stylesheet); |
There was a problem hiding this comment.
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 hide()?
There was a problem hiding this comment.
Does not happen as we will check with contains. There is also a test for this scenario.
There was a problem hiding this comment.
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.
| if (ownerScene.getUserAgentStylesheet() != null) { | ||
| scene.setUserAgentStylesheet(ownerScene.getUserAgentStylesheet()); | ||
| if (scene.getUserAgentStylesheet() == null && ownerScene.getUserAgentStylesheet() != null) { | ||
| scene.setUserAgentStylesheet(ownerScene.getUserAgentStylesheet()); |
There was a problem hiding this comment.
there might be another issue:
- owner A has the user agent stylesheet A.css
- a popup with no stylesheet is shown with the owner A. A.css is shown
- the popup gets hidden
- either owner A changes the stylesheet to B.css, or the popup is reusing with a different owner
- the popup gets shown
since scene.getUserAgentStylesheet() is already A.css, this code does not set the new stylesheet, leaving the popup with a wrong style.
I know reusing the popup (context menu, etc.) is a bad idea, but surprisingly, I saw this happen many times in the past.
There was a problem hiding this comment.
AFAIK, there is no way that you can actually change the user agent stylesheet. It is read once and then cached.
See e.g. #525, where a user proposed to change that.
So I think we are actually good here
There was a problem hiding this comment.
Here is the case for the PopupTest that works in master and fails with this fix:
@Test
public void updateStylesheetFromOwnerStage() {
String cssA = toBase64(".root { -fx-fill: green; }");
String cssB = toBase64(".root { -fx-fill: red; }");
scene.setUserAgentStylesheet(cssA);
Popup p = new Popup();
p.show(stage);
assertEquals(cssA, p.getScene().getUserAgentStylesheet());
p.hide();
scene.setUserAgentStylesheet(cssB);
p.show(stage);
assertEquals(cssB, p.getScene().getUserAgentStylesheet());
}
| assertEquals(List.of(sharedStylesheet), popup.getScene().getStylesheets()); | ||
| } | ||
|
|
||
| private String toBase64(String stylesheet) { |
There was a problem hiding this comment.
should probably be static...
and since we are using it more than once, maybe we can move it to a new graphics-specific Utils class?
PopupWindowwill always overwrite itsScene(user agent) stylesheets when an owner was set andshowis called.Code like this:
will do nothing, because your added stylesheets will be later overwritten when
showis called.Andy and I were already wondering about this behavior two years ago: #1394 (comment)
I can't see any reason why we should do that. Instead, this PR will only add the stylesheets of the owner if they do not exist already.
Additionally, we will not overwrite the user agent stylesheet if it was already set.
Added tests for all combinations I can think of. This PR also fixes NPEs that can happen when the owner window has no 'root window'.
There is already a test for the
Cursorbehavior, but there was none that verifies that theCursoris not overwritten, so added one as well.jfx/modules/javafx.graphics/src/main/java/javafx/stage/PopupWindow.java
Lines 479 to 481 in 05a7b6d
Now, we will never overwrite anything that the developer set (before
showing). And as we can see above, this was already done this way with theCursor.Progress
Issue
Reviewing
Using
gitCheckout this PR locally:
$ git fetch https://git.openjdk.org/jfx.git pull/2236/head:pull/2236$ git checkout pull/2236Update a local copy of the PR:
$ git checkout pull/2236$ git pull https://git.openjdk.org/jfx.git pull/2236/headUsing Skara CLI tools
Checkout this PR locally:
$ git pr checkout 2236View PR using the GUI difftool:
$ git pr show -t 2236Using diff file
Download this PR as a diff file:
https://git.openjdk.org/jfx/pull/2236.diff
Using Webrev
Link to Webrev Comment