diff --git a/lib/web_ui/lib/src/engine/keyboard_binding.dart b/lib/web_ui/lib/src/engine/keyboard_binding.dart index 7efd102023c70..01f00867c894c 100644 --- a/lib/web_ui/lib/src/engine/keyboard_binding.dart +++ b/lib/web_ui/lib/src/engine/keyboard_binding.dart @@ -475,20 +475,25 @@ class KeyboardConverter { // After updating _pressingRecords, synchronize modifier states. The // `event.***Key` fields can be used to reduce some omitted modifier key - // events. We can deduce key cancel events if they are false. Key sync - // events can not be deduced since we don't know which physical key they + // events. We can synthesize key up events if they are false. Key down + // events can not be synthesized since we don't know which physical key they // represent. - _kLogicalKeyToModifierGetter.forEach((int logicalKey, _ModifierGetter getModifier) { - if (_pressingRecords.containsValue(logicalKey) && !getModifier(event)) { + _kLogicalKeyToModifierGetter.forEach((int testeeLogicalKey, _ModifierGetter getModifier) { + // Do not synthesize for the key of the current event. The event is the + // ground truth. + if (logicalKey == testeeLogicalKey) { + return; + } + if (_pressingRecords.containsValue(testeeLogicalKey) && !getModifier(event)) { _pressingRecords.removeWhere((int physicalKey, int logicalRecord) { - if (logicalRecord != logicalKey) + if (logicalRecord != testeeLogicalKey) return false; _dispatchKeyData!(ui.KeyData( timeStamp: timeStamp, type: ui.KeyEventType.up, physical: physicalKey, - logical: logicalKey, + logical: testeeLogicalKey, character: null, synthesized: true, )); diff --git a/lib/web_ui/test/keyboard_converter_test.dart b/lib/web_ui/test/keyboard_converter_test.dart index 3391a703c4c3e..234f26018cf93 100644 --- a/lib/web_ui/test/keyboard_converter_test.dart +++ b/lib/web_ui/test/keyboard_converter_test.dart @@ -996,6 +996,60 @@ void testMain() { character: 'a', ); }); + + // Regression test for https://github.com/flutter/flutter/issues/99297. + // + // On Linux Chrome, when holding ShiftLeft and pressing MetaLeft (Win key), + // the MetaLeft down event has metaKey true, while the Meta up event has + // metaKey false. This violates the definition of metaKey, and does not happen + // in nearly any other cases for any other keys. + test('Ignore inconsistent modifier flag of the current modifier', () { + final List keyDataList = []; + final KeyboardConverter converter = KeyboardConverter((ui.KeyData key) { + keyDataList.add(key); + return true; + }, onMacOs: false); + + converter.handleEvent(keyDownEvent('ShiftLeft', 'Shift', kShift, kLocationLeft)); + expectKeyData(keyDataList.last, + type: ui.KeyEventType.down, + physical: kPhysicalShiftLeft, + logical: kLogicalShiftLeft, + character: null, + ); + keyDataList.clear(); + + converter.handleEvent(keyDownEvent('MetaLeft', 'Meta', kShift /* No kMeta here! */, kLocationLeft)); + // Only a MetaLeft down event, no synthesized MetaLeft up events. + expect(keyDataList, hasLength(1)); + expectKeyData(keyDataList.first, + type: ui.KeyEventType.down, + physical: kPhysicalMetaLeft, + logical: kLogicalMetaLeft, + character: null, + ); + keyDataList.clear(); + + converter.handleEvent(keyUpEvent('MetaLeft', 'Meta', kShift | kMeta /* Yes, kMeta here! */, kLocationLeft)); + // Only a MetaLeft down event, no synthesized MetaLeft up events. + expect(keyDataList, hasLength(1)); + expectKeyData(keyDataList.first, + type: ui.KeyEventType.up, + physical: kPhysicalMetaLeft, + logical: kLogicalMetaLeft, + character: null, + ); + keyDataList.clear(); + + converter.handleEvent(keyUpEvent('ShiftLeft', 'Shift', 0, kLocationLeft)); + expectKeyData(keyDataList.last, + type: ui.KeyEventType.up, + physical: kPhysicalShiftLeft, + logical: kLogicalShiftLeft, + character: null, + ); + keyDataList.clear(); + }); } class MockKeyboardEvent implements FlutterHtmlKeyboardEvent {