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
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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