Make RawKeyboard assert when no keys are in keysPressed when sending a key down event. (#63426)

This adds an assert in RawKeyboard that catches the case where it tries to send a key down event, but (after synchronizing modifiers) there are no keys in keysPressed. This state can occur if the modifier flags are not set properly for the platform.

Also prevents shortcuts attempting to handle a key down when no keys are pressed at the moment (which was causing a crash in release mode).
This commit is contained in:
Greg Spencer 2020-08-11 09:37:30 -07:00 committed by GitHub
parent e3c7fb5bef
commit 0ec0bf505e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 51 additions and 1 deletions

View File

@ -581,6 +581,11 @@ class RawKeyboard {
// Make sure that the modifiers reflect reality, in case a modifier key was
// pressed/released while the app didn't have focus.
_synchronizeModifiers(event);
assert(event is! RawKeyDownEvent || _keysPressed.isNotEmpty,
'Attempted to send a key down event when no keys are in keysPressed. '
"This state can occur if the key event being sent doesn't properly "
'set its modifier flags. This was the event: $event and its data: '
'${event.data}');
// Send the event to passive listeners.
for (final ValueChanged<RawKeyEvent> listener in List<ValueChanged<RawKeyEvent>>.from(_listeners)) {
if (_listeners.contains(listener)) {

View File

@ -306,7 +306,21 @@ class ShortcutManager extends ChangeNotifier with Diagnosticable {
return false;
}
assert(context != null);
final LogicalKeySet keySet = keysPressed ?? LogicalKeySet.fromSet(RawKeyboard.instance.keysPressed);
LogicalKeySet keySet = keysPressed;
if (keySet == null) {
assert(RawKeyboard.instance.keysPressed.isNotEmpty,
'Received a key down event when no keys are in keysPressed. '
"This state can occur if the key event being sent doesn't properly "
'set its modifier flags. This was the event: $event and its data: '
'${event.data}');
// Avoid the crash in release mode, since it's easy to miss a particular
// bad key sequence in testing, and so shouldn't crash the app in release.
if (RawKeyboard.instance.keysPressed.isNotEmpty) {
keySet = LogicalKeySet.fromSet(RawKeyboard.instance.keysPressed);
} else {
return false;
}
}
Intent matchedIntent = _shortcuts[keySet];
if (matchedIntent == null) {
// If there's not a more specific match, We also look for any keys that

View File

@ -4,6 +4,7 @@
// @dart = 2.8
import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_test/flutter_test.dart';
@ -277,6 +278,36 @@ void main() {
),
);
});
testWidgets('RawKeyboard asserts if no keys are in keysPressed after receiving a key down event', (WidgetTester tester) async {
FlutterErrorDetails errorDetails;
final FlutterExceptionHandler oldHandler = FlutterError.onError;
FlutterError.onError = (FlutterErrorDetails details) {
errorDetails = details;
};
try {
await ServicesBinding.instance.defaultBinaryMessenger
.handlePlatformMessage(
SystemChannels.keyEvent.name,
SystemChannels.keyEvent.codec.encodeMessage(const <String, dynamic>{
'type': 'keydown',
'keymap': 'android',
'keyCode': 0x3b, // Left shift key keyCode
'scanCode': 0x2a,
'metaState': 0x0, // No shift key metaState set!
'source': 0x101,
'deviceId': 1,
}),
(ByteData data) {},
);
} finally {
FlutterError.onError = oldHandler;
}
expect(errorDetails, isNotNull);
expect(errorDetails.stack, isNotNull);
final String fullErrorMessage = errorDetails.toString().replaceAll('\n', ' ');
expect(fullErrorMessage, contains('Attempted to send a key down event when no keys are in keysPressed'));
});
});
group('RawKeyEventDataAndroid', () {