mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
Support correct keymap for web (#12712)
This commit is contained in:
parent
73c0397d4e
commit
e9edb8e14f
@ -52,28 +52,51 @@ class Keyboard {
|
||||
void _handleHtmlEvent(html.KeyboardEvent event) {
|
||||
final Map<String, dynamic> eventData = <String, dynamic>{
|
||||
'type': event.type,
|
||||
// TODO(yjbanov): this emulates Android because that the only reasonable
|
||||
// thing to map to right now (the other choice is fuchsia).
|
||||
// However, eventually we need to have something that maps
|
||||
// better to Web.
|
||||
'keymap': 'android',
|
||||
'keyCode': event.keyCode,
|
||||
'keymap': 'web',
|
||||
'code': event.code,
|
||||
'key': event.key,
|
||||
'metaState': _getMetaState(event),
|
||||
};
|
||||
|
||||
// TODO(yjbanov): The browser does not report `charCode` for 'keydown' and
|
||||
// 'keyup', only for 'keypress'. This restores the value
|
||||
// from the 'key' field. However, we need to verify how
|
||||
// many code units a single key can have. Right now it
|
||||
// assumes exactly one unit (that's what Flutter framework
|
||||
// expects). But we'll need a different strategy if other
|
||||
// code unit counts are possible.
|
||||
if (event.key.codeUnits.length == 1) {
|
||||
eventData['codePoint'] = event.key.codeUnits.first;
|
||||
}
|
||||
|
||||
ui.window.onPlatformMessage('flutter/keyevent',
|
||||
_messageCodec.encodeMessage(eventData), _noopCallback);
|
||||
}
|
||||
}
|
||||
|
||||
const int _modifierNone = 0x00;
|
||||
const int _modifierShift = 0x01;
|
||||
const int _modifierAlt = 0x02;
|
||||
const int _modifierControl = 0x04;
|
||||
const int _modifierMeta = 0x08;
|
||||
const int _modifierNumLock = 0x10;
|
||||
const int _modifierCapsLock = 0x20;
|
||||
const int _modifierScrollLock = 0x40;
|
||||
|
||||
/// Creates a bitmask representing the meta state of the [event].
|
||||
int _getMetaState(html.KeyboardEvent event) {
|
||||
int metaState = _modifierNone;
|
||||
if (event.getModifierState('Shift')) {
|
||||
metaState |= _modifierShift;
|
||||
}
|
||||
if (event.getModifierState('Alt')) {
|
||||
metaState |= _modifierAlt;
|
||||
}
|
||||
if (event.getModifierState('Control')) {
|
||||
metaState |= _modifierControl;
|
||||
}
|
||||
if (event.getModifierState('Meta')) {
|
||||
metaState |= _modifierMeta;
|
||||
}
|
||||
if (event.getModifierState('NumLock')) {
|
||||
metaState |= _modifierNumLock;
|
||||
}
|
||||
if (event.getModifierState('CapsLock')) {
|
||||
metaState |= _modifierCapsLock;
|
||||
}
|
||||
if (event.getModifierState('ScrollLock')) {
|
||||
metaState |= _modifierScrollLock;
|
||||
}
|
||||
return metaState;
|
||||
}
|
||||
|
||||
void _noopCallback(ByteData data) {}
|
||||
|
||||
@ -3,6 +3,7 @@
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'dart:html' as html;
|
||||
import 'dart:js_util' as js_util;
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:ui/src/engine.dart';
|
||||
@ -15,7 +16,7 @@ void main() {
|
||||
test('initializes', () {
|
||||
expect(Keyboard.instance, isNull);
|
||||
Keyboard.initialize();
|
||||
expect(Keyboard.instance, isNotNull);
|
||||
expect(Keyboard.instance, isA<Keyboard>());
|
||||
});
|
||||
|
||||
test('dispatches keyup to flutter/keyevent channel', () {
|
||||
@ -27,17 +28,16 @@ void main() {
|
||||
dataReceived = const JSONMessageCodec().decodeMessage(data);
|
||||
};
|
||||
|
||||
html.window.dispatchEvent(html.KeyboardEvent('keyup'));
|
||||
dispatchKeyboardEvent('keyup', key: 'SomeKey', code: 'SomeCode');
|
||||
|
||||
expect(channelReceived, 'flutter/keyevent');
|
||||
expect(dataReceived['type'], 'keyup');
|
||||
expect(dataReceived['keymap'], 'android');
|
||||
|
||||
// Unfortunately there's no way to fake `keyCode`.
|
||||
expect(dataReceived['keyCode'], 0);
|
||||
|
||||
// Unfortunately there's no way to fake `key`.
|
||||
expect(dataReceived, isNot(contains('codePoint')));
|
||||
expect(dataReceived, <String, dynamic>{
|
||||
'type': 'keyup',
|
||||
'keymap': 'web',
|
||||
'code': 'SomeCode',
|
||||
'key': 'SomeKey',
|
||||
'metaState': 0x0,
|
||||
});
|
||||
});
|
||||
|
||||
test('dispatches keydown to flutter/keyevent channel', () {
|
||||
@ -49,17 +49,96 @@ void main() {
|
||||
dataReceived = const JSONMessageCodec().decodeMessage(data);
|
||||
};
|
||||
|
||||
html.window.dispatchEvent(html.KeyboardEvent('keydown'));
|
||||
dispatchKeyboardEvent('keydown', key: 'SomeKey', code: 'SomeCode');
|
||||
|
||||
expect(channelReceived, 'flutter/keyevent');
|
||||
expect(dataReceived['type'], 'keydown');
|
||||
expect(dataReceived['keymap'], 'android');
|
||||
expect(dataReceived, <String, dynamic>{
|
||||
'type': 'keydown',
|
||||
'keymap': 'web',
|
||||
'code': 'SomeCode',
|
||||
'key': 'SomeKey',
|
||||
'metaState': 0x0,
|
||||
});
|
||||
});
|
||||
|
||||
// Unfortunately there's no way to fake `keyCode`.
|
||||
expect(dataReceived['keyCode'], 0);
|
||||
test('dispatches correct meta state', () {
|
||||
Map<String, dynamic> dataReceived;
|
||||
ui.window.onPlatformMessage = (String channel, ByteData data,
|
||||
ui.PlatformMessageResponseCallback callback) {
|
||||
dataReceived = const JSONMessageCodec().decodeMessage(data);
|
||||
};
|
||||
|
||||
// Unfortunately there's no way to fake `key`.
|
||||
expect(dataReceived, isNot(contains('codePoint')));
|
||||
dispatchKeyboardEvent(
|
||||
'keydown',
|
||||
key: 'SomeKey',
|
||||
code: 'SomeCode',
|
||||
isControlPressed: true,
|
||||
);
|
||||
expect(dataReceived, <String, dynamic>{
|
||||
'type': 'keydown',
|
||||
'keymap': 'web',
|
||||
'code': 'SomeCode',
|
||||
'key': 'SomeKey',
|
||||
// ctrl
|
||||
'metaState': 0x4,
|
||||
});
|
||||
|
||||
dispatchKeyboardEvent(
|
||||
'keydown',
|
||||
key: 'SomeKey',
|
||||
code: 'SomeCode',
|
||||
isShiftPressed: true,
|
||||
isAltPressed: true,
|
||||
isMetaPressed: true,
|
||||
);
|
||||
expect(dataReceived, <String, dynamic>{
|
||||
'type': 'keydown',
|
||||
'keymap': 'web',
|
||||
'code': 'SomeCode',
|
||||
'key': 'SomeKey',
|
||||
// shift alt meta
|
||||
'metaState': 0x1 | 0x2 | 0x8,
|
||||
});
|
||||
});
|
||||
|
||||
test('dispatches repeat events', () {
|
||||
List<Map<String, dynamic>> messages = <Map<String, dynamic>>[];
|
||||
ui.window.onPlatformMessage = (String channel, ByteData data,
|
||||
ui.PlatformMessageResponseCallback callback) {
|
||||
messages.add(const JSONMessageCodec().decodeMessage(data));
|
||||
};
|
||||
|
||||
dispatchKeyboardEvent(
|
||||
'keydown',
|
||||
key: 'SomeKey',
|
||||
code: 'SomeCode',
|
||||
repeat: true,
|
||||
);
|
||||
dispatchKeyboardEvent(
|
||||
'keydown',
|
||||
key: 'SomeKey',
|
||||
code: 'SomeCode',
|
||||
repeat: true,
|
||||
);
|
||||
dispatchKeyboardEvent(
|
||||
'keydown',
|
||||
key: 'SomeKey',
|
||||
code: 'SomeCode',
|
||||
repeat: true,
|
||||
);
|
||||
|
||||
final Map<String, dynamic> expectedMessage = <String, dynamic>{
|
||||
'type': 'keydown',
|
||||
'keymap': 'web',
|
||||
'code': 'SomeCode',
|
||||
'key': 'SomeKey',
|
||||
'metaState': 0,
|
||||
};
|
||||
expect(messages, <Map<String, dynamic>>[
|
||||
expectedMessage,
|
||||
expectedMessage,
|
||||
expectedMessage,
|
||||
]);
|
||||
});
|
||||
|
||||
test('stops dispatching events after dispose', () {
|
||||
@ -69,19 +148,48 @@ void main() {
|
||||
count += 1;
|
||||
};
|
||||
|
||||
html.window.dispatchEvent(html.KeyboardEvent('keydown'));
|
||||
dispatchKeyboardEvent('keydown');
|
||||
expect(count, 1);
|
||||
html.window.dispatchEvent(html.KeyboardEvent('keyup'));
|
||||
dispatchKeyboardEvent('keyup');
|
||||
expect(count, 2);
|
||||
|
||||
Keyboard.instance.dispose();
|
||||
expect(Keyboard.instance, isNull);
|
||||
|
||||
// No more event dispatching.
|
||||
html.window.dispatchEvent(html.KeyboardEvent('keydown'));
|
||||
dispatchKeyboardEvent('keydown');
|
||||
expect(count, 2);
|
||||
html.window.dispatchEvent(html.KeyboardEvent('keyup'));
|
||||
dispatchKeyboardEvent('keyup');
|
||||
expect(count, 2);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
void dispatchKeyboardEvent(
|
||||
String type, {
|
||||
String key,
|
||||
String code,
|
||||
bool repeat = false,
|
||||
bool isShiftPressed = false,
|
||||
bool isAltPressed = false,
|
||||
bool isControlPressed = false,
|
||||
bool isMetaPressed = false,
|
||||
}) {
|
||||
final Function jsKeyboardEvent =
|
||||
js_util.getProperty(html.window, 'KeyboardEvent');
|
||||
final List<dynamic> eventArgs = <dynamic>[
|
||||
type,
|
||||
<String, dynamic>{
|
||||
'key': key,
|
||||
'code': code,
|
||||
'repeat': repeat,
|
||||
'shiftKey': isShiftPressed,
|
||||
'altKey': isAltPressed,
|
||||
'ctrlKey': isControlPressed,
|
||||
'metaKey': isMetaPressed,
|
||||
}
|
||||
];
|
||||
final event =
|
||||
js_util.callConstructor(jsKeyboardEvent, js_util.jsify(eventArgs));
|
||||
html.window.dispatchEvent(event);
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user