Impl and test (#37972)
diff --git a/lib/web_ui/lib/src/engine/keyboard_binding.dart b/lib/web_ui/lib/src/engine/keyboard_binding.dart
index 92d5ec4..97b1a9d 100644
--- a/lib/web_ui/lib/src/engine/keyboard_binding.dart
+++ b/lib/web_ui/lib/src/engine/keyboard_binding.dart
@@ -219,13 +219,13 @@
// dispatched asynchronously.
class KeyboardConverter {
KeyboardConverter(this.performDispatchKeyData, OperatingSystem platform)
- : onMacOs = platform == OperatingSystem.macOs,
+ : onDarwin = platform == OperatingSystem.macOs || platform == OperatingSystem.iOs,
_mapping = _mappingFromPlatform(platform);
final DispatchKeyData performDispatchKeyData;
- /// Whether the current platform is macOS, which affects how certain key events
- /// are comprehended.
- final bool onMacOs;
+ /// Whether the current platform is macOS or iOS, which affects how certain key
+ /// events are comprehended, including CapsLock and key guarding.
+ final bool onDarwin;
/// Maps logical keys from key event properties.
final locale_keymap.LocaleKeymap _mapping;
@@ -261,7 +261,7 @@
// key down, and synthesizes immediate cancel events following them. The state
// of "whether CapsLock is on" should be accessed by "activeLocks".
bool _shouldSynthesizeCapsLockUp() {
- return onMacOs;
+ return onDarwin;
}
// ## About Key guards
@@ -272,10 +272,10 @@
//
// To avoid this, we rely on the fact that browsers send repeat events
// while the key is held down by the user. If we don't receive a repeat
- // event within a specific duration ([_keydownCancelDurationMac]) we assume
+ // event within a specific duration (_kKeydownCancelDurationMac) we assume
// the user has released the key and we synthesize a keyup event.
bool _shouldDoKeyGuard() {
- return onMacOs;
+ return onDarwin;
}
/// After a keydown is received, this is the duration we wait for a repeat event
diff --git a/lib/web_ui/test/keyboard_converter_test.dart b/lib/web_ui/test/keyboard_converter_test.dart
index 3bbbeb8..a5c81b3 100644
--- a/lib/web_ui/test/keyboard_converter_test.dart
+++ b/lib/web_ui/test/keyboard_converter_test.dart
@@ -541,84 +541,86 @@
);
});
- testFakeAsync('CapsLock down synthesizes an immediate cancel on macOS', (FakeAsync async) {
- final List<ui.KeyData> keyDataList = <ui.KeyData>[];
- final KeyboardConverter converter = KeyboardConverter((ui.KeyData key) {
- keyDataList.add(key);
- return true;
- }, OperatingSystem.macOs);
+ for (final OperatingSystem system in <OperatingSystem>[OperatingSystem.macOs, OperatingSystem.iOs]) {
+ testFakeAsync('CapsLock down synthesizes an immediate cancel on $system', (FakeAsync async) {
+ final List<ui.KeyData> keyDataList = <ui.KeyData>[];
+ final KeyboardConverter converter = KeyboardConverter((ui.KeyData key) {
+ keyDataList.add(key);
+ return true;
+ }, system);
- // A KeyDown of ShiftRight is missed due to loss of focus.
- converter.handleEvent(keyDownEvent('CapsLock', 'CapsLock'));
- expect(keyDataList, hasLength(1));
- expectKeyData(keyDataList.last,
- type: ui.KeyEventType.down,
- physical: kPhysicalCapsLock,
- logical: kLogicalCapsLock,
- character: null,
- );
- expect(MockKeyboardEvent.lastDefaultPrevented, isTrue);
- keyDataList.clear();
+ // A KeyDown of ShiftRight is missed due to loss of focus.
+ converter.handleEvent(keyDownEvent('CapsLock', 'CapsLock'));
+ expect(keyDataList, hasLength(1));
+ expectKeyData(keyDataList.last,
+ type: ui.KeyEventType.down,
+ physical: kPhysicalCapsLock,
+ logical: kLogicalCapsLock,
+ character: null,
+ );
+ expect(MockKeyboardEvent.lastDefaultPrevented, isTrue);
+ keyDataList.clear();
- async.elapse(const Duration(microseconds: 1));
- expect(keyDataList, hasLength(1));
- expectKeyData(keyDataList.last,
- type: ui.KeyEventType.up,
- physical: kPhysicalCapsLock,
- logical: kLogicalCapsLock,
- character: null,
- synthesized: true,
- );
- expect(MockKeyboardEvent.lastDefaultPrevented, isTrue);
- keyDataList.clear();
+ async.elapse(const Duration(microseconds: 1));
+ expect(keyDataList, hasLength(1));
+ expectKeyData(keyDataList.last,
+ type: ui.KeyEventType.up,
+ physical: kPhysicalCapsLock,
+ logical: kLogicalCapsLock,
+ character: null,
+ synthesized: true,
+ );
+ expect(MockKeyboardEvent.lastDefaultPrevented, isTrue);
+ keyDataList.clear();
- converter.handleEvent(keyUpEvent('CapsLock', 'CapsLock'));
- expect(keyDataList, hasLength(1));
- expectKeyData(keyDataList.last,
- type: ui.KeyEventType.down,
- physical: kPhysicalCapsLock,
- logical: kLogicalCapsLock,
- character: null,
- );
- expect(MockKeyboardEvent.lastDefaultPrevented, isTrue);
- keyDataList.clear();
+ converter.handleEvent(keyUpEvent('CapsLock', 'CapsLock'));
+ expect(keyDataList, hasLength(1));
+ expectKeyData(keyDataList.last,
+ type: ui.KeyEventType.down,
+ physical: kPhysicalCapsLock,
+ logical: kLogicalCapsLock,
+ character: null,
+ );
+ expect(MockKeyboardEvent.lastDefaultPrevented, isTrue);
+ keyDataList.clear();
- async.elapse(const Duration(microseconds: 1));
- expect(keyDataList, hasLength(1));
- expectKeyData(keyDataList.last,
- type: ui.KeyEventType.up,
- physical: kPhysicalCapsLock,
- logical: kLogicalCapsLock,
- character: null,
- synthesized: true,
- );
- expect(MockKeyboardEvent.lastDefaultPrevented, isTrue);
- keyDataList.clear();
+ async.elapse(const Duration(microseconds: 1));
+ expect(keyDataList, hasLength(1));
+ expectKeyData(keyDataList.last,
+ type: ui.KeyEventType.up,
+ physical: kPhysicalCapsLock,
+ logical: kLogicalCapsLock,
+ character: null,
+ synthesized: true,
+ );
+ expect(MockKeyboardEvent.lastDefaultPrevented, isTrue);
+ keyDataList.clear();
- // Another key down works
- converter.handleEvent(keyDownEvent('CapsLock', 'CapsLock'));
- expect(keyDataList, hasLength(1));
- expectKeyData(keyDataList.last,
- type: ui.KeyEventType.down,
- physical: kPhysicalCapsLock,
- logical: kLogicalCapsLock,
- character: null,
- );
- keyDataList.clear();
+ // Another key down works
+ converter.handleEvent(keyDownEvent('CapsLock', 'CapsLock'));
+ expect(keyDataList, hasLength(1));
+ expectKeyData(keyDataList.last,
+ type: ui.KeyEventType.down,
+ physical: kPhysicalCapsLock,
+ logical: kLogicalCapsLock,
+ character: null,
+ );
+ keyDataList.clear();
- // Schedules are canceled after disposal
- converter.dispose();
- async.elapse(const Duration(seconds: 10));
- expect(keyDataList, isEmpty);
- });
+ // Schedules are canceled after disposal
+ converter.dispose();
+ async.elapse(const Duration(seconds: 10));
+ expect(keyDataList, isEmpty);
+ });
+ }
testFakeAsync('CapsLock behaves normally on non-macOS', (FakeAsync async) {
final List<ui.KeyData> keyDataList = <ui.KeyData>[];
final KeyboardConverter converter = KeyboardConverter((ui.KeyData key) {
keyDataList.add(key);
return true;
- }, OperatingSystem.linux); // onMacOs: false
+ }, OperatingSystem.linux);
converter.handleEvent(keyDownEvent('CapsLock', 'CapsLock'));
expect(keyDataList, hasLength(1));
@@ -663,69 +665,71 @@
);
});
- testFakeAsync('Key guards: key down events are guarded on macOS', (FakeAsync async) {
- final List<ui.KeyData> keyDataList = <ui.KeyData>[];
- final KeyboardConverter converter = KeyboardConverter((ui.KeyData key) {
- keyDataList.add(key);
- return true;
- }, OperatingSystem.macOs);
+ for (final OperatingSystem system in <OperatingSystem>[OperatingSystem.macOs, OperatingSystem.iOs]) {
+ testFakeAsync('Key guards: key down events are guarded on $system', (FakeAsync async) {
+ final List<ui.KeyData> keyDataList = <ui.KeyData>[];
+ final KeyboardConverter converter = KeyboardConverter((ui.KeyData key) {
+ keyDataList.add(key);
+ return true;
+ }, system);
- converter.handleEvent(keyDownEvent('MetaLeft', 'Meta', kMeta, kLocationLeft)..timeStamp = 100);
- async.elapse(const Duration(milliseconds: 100));
+ converter.handleEvent(keyDownEvent('MetaLeft', 'Meta', kMeta, kLocationLeft)..timeStamp = 100);
+ async.elapse(const Duration(milliseconds: 100));
- converter.handleEvent(keyDownEvent('KeyA', 'a', kMeta)..timeStamp = 200);
- expectKeyData(keyDataList.last,
- timeStamp: const Duration(milliseconds: 200),
- type: ui.KeyEventType.down,
- physical: kPhysicalKeyA,
- logical: kLogicalKeyA,
- character: 'a',
- );
- keyDataList.clear();
+ converter.handleEvent(keyDownEvent('KeyA', 'a', kMeta)..timeStamp = 200);
+ expectKeyData(keyDataList.last,
+ timeStamp: const Duration(milliseconds: 200),
+ type: ui.KeyEventType.down,
+ physical: kPhysicalKeyA,
+ logical: kLogicalKeyA,
+ character: 'a',
+ );
+ keyDataList.clear();
- // Keyup of KeyA is omitted due to being a shortcut.
+ // Keyup of KeyA is omitted due to being a shortcut.
- async.elapse(const Duration(milliseconds: 2500));
- expectKeyData(keyDataList.last,
- timeStamp: const Duration(milliseconds: 2200),
- type: ui.KeyEventType.up,
- physical: kPhysicalKeyA,
- logical: kLogicalKeyA,
- character: null,
- synthesized: true,
- );
- keyDataList.clear();
+ async.elapse(const Duration(milliseconds: 2500));
+ expectKeyData(keyDataList.last,
+ timeStamp: const Duration(milliseconds: 2200),
+ type: ui.KeyEventType.up,
+ physical: kPhysicalKeyA,
+ logical: kLogicalKeyA,
+ character: null,
+ synthesized: true,
+ );
+ keyDataList.clear();
- converter.handleEvent(keyUpEvent('MetaLeft', 'Meta', 0, kLocationLeft)..timeStamp = 2700);
- expectKeyData(keyDataList.last,
- timeStamp: const Duration(milliseconds: 2700),
- type: ui.KeyEventType.up,
- physical: kPhysicalMetaLeft,
- logical: kLogicalMetaLeft,
- character: null,
- );
- async.elapse(const Duration(milliseconds: 100));
+ converter.handleEvent(keyUpEvent('MetaLeft', 'Meta', 0, kLocationLeft)..timeStamp = 2700);
+ expectKeyData(keyDataList.last,
+ timeStamp: const Duration(milliseconds: 2700),
+ type: ui.KeyEventType.up,
+ physical: kPhysicalMetaLeft,
+ logical: kLogicalMetaLeft,
+ character: null,
+ );
+ async.elapse(const Duration(milliseconds: 100));
- // Key A states are cleared
- converter.handleEvent(keyDownEvent('KeyA', 'a')..timeStamp = 2800);
- expectKeyData(keyDataList.last,
- timeStamp: const Duration(milliseconds: 2800),
- type: ui.KeyEventType.down,
- physical: kPhysicalKeyA,
- logical: kLogicalKeyA,
- character: 'a',
- );
- async.elapse(const Duration(milliseconds: 100));
+ // Key A states are cleared
+ converter.handleEvent(keyDownEvent('KeyA', 'a')..timeStamp = 2800);
+ expectKeyData(keyDataList.last,
+ timeStamp: const Duration(milliseconds: 2800),
+ type: ui.KeyEventType.down,
+ physical: kPhysicalKeyA,
+ logical: kLogicalKeyA,
+ character: 'a',
+ );
+ async.elapse(const Duration(milliseconds: 100));
- converter.handleEvent(keyUpEvent('KeyA', 'a')..timeStamp = 2900);
- expectKeyData(keyDataList.last,
- timeStamp: const Duration(milliseconds: 2900),
- type: ui.KeyEventType.up,
- physical: kPhysicalKeyA,
- logical: kLogicalKeyA,
- character: null,
- );
- });
+ converter.handleEvent(keyUpEvent('KeyA', 'a')..timeStamp = 2900);
+ expectKeyData(keyDataList.last,
+ timeStamp: const Duration(milliseconds: 2900),
+ type: ui.KeyEventType.up,
+ physical: kPhysicalKeyA,
+ logical: kLogicalKeyA,
+ character: null,
+ );
+ });
+ }
testFakeAsync('Key guards: key repeated down events refreshes guards', (FakeAsync async) {
final List<ui.KeyData> keyDataList = <ui.KeyData>[];
@@ -795,7 +799,7 @@
final KeyboardConverter converter = KeyboardConverter((ui.KeyData key) {
keyDataList.add(key);
return true;
- }, OperatingSystem.linux);
+ }, OperatingSystem.macOs);
converter.handleEvent(keyDownEvent('MetaLeft', 'Meta', kMeta, kLocationLeft)..timeStamp = 100);
async.elapse(const Duration(milliseconds: 100));