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

Commit 8bce406

Browse files
committed
Reland: [web] Ensure handled key event is not propagated to IM
1 parent 43653c5 commit 8bce406

File tree

6 files changed

+74
-20
lines changed

6 files changed

+74
-20
lines changed

lib/web_ui/lib/src/engine/keyboard_binding.dart

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import 'browser_detection.dart';
1313
import 'dom.dart';
1414
import 'key_map.g.dart';
1515
import 'platform_dispatcher.dart';
16+
import 'raw_keyboard.dart';
1617
import 'semantics.dart';
1718

1819
typedef _VoidCallback = void Function();
@@ -104,9 +105,12 @@ class KeyboardBinding {
104105
_addEventListener('keydown', (DomEvent domEvent) {
105106
final FlutterHtmlKeyboardEvent event = FlutterHtmlKeyboardEvent(domEvent as DomKeyboardEvent);
106107
_converter.handleEvent(event);
108+
RawKeyboard.instance?.handleHtmlEvent(domEvent);
107109
});
108-
_addEventListener('keyup', (DomEvent event) {
109-
_converter.handleEvent(FlutterHtmlKeyboardEvent(event as DomKeyboardEvent));
110+
_addEventListener('keyup', (DomEvent domEvent) {
111+
final FlutterHtmlKeyboardEvent event = FlutterHtmlKeyboardEvent(domEvent as DomKeyboardEvent);
112+
_converter.handleEvent(event);
113+
RawKeyboard.instance?.handleHtmlEvent(domEvent);
110114
});
111115
}
112116

@@ -209,6 +213,7 @@ class FlutterHtmlKeyboardEvent {
209213

210214
bool getModifierState(String key) => _event.getModifierState(key);
211215
void preventDefault() => _event.preventDefault();
216+
void stopPropagation() => _event.stopPropagation();
212217
bool get defaultPrevented => _event.defaultPrevented;
213218
}
214219

lib/web_ui/lib/src/engine/raw_keyboard.dart

Lines changed: 5 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -15,15 +15,6 @@ import 'services.dart';
1515
/// Provides keyboard bindings, such as the `flutter/keyevent` channel.
1616
class RawKeyboard {
1717
RawKeyboard._(this._onMacOs) {
18-
_keydownListener = createDomEventListener((DomEvent event) {
19-
_handleHtmlEvent(event);
20-
});
21-
domWindow.addEventListener('keydown', _keydownListener);
22-
23-
_keyupListener = createDomEventListener((DomEvent event) {
24-
_handleHtmlEvent(event);
25-
});
26-
domWindow.addEventListener('keyup', _keyupListener);
2718
registerHotRestartListener(() {
2819
dispose();
2920
});
@@ -34,6 +25,9 @@ class RawKeyboard {
3425
/// Use the [instance] getter to get the singleton after calling this method.
3526
static void initialize({bool onMacOs = false}) {
3627
_instance ??= RawKeyboard._(onMacOs);
28+
// KeyboardBinding is responsible for forwarding the keyboard
29+
// events to the RawKeyboard handler.
30+
KeyboardBinding.initInstance();
3731
}
3832

3933
/// The [RawKeyboard] singleton.
@@ -46,24 +40,16 @@ class RawKeyboard {
4640
/// if no repeat events were received.
4741
final Map<String, Timer> _keydownTimers = <String, Timer>{};
4842

49-
DomEventListener? _keydownListener;
50-
DomEventListener? _keyupListener;
51-
5243
/// Uninitializes the [RawKeyboard] singleton.
5344
///
5445
/// After calling this method this object becomes unusable and [instance]
5546
/// becomes `null`. Call [initialize] again to initialize a new singleton.
5647
void dispose() {
57-
domWindow.removeEventListener('keydown', _keydownListener);
58-
domWindow.removeEventListener('keyup', _keyupListener);
59-
6048
for (final String key in _keydownTimers.keys) {
6149
_keydownTimers[key]!.cancel();
6250
}
6351
_keydownTimers.clear();
6452

65-
_keydownListener = null;
66-
_keyupListener = null;
6753
_instance = null;
6854
}
6955

@@ -96,7 +82,7 @@ class RawKeyboard {
9682
return event.type == 'keydown' && event.key == 'Tab' && event.isComposing;
9783
}
9884

99-
void _handleHtmlEvent(DomEvent domEvent) {
85+
void handleHtmlEvent(DomEvent domEvent) {
10086
if (!domInstanceOfString(domEvent, 'KeyboardEvent')) {
10187
return;
10288
}
@@ -158,6 +144,7 @@ class RawKeyboard {
158144
if (jsonResponse['handled'] as bool) {
159145
// If the framework handled it, then don't propagate it any further.
160146
event.preventDefault();
147+
event.stopPropagation();
161148
}
162149
},
163150
);

lib/web_ui/test/common/keyboard_test_common.dart

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ class MockKeyboardEvent implements FlutterHtmlKeyboardEvent {
2222
bool altGrKey = false,
2323
this.location = 0,
2424
this.onPreventDefault,
25+
this.onStopPropagation,
2526
}) : modifierState =
2627
<String>{
2728
if (altKey) 'Alt',
@@ -84,6 +85,12 @@ class MockKeyboardEvent implements FlutterHtmlKeyboardEvent {
8485
bool get defaultPrevented => _defaultPrevented;
8586
bool _defaultPrevented = false;
8687

88+
@override
89+
void stopPropagation() {
90+
onStopPropagation?.call();
91+
}
92+
VoidCallback? onStopPropagation;
93+
8794
static bool get lastDefaultPrevented => _lastEvent?.defaultPrevented ?? false;
8895
static MockKeyboardEvent? _lastEvent;
8996
}

lib/web_ui/test/engine/raw_keyboard_test.dart

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,10 @@ void testMain() {
5252

5353
DomKeyboardEvent event;
5454

55+
// Dispatch a keydown event first so that KeyboardBinding will recognize the keyup event.
56+
// and will not set preventDefault on it.
57+
event = dispatchKeyboardEvent('keydown', key: 'SomeKey', code: 'SomeCode', keyCode: 1);
58+
5559
event = dispatchKeyboardEvent('keyup', key: 'SomeKey', code: 'SomeCode', keyCode: 1);
5660

5761
expect(event.defaultPrevented, isFalse);

lib/web_ui/test/engine/text_editing_test.dart

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,14 @@ import 'package:test/test.dart';
1212
import 'package:ui/src/engine.dart' show flutterViewEmbedder;
1313
import 'package:ui/src/engine/browser_detection.dart';
1414
import 'package:ui/src/engine/dom.dart';
15+
import 'package:ui/src/engine/raw_keyboard.dart';
1516
import 'package:ui/src/engine/services.dart';
1617
import 'package:ui/src/engine/text_editing/autofill_hint.dart';
1718
import 'package:ui/src/engine/text_editing/input_type.dart';
1819
import 'package:ui/src/engine/text_editing/text_editing.dart';
1920
import 'package:ui/src/engine/util.dart';
2021
import 'package:ui/src/engine/vector_math.dart';
22+
import 'package:ui/ui.dart' as ui;
2123

2224
import '../common/spy.dart';
2325
import '../common/test_initialization.dart';
@@ -370,6 +372,52 @@ Future<void> testMain() async {
370372
expect(lastInputAction, 'TextInputAction.done');
371373
});
372374

375+
test('handling keyboard event prevents triggering input action', () {
376+
final ui.PlatformMessageCallback? savedCallback = ui.window.onPlatformMessage;
377+
378+
bool markTextEventHandled = false;
379+
ui.window.onPlatformMessage = (String channel, ByteData? data,
380+
ui.PlatformMessageResponseCallback? callback) {
381+
final ByteData response = const JSONMessageCodec()
382+
.encodeMessage(<String, dynamic>{'handled': markTextEventHandled})!;
383+
callback!(response);
384+
};
385+
RawKeyboard.initialize();
386+
387+
final InputConfiguration config = InputConfiguration();
388+
editingStrategy!.enable(
389+
config,
390+
onChange: trackEditingState,
391+
onAction: trackInputAction,
392+
);
393+
394+
// No input action so far.
395+
expect(lastInputAction, isNull);
396+
397+
markTextEventHandled = true;
398+
dispatchKeyboardEvent(
399+
editingStrategy!.domElement!,
400+
'keydown',
401+
keyCode: _kReturnKeyCode,
402+
);
403+
404+
// Input action prevented by platform message callback.
405+
expect(lastInputAction, isNull);
406+
407+
markTextEventHandled = false;
408+
dispatchKeyboardEvent(
409+
editingStrategy!.domElement!,
410+
'keydown',
411+
keyCode: _kReturnKeyCode,
412+
);
413+
414+
// Input action received.
415+
expect(lastInputAction, 'TextInputAction.done');
416+
417+
ui.window.onPlatformMessage = savedCallback;
418+
RawKeyboard.instance?.dispose();
419+
});
420+
373421
test('Triggers input action in multi-line mode', () {
374422
final InputConfiguration config = InputConfiguration(
375423
inputType: EngineInputType.multiline,

third_party/web_locale_keymap/lib/web_locale_keymap/locale_keymap.dart

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,9 @@ class LocaleKeymap {
4141
return eventKeyCode;
4242
}
4343
if (result == null) {
44+
if ((eventCode ?? '').isEmpty && (eventKey ?? '').isEmpty) {
45+
return null;
46+
}
4447
final int? heuristicResult = heuristicMapper(eventCode ?? '', eventKey ?? '');
4548
if (heuristicResult != null) {
4649
return heuristicResult;

0 commit comments

Comments
 (0)