Ignore keydown event for tab during IME composition (#37753)
* Stop propagation to prevent additional tab keydown during composition
* Intercept keydown in raw_keyboard, add tests
* Fix dart errors
* Fix dart issue
* Refactor
* Move comment
diff --git a/lib/web_ui/lib/src/engine/dom.dart b/lib/web_ui/lib/src/engine/dom.dart
index 6005f1f..1cb4a75 100644
--- a/lib/web_ui/lib/src/engine/dom.dart
+++ b/lib/web_ui/lib/src/engine/dom.dart
@@ -932,6 +932,7 @@
external bool get metaKey;
external bool? get repeat;
external bool get shiftKey;
+ external bool get isComposing;
external bool getModifierState(String keyArg);
}
diff --git a/lib/web_ui/lib/src/engine/keyboard_binding.dart b/lib/web_ui/lib/src/engine/keyboard_binding.dart
index 0e8226a..e7f67bc 100644
--- a/lib/web_ui/lib/src/engine/keyboard_binding.dart
+++ b/lib/web_ui/lib/src/engine/keyboard_binding.dart
@@ -198,6 +198,7 @@
bool get ctrlKey => _event.ctrlKey;
bool get shiftKey => _event.shiftKey;
bool get metaKey => _event.metaKey;
+ bool get isComposing => _event.isComposing;
bool getModifierState(String key) => _event.getModifierState(key);
void preventDefault() => _event.preventDefault();
diff --git a/lib/web_ui/lib/src/engine/raw_keyboard.dart b/lib/web_ui/lib/src/engine/raw_keyboard.dart
index 57d1611..6157bd7 100644
--- a/lib/web_ui/lib/src/engine/raw_keyboard.dart
+++ b/lib/web_ui/lib/src/engine/raw_keyboard.dart
@@ -88,6 +88,14 @@
return _onMacOs;
}
+ bool _shouldIgnore(FlutterHtmlKeyboardEvent event) {
+ // During IME composition, Tab fires twice (once for composition and once
+ // for regular tabbing behavior), which causes issues. Intercepting the
+ // tab keydown event during composition prevents these issues from occurring.
+ // https://developer.mozilla.org/en-US/docs/Web/API/Element/keydown_event#ignoring_keydown_during_ime_composition
+ return event.type == 'keydown' && event.key == 'Tab' && event.isComposing;
+ }
+
void _handleHtmlEvent(DomEvent domEvent) {
if (!domInstanceOfString(domEvent, 'KeyboardEvent')) {
return;
@@ -96,6 +104,10 @@
final FlutterHtmlKeyboardEvent event = FlutterHtmlKeyboardEvent(domEvent as DomKeyboardEvent);
final String timerKey = event.code!;
+ if (_shouldIgnore(event)) {
+ return;
+ }
+
// Don't handle synthesizing a keyup event for modifier keys
if (!_isModifierKey(event) && _shouldDoKeyGuard()) {
_keydownTimers[timerKey]?.cancel();
diff --git a/lib/web_ui/test/keyboard_test_common.dart b/lib/web_ui/test/keyboard_test_common.dart
index b4b2113..0b447d8 100644
--- a/lib/web_ui/test/keyboard_test_common.dart
+++ b/lib/web_ui/test/keyboard_test_common.dart
@@ -14,6 +14,7 @@
this.timeStamp = 0,
this.repeat = false,
this.keyCode = 0,
+ this.isComposing = false,
bool altKey = false,
bool ctrlKey = false,
bool shiftKey = false,
@@ -51,6 +52,9 @@
num? timeStamp;
@override
+ bool isComposing;
+
+ @override
bool get altKey => modifierState.contains('Alt');
@override
diff --git a/lib/web_ui/test/raw_keyboard_test.dart b/lib/web_ui/test/raw_keyboard_test.dart
index bb490b6..0f137ff 100644
--- a/lib/web_ui/test/raw_keyboard_test.dart
+++ b/lib/web_ui/test/raw_keyboard_test.dart
@@ -299,7 +299,9 @@
RawKeyboard.instance!.dispose();
});
- test('the "Tab" key should never be ignored', () {
+ test(
+ 'the "Tab" key should never be ignored when it is not a part of IME composition',
+ () {
RawKeyboard.initialize();
int count = 0;
@@ -325,6 +327,28 @@
RawKeyboard.instance!.dispose();
});
+ test('Ignores event when Tab key is hit during IME composition', () {
+ RawKeyboard.initialize();
+
+ int count = 0;
+ ui.window.onPlatformMessage = (String channel, ByteData? data,
+ ui.PlatformMessageResponseCallback? callback) {
+ count += 1;
+ final ByteData response = const JSONMessageCodec()
+ .encodeMessage(<String, dynamic>{'handled': true})!;
+ callback!(response);
+ };
+
+ useTextEditingElement((DomElement element) {
+ dispatchKeyboardEvent('keydown',
+ key: 'Tab', code: 'Tab', target: element, isComposing: true);
+
+ expect(count, 0); // no message sent to framework
+ });
+
+ RawKeyboard.instance!.dispose();
+ });
+
testFakeAsync(
'On macOS, synthesize keyup when shortcut is handled by the system',
(FakeAsync async) {
@@ -719,6 +743,7 @@
bool isAltPressed = false,
bool isControlPressed = false,
bool isMetaPressed = false,
+ bool isComposing = false,
int keyCode = 0,
}) {
target ??= domWindow;
@@ -736,6 +761,7 @@
'altKey': isAltPressed,
'ctrlKey': isControlPressed,
'metaKey': isMetaPressed,
+ 'isComposing': isComposing,
'keyCode': keyCode,
'bubbles': true,
'cancelable': true,