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

diff --git a/packages/flutter/lib/src/services/raw_keyboard.dart b/packages/flutter/lib/src/services/raw_keyboard.dart
index 41e927a..bcddb0d 100644
--- a/packages/flutter/lib/src/services/raw_keyboard.dart
+++ b/packages/flutter/lib/src/services/raw_keyboard.dart
@@ -267,92 +267,106 @@
     String? character;
 
     final String keymap = message['keymap'] as String;
-    switch (keymap) {
-      case 'android':
-        data = RawKeyEventDataAndroid(
-          flags: message['flags'] as int? ?? 0,
-          codePoint: message['codePoint'] as int? ?? 0,
-          keyCode: message['keyCode'] as int? ?? 0,
-          plainCodePoint: message['plainCodePoint'] as int? ?? 0,
-          scanCode: message['scanCode'] as int? ?? 0,
-          metaState: message['metaState'] as int? ?? 0,
-          eventSource: message['source'] as int? ?? 0,
-          vendorId: message['vendorId'] as int? ?? 0,
-          productId: message['productId'] as int? ?? 0,
-          deviceId: message['deviceId'] as int? ?? 0,
-          repeatCount: message['repeatCount'] as int? ?? 0,
-        );
-        if (message.containsKey('character')) {
-          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? ?? '',
+    if (kIsWeb) {
+      final String? key = message['key'] as String?;
+      data = RawKeyEventDataWeb(
+        code: message['code'] as String? ?? '',
+        key: key ?? '',
+        metaState: message['metaState'] as int? ?? 0,
+      );
+      if (key != null && key.isNotEmpty) {
+        character = key;
+      }
+    } else {
+      switch (keymap) {
+        case 'android':
+          data = RawKeyEventDataAndroid(
+            flags: message['flags'] as int? ?? 0,
+            codePoint: message['codePoint'] as int? ?? 0,
             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,
+            plainCodePoint: message['plainCodePoint'] as int? ?? 0,
+            scanCode: message['scanCode'] as int? ?? 0,
+            metaState: message['metaState'] as int? ?? 0,
+            eventSource: message['source'] as int? ?? 0,
+            vendorId: message['vendorId'] as int? ?? 0,
+            productId: message['productId'] as int? ?? 0,
+            deviceId: message['deviceId'] as int? ?? 0,
+            repeatCount: message['repeatCount'] as int? ?? 0,
+          );
+          if (message.containsKey('character')) {
+            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,
+              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,
             scanCode: message['scanCode'] as int? ?? 0,
+            characterCodePoint: characterCodePoint,
             modifiers: message['modifiers'] as int? ?? 0,
-            isDown: message['type'] == 'keydown');
-        if (unicodeScalarValues != 0) {
-          character = String.fromCharCode(unicodeScalarValues);
-        }
-        break;
-      case 'web':
-        data = RawKeyEventDataWeb(
-          code: message['code'] as String? ?? '',
-          key: message['key'] as String? ?? '',
-          metaState: message['metaState'] as int? ?? 0,
-        );
-        character = message['key'] as String?;
-        break;
-      case 'windows':
-        final int characterCodePoint = message['characterCodePoint'] as int? ?? 0;
-        data = RawKeyEventDataWindows(
-          keyCode: message['keyCode'] as int? ?? 0,
-          scanCode: message['scanCode'] as int? ?? 0,
-          characterCodePoint: characterCodePoint,
-          modifiers: message['modifiers'] as int? ?? 0,
-        );
-        if (characterCodePoint != 0) {
-          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');
+          );
+          if (characterCodePoint != 0) {
+            character = String.fromCharCode(characterCodePoint);
+          }
+          break;
+        case 'web':
+          final String? key = message['key'] as String?;
+          data = RawKeyEventDataWeb(
+            code: message['code'] as String? ?? '',
+            key: key ?? '',
+            metaState: message['metaState'] as int? ?? 0,
+          );
+          if (key != null && key.isNotEmpty) {
+            character = key;
+          }
+          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;
     switch (type) {
       case 'keydown':
diff --git a/packages/flutter/test/services/raw_keyboard_test.dart b/packages/flutter/test/services/raw_keyboard_test.dart
index 2e0b3ef..1b40acb 100644
--- a/packages/flutter/test/services/raw_keyboard_test.dart
+++ b/packages/flutter/test/services/raw_keyboard_test.dart
@@ -31,7 +31,7 @@
       }
     });
     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) {
           expect(event.character, isNull, reason: 'on $platform');
         }
@@ -194,7 +194,7 @@
         await simulateKeyUpEvent(LogicalKeyboardKey.keyA, platform: platform, physicalKey: PhysicalKeyboardKey.keyA);
         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 {
       expect(RawKeyboard.instance.keysPressed, isEmpty);
@@ -219,7 +219,7 @@
           <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 {
       expect(RawKeyboard.instance.keysPressed, isEmpty);
@@ -244,7 +244,7 @@
           <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 {
       expect(RawKeyboard.instance.keysPressed, isEmpty);
@@ -269,7 +269,7 @@
           <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 {
       expect(RawKeyboard.instance.keysPressed, isEmpty);
@@ -294,7 +294,7 @@
           <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 {
       expect(RawKeyboard.instance.keysPressed, isEmpty);
@@ -319,7 +319,7 @@
           <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 {
       expect(RawKeyboard.instance.keysPressed, isEmpty);
@@ -350,6 +350,37 @@
           },
         ),
       );
+    }, 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 {
@@ -388,7 +419,7 @@
           },
         ),
       );
-    });
+    }, skip: isBrowser); // This is an Android-specific test.
 
     testWidgets('sided modifiers without a side set return all sides on macOS', (WidgetTester tester) async {
       expect(RawKeyboard.instance.keysPressed, isEmpty);
@@ -426,7 +457,7 @@
           },
         ),
       );
-    });
+    }, skip: isBrowser); // This is a macOS-specific test.
 
     testWidgets('sided modifiers without a side set return all sides on iOS', (WidgetTester tester) async {
       expect(RawKeyboard.instance.keysPressed, isEmpty);
@@ -464,7 +495,7 @@
           },
         ),
       );
-    });
+    }, skip: isBrowser); // This is an iOS-specific test.
 
     testWidgets('sided modifiers without a side set return all sides on Windows', (WidgetTester tester) async {
       expect(RawKeyboard.instance.keysPressed, isEmpty);
@@ -500,7 +531,7 @@
           },
         ),
       );
-    });
+    }, skip: isBrowser); // This is a Windows-specific test.
 
     testWidgets('sided modifiers without a side set return all sides on Linux GLFW', (WidgetTester tester) async {
       expect(RawKeyboard.instance.keysPressed, isEmpty);
@@ -539,6 +570,44 @@
           },
         ),
       );
+    }, 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 {
@@ -547,19 +616,32 @@
       FlutterError.onError = (FlutterErrorDetails 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 {
         await ServicesBinding.instance!.defaultBinaryMessenger
             .handlePlatformMessage(
           SystemChannels.keyEvent.name,
-          SystemChannels.keyEvent.codec.encodeMessage(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,
-          }),
+          SystemChannels.keyEvent.codec.encodeMessage(keyEventMessage),
               (ByteData? data) {},
         );
       } finally {
@@ -837,7 +919,8 @@
       expect(message, equals(<String, dynamic>{ 'handled': true }));
       ServicesBinding.instance!.defaultBinaryMessenger.setMockMessageHandler(SystemChannels.keyEvent.name, null);
     });
-  });
+  }, skip: isBrowser); // This is an Android-specific group.
+
   group('RawKeyEventDataFuchsia', () {
     const Map<int, _ModifierCheck> modifierTests = <int, _ModifierCheck>{
       RawKeyEventDataFuchsia.modifierAlt: _ModifierCheck(ModifierKey.altModifier, KeyboardSide.any),
@@ -951,7 +1034,7 @@
       expect(data.logicalKey, equals(LogicalKeyboardKey.shiftLeft));
       expect(data.keyLabel, isEmpty);
     }, skip: isBrowser); // https://github.com/flutter/flutter/issues/35347
-  });
+  }, skip: isBrowser); // This is a Fuchsia-specific group.
 
   group('RawKeyEventDataMacOs', () {
     const Map<int, _ModifierCheck> modifierTests = <int, _ModifierCheck>{
@@ -1097,7 +1180,7 @@
       expect(data.logicalKey, equals(LogicalKeyboardKey.arrowLeft));
       expect(data.logicalKey.keyLabel, isEmpty);
     }, skip: isBrowser); // https://github.com/flutter/flutter/issues/35347
-  });
+  }, skip: isBrowser); // This is a macOS-specific group.
 
   group('RawKeyEventDataIos', () {
     const Map<int, _ModifierCheck> modifierTests = <int, _ModifierCheck>{
@@ -1243,7 +1326,7 @@
       expect(data.logicalKey, equals(LogicalKeyboardKey.arrowLeft));
       expect(data.logicalKey.keyLabel, isEmpty);
     }, skip: isBrowser); // https://github.com/flutter/flutter/issues/35347
-  });
+  }, skip: isBrowser); // This is an iOS-specific group.
 
   group('RawKeyEventDataWindows', () {
     const Map<int, _ModifierCheck> modifierTests = <int, _ModifierCheck>{
@@ -1388,7 +1471,7 @@
       expect(data.logicalKey, equals(LogicalKeyboardKey.arrowLeft));
       expect(data.logicalKey.keyLabel, isEmpty);
     });
-  });
+  }, skip: isBrowser); // This is a Windows-specific group.
 
   group('RawKeyEventDataLinux-GFLW', () {
     const Map<int, _ModifierCheck> modifierTests = <int, _ModifierCheck>{
@@ -1572,7 +1655,7 @@
       expect(data.logicalKey, equals(LogicalKeyboardKey.shiftLeft));
       expect(data.keyLabel, isEmpty);
     });
-  });
+  }, skip: isBrowser); // This is a GLFW-specific group.
 
   group('RawKeyEventDataLinux-GTK', () {
     const Map<int, _ModifierCheck> modifierTests = <int, _ModifierCheck>{
@@ -1756,7 +1839,7 @@
       expect(data.logicalKey, equals(LogicalKeyboardKey.shiftLeft));
       expect(data.keyLabel, isEmpty);
     });
-  });
+  }, skip: isBrowser); // This is a GTK-specific group.
 
   group('RawKeyEventDataWeb', () {
     const Map<int, ModifierKey> modifierTests = <int, ModifierKey>{
diff --git a/packages/flutter/test/widgets/raw_keyboard_listener_test.dart b/packages/flutter/test/widgets/raw_keyboard_listener_test.dart
index bcac58d..3149e0d 100644
--- a/packages/flutter/test/widgets/raw_keyboard_listener_test.dart
+++ b/packages/flutter/test/widgets/raw_keyboard_listener_test.dart
@@ -44,6 +44,37 @@
 
     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());
+    focusNode.dispose();
   });
 
   testWidgets('Defunct listeners do not receive events', (WidgetTester tester) async {
diff --git a/packages/flutter_test/lib/src/event_simulation.dart b/packages/flutter_test/lib/src/event_simulation.dart
index 82451d2..b99dab8 100644
--- a/packages/flutter_test/lib/src/event_simulation.dart
+++ b/packages/flutter_test/lib/src/event_simulation.dart
@@ -4,6 +4,7 @@
 
 import 'dart:io';
 
+import 'package:flutter/foundation.dart' show kIsWeb;
 import 'package:flutter/services.dart';
 import 'test_async_utils.dart';
 
@@ -85,40 +86,47 @@
 
   static int _getKeyCode(LogicalKeyboardKey key, String platform) {
     assert(_osIsSupported(platform), 'Platform $platform not supported for key simulation');
-    late Map<int, LogicalKeyboardKey> map;
-    switch (platform) {
-      case 'android':
-        map = kAndroidToLogicalKey;
-        break;
-      case 'fuchsia':
-        map = kFuchsiaToLogicalKey;
-        break;
-      case 'macos':
-      // macOS doesn't do key codes, just scan codes.
-        return -1;
-      case 'ios':
-      // iOS doesn't do key codes, just scan codes.
-        return -1;
-      case 'web':
-        // web doesn't have int type code
-        return -1;
-      case 'linux':
-        map = kGlfwToLogicalKey;
-        break;
-      case 'windows':
-        map = kWindowsToLogicalKey;
-        break;
-    }
-    int? keyCode;
-    for (final int code in map.keys) {
-      if (key.keyId == map[code]!.keyId) {
-        keyCode = code;
-        break;
+    if (kIsWeb) {
+      // web doesn't have int type code. This check is used to treeshake
+      // keyboard map code.
+      return -1;
+    } else {
+      late Map<int, LogicalKeyboardKey> map;
+      switch (platform) {
+        case 'android':
+          map = kAndroidToLogicalKey;
+          break;
+        case 'fuchsia':
+          map = kFuchsiaToLogicalKey;
+          break;
+        case 'macos':
+        // macOS doesn't do key codes, just scan codes.
+          return -1;
+        case 'ios':
+        // iOS doesn't do key codes, just scan codes.
+          return -1;
+        case 'web':
+          // web doesn't have int type code.
+          return -1;
+        case 'linux':
+          map = kGlfwToLogicalKey;
+          break;
+        case 'windows':
+          map = kWindowsToLogicalKey;
+          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) {
     String? result;
     for (final String code in kWebToLogicalKey.keys) {
@@ -134,28 +142,33 @@
   static PhysicalKeyboardKey _findPhysicalKey(LogicalKeyboardKey key, String platform) {
     assert(_osIsSupported(platform), 'Platform $platform not supported for key simulation');
     late Map<dynamic, PhysicalKeyboardKey> map;
-    switch (platform) {
-      case 'android':
-        map = kAndroidToPhysicalKey;
-        break;
-      case 'fuchsia':
-        map = kFuchsiaToPhysicalKey;
-        break;
-      case 'macos':
-        map = kMacOsToPhysicalKey;
-        break;
-      case 'ios':
-        map = kIosToPhysicalKey;
-        break;
-      case 'linux':
-        map = kLinuxToPhysicalKey;
-        break;
-      case 'web':
-        map = kWebToPhysicalKey;
-        break;
-      case 'windows':
-        map = kWindowsToPhysicalKey;
-        break;
+    if (kIsWeb) {
+      // This check is used to treeshake keymap code.
+      map = kWebToPhysicalKey;
+    } else {
+      switch (platform) {
+        case 'android':
+          map = kAndroidToPhysicalKey;
+          break;
+        case 'fuchsia':
+          map = kFuchsiaToPhysicalKey;
+          break;
+        case 'macos':
+          map = kMacOsToPhysicalKey;
+          break;
+        case 'ios':
+          map = kIosToPhysicalKey;
+          break;
+        case 'linux':
+          map = kLinuxToPhysicalKey;
+          break;
+        case 'web':
+          map = kWebToPhysicalKey;
+          break;
+        case 'windows':
+          map = kWindowsToPhysicalKey;
+          break;
+      }
     }
     PhysicalKeyboardKey? result;
     for (final PhysicalKeyboardKey physicalKey in map.values) {
@@ -191,6 +204,13 @@
       'keymap': platform,
     };
 
+    if (kIsWeb) {
+      result['code'] = _getWebKeyCode(key);
+      result['key'] = key.keyLabel;
+      result['metaState'] = _getWebModifierFlags(key, isDown);
+      return result;
+    }
+
     switch (platform) {
       case 'android':
         result['keyCode'] = keyCode;