Ignore keydown event for tab during IME composition (#37753)

* Stop propagation to prevent additional tab keydown during composition

* Intercept keydown in raw_keyboard, add tests

* Fix dart errors

* Fix dart issue

* Refactor

* Move comment
diff --git a/lib/web_ui/lib/src/engine/dom.dart b/lib/web_ui/lib/src/engine/dom.dart
index 6005f1f..1cb4a75 100644
--- a/lib/web_ui/lib/src/engine/dom.dart
+++ b/lib/web_ui/lib/src/engine/dom.dart
@@ -932,6 +932,7 @@
   external bool get metaKey;
   external bool? get repeat;
   external bool get shiftKey;
+  external bool get isComposing;
   external bool getModifierState(String keyArg);
 }
 
diff --git a/lib/web_ui/lib/src/engine/keyboard_binding.dart b/lib/web_ui/lib/src/engine/keyboard_binding.dart
index 0e8226a..e7f67bc 100644
--- a/lib/web_ui/lib/src/engine/keyboard_binding.dart
+++ b/lib/web_ui/lib/src/engine/keyboard_binding.dart
@@ -198,6 +198,7 @@
   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();
diff --git a/lib/web_ui/lib/src/engine/raw_keyboard.dart b/lib/web_ui/lib/src/engine/raw_keyboard.dart
index 57d1611..6157bd7 100644
--- a/lib/web_ui/lib/src/engine/raw_keyboard.dart
+++ b/lib/web_ui/lib/src/engine/raw_keyboard.dart
@@ -88,6 +88,14 @@
     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;
@@ -96,6 +104,10 @@
     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();
diff --git a/lib/web_ui/test/keyboard_test_common.dart b/lib/web_ui/test/keyboard_test_common.dart
index b4b2113..0b447d8 100644
--- a/lib/web_ui/test/keyboard_test_common.dart
+++ b/lib/web_ui/test/keyboard_test_common.dart
@@ -14,6 +14,7 @@
     this.timeStamp = 0,
     this.repeat = false,
     this.keyCode = 0,
+    this.isComposing = false,
     bool altKey = false,
     bool ctrlKey = false,
     bool shiftKey = false,
@@ -51,6 +52,9 @@
   num? timeStamp;
 
   @override
+  bool isComposing;
+
+  @override
   bool get altKey => modifierState.contains('Alt');
 
   @override
diff --git a/lib/web_ui/test/raw_keyboard_test.dart b/lib/web_ui/test/raw_keyboard_test.dart
index bb490b6..0f137ff 100644
--- a/lib/web_ui/test/raw_keyboard_test.dart
+++ b/lib/web_ui/test/raw_keyboard_test.dart
@@ -299,7 +299,9 @@
       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;
@@ -325,6 +327,28 @@
       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) {
@@ -719,6 +743,7 @@
   bool isAltPressed = false,
   bool isControlPressed = false,
   bool isMetaPressed = false,
+  bool isComposing = false,
   int keyCode = 0,
 }) {
   target ??= domWindow;
@@ -736,6 +761,7 @@
       'altKey': isAltPressed,
       'ctrlKey': isControlPressed,
       'metaKey': isMetaPressed,
+      'isComposing': isComposing,
       'keyCode': keyCode,
       'bubbles': true,
       'cancelable': true,