Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 30 additions & 11 deletions lib/web_ui/lib/src/engine/keyboard_binding.dart
Original file line number Diff line number Diff line change
Expand Up @@ -371,9 +371,7 @@ class KeyboardConverter {
// followed by an immediate cancel event.
(_shouldSynthesizeCapsLockUp() && event.code! == _kPhysicalCapsLock);

final int? lastLogicalRecord = _pressingRecords[physicalKey];

ui.KeyEventType type;
final ui.KeyEventType type;

if (_shouldSynthesizeCapsLockUp() && event.code! == _kPhysicalCapsLock) {
// Case 1: Handle CapsLock on macOS
Expand All @@ -399,28 +397,45 @@ class KeyboardConverter {

} else if (isPhysicalDown) {
// Case 2: Handle key down of normal keys
type = ui.KeyEventType.down;
if (lastLogicalRecord != null) {
if (_pressingRecords[physicalKey] != null) {
// This physical key is being pressed according to the record.
if (event.repeat ?? false) {
// A normal repeated key.
type = ui.KeyEventType.repeat;
} else {
// A non-repeated key has been pressed that has the exact physical key as
// a currently pressed one, usually indicating multiple keyboards are
// pressing keys with the same physical key, or the up event was lost
// during a loss of focus. The down event is ignored.
event.preventDefault();
return;
// a currently pressed one. This can mean one of the following cases:
//
// * Multiple keyboards are pressing keys with the same physical key.
// * The up event was lost during a loss of focus.
// * The previous down event was a system shortcut and its release
// was skipped (see `_startGuardingKey`,) such as holding Ctrl and
// pressing V then V, within the "guard window".
//
// The three cases can't be distinguished, and in the 3rd case, the
// latter event must be dispatched as down events for the framework to
// correctly recognize and choose to not to handle. Therefore, an up
// event is synthesized before it.
_dispatchKeyData!(ui.KeyData(
timeStamp: timeStamp,
type: ui.KeyEventType.up,
physical: physicalKey,
logical: logicalKey,
character: null,
synthesized: true,
));
_pressingRecords.remove(physicalKey);
type = ui.KeyEventType.down;
}
} else {
// This physical key is not being pressed according to the record. It's a
// normal down event, whether the system event is a repeat or not.
type = ui.KeyEventType.down;
}

} else { // isPhysicalDown is false and not CapsLock
// Case 2: Handle key up of normal keys
if (lastLogicalRecord == null) {
if (_pressingRecords[physicalKey] == null) {
// The physical key has been released before. It indicates multiple
// keyboards pressed keys with the same physical key. Ignore the up event.
event.preventDefault();
Expand All @@ -430,6 +445,10 @@ class KeyboardConverter {
type = ui.KeyEventType.up;
}

// The _pressingRecords[physicalKey] might have been changed during the last
// `if` clause.
final int? lastLogicalRecord = _pressingRecords[physicalKey];

final int? nextLogicalRecord;
switch (type) {
case ui.KeyEventType.down:
Expand Down
21 changes: 16 additions & 5 deletions lib/web_ui/test/keyboard_converter_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -378,7 +378,7 @@ void testMain() {
converter.handleEvent(keyUpEvent('ShiftLeft', 'Shift', 0, kLocationLeft));
});

test('Duplicate down is ignored', () {
test('Duplicate down is preceded with synthesized up', () {
final List<ui.KeyData> keyDataList = <ui.KeyData>[];
final KeyboardConverter converter = KeyboardConverter((ui.KeyData key) {
keyDataList.add(key);
Expand All @@ -392,15 +392,26 @@ void testMain() {
);
expect(preventedDefault, isTrue);
preventedDefault = false;
// A KeyUp of ShiftLeft is missed due to loss of focus.
// A KeyUp of ShiftLeft is missed.

keyDataList.clear();
converter.handleEvent(keyDownEvent('ShiftLeft', 'Shift', kShift, kLocationLeft)
..onPreventDefault = onPreventDefault
);
expect(keyDataList, hasLength(1));
expect(keyDataList[0].physical, 0);
expect(keyDataList[0].logical, 0);
expect(keyDataList, hasLength(2));
expectKeyData(keyDataList.first,
type: ui.KeyEventType.up,
physical: kPhysicalShiftLeft,
logical: kLogicalShiftLeft,
character: null,
synthesized: true,
);
expectKeyData(keyDataList.last,
type: ui.KeyEventType.down,
physical: kPhysicalShiftLeft,
logical: kLogicalShiftLeft,
character: null,
);
expect(preventedDefault, isTrue);

keyDataList.clear();
Expand Down