8389970: StringIndexOutOfBoundsException when a text containing a mnemonic is removed - #2253
Conversation
|
👋 Welcome back eduardsdv! 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: |
Webrevs
|
|
Thank you for logging the bug and offering to fix it! |
andy-goryachev-oracle
left a comment
There was a problem hiding this comment.
Looks like a good solution.
|
as I commented in the ticket, the mnemonic decorations are not visible on macOS so this issue does not occur there. It still needs to be tested on linux. @arapte could you be the second reviewer please? |
|
It would be nice to have this reviewed. Could you be the second reviewer, @arapte? |
| // Have to do this just in case it needs to be recomputed | ||
| // This is especially important because it also recomputes the 'containsMnemonic' value, | ||
| // which is used by this method later on. | ||
| updateDisplayedText(w, h); | ||
|
|
||
| if (ignoreText) { | ||
| textWidth = textHeight = 0; | ||
| text.setText(""); | ||
| } else { | ||
| updateDisplayedText(w, h); // Have to do this just in case it needs to be recomputed | ||
| textWidth = snapSizeX(Math.min(text.getLayoutBounds().getWidth(), wrapWidth)); |
There was a problem hiding this comment.
ignoreText would be true in two cases, either
- when the text is empty / null or
ContentDisplay.GRAPHIC_ONLYis true. refer LabeledSkinBase.isIgnoreText()
With this change, updateDisplayedText() gets invoked unconditionally even for the scenario when the text is non-empty, but needs to be ignored because ContentDisplay.GRAPHIC_ONLY is true.
Please check if this can cause any issue.
There was a problem hiding this comment.
You are right.
This is my approach to fix the issue by calling the updateDisplayedText() regardless of the value of ignoreText. Otherwise, the containsMnemonic value may be stale.
I added additional tests to check combinations ContentDisplay.GRAPHIC_ONLY and ContentDisplay.TEXT_ONLY with empty and null text.
There was a problem hiding this comment.
When ContentDisplay.GRAPHIC_ONLY is true, and text is non-empty: Calling updateDisplayedText() may result in invoking addMnemonic(); and getChildren().add(mnemonic_underscore);. [ refer the method updateDisplayedText() , unable to share links due to Github outage ]
This can result in addition of an un-required node to scenegraph.
The proposed change fixes the exception, but it seems to be introducing a regression.
There was a problem hiding this comment.
Perhaps another option would be to reset the containsMnemonic flag in the if (ignoreText) block rather than calling updateDisplayedText?
There was a problem hiding this comment.
I also tested a new condition (!ignoreText) in the layoutLabelInArea() method in the following if-clause.
The test are running green and also no additional children are added in case of ContentDisplay.GRAPHIC_ONLY.
Would this be a preferable solution?
Point2D mnemonicPos = null;
double mnemonicWidth = 0.0;
double mnemonicHeight = 0.0;
if (!ignoreText && containsMnemonic) {
final Font font = text.getFont();
String preSt = mnemonicInfo.getText();
boolean isRTL = (labeledNode.getEffectiveNodeOrientation() == NodeOrientation.RIGHT_TO_LEFT);
mnemonicPos = Utils.computeMnemonicPosition(font, preSt, mnemonicInfo.getMnemonicIndex(), this.wrapWidth, labeled.getLineSpacing(), isRTL);
mnemonicWidth = Utils.computeTextWidth(font, preSt.substring(mnemonicInfo.getMnemonicIndex(), mnemonicInfo.getMnemonicIndex() + 1), 0);
mnemonicHeight = Utils.computeTextHeight(font, "_", 0, text.getBoundsType());
}There was a problem hiding this comment.
That might be OK, but I'll defer to @arapte and @andy-goryachev-oracle
There was a problem hiding this comment.
alternatively, to keep the changes to a minimum, we only need to clear containsMnemonic in updateDisplayedText() in case of ContentDisplay.GRAPHIC_ONLY ?
There was a problem hiding this comment.
to keep the changes to a minimum
This is the key point.
The StringIndexOutOfBoundsException occurs because the mnemonicIndex is -1.
Since the mnemonicIndex is checked for being >= 0 in other places in javafx.scene.control.skin.LabeledSkinBase and com.sun.javafx.scene.control.skin.Util classes, I decided to move the updateDisplayedText() back behind the ignoreText condition and to add a new mnemonicIndex >=0 condition to the if-clause instead.
This avoids the error and minimizes the risk of regressions.
Point2D mnemonicPos = null;
double mnemonicWidth = 0.0;
double mnemonicHeight = 0.0;
int mnemonicIndex = mnemonicInfo != null ? mnemonicInfo.getMnemonicIndex() : -1;
if (containsMnemonic && mnemonicIndex >= 0) {
final Font font = text.getFont();
String preSt = mnemonicInfo.getText();
boolean isRTL = (labeledNode.getEffectiveNodeOrientation() == NodeOrientation.RIGHT_TO_LEFT);
mnemonicPos = Utils.computeMnemonicPosition(font, preSt, mnemonicIndex, this.wrapWidth, labeled.getLineSpacing(), isRTL);
mnemonicWidth = Utils.computeTextWidth(font, preSt.substring(mnemonicIndex, mnemonicIndex + 1), 0);
mnemonicHeight = Utils.computeTextHeight(font, "_", 0, text.getBoundsType());
}| assertTrue(containsMnemonicNode.get()); | ||
| } | ||
|
|
||
| label.setContentDisplay(ContentDisplay.GRAPHIC_ONLY); |
There was a problem hiding this comment.
here we tested mnemonic behavior when content display was set to GRAPHIC_ONLY.
would it make sense to set the content display to TEXT_ONLY and re-check as a part of this test?
| tk.firePulse(); | ||
| assertFalse(containsMnemonicNode.get()); | ||
|
|
||
| sl.dispose(); |
There was a problem hiding this comment.
suggestion: to make sure the test cleans the environment even when it fails, you could create a private StageLoader stageLoader field instead of a local variable, and dispose of it if it's not null in @AfterEach.
alternatively, you'll need to dispose of it in a finally block.
| import com.sun.javafx.tk.Toolkit; | ||
| import test.com.sun.javafx.scene.control.infrastructure.StageLoader; | ||
|
|
||
|
|
There was a problem hiding this comment.
unnecessary blank line, please remove.
|
|
||
| // mnemonic -> empty | ||
| label.setText(""); | ||
| tk.firePulse(); |
There was a problem hiding this comment.
could we also check the mnemonic registration?
the registration should exist before L2171 and be removed after L2172
| StageLoader sl = new StageLoader(label); | ||
| tk.firePulse(); | ||
|
|
||
| label.setText("foo_bar"); |
There was a problem hiding this comment.
I am getting SOOB exception if I do
label.setText("test_(t)");
here
This PR fixes the StringIndexOutOfBoundsException, that occurs when an empty text is set to a Labeled, that previously contained a mnemonic.
The reason for the error is that the
updateDisplayedText(double, double), which also updatescontainsMnemonicflag, was not invoked if the text was empty. The value of this flag was stilltruebut the index of the mnemonic character insideMnemonicInfohad already been updated to -1. This led to the StringIndexOutOfBoundsException in the line 611.I fixed it by moving the call to
updateDisplayedText(double, double)outside the if-clause, so that it is always called when the text is being laid out. This should not affect performance because the method already checks whether recalculation is required. If not, it exits quickly.Progress
Issue
Reviewers
Reviewing
Using
gitCheckout this PR locally:
$ git fetch https://git.openjdk.org/jfx.git pull/2253/head:pull/2253$ git checkout pull/2253Update a local copy of the PR:
$ git checkout pull/2253$ git pull https://git.openjdk.org/jfx.git pull/2253/headUsing Skara CLI tools
Checkout this PR locally:
$ git pr checkout 2253View PR using the GUI difftool:
$ git pr show -t 2253Using diff file
Download this PR as a diff file:
https://git.openjdk.org/jfx/pull/2253.diff
Using Webrev
Link to Webrev Comment