-
Notifications
You must be signed in to change notification settings - Fork 481
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
8176270: Adding ChangeListener to TextField.selectedTextProperty causes StringOutOfBoundsException #73
8176270: Adding ChangeListener to TextField.selectedTextProperty causes StringOutOfBoundsException #73
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 |
---|---|---|
|
@@ -164,8 +164,10 @@ protected TextInputControl(final Content content) { | |
int start = sel.getStart(); | ||
int end = sel.getEnd(); | ||
int length = txt.length(); | ||
if (end > start + length) end = length; | ||
if (start > length-1) start = end = 0; | ||
// Ensure that the last character to get is within the bounds of the txt string | ||
if (end >= start + length) end = length-1; | ||
// In case the start is after the whole txt, nothing valid is selected. Thus, return the default. | ||
if (start >= length) return ""; | ||
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 change seems OK, and might be clearer than the existing code, but the existing code is correct, and produces the same effect. 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. yeah, it is correct - but while we are changing the impl, we might go the whole way and clean up :) my pref would be to add the check for start to the short-circuit at the beginning of the method, something like
|
||
return txt.substring(start, end); | ||
} | ||
}); | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -25,6 +25,7 @@ | |
|
||
package test.javafx.scene.control; | ||
|
||
import javafx.application.Platform; | ||
import javafx.beans.InvalidationListener; | ||
import javafx.beans.Observable; | ||
import javafx.beans.property.BooleanProperty; | ||
|
@@ -35,17 +36,21 @@ | |
import javafx.beans.value.ObservableValue; | ||
import javafx.css.CssMetaData; | ||
import javafx.css.StyleableProperty; | ||
import javafx.event.Event; | ||
import javafx.event.EventHandler; | ||
import javafx.scene.Scene; | ||
import javafx.scene.input.KeyCode; | ||
import javafx.scene.input.KeyEvent; | ||
import javafx.scene.input.Clipboard; | ||
import javafx.scene.input.ClipboardContent; | ||
import javafx.scene.layout.VBox; | ||
import javafx.scene.text.Font; | ||
import javafx.scene.layout.StackPane; | ||
import javafx.stage.Stage; | ||
import java.util.Arrays; | ||
import java.util.Collection; | ||
import java.util.concurrent.Semaphore; | ||
|
||
import javafx.scene.control.IndexRange; | ||
import javafx.scene.control.PasswordField; | ||
import javafx.scene.control.TextArea; | ||
|
@@ -2043,6 +2048,105 @@ public void caretAndAnchorPositionAfterSettingText() { | |
tk.firePulse(); | ||
} | ||
|
||
// Test for case 1 of JDK-8176270 | ||
@Test public void addingListenerWorks() { | ||
VBox vBox = new VBox(); | ||
TextField textField = new TextField(); | ||
textField.setText("1234 5678"); | ||
vBox.getChildren().add(textField); | ||
textField.selectedTextProperty() | ||
.addListener((observable -> {})); | ||
|
||
Scene scene = new Scene(vBox); | ||
Stage stage = new Stage(); | ||
stage.setScene(scene); | ||
stage.show(); | ||
} | ||
|
||
// Test for case 2 of JDK-8176270 | ||
@Test public void replaceSelectionWorks() throws Exception { | ||
VBox vBox = new VBox(); | ||
TextField textField = new TextField(); | ||
textField.setText("1234 5678"); | ||
vBox.getChildren().add(textField); | ||
textField.selectedTextProperty() | ||
.addListener((observable -> {})); | ||
|
||
Scene scene = new Scene(vBox); | ||
Stage stage = new Stage(); | ||
stage.setScene(scene); | ||
stage.show(); | ||
|
||
textField.selectedTextProperty() | ||
.addListener(observable -> { | ||
// accessing the selectedTextProperty causes a | ||
// StringOutOfBoundsException | ||
observable.toString(); | ||
}); | ||
textField.positionCaret(5); | ||
Semaphore semaphore = new Semaphore(0); | ||
Platform.runLater(semaphore::release); | ||
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. Since controls tests run using the StubToolkit, |
||
semaphore.acquire(); | ||
|
||
// select 2nd word | ||
textField.selectNextWord(); | ||
semaphore = new Semaphore(0); | ||
Platform.runLater(semaphore::release); | ||
semaphore.acquire(); | ||
|
||
// replace selection | ||
Platform.runLater(() -> {Event.fireEvent(scene, new KeyEvent(KeyEvent.KEY_PRESSED, "", KeyCode.DIGIT0.getName(), KeyCode.DIGIT0, false, false, false, false));}); | ||
Platform.runLater(() -> {Event.fireEvent(scene, new KeyEvent(KeyEvent.KEY_RELEASED, "", KeyCode.DIGIT0.getName(), KeyCode.DIGIT0, false, false, false, false));}); | ||
semaphore = new Semaphore(0); | ||
Platform.runLater(semaphore::release); | ||
semaphore.acquire(); | ||
} | ||
|
||
// Test for workaround of JDK-8176270 | ||
@Test public void accessingTheValueInInvalidationListenerWorks() throws Exception { | ||
VBox vBox = new VBox(); | ||
TextField textField = new TextField(); | ||
textField.setText("1234 5678"); | ||
vBox.getChildren().add(textField); | ||
textField.selectedTextProperty() | ||
.addListener((observable -> {})); | ||
|
||
Scene scene = new Scene(vBox); | ||
Stage stage = new Stage(); | ||
stage.setScene(scene); | ||
stage.show(); | ||
|
||
textField.selectedTextProperty() | ||
.addListener(new InvalidationListener() { | ||
@Override | ||
public void invalidated(Observable observable) { | ||
Platform.runLater(() -> observable.toString()); | ||
} | ||
}); | ||
|
||
textField.positionCaret(5); | ||
Semaphore semaphore = new Semaphore(0); | ||
Platform.runLater(semaphore::release); | ||
semaphore.acquire(); | ||
|
||
// select 2nd word | ||
textField.selectNextWord(); | ||
semaphore = new Semaphore(0); | ||
Platform.runLater(semaphore::release); | ||
semaphore.acquire(); | ||
|
||
// replace selection | ||
Platform.runLater(() -> {Event.fireEvent(scene, | ||
new KeyEvent(KeyEvent.KEY_PRESSED, "", KeyCode.DIGIT0.getName(), KeyCode.DIGIT0, | ||
false, false, false, false));}); | ||
Platform.runLater(() -> {Event.fireEvent(scene, | ||
new KeyEvent(KeyEvent.KEY_RELEASED, "", KeyCode.DIGIT0.getName(), KeyCode.DIGIT0, | ||
false, false, false, false));}); | ||
semaphore = new Semaphore(0); | ||
Platform.runLater(semaphore::release); | ||
semaphore.acquire(); | ||
} | ||
|
||
// TODO tests for Content firing event notification properly | ||
|
||
// TODO tests for Content not allowing illegal characters | ||
|
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.
First, I think the existing test is simply wrong: Why should the
start
value matter when checking whether theend
value needs to be clamped -- it's anend
point not a number of characters. I think the entire problem might be the fact that it is comparing againststart + length
. Second, the value to be clamped to was correct before your change. Thesubstring
method to whichend
is passed is documented as subtracting 1.I think the test should be something like:
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.
on second thought: correct clamping or not, the start/end indices of selection are invalid whenever text was removed/added at an index before the selection - they are in coordinates of the old text, not the new. If they point to an index > new textLength, incorrect clamping will throw. If they are both < new textLength, it'll fire an intermediate changeEvent with incorrect selection, the total sequence beining
oldSelectedText -> incorrectly-shifted-selectedText
incorrectly-shifted-selectedText -> empty
The correct change notification would be, because at the end of the text change, the selection is cleared,
oldSelectedText -> empty
To me, it looks like using a binding here is not the best of options.