[web] Treeshake keymaps for web (4% code size reduction in hello world) (#75945)

This commit is contained in:
Ferhat 2021-02-25 13:26:03 -08:00 committed by GitHub
parent 3e77f5e42e
commit cc9b78fc5c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 309 additions and 161 deletions

View file

@ -267,92 +267,106 @@ abstract class RawKeyEvent with Diagnosticable {
String? character; String? character;
final String keymap = message['keymap'] as String; final String keymap = message['keymap'] as String;
switch (keymap) { if (kIsWeb) {
case 'android': final String? key = message['key'] as String?;
data = RawKeyEventDataAndroid( data = RawKeyEventDataWeb(
flags: message['flags'] as int? ?? 0, code: message['code'] as String? ?? '',
codePoint: message['codePoint'] as int? ?? 0, key: key ?? '',
keyCode: message['keyCode'] as int? ?? 0, metaState: message['metaState'] as int? ?? 0,
plainCodePoint: message['plainCodePoint'] as int? ?? 0, );
scanCode: message['scanCode'] as int? ?? 0, if (key != null && key.isNotEmpty) {
metaState: message['metaState'] as int? ?? 0, character = key;
eventSource: message['source'] as int? ?? 0, }
vendorId: message['vendorId'] as int? ?? 0, } else {
productId: message['productId'] as int? ?? 0, switch (keymap) {
deviceId: message['deviceId'] as int? ?? 0, case 'android':
repeatCount: message['repeatCount'] as int? ?? 0, data = RawKeyEventDataAndroid(
); flags: message['flags'] as int? ?? 0,
if (message.containsKey('character')) { codePoint: message['codePoint'] as int? ?? 0,
character = message['character'] as String?;
}
break;
case 'fuchsia':
final int codePoint = message['codePoint'] as int? ?? 0;
data = RawKeyEventDataFuchsia(
hidUsage: message['hidUsage'] as int? ?? 0,
codePoint: codePoint,
modifiers: message['modifiers'] as int? ?? 0,
);
if (codePoint != 0) {
character = String.fromCharCode(codePoint);
}
break;
case 'macos':
data = RawKeyEventDataMacOs(
characters: message['characters'] as String? ?? '',
charactersIgnoringModifiers: message['charactersIgnoringModifiers'] as String? ?? '',
keyCode: message['keyCode'] as int? ?? 0, keyCode: message['keyCode'] as int? ?? 0,
modifiers: message['modifiers'] as int? ?? 0); plainCodePoint: message['plainCodePoint'] as int? ?? 0,
character = message['characters'] as String?; scanCode: message['scanCode'] as int? ?? 0,
break; metaState: message['metaState'] as int? ?? 0,
case 'ios': eventSource: message['source'] as int? ?? 0,
data = RawKeyEventDataIos( vendorId: message['vendorId'] as int? ?? 0,
characters: message['characters'] as String? ?? '', productId: message['productId'] as int? ?? 0,
charactersIgnoringModifiers: message['charactersIgnoringModifiers'] as String? ?? '', deviceId: message['deviceId'] as int? ?? 0,
keyCode: message['keyCode'] as int? ?? 0, repeatCount: message['repeatCount'] as int? ?? 0,
modifiers: message['modifiers'] as int? ?? 0); );
break; if (message.containsKey('character')) {
case 'linux': character = message['character'] as String?;
final int unicodeScalarValues = message['unicodeScalarValues'] as int? ?? 0; }
data = RawKeyEventDataLinux( break;
keyHelper: KeyHelper(message['toolkit'] as String? ?? ''), case 'fuchsia':
unicodeScalarValues: unicodeScalarValues, final int codePoint = message['codePoint'] as int? ?? 0;
data = RawKeyEventDataFuchsia(
hidUsage: message['hidUsage'] as int? ?? 0,
codePoint: codePoint,
modifiers: message['modifiers'] as int? ?? 0,
);
if (codePoint != 0) {
character = String.fromCharCode(codePoint);
}
break;
case 'macos':
data = RawKeyEventDataMacOs(
characters: message['characters'] as String? ?? '',
charactersIgnoringModifiers: message['charactersIgnoringModifiers'] as String? ?? '',
keyCode: message['keyCode'] as int? ?? 0,
modifiers: message['modifiers'] as int? ?? 0);
character = message['characters'] as String?;
break;
case 'ios':
data = RawKeyEventDataIos(
characters: message['characters'] as String? ?? '',
charactersIgnoringModifiers: message['charactersIgnoringModifiers'] as String? ?? '',
keyCode: message['keyCode'] as int? ?? 0,
modifiers: message['modifiers'] as int? ?? 0);
break;
case 'linux':
final int unicodeScalarValues = message['unicodeScalarValues'] as int? ?? 0;
data = RawKeyEventDataLinux(
keyHelper: KeyHelper(message['toolkit'] as String? ?? ''),
unicodeScalarValues: unicodeScalarValues,
keyCode: message['keyCode'] as int? ?? 0,
scanCode: message['scanCode'] as int? ?? 0,
modifiers: message['modifiers'] as int? ?? 0,
isDown: message['type'] == 'keydown');
if (unicodeScalarValues != 0) {
character = String.fromCharCode(unicodeScalarValues);
}
break;
case 'windows':
final int characterCodePoint = message['characterCodePoint'] as int? ?? 0;
data = RawKeyEventDataWindows(
keyCode: message['keyCode'] as int? ?? 0, keyCode: message['keyCode'] as int? ?? 0,
scanCode: message['scanCode'] as int? ?? 0, scanCode: message['scanCode'] as int? ?? 0,
characterCodePoint: characterCodePoint,
modifiers: message['modifiers'] as int? ?? 0, modifiers: message['modifiers'] as int? ?? 0,
isDown: message['type'] == 'keydown'); );
if (unicodeScalarValues != 0) { if (characterCodePoint != 0) {
character = String.fromCharCode(unicodeScalarValues); character = String.fromCharCode(characterCodePoint);
} }
break; break;
case 'web': case 'web':
data = RawKeyEventDataWeb( final String? key = message['key'] as String?;
code: message['code'] as String? ?? '', data = RawKeyEventDataWeb(
key: message['key'] as String? ?? '', code: message['code'] as String? ?? '',
metaState: message['metaState'] as int? ?? 0, key: key ?? '',
); metaState: message['metaState'] as int? ?? 0,
character = message['key'] as String?; );
break; if (key != null && key.isNotEmpty) {
case 'windows': character = key;
final int characterCodePoint = message['characterCodePoint'] as int? ?? 0; }
data = RawKeyEventDataWindows( break;
keyCode: message['keyCode'] as int? ?? 0, default:
scanCode: message['scanCode'] as int? ?? 0, /// This exception would only be hit on platforms that haven't yet
characterCodePoint: characterCodePoint, /// implemented raw key events, but will only be triggered if the
modifiers: message['modifiers'] as int? ?? 0, /// engine for those platforms sends raw key event messages in the
); /// first place.
if (characterCodePoint != 0) { throw FlutterError('Unknown keymap for key events: $keymap');
character = String.fromCharCode(characterCodePoint); }
}
break;
default:
/// This exception would only be hit on platforms that haven't yet
/// implemented raw key events, but will only be triggered if the
/// engine for those platforms sends raw key event messages in the
/// first place.
throw FlutterError('Unknown keymap for key events: $keymap');
} }
final String type = message['type'] as String; final String type = message['type'] as String;
switch (type) { switch (type) {
case 'keydown': case 'keydown':

View file

@ -31,7 +31,7 @@ void main() {
} }
}); });
testWidgets('No character is produced for non-printables', (WidgetTester tester) async { testWidgets('No character is produced for non-printables', (WidgetTester tester) async {
for (final String platform in <String>['linux', 'android', 'macos', 'fuchsia', 'windows']) { for (final String platform in <String>['linux', 'android', 'macos', 'fuchsia', 'windows', 'web']) {
void handleKey(RawKeyEvent event) { void handleKey(RawKeyEvent event) {
expect(event.character, isNull, reason: 'on $platform'); expect(event.character, isNull, reason: 'on $platform');
} }
@ -194,7 +194,7 @@ void main() {
await simulateKeyUpEvent(LogicalKeyboardKey.keyA, platform: platform, physicalKey: PhysicalKeyboardKey.keyA); await simulateKeyUpEvent(LogicalKeyboardKey.keyA, platform: platform, physicalKey: PhysicalKeyboardKey.keyA);
expect(RawKeyboard.instance.keysPressed, isEmpty, reason: 'on $platform'); expect(RawKeyboard.instance.keysPressed, isEmpty, reason: 'on $platform');
} }
}); }, skip: isBrowser); // https://github.com/flutter/flutter/issues/76741
testWidgets('keysPressed modifiers are synchronized with key events on macOS', (WidgetTester tester) async { testWidgets('keysPressed modifiers are synchronized with key events on macOS', (WidgetTester tester) async {
expect(RawKeyboard.instance.keysPressed, isEmpty); expect(RawKeyboard.instance.keysPressed, isEmpty);
@ -219,7 +219,7 @@ void main() {
<LogicalKeyboardKey>{LogicalKeyboardKey.shiftLeft, LogicalKeyboardKey.keyA}, <LogicalKeyboardKey>{LogicalKeyboardKey.shiftLeft, LogicalKeyboardKey.keyA},
), ),
); );
}); }, skip: isBrowser); // This is a macOS-specific test.
testWidgets('keysPressed modifiers are synchronized with key events on iOS', (WidgetTester tester) async { testWidgets('keysPressed modifiers are synchronized with key events on iOS', (WidgetTester tester) async {
expect(RawKeyboard.instance.keysPressed, isEmpty); expect(RawKeyboard.instance.keysPressed, isEmpty);
@ -244,7 +244,7 @@ void main() {
<LogicalKeyboardKey>{LogicalKeyboardKey.shiftLeft, LogicalKeyboardKey.keyA}, <LogicalKeyboardKey>{LogicalKeyboardKey.shiftLeft, LogicalKeyboardKey.keyA},
), ),
); );
}); }, skip: isBrowser); // This is an iOS-specific test.
testWidgets('keysPressed modifiers are synchronized with key events on Windows', (WidgetTester tester) async { testWidgets('keysPressed modifiers are synchronized with key events on Windows', (WidgetTester tester) async {
expect(RawKeyboard.instance.keysPressed, isEmpty); expect(RawKeyboard.instance.keysPressed, isEmpty);
@ -269,7 +269,7 @@ void main() {
<LogicalKeyboardKey>{LogicalKeyboardKey.shiftLeft, LogicalKeyboardKey.keyA}, <LogicalKeyboardKey>{LogicalKeyboardKey.shiftLeft, LogicalKeyboardKey.keyA},
), ),
); );
}); }, skip: isBrowser); // This is a Windows-specific test.
testWidgets('keysPressed modifiers are synchronized with key events on android', (WidgetTester tester) async { testWidgets('keysPressed modifiers are synchronized with key events on android', (WidgetTester tester) async {
expect(RawKeyboard.instance.keysPressed, isEmpty); expect(RawKeyboard.instance.keysPressed, isEmpty);
@ -294,7 +294,7 @@ void main() {
<LogicalKeyboardKey>{LogicalKeyboardKey.shiftLeft, LogicalKeyboardKey.keyA}, <LogicalKeyboardKey>{LogicalKeyboardKey.shiftLeft, LogicalKeyboardKey.keyA},
), ),
); );
}); }, skip: isBrowser); // This is an Android-specific test.
testWidgets('keysPressed modifiers are synchronized with key events on fuchsia', (WidgetTester tester) async { testWidgets('keysPressed modifiers are synchronized with key events on fuchsia', (WidgetTester tester) async {
expect(RawKeyboard.instance.keysPressed, isEmpty); expect(RawKeyboard.instance.keysPressed, isEmpty);
@ -319,7 +319,7 @@ void main() {
<LogicalKeyboardKey>{LogicalKeyboardKey.shiftLeft, LogicalKeyboardKey.keyA}, <LogicalKeyboardKey>{LogicalKeyboardKey.shiftLeft, LogicalKeyboardKey.keyA},
), ),
); );
}); }, skip: isBrowser); // This is a Fuchsia-specific test.
testWidgets('keysPressed modifiers are synchronized with key events on Linux GLFW', (WidgetTester tester) async { testWidgets('keysPressed modifiers are synchronized with key events on Linux GLFW', (WidgetTester tester) async {
expect(RawKeyboard.instance.keysPressed, isEmpty); expect(RawKeyboard.instance.keysPressed, isEmpty);
@ -350,6 +350,37 @@ void main() {
}, },
), ),
); );
}, skip: isBrowser); // This is a GLFW-specific test.
testWidgets('keysPressed modifiers are synchronized with key events on web', (WidgetTester tester) async {
expect(RawKeyboard.instance.keysPressed, isEmpty);
// Generate the data for a regular key down event.
final Map<String, dynamic> data = KeyEventSimulator.getKeyData(
LogicalKeyboardKey.keyA,
platform: 'web',
isDown: true,
);
// Change the modifiers so that they show the shift key as already down
// when this event is received, but it's not in keysPressed yet.
data['metaState'] |= RawKeyEventDataWeb.modifierShift;
// dispatch the modified data.
await ServicesBinding.instance!.defaultBinaryMessenger.handlePlatformMessage(
SystemChannels.keyEvent.name,
SystemChannels.keyEvent.codec.encodeMessage(data),
(ByteData? data) {},
);
expect(
RawKeyboard.instance.keysPressed,
equals(
<LogicalKeyboardKey>{
LogicalKeyboardKey.shiftLeft,
// Web doesn't distinguish between left and right keys, so they're
// all shown as down when either is pressed.
LogicalKeyboardKey.shiftRight,
LogicalKeyboardKey.keyA,
},
),
);
}); });
testWidgets('sided modifiers without a side set return all sides on Android', (WidgetTester tester) async { testWidgets('sided modifiers without a side set return all sides on Android', (WidgetTester tester) async {
@ -388,7 +419,7 @@ void main() {
}, },
), ),
); );
}); }, skip: isBrowser); // This is an Android-specific test.
testWidgets('sided modifiers without a side set return all sides on macOS', (WidgetTester tester) async { testWidgets('sided modifiers without a side set return all sides on macOS', (WidgetTester tester) async {
expect(RawKeyboard.instance.keysPressed, isEmpty); expect(RawKeyboard.instance.keysPressed, isEmpty);
@ -426,7 +457,7 @@ void main() {
}, },
), ),
); );
}); }, skip: isBrowser); // This is a macOS-specific test.
testWidgets('sided modifiers without a side set return all sides on iOS', (WidgetTester tester) async { testWidgets('sided modifiers without a side set return all sides on iOS', (WidgetTester tester) async {
expect(RawKeyboard.instance.keysPressed, isEmpty); expect(RawKeyboard.instance.keysPressed, isEmpty);
@ -464,7 +495,7 @@ void main() {
}, },
), ),
); );
}); }, skip: isBrowser); // This is an iOS-specific test.
testWidgets('sided modifiers without a side set return all sides on Windows', (WidgetTester tester) async { testWidgets('sided modifiers without a side set return all sides on Windows', (WidgetTester tester) async {
expect(RawKeyboard.instance.keysPressed, isEmpty); expect(RawKeyboard.instance.keysPressed, isEmpty);
@ -500,7 +531,7 @@ void main() {
}, },
), ),
); );
}); }, skip: isBrowser); // This is a Windows-specific test.
testWidgets('sided modifiers without a side set return all sides on Linux GLFW', (WidgetTester tester) async { testWidgets('sided modifiers without a side set return all sides on Linux GLFW', (WidgetTester tester) async {
expect(RawKeyboard.instance.keysPressed, isEmpty); expect(RawKeyboard.instance.keysPressed, isEmpty);
@ -539,6 +570,44 @@ void main() {
}, },
), ),
); );
}, skip: isBrowser); // This is a GLFW-specific test.
testWidgets('sided modifiers without a side set return all sides on web', (WidgetTester tester) async {
expect(RawKeyboard.instance.keysPressed, isEmpty);
// Generate the data for a regular key down event.
final Map<String, dynamic> data = KeyEventSimulator.getKeyData(
LogicalKeyboardKey.keyA,
platform: 'web',
isDown: true,
);
// Set only the generic "shift down" modifier, without setting a side.
data['metaState'] |=
RawKeyEventDataWeb.modifierShift |
RawKeyEventDataWeb.modifierAlt |
RawKeyEventDataWeb.modifierControl |
RawKeyEventDataWeb.modifierMeta;
// dispatch the modified data.
await ServicesBinding.instance!.defaultBinaryMessenger.handlePlatformMessage(
SystemChannels.keyEvent.name,
SystemChannels.keyEvent.codec.encodeMessage(data),
(ByteData? data) {},
);
expect(
RawKeyboard.instance.keysPressed,
equals(
<LogicalKeyboardKey>{
LogicalKeyboardKey.shiftLeft,
LogicalKeyboardKey.shiftRight,
LogicalKeyboardKey.altLeft,
LogicalKeyboardKey.altRight,
LogicalKeyboardKey.controlLeft,
LogicalKeyboardKey.controlRight,
LogicalKeyboardKey.metaLeft,
LogicalKeyboardKey.metaRight,
LogicalKeyboardKey.keyA,
},
),
);
}); });
testWidgets('RawKeyboard asserts if no keys are in keysPressed after receiving a key down event', (WidgetTester tester) async { testWidgets('RawKeyboard asserts if no keys are in keysPressed after receiving a key down event', (WidgetTester tester) async {
@ -547,19 +616,32 @@ void main() {
FlutterError.onError = (FlutterErrorDetails details) { FlutterError.onError = (FlutterErrorDetails details) {
errorDetails = details; errorDetails = details;
}; };
final Map<String, dynamic> keyEventMessage;
if (kIsWeb) {
keyEventMessage = const <String, dynamic>{
'type': 'keydown',
'keymap': 'web',
'code': 'ShiftLeft', // Left shift code
'metaState': 0x0, // No shift key metaState set!
};
} else {
keyEventMessage = const <String, dynamic>{
'type': 'keydown',
'keymap': 'android',
'keyCode': 0x3b, // Left shift key keyCode
'scanCode': 0x2a,
'metaState': 0x0, // No shift key metaState set!
'source': 0x101,
'deviceId': 1,
};
}
try { try {
await ServicesBinding.instance!.defaultBinaryMessenger await ServicesBinding.instance!.defaultBinaryMessenger
.handlePlatformMessage( .handlePlatformMessage(
SystemChannels.keyEvent.name, SystemChannels.keyEvent.name,
SystemChannels.keyEvent.codec.encodeMessage(const <String, dynamic>{ SystemChannels.keyEvent.codec.encodeMessage(keyEventMessage),
'type': 'keydown',
'keymap': 'android',
'keyCode': 0x3b, // Left shift key keyCode
'scanCode': 0x2a,
'metaState': 0x0, // No shift key metaState set!
'source': 0x101,
'deviceId': 1,
}),
(ByteData? data) {}, (ByteData? data) {},
); );
} finally { } finally {
@ -837,7 +919,8 @@ void main() {
expect(message, equals(<String, dynamic>{ 'handled': true })); expect(message, equals(<String, dynamic>{ 'handled': true }));
ServicesBinding.instance!.defaultBinaryMessenger.setMockMessageHandler(SystemChannels.keyEvent.name, null); ServicesBinding.instance!.defaultBinaryMessenger.setMockMessageHandler(SystemChannels.keyEvent.name, null);
}); });
}); }, skip: isBrowser); // This is an Android-specific group.
group('RawKeyEventDataFuchsia', () { group('RawKeyEventDataFuchsia', () {
const Map<int, _ModifierCheck> modifierTests = <int, _ModifierCheck>{ const Map<int, _ModifierCheck> modifierTests = <int, _ModifierCheck>{
RawKeyEventDataFuchsia.modifierAlt: _ModifierCheck(ModifierKey.altModifier, KeyboardSide.any), RawKeyEventDataFuchsia.modifierAlt: _ModifierCheck(ModifierKey.altModifier, KeyboardSide.any),
@ -951,7 +1034,7 @@ void main() {
expect(data.logicalKey, equals(LogicalKeyboardKey.shiftLeft)); expect(data.logicalKey, equals(LogicalKeyboardKey.shiftLeft));
expect(data.keyLabel, isEmpty); expect(data.keyLabel, isEmpty);
}, skip: isBrowser); // https://github.com/flutter/flutter/issues/35347 }, skip: isBrowser); // https://github.com/flutter/flutter/issues/35347
}); }, skip: isBrowser); // This is a Fuchsia-specific group.
group('RawKeyEventDataMacOs', () { group('RawKeyEventDataMacOs', () {
const Map<int, _ModifierCheck> modifierTests = <int, _ModifierCheck>{ const Map<int, _ModifierCheck> modifierTests = <int, _ModifierCheck>{
@ -1097,7 +1180,7 @@ void main() {
expect(data.logicalKey, equals(LogicalKeyboardKey.arrowLeft)); expect(data.logicalKey, equals(LogicalKeyboardKey.arrowLeft));
expect(data.logicalKey.keyLabel, isEmpty); expect(data.logicalKey.keyLabel, isEmpty);
}, skip: isBrowser); // https://github.com/flutter/flutter/issues/35347 }, skip: isBrowser); // https://github.com/flutter/flutter/issues/35347
}); }, skip: isBrowser); // This is a macOS-specific group.
group('RawKeyEventDataIos', () { group('RawKeyEventDataIos', () {
const Map<int, _ModifierCheck> modifierTests = <int, _ModifierCheck>{ const Map<int, _ModifierCheck> modifierTests = <int, _ModifierCheck>{
@ -1243,7 +1326,7 @@ void main() {
expect(data.logicalKey, equals(LogicalKeyboardKey.arrowLeft)); expect(data.logicalKey, equals(LogicalKeyboardKey.arrowLeft));
expect(data.logicalKey.keyLabel, isEmpty); expect(data.logicalKey.keyLabel, isEmpty);
}, skip: isBrowser); // https://github.com/flutter/flutter/issues/35347 }, skip: isBrowser); // https://github.com/flutter/flutter/issues/35347
}); }, skip: isBrowser); // This is an iOS-specific group.
group('RawKeyEventDataWindows', () { group('RawKeyEventDataWindows', () {
const Map<int, _ModifierCheck> modifierTests = <int, _ModifierCheck>{ const Map<int, _ModifierCheck> modifierTests = <int, _ModifierCheck>{
@ -1388,7 +1471,7 @@ void main() {
expect(data.logicalKey, equals(LogicalKeyboardKey.arrowLeft)); expect(data.logicalKey, equals(LogicalKeyboardKey.arrowLeft));
expect(data.logicalKey.keyLabel, isEmpty); expect(data.logicalKey.keyLabel, isEmpty);
}); });
}); }, skip: isBrowser); // This is a Windows-specific group.
group('RawKeyEventDataLinux-GFLW', () { group('RawKeyEventDataLinux-GFLW', () {
const Map<int, _ModifierCheck> modifierTests = <int, _ModifierCheck>{ const Map<int, _ModifierCheck> modifierTests = <int, _ModifierCheck>{
@ -1572,7 +1655,7 @@ void main() {
expect(data.logicalKey, equals(LogicalKeyboardKey.shiftLeft)); expect(data.logicalKey, equals(LogicalKeyboardKey.shiftLeft));
expect(data.keyLabel, isEmpty); expect(data.keyLabel, isEmpty);
}); });
}); }, skip: isBrowser); // This is a GLFW-specific group.
group('RawKeyEventDataLinux-GTK', () { group('RawKeyEventDataLinux-GTK', () {
const Map<int, _ModifierCheck> modifierTests = <int, _ModifierCheck>{ const Map<int, _ModifierCheck> modifierTests = <int, _ModifierCheck>{
@ -1756,7 +1839,7 @@ void main() {
expect(data.logicalKey, equals(LogicalKeyboardKey.shiftLeft)); expect(data.logicalKey, equals(LogicalKeyboardKey.shiftLeft));
expect(data.keyLabel, isEmpty); expect(data.keyLabel, isEmpty);
}); });
}); }, skip: isBrowser); // This is a GTK-specific group.
group('RawKeyEventDataWeb', () { group('RawKeyEventDataWeb', () {
const Map<int, ModifierKey> modifierTests = <int, ModifierKey>{ const Map<int, ModifierKey> modifierTests = <int, ModifierKey>{

View file

@ -42,6 +42,37 @@ void main() {
expect(typedData.modifiers, RawKeyEventDataFuchsia.modifierLeftMeta); expect(typedData.modifiers, RawKeyEventDataFuchsia.modifierLeftMeta);
expect(typedData.isModifierPressed(ModifierKey.metaModifier, side: KeyboardSide.left), isTrue); expect(typedData.isModifierPressed(ModifierKey.metaModifier, side: KeyboardSide.left), isTrue);
await tester.pumpWidget(Container());
focusNode.dispose();
}, skip: isBrowser); // This is a Fuchsia-specific test.
testWidgets('Web key event', (WidgetTester tester) async {
final List<RawKeyEvent> events = <RawKeyEvent>[];
final FocusNode focusNode = FocusNode();
await tester.pumpWidget(
RawKeyboardListener(
focusNode: focusNode,
onKey: events.add,
child: Container(),
),
);
focusNode.requestFocus();
await tester.idle();
await tester.sendKeyEvent(LogicalKeyboardKey.metaLeft, platform: 'web');
await tester.idle();
expect(events.length, 2);
expect(events[0].runtimeType, equals(RawKeyDownEvent));
expect(events[0].data, isA<RawKeyEventDataWeb>());
final RawKeyEventDataWeb typedData = events[0].data as RawKeyEventDataWeb;
expect(typedData.code, 'MetaLeft');
expect(typedData.metaState, RawKeyEventDataWeb.modifierMeta);
expect(typedData.isModifierPressed(ModifierKey.metaModifier, side: KeyboardSide.left), isTrue);
await tester.pumpWidget(Container()); await tester.pumpWidget(Container());
focusNode.dispose(); focusNode.dispose();
}); });

View file

@ -4,6 +4,7 @@
import 'dart:io'; import 'dart:io';
import 'package:flutter/foundation.dart' show kIsWeb;
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'test_async_utils.dart'; import 'test_async_utils.dart';
@ -85,40 +86,47 @@ class KeyEventSimulator {
static int _getKeyCode(LogicalKeyboardKey key, String platform) { static int _getKeyCode(LogicalKeyboardKey key, String platform) {
assert(_osIsSupported(platform), 'Platform $platform not supported for key simulation'); assert(_osIsSupported(platform), 'Platform $platform not supported for key simulation');
late Map<int, LogicalKeyboardKey> map; if (kIsWeb) {
switch (platform) { // web doesn't have int type code. This check is used to treeshake
case 'android': // keyboard map code.
map = kAndroidToLogicalKey; return -1;
break; } else {
case 'fuchsia': late Map<int, LogicalKeyboardKey> map;
map = kFuchsiaToLogicalKey; switch (platform) {
break; case 'android':
case 'macos': map = kAndroidToLogicalKey;
// macOS doesn't do key codes, just scan codes. break;
return -1; case 'fuchsia':
case 'ios': map = kFuchsiaToLogicalKey;
// iOS doesn't do key codes, just scan codes. break;
return -1; case 'macos':
case 'web': // macOS doesn't do key codes, just scan codes.
// web doesn't have int type code return -1;
return -1; case 'ios':
case 'linux': // iOS doesn't do key codes, just scan codes.
map = kGlfwToLogicalKey; return -1;
break; case 'web':
case 'windows': // web doesn't have int type code.
map = kWindowsToLogicalKey; return -1;
break; case 'linux':
} map = kGlfwToLogicalKey;
int? keyCode; break;
for (final int code in map.keys) { case 'windows':
if (key.keyId == map[code]!.keyId) { map = kWindowsToLogicalKey;
keyCode = code; break;
break;
} }
int? keyCode;
for (final int code in map.keys) {
if (key.keyId == map[code]!.keyId) {
keyCode = code;
break;
}
}
assert(keyCode != null, 'Key $key not found in $platform keyCode map');
return keyCode!;
} }
assert(keyCode != null, 'Key $key not found in $platform keyCode map');
return keyCode!;
} }
static String _getWebKeyCode(LogicalKeyboardKey key) { static String _getWebKeyCode(LogicalKeyboardKey key) {
String? result; String? result;
for (final String code in kWebToLogicalKey.keys) { for (final String code in kWebToLogicalKey.keys) {
@ -134,28 +142,33 @@ class KeyEventSimulator {
static PhysicalKeyboardKey _findPhysicalKey(LogicalKeyboardKey key, String platform) { static PhysicalKeyboardKey _findPhysicalKey(LogicalKeyboardKey key, String platform) {
assert(_osIsSupported(platform), 'Platform $platform not supported for key simulation'); assert(_osIsSupported(platform), 'Platform $platform not supported for key simulation');
late Map<dynamic, PhysicalKeyboardKey> map; late Map<dynamic, PhysicalKeyboardKey> map;
switch (platform) { if (kIsWeb) {
case 'android': // This check is used to treeshake keymap code.
map = kAndroidToPhysicalKey; map = kWebToPhysicalKey;
break; } else {
case 'fuchsia': switch (platform) {
map = kFuchsiaToPhysicalKey; case 'android':
break; map = kAndroidToPhysicalKey;
case 'macos': break;
map = kMacOsToPhysicalKey; case 'fuchsia':
break; map = kFuchsiaToPhysicalKey;
case 'ios': break;
map = kIosToPhysicalKey; case 'macos':
break; map = kMacOsToPhysicalKey;
case 'linux': break;
map = kLinuxToPhysicalKey; case 'ios':
break; map = kIosToPhysicalKey;
case 'web': break;
map = kWebToPhysicalKey; case 'linux':
break; map = kLinuxToPhysicalKey;
case 'windows': break;
map = kWindowsToPhysicalKey; case 'web':
break; map = kWebToPhysicalKey;
break;
case 'windows':
map = kWindowsToPhysicalKey;
break;
}
} }
PhysicalKeyboardKey? result; PhysicalKeyboardKey? result;
for (final PhysicalKeyboardKey physicalKey in map.values) { for (final PhysicalKeyboardKey physicalKey in map.values) {
@ -191,6 +204,13 @@ class KeyEventSimulator {
'keymap': platform, 'keymap': platform,
}; };
if (kIsWeb) {
result['code'] = _getWebKeyCode(key);
result['key'] = key.keyLabel;
result['metaState'] = _getWebModifierFlags(key, isDown);
return result;
}
switch (platform) { switch (platform) {
case 'android': case 'android':
result['keyCode'] = keyCode; result['keyCode'] = keyCode;