Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

Commit a620595

Browse files
authored
Impl and test (#37972)
1 parent be4c7e2 commit a620595

File tree

2 files changed

+147
-143
lines changed

2 files changed

+147
-143
lines changed

lib/web_ui/lib/src/engine/keyboard_binding.dart

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -219,13 +219,13 @@ class FlutterHtmlKeyboardEvent {
219219
// dispatched asynchronously.
220220
class KeyboardConverter {
221221
KeyboardConverter(this.performDispatchKeyData, OperatingSystem platform)
222-
: onMacOs = platform == OperatingSystem.macOs,
222+
: onDarwin = platform == OperatingSystem.macOs || platform == OperatingSystem.iOs,
223223
_mapping = _mappingFromPlatform(platform);
224224

225225
final DispatchKeyData performDispatchKeyData;
226-
/// Whether the current platform is macOS, which affects how certain key events
227-
/// are comprehended.
228-
final bool onMacOs;
226+
/// Whether the current platform is macOS or iOS, which affects how certain key
227+
/// events are comprehended, including CapsLock and key guarding.
228+
final bool onDarwin;
229229
/// Maps logical keys from key event properties.
230230
final locale_keymap.LocaleKeymap _mapping;
231231

@@ -261,7 +261,7 @@ class KeyboardConverter {
261261
// key down, and synthesizes immediate cancel events following them. The state
262262
// of "whether CapsLock is on" should be accessed by "activeLocks".
263263
bool _shouldSynthesizeCapsLockUp() {
264-
return onMacOs;
264+
return onDarwin;
265265
}
266266

267267
// ## About Key guards
@@ -272,10 +272,10 @@ class KeyboardConverter {
272272
//
273273
// To avoid this, we rely on the fact that browsers send repeat events
274274
// while the key is held down by the user. If we don't receive a repeat
275-
// event within a specific duration ([_keydownCancelDurationMac]) we assume
275+
// event within a specific duration (_kKeydownCancelDurationMac) we assume
276276
// the user has released the key and we synthesize a keyup event.
277277
bool _shouldDoKeyGuard() {
278-
return onMacOs;
278+
return onDarwin;
279279
}
280280

281281
/// After a keydown is received, this is the duration we wait for a repeat event

lib/web_ui/test/keyboard_converter_test.dart

Lines changed: 140 additions & 136 deletions
Original file line numberDiff line numberDiff line change
@@ -541,84 +541,86 @@ void testMain() {
541541
);
542542
});
543543

544-
testFakeAsync('CapsLock down synthesizes an immediate cancel on macOS', (FakeAsync async) {
545-
final List<ui.KeyData> keyDataList = <ui.KeyData>[];
546-
final KeyboardConverter converter = KeyboardConverter((ui.KeyData key) {
547-
keyDataList.add(key);
548-
return true;
549-
}, OperatingSystem.macOs);
550-
551-
// A KeyDown of ShiftRight is missed due to loss of focus.
552-
converter.handleEvent(keyDownEvent('CapsLock', 'CapsLock'));
553-
expect(keyDataList, hasLength(1));
554-
expectKeyData(keyDataList.last,
555-
type: ui.KeyEventType.down,
556-
physical: kPhysicalCapsLock,
557-
logical: kLogicalCapsLock,
558-
character: null,
559-
);
560-
expect(MockKeyboardEvent.lastDefaultPrevented, isTrue);
561-
keyDataList.clear();
562-
563-
async.elapse(const Duration(microseconds: 1));
564-
expect(keyDataList, hasLength(1));
565-
expectKeyData(keyDataList.last,
566-
type: ui.KeyEventType.up,
567-
physical: kPhysicalCapsLock,
568-
logical: kLogicalCapsLock,
569-
character: null,
570-
synthesized: true,
571-
);
572-
expect(MockKeyboardEvent.lastDefaultPrevented, isTrue);
573-
keyDataList.clear();
574-
575-
converter.handleEvent(keyUpEvent('CapsLock', 'CapsLock'));
576-
expect(keyDataList, hasLength(1));
577-
expectKeyData(keyDataList.last,
578-
type: ui.KeyEventType.down,
579-
physical: kPhysicalCapsLock,
580-
logical: kLogicalCapsLock,
581-
character: null,
582-
);
583-
expect(MockKeyboardEvent.lastDefaultPrevented, isTrue);
584-
keyDataList.clear();
585-
586-
async.elapse(const Duration(microseconds: 1));
587-
expect(keyDataList, hasLength(1));
588-
expectKeyData(keyDataList.last,
589-
type: ui.KeyEventType.up,
590-
physical: kPhysicalCapsLock,
591-
logical: kLogicalCapsLock,
592-
character: null,
593-
synthesized: true,
594-
);
595-
expect(MockKeyboardEvent.lastDefaultPrevented, isTrue);
596-
keyDataList.clear();
597-
598-
// Another key down works
599-
converter.handleEvent(keyDownEvent('CapsLock', 'CapsLock'));
600-
expect(keyDataList, hasLength(1));
601-
expectKeyData(keyDataList.last,
602-
type: ui.KeyEventType.down,
603-
physical: kPhysicalCapsLock,
604-
logical: kLogicalCapsLock,
605-
character: null,
606-
);
607-
keyDataList.clear();
608-
609-
610-
// Schedules are canceled after disposal
611-
converter.dispose();
612-
async.elapse(const Duration(seconds: 10));
613-
expect(keyDataList, isEmpty);
614-
});
544+
for (final OperatingSystem system in <OperatingSystem>[OperatingSystem.macOs, OperatingSystem.iOs]) {
545+
testFakeAsync('CapsLock down synthesizes an immediate cancel on $system', (FakeAsync async) {
546+
final List<ui.KeyData> keyDataList = <ui.KeyData>[];
547+
final KeyboardConverter converter = KeyboardConverter((ui.KeyData key) {
548+
keyDataList.add(key);
549+
return true;
550+
}, system);
551+
552+
// A KeyDown of ShiftRight is missed due to loss of focus.
553+
converter.handleEvent(keyDownEvent('CapsLock', 'CapsLock'));
554+
expect(keyDataList, hasLength(1));
555+
expectKeyData(keyDataList.last,
556+
type: ui.KeyEventType.down,
557+
physical: kPhysicalCapsLock,
558+
logical: kLogicalCapsLock,
559+
character: null,
560+
);
561+
expect(MockKeyboardEvent.lastDefaultPrevented, isTrue);
562+
keyDataList.clear();
563+
564+
async.elapse(const Duration(microseconds: 1));
565+
expect(keyDataList, hasLength(1));
566+
expectKeyData(keyDataList.last,
567+
type: ui.KeyEventType.up,
568+
physical: kPhysicalCapsLock,
569+
logical: kLogicalCapsLock,
570+
character: null,
571+
synthesized: true,
572+
);
573+
expect(MockKeyboardEvent.lastDefaultPrevented, isTrue);
574+
keyDataList.clear();
575+
576+
converter.handleEvent(keyUpEvent('CapsLock', 'CapsLock'));
577+
expect(keyDataList, hasLength(1));
578+
expectKeyData(keyDataList.last,
579+
type: ui.KeyEventType.down,
580+
physical: kPhysicalCapsLock,
581+
logical: kLogicalCapsLock,
582+
character: null,
583+
);
584+
expect(MockKeyboardEvent.lastDefaultPrevented, isTrue);
585+
keyDataList.clear();
586+
587+
async.elapse(const Duration(microseconds: 1));
588+
expect(keyDataList, hasLength(1));
589+
expectKeyData(keyDataList.last,
590+
type: ui.KeyEventType.up,
591+
physical: kPhysicalCapsLock,
592+
logical: kLogicalCapsLock,
593+
character: null,
594+
synthesized: true,
595+
);
596+
expect(MockKeyboardEvent.lastDefaultPrevented, isTrue);
597+
keyDataList.clear();
598+
599+
// Another key down works
600+
converter.handleEvent(keyDownEvent('CapsLock', 'CapsLock'));
601+
expect(keyDataList, hasLength(1));
602+
expectKeyData(keyDataList.last,
603+
type: ui.KeyEventType.down,
604+
physical: kPhysicalCapsLock,
605+
logical: kLogicalCapsLock,
606+
character: null,
607+
);
608+
keyDataList.clear();
609+
610+
611+
// Schedules are canceled after disposal
612+
converter.dispose();
613+
async.elapse(const Duration(seconds: 10));
614+
expect(keyDataList, isEmpty);
615+
});
616+
}
615617

616618
testFakeAsync('CapsLock behaves normally on non-macOS', (FakeAsync async) {
617619
final List<ui.KeyData> keyDataList = <ui.KeyData>[];
618620
final KeyboardConverter converter = KeyboardConverter((ui.KeyData key) {
619621
keyDataList.add(key);
620622
return true;
621-
}, OperatingSystem.linux); // onMacOs: false
623+
}, OperatingSystem.linux);
622624

623625
converter.handleEvent(keyDownEvent('CapsLock', 'CapsLock'));
624626
expect(keyDataList, hasLength(1));
@@ -663,69 +665,71 @@ void testMain() {
663665
);
664666
});
665667

666-
testFakeAsync('Key guards: key down events are guarded on macOS', (FakeAsync async) {
667-
final List<ui.KeyData> keyDataList = <ui.KeyData>[];
668-
final KeyboardConverter converter = KeyboardConverter((ui.KeyData key) {
669-
keyDataList.add(key);
670-
return true;
671-
}, OperatingSystem.macOs);
672-
673-
converter.handleEvent(keyDownEvent('MetaLeft', 'Meta', kMeta, kLocationLeft)..timeStamp = 100);
674-
async.elapse(const Duration(milliseconds: 100));
675-
676-
converter.handleEvent(keyDownEvent('KeyA', 'a', kMeta)..timeStamp = 200);
677-
expectKeyData(keyDataList.last,
678-
timeStamp: const Duration(milliseconds: 200),
679-
type: ui.KeyEventType.down,
680-
physical: kPhysicalKeyA,
681-
logical: kLogicalKeyA,
682-
character: 'a',
683-
);
684-
keyDataList.clear();
685-
686-
// Keyup of KeyA is omitted due to being a shortcut.
687-
688-
async.elapse(const Duration(milliseconds: 2500));
689-
expectKeyData(keyDataList.last,
690-
timeStamp: const Duration(milliseconds: 2200),
691-
type: ui.KeyEventType.up,
692-
physical: kPhysicalKeyA,
693-
logical: kLogicalKeyA,
694-
character: null,
695-
synthesized: true,
696-
);
697-
keyDataList.clear();
698-
699-
converter.handleEvent(keyUpEvent('MetaLeft', 'Meta', 0, kLocationLeft)..timeStamp = 2700);
700-
expectKeyData(keyDataList.last,
701-
timeStamp: const Duration(milliseconds: 2700),
702-
type: ui.KeyEventType.up,
703-
physical: kPhysicalMetaLeft,
704-
logical: kLogicalMetaLeft,
705-
character: null,
706-
);
707-
async.elapse(const Duration(milliseconds: 100));
708-
709-
// Key A states are cleared
710-
converter.handleEvent(keyDownEvent('KeyA', 'a')..timeStamp = 2800);
711-
expectKeyData(keyDataList.last,
712-
timeStamp: const Duration(milliseconds: 2800),
713-
type: ui.KeyEventType.down,
714-
physical: kPhysicalKeyA,
715-
logical: kLogicalKeyA,
716-
character: 'a',
717-
);
718-
async.elapse(const Duration(milliseconds: 100));
719-
720-
converter.handleEvent(keyUpEvent('KeyA', 'a')..timeStamp = 2900);
721-
expectKeyData(keyDataList.last,
722-
timeStamp: const Duration(milliseconds: 2900),
723-
type: ui.KeyEventType.up,
724-
physical: kPhysicalKeyA,
725-
logical: kLogicalKeyA,
726-
character: null,
727-
);
728-
});
668+
for (final OperatingSystem system in <OperatingSystem>[OperatingSystem.macOs, OperatingSystem.iOs]) {
669+
testFakeAsync('Key guards: key down events are guarded on $system', (FakeAsync async) {
670+
final List<ui.KeyData> keyDataList = <ui.KeyData>[];
671+
final KeyboardConverter converter = KeyboardConverter((ui.KeyData key) {
672+
keyDataList.add(key);
673+
return true;
674+
}, system);
675+
676+
converter.handleEvent(keyDownEvent('MetaLeft', 'Meta', kMeta, kLocationLeft)..timeStamp = 100);
677+
async.elapse(const Duration(milliseconds: 100));
678+
679+
converter.handleEvent(keyDownEvent('KeyA', 'a', kMeta)..timeStamp = 200);
680+
expectKeyData(keyDataList.last,
681+
timeStamp: const Duration(milliseconds: 200),
682+
type: ui.KeyEventType.down,
683+
physical: kPhysicalKeyA,
684+
logical: kLogicalKeyA,
685+
character: 'a',
686+
);
687+
keyDataList.clear();
688+
689+
// Keyup of KeyA is omitted due to being a shortcut.
690+
691+
async.elapse(const Duration(milliseconds: 2500));
692+
expectKeyData(keyDataList.last,
693+
timeStamp: const Duration(milliseconds: 2200),
694+
type: ui.KeyEventType.up,
695+
physical: kPhysicalKeyA,
696+
logical: kLogicalKeyA,
697+
character: null,
698+
synthesized: true,
699+
);
700+
keyDataList.clear();
701+
702+
converter.handleEvent(keyUpEvent('MetaLeft', 'Meta', 0, kLocationLeft)..timeStamp = 2700);
703+
expectKeyData(keyDataList.last,
704+
timeStamp: const Duration(milliseconds: 2700),
705+
type: ui.KeyEventType.up,
706+
physical: kPhysicalMetaLeft,
707+
logical: kLogicalMetaLeft,
708+
character: null,
709+
);
710+
async.elapse(const Duration(milliseconds: 100));
711+
712+
// Key A states are cleared
713+
converter.handleEvent(keyDownEvent('KeyA', 'a')..timeStamp = 2800);
714+
expectKeyData(keyDataList.last,
715+
timeStamp: const Duration(milliseconds: 2800),
716+
type: ui.KeyEventType.down,
717+
physical: kPhysicalKeyA,
718+
logical: kLogicalKeyA,
719+
character: 'a',
720+
);
721+
async.elapse(const Duration(milliseconds: 100));
722+
723+
converter.handleEvent(keyUpEvent('KeyA', 'a')..timeStamp = 2900);
724+
expectKeyData(keyDataList.last,
725+
timeStamp: const Duration(milliseconds: 2900),
726+
type: ui.KeyEventType.up,
727+
physical: kPhysicalKeyA,
728+
logical: kLogicalKeyA,
729+
character: null,
730+
);
731+
});
732+
}
729733

730734
testFakeAsync('Key guards: key repeated down events refreshes guards', (FakeAsync async) {
731735
final List<ui.KeyData> keyDataList = <ui.KeyData>[];
@@ -795,7 +799,7 @@ void testMain() {
795799
final KeyboardConverter converter = KeyboardConverter((ui.KeyData key) {
796800
keyDataList.add(key);
797801
return true;
798-
}, OperatingSystem.linux);
802+
}, OperatingSystem.macOs);
799803

800804
converter.handleEvent(keyDownEvent('MetaLeft', 'Meta', kMeta, kLocationLeft)..timeStamp = 100);
801805
async.elapse(const Duration(milliseconds: 100));

0 commit comments

Comments
 (0)