Skip to content

Commit 93ee882

Browse files
vaadin-botArtur-
andauthored
fix: Prevent NPE in ShortcutRegistration when lifecycleOwner is null (#23465) (#23478)
Add null guard in fireShortcutEvent() to handle the case where a KeyDown event fires after the shortcut's lifecycle owner has been detached and remove() has set lifecycleOwner to null. Fixes #19769 Co-authored-by: Artur Signell <artur@vaadin.com>
1 parent e720951 commit 93ee882

File tree

2 files changed

+30
-0
lines changed

2 files changed

+30
-0
lines changed

flow-server/src/main/java/com/vaadin/flow/component/ShortcutRegistration.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -673,6 +673,9 @@ private Component getComponentEventSource(int listenOnIndex) {
673673
}
674674

675675
private void fireShortcutEvent(Component component) {
676+
if (lifecycleOwner == null) {
677+
return;
678+
}
676679
if (ancestorsOrSelfAreVisible(lifecycleOwner) && (lifecycleOwner
677680
.getElement().isEnabled()
678681
|| DisabledUpdateMode.ALWAYS.equals(getDisabledUpdateMode()))) {

flow-server/src/test/java/com/vaadin/flow/component/ShortcutRegistrationTest.java

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
package com.vaadin.flow.component;
1818

19+
import java.lang.reflect.Field;
1920
import java.util.ArrayList;
2021
import java.util.Arrays;
2122
import java.util.List;
@@ -617,6 +618,32 @@ public void constructedRegistration_lifecycleOnwerHasInvisibleParent_shorcutEven
617618
Assert.assertNull(event.get());
618619
}
619620

621+
@Test
622+
public void constructedRegistration_lifecycleOwnerNulledBeforeKeyEvent_noNPE()
623+
throws Exception {
624+
AtomicReference<ShortcutEvent> event = new AtomicReference<>();
625+
626+
ShortcutRegistration registration = new ShortcutRegistration(
627+
lifecycleOwner, () -> listenOn, event::set, Key.KEY_A);
628+
629+
mockLifecycle(true);
630+
clientResponse();
631+
632+
// Simulate the race condition: null out lifecycleOwner while
633+
// the KeyDown listener is still registered
634+
Field field = ShortcutRegistration.class
635+
.getDeclaredField("lifecycleOwner");
636+
field.setAccessible(true);
637+
field.set(registration, null);
638+
639+
// Fire KeyDown event — should not throw NPE
640+
listenOn[0].getEventBus()
641+
.fireEvent(new KeyDownEvent(listenOn[0], Key.KEY_A.toString()));
642+
643+
// Shortcut event should not have been fired
644+
Assert.assertNull(event.get());
645+
}
646+
620647
@Test
621648
public void constructedRegistration_lifeCycleOwnerIsDetached_detachListenerIsDeregisteredFromListenOnComponents() {
622649
AtomicReference<ComponentEventListener> detachListener = new AtomicReference<>();

0 commit comments

Comments
 (0)