-
Notifications
You must be signed in to change notification settings - Fork 60
8301989: new javax.swing.text.DefaultCaret().setBlinkRate(N) results in NPE #122
Conversation
…in NPE Check if the component is associated with the caret before calling methods from it. Added test case that will make sure that will not happen again.
👋 Welcome back kizune! A progress list of the required criteria for merging this PR into |
@azuev-java The following label will be automatically applied to this pull request:
When this pull request is ready to be reviewed, an "RFR" email will be sent to the corresponding mailing list. If you would like to change these labels, use the /label pull request command. |
Webrevs
|
@@ -1050,7 +1050,7 @@ public void setBlinkRate(int rate) { | |||
throw new IllegalArgumentException("Invalid blink rate: " + rate); | |||
} | |||
if (rate != 0) { | |||
if (component.isEditable()) { | |||
if (component != null && component.isEditable()) { | |||
if (flasher == null) { |
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.
So later if there is a non-editable component, what happens ?
Some existing logic for that will kick in ?
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.
Yes, when the the caret is to be assigned to a component it will reset the blink rate and the logic will kick in. Manually tested with the possible scenarios like adding custom set blink rate cursor to a non-editable component and then turn it into editable later, add to editable and switch to non-editable later and so on.
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.
So if I go back and look at the code before your previous fix, there was no check,
so the timer is created and then I'd expect applies when the caret is installed on a component.
Here that behaviour is changed, so that if we create a caret, then call setBlinkRate(), and then install()
then the blink rate appears to be ignored whereas before 4512626 it was applied.
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.
if we create a caret, then call setBlinkRate(), and then install()
then the blink rate appears to be ignored whereas before 4512626 it was applied.
Ok, if the text component is already visible and editable and focused and we install the new caret then the blink rate will not have an effect until the focus is lost and regained or until it became non-editable - and i think that was close to what we had before and there is a bug to investigate and optimize such corner case. In all other cases the code works fine. The value set is being preserved and will be reactivated when any of the methods such as focusGained or when the component installUI is called - if the cursor is set before component becomes visible - will cause to re-evaluate the blink rate and the stored value set by user will take place.
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.
Suppose I have this code
Caret c = new DefaultCaret()
c.setBlinkRate(50);
c.install(editableJComponent);
BEFORE 4512626 the JDK code looked like this
if (rate != 0) {
if (flasher == null) {
flasher = new Timer(rate, handler);
}
flasher.setDelay(rate);
}
With your fix it looks like this
if (rate != 0) {
if (component != null && component.isEditable()) {
if (flasher == null) {
flasher = new Timer(rate, handler);
}
}
}
So with my sample application code there's a change that my blink rate is completely lost.
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.
So it seems that in such a case, "savedBlinkRate" is still set and that is used to both report the value of blink rate and create & start a Timer when it is needed.
It is a little hard to follow through all those "when it is needed" cases but if we are attached to an enabled & editable component when focusGained() is called it should then start the flasher timer.
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.
Using the simple code like this one:
caret.setBlinkRate(10);
JFrame frame = new JFrame("test");
JTextArea area = new JTextArea(null, "Some text to make it non-empty", 5, 40);
area.setEditable(false);
area.setCaret(caret);
frame.setLayout(new BorderLayout());
frame.add(area, BorderLayout.CENTER);
frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
frame.pack();
frame.setVisible(true);
SwingUtilities.invokeAndWait(() -> area.requestFocus(false));
SwingUtilities.invokeAndWait(() -> area.setEditable(true));
SwingUtilities.invokeAndWait(() -> {
System.out.println(area.getCaret().getBlinkRate());
});
i tested many possible combinations to see that the blink rate specified in setBlinkRate is preserved and not being overwritten by some code. In some corner cases the cursor does not blink despite reporting the correct blink rate until the focus is lost and regained - but that is still better than the version before JDK-4512626 where in the same corner case the cursor is not visible until the focus is lost and gained again. There is a bug JDK-8299048 that tracks the corner case i am talking about.
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.
The changes seem to fix the NPE as expected. When testing, the provided test fails before the patch and passes after the patch is applied.
I was curious if the 2nd conditional was necessary to check for "component == null", but it definitely is. Also, your test checks for this, so that helped clear it up.
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.
The patch works as expected. NPE problem is resolved. Additionally checked the fix by adding DefaultCaret to component (JTextField) in enabled and disabled states, it works as expected - Blinking caret when enabled and no caret when disabled.
Another question, in the branch where the app calls Why do we not clear these unconditionally ? |
Because then when we set component non-editable the stored blink rate will be lost and we will not be able to restore it when component will be switched to editable again. Same with the component not assigned - if we assign it to the non-editable component and then flip the editable property then we would not have the previous blink rate saved and will not know what value to set. |
I am not following .. this is about the branch where the app is setting blink rate to zero. |
When we receive focus on non-editable component we set blink rate to 0 to stop caret blinking. If we do not add this condition then we immediately clear off the saved blink rate. |
How do you tell that apart from the app directly clearing it ? ie the APP calling setBlinkRate(0) ? |
I do not - while component is non-editable you can not change blink rate to 0, once the component will become editable the last non-zero value will be restored. |
But that seems wrong. Why is it not wrong ? |
It might be an oversight of the initial fix, if you think it is wrong we can submit a bug and i can fix it. I definitely do not think it is critical and i do not think it is related to the nature of this change. |
Well .. it does seem wrong to me .. I was hoping you'd be able to explain why its OK. |
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.
Approving as critical to fix the NPE.
There seems possible there may be followup work in a later release.
@azuev-java This change now passes all automated pre-integration checks. ℹ️ This project also has non-automated pre-integration requirements. Please see the file CONTRIBUTING.md for details. After integration, the commit message for the final commit will be:
You can use pull request commands such as /summary, /contributor and /issue to adjust it as needed. At the time when this comment was updated there had been 1 new commit pushed to the
Please see this link for an up-to-date comparison between the source branch of this pull request and the ➡️ To integrate this PR with the above commit message to the |
There will be. |
/integrate |
@azuev-java Pushed as commit e81f20b. 💡 You may see a message that your pull request was closed with unmerged commits. This can be safely ignored. |
Check if the component is associated with the caret before calling methods from it. Added test case that will make sure that will not happen again.
Progress
Issue
Reviewers
Reviewing
Using
git
Checkout this PR locally:
$ git fetch https://git.openjdk.org/jdk20 pull/122/head:pull/122
$ git checkout pull/122
Update a local copy of the PR:
$ git checkout pull/122
$ git pull https://git.openjdk.org/jdk20 pull/122/head
Using Skara CLI tools
Checkout this PR locally:
$ git pr checkout 122
View PR using the GUI difftool:
$ git pr show -t 122
Using diff file
Download this PR as a diff file:
https://git.openjdk.org/jdk20/pull/122.diff