Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

[web] Ignore keydown event for tab during IME composition #37753

Merged
merged 9 commits into from
Nov 28, 2022
Merged
1 change: 1 addition & 0 deletions lib/web_ui/lib/src/engine/dom.dart
Original file line number Diff line number Diff line change
Expand Up @@ -932,6 +932,7 @@ extension DomKeyboardEventExtension on DomKeyboardEvent {
external bool get metaKey;
external bool? get repeat;
external bool get shiftKey;
external bool get isComposing;
external bool getModifierState(String keyArg);
}

Expand Down
1 change: 1 addition & 0 deletions lib/web_ui/lib/src/engine/keyboard_binding.dart
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,7 @@ class FlutterHtmlKeyboardEvent {
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();
Expand Down
12 changes: 12 additions & 0 deletions lib/web_ui/lib/src/engine/raw_keyboard.dart
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,14 @@ class RawKeyboard {
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;
Expand All @@ -96,6 +104,10 @@ class RawKeyboard {
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();
Expand Down
4 changes: 4 additions & 0 deletions lib/web_ui/test/keyboard_test_common.dart
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ class MockKeyboardEvent implements FlutterHtmlKeyboardEvent {
this.timeStamp = 0,
this.repeat = false,
this.keyCode = 0,
this.isComposing = false,
bool altKey = false,
bool ctrlKey = false,
bool shiftKey = false,
Expand Down Expand Up @@ -50,6 +51,9 @@ class MockKeyboardEvent implements FlutterHtmlKeyboardEvent {
@override
num? timeStamp;

@override
bool isComposing;

@override
bool get altKey => modifierState.contains('Alt');

Expand Down
28 changes: 27 additions & 1 deletion lib/web_ui/test/raw_keyboard_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -299,7 +299,9 @@ void testMain() {
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;
Expand All @@ -325,6 +327,28 @@ void testMain() {
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) {
Expand Down Expand Up @@ -719,6 +743,7 @@ DomKeyboardEvent dispatchKeyboardEvent(
bool isAltPressed = false,
bool isControlPressed = false,
bool isMetaPressed = false,
bool isComposing = false,
int keyCode = 0,
}) {
target ??= domWindow;
Expand All @@ -736,6 +761,7 @@ DomKeyboardEvent dispatchKeyboardEvent(
'altKey': isAltPressed,
'ctrlKey': isControlPressed,
'metaKey': isMetaPressed,
'isComposing': isComposing,
'keyCode': keyCode,
'bubbles': true,
'cancelable': true,
Expand Down