diff --git a/lib/web_ui/lib/src/engine/embedder.dart b/lib/web_ui/lib/src/engine/embedder.dart index 44677dbbefaf9..42479a07c8b2f 100644 --- a/lib/web_ui/lib/src/engine/embedder.dart +++ b/lib/web_ui/lib/src/engine/embedder.dart @@ -323,8 +323,8 @@ class FlutterViewEmbedder { _sceneHostElement!.style.opacity = '0.3'; } - PointerBinding.initInstance(glassPaneElement); KeyboardBinding.initInstance(); + PointerBinding.initInstance(glassPaneElement, KeyboardBinding.instance!.converter); if (domWindow.visualViewport == null && isWebKit) { // Older Safari versions sometimes give us bogus innerWidth/innerHeight diff --git a/lib/web_ui/lib/src/engine/keyboard_binding.dart b/lib/web_ui/lib/src/engine/keyboard_binding.dart index 74e0fa5f7d050..bb451771adc0c 100644 --- a/lib/web_ui/lib/src/engine/keyboard_binding.dart +++ b/lib/web_ui/lib/src/engine/keyboard_binding.dart @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +import 'package:meta/meta.dart'; import 'package:ui/ui.dart' as ui; import '../engine.dart' show registerHotRestartListener; @@ -30,6 +31,16 @@ final int _kLogicalShiftLeft = kWebLogicalLocationMap['Shift']![_kLocationLeft]! final int _kLogicalShiftRight = kWebLogicalLocationMap['Shift']![_kLocationRight]!; final int _kLogicalMetaLeft = kWebLogicalLocationMap['Meta']![_kLocationLeft]!; final int _kLogicalMetaRight = kWebLogicalLocationMap['Meta']![_kLocationRight]!; + +final int _kPhysicalAltLeft = kWebToPhysicalKey['AltLeft']!; +final int _kPhysicalAltRight = kWebToPhysicalKey['AltRight']!; +final int _kPhysicalControlLeft = kWebToPhysicalKey['ControlLeft']!; +final int _kPhysicalControlRight = kWebToPhysicalKey['ControlRight']!; +final int _kPhysicalShiftLeft = kWebToPhysicalKey['ShiftLeft']!; +final int _kPhysicalShiftRight = kWebToPhysicalKey['ShiftRight']!; +final int _kPhysicalMetaLeft = kWebToPhysicalKey['MetaLeft']!; +final int _kPhysicalMetaRight = kWebToPhysicalKey['MetaRight']!; + // Map logical keys for modifier keys to the functions that can get their // modifier flag out of an event. final Map _kLogicalKeyToModifierGetter = { @@ -106,6 +117,7 @@ class KeyboardBinding { } } + KeyboardConverter get converter => _converter; late final KeyboardConverter _converter; final Map _listeners = {}; @@ -559,4 +571,107 @@ class KeyboardConverter { _dispatchKeyData = null; } } + + // Synthesize modifier keys up or down events only when the known pressing states are different. + void synthesizeModifiersIfNeeded( + bool altPressed, + bool controlPressed, + bool metaPressed, + bool shiftPressed, + num eventTimestamp, + ) { + _synthesizeModifierIfNeeded( + _kPhysicalAltLeft, + _kPhysicalAltRight, + _kLogicalAltLeft, + _kLogicalAltRight, + altPressed ? ui.KeyEventType.down : ui.KeyEventType.up, + eventTimestamp, + ); + _synthesizeModifierIfNeeded( + _kPhysicalControlLeft, + _kPhysicalControlRight, + _kLogicalControlLeft, + _kLogicalControlRight, + controlPressed ? ui.KeyEventType.down : ui.KeyEventType.up, + eventTimestamp, + ); + _synthesizeModifierIfNeeded( + _kPhysicalMetaLeft, + _kPhysicalMetaRight, + _kLogicalMetaLeft, + _kLogicalMetaRight, + metaPressed ? ui.KeyEventType.down : ui.KeyEventType.up, + eventTimestamp, + ); + _synthesizeModifierIfNeeded( + _kPhysicalShiftLeft, + _kPhysicalShiftRight, + _kLogicalShiftLeft, + _kLogicalShiftRight, + shiftPressed ? ui.KeyEventType.down : ui.KeyEventType.up, + eventTimestamp, + ); + } + + void _synthesizeModifierIfNeeded( + int physicalLeft, + int physicalRight, + int logicalLeft, + int logicalRight, + ui.KeyEventType type, + num domTimestamp, + ) { + final bool leftPressed = _pressingRecords.containsKey(physicalLeft); + final bool rightPressed = _pressingRecords.containsKey(physicalRight); + final bool alreadyPressed = leftPressed || rightPressed; + final bool synthesizeDown = type == ui.KeyEventType.down && !alreadyPressed; + final bool synthesizeUp = type == ui.KeyEventType.up && alreadyPressed; + + // Synthesize a down event only for the left key if right and left are not pressed + if (synthesizeDown) { + _synthesizeKeyDownEvent(domTimestamp, physicalLeft, logicalLeft); + } + + // Synthesize an up event for left key if pressed + if (synthesizeUp && leftPressed) { + _synthesizeKeyUpEvent(domTimestamp, physicalLeft, logicalLeft); + } + + // Synthesize an up event for right key if pressed + if (synthesizeUp && rightPressed) { + _synthesizeKeyUpEvent(domTimestamp, physicalRight, logicalRight); + } + } + + void _synthesizeKeyDownEvent(num domTimestamp, int physical, int logical) { + performDispatchKeyData(ui.KeyData( + timeStamp: _eventTimeStampToDuration(domTimestamp), + type: ui.KeyEventType.down, + physical: physical, + logical: logical, + character: null, + synthesized: true, + )); + // Update pressing state + _pressingRecords[physical] = logical; + } + + void _synthesizeKeyUpEvent(num domTimestamp, int physical, int logical) { + performDispatchKeyData(ui.KeyData( + timeStamp: _eventTimeStampToDuration(domTimestamp), + type: ui.KeyEventType.up, + physical: physical, + logical: logical, + character: null, + synthesized: true, + )); + // Update pressing states + _pressingRecords.remove(physical); + } + + @visibleForTesting + bool debugKeyIsPressed(int physical) { + return _pressingRecords.containsKey(physical); + } } diff --git a/lib/web_ui/lib/src/engine/pointer_binding.dart b/lib/web_ui/lib/src/engine/pointer_binding.dart index 75ad7b4387d4d..72f646c7c473a 100644 --- a/lib/web_ui/lib/src/engine/pointer_binding.dart +++ b/lib/web_ui/lib/src/engine/pointer_binding.dart @@ -5,6 +5,7 @@ import 'dart:math' as math; import 'package:meta/meta.dart'; +import 'package:ui/src/engine/keyboard_binding.dart'; import 'package:ui/ui.dart' as ui; import '../engine.dart' show registerHotRestartListener; @@ -73,7 +74,7 @@ class SafariPointerEventWorkaround { } class PointerBinding { - PointerBinding(this.glassPaneElement) + PointerBinding(this.glassPaneElement, this._keyboardConverter) : _pointerDataConverter = PointerDataConverter(), _detector = const PointerSupportDetector() { if (isIosSafari) { @@ -86,9 +87,9 @@ class PointerBinding { static PointerBinding? get instance => _instance; static PointerBinding? _instance; - static void initInstance(DomElement glassPaneElement) { + static void initInstance(DomElement glassPaneElement, KeyboardConverter keyboardConverter) { if (_instance == null) { - _instance = PointerBinding(glassPaneElement); + _instance = PointerBinding(glassPaneElement, keyboardConverter); assert(() { registerHotRestartListener(_instance!.dispose); return true; @@ -107,6 +108,7 @@ class PointerBinding { PointerSupportDetector _detector; final PointerDataConverter _pointerDataConverter; + KeyboardConverter _keyboardConverter; late _BaseAdapter _adapter; /// Should be used in tests to define custom detection of pointer support. @@ -137,15 +139,23 @@ class PointerBinding { } } + @visibleForTesting + void debugOverrideKeyboardConverter(KeyboardConverter keyboardConverter) { + _keyboardConverter = keyboardConverter; + _adapter.clearListeners(); + _adapter = _createAdapter(); + _pointerDataConverter.clearPointerState(); + } + _BaseAdapter _createAdapter() { if (_detector.hasPointerEvents) { - return _PointerAdapter(_onPointerData, glassPaneElement, _pointerDataConverter); + return _PointerAdapter(_onPointerData, glassPaneElement, _pointerDataConverter, _keyboardConverter); } if (_detector.hasTouchEvents) { - return _TouchAdapter(_onPointerData, glassPaneElement, _pointerDataConverter); + return _TouchAdapter(_onPointerData, glassPaneElement, _pointerDataConverter, _keyboardConverter); } if (_detector.hasMouseEvents) { - return _MouseAdapter(_onPointerData, glassPaneElement, _pointerDataConverter); + return _MouseAdapter(_onPointerData, glassPaneElement, _pointerDataConverter, _keyboardConverter); } throw UnsupportedError('This browser does not support pointer, touch, or mouse events.'); } @@ -239,7 +249,12 @@ class _Listener { /// Common functionality that's shared among adapters. abstract class _BaseAdapter { - _BaseAdapter(this._callback, this.glassPaneElement, this._pointerDataConverter) { + _BaseAdapter( + this._callback, + this.glassPaneElement, + this._pointerDataConverter, + this._keyboardConverter, + ) { setup(); } @@ -247,6 +262,7 @@ abstract class _BaseAdapter { final DomElement glassPaneElement; final _PointerDataCallback _callback; final PointerDataConverter _pointerDataConverter; + final KeyboardConverter _keyboardConverter; /// Each subclass is expected to override this method to attach its own event /// listeners and convert events into pointer events. @@ -570,7 +586,8 @@ class _PointerAdapter extends _BaseAdapter with _WheelEventListenerMixin { _PointerAdapter( super.callback, super.glassPaneElement, - super.pointerDataConverter + super.pointerDataConverter, + super.keyboardConverter, ); final Map _sanitizers = {}; @@ -602,13 +619,27 @@ class _PointerAdapter extends _BaseAdapter with _WheelEventListenerMixin { String eventName, _PointerEventListener handler, { bool useCapture = true, + bool checkModifiers = true, }) { addEventListener(target, eventName, (DomEvent event) { final DomPointerEvent pointerEvent = event as DomPointerEvent; + if (checkModifiers) { + _checkModifiersState(event); + } handler(pointerEvent); }, useCapture: useCapture); } + void _checkModifiersState(DomPointerEvent event) { + _keyboardConverter.synthesizeModifiersIfNeeded( + event.getModifierState('Alt'), + event.getModifierState('Control'), + event.getModifierState('Meta'), + event.getModifierState('Shift'), + event.timeStamp!, + ); + } + @override void setup() { _addPointerEventListener(glassPaneElement, 'pointerdown', (DomPointerEvent event) { @@ -654,7 +685,7 @@ class _PointerAdapter extends _BaseAdapter with _WheelEventListenerMixin { _convertEventsToPointerData(data: pointerData, event: event, details: details); _callback(pointerData); } - }, useCapture: false); + }, useCapture: false, checkModifiers: false); _addPointerEventListener(domWindow, 'pointerup', (DomPointerEvent event) { final int device = _getPointerId(event); @@ -680,7 +711,7 @@ class _PointerAdapter extends _BaseAdapter with _WheelEventListenerMixin { _convertEventsToPointerData(data: pointerData, event: event, details: details); _callback(pointerData); } - }); + }, checkModifiers: false); _addWheelEventListener((DomEvent event) { _handleWheelEvent(event); @@ -767,7 +798,8 @@ class _TouchAdapter extends _BaseAdapter { _TouchAdapter( super.callback, super.glassPaneElement, - super.pointerDataConverter + super.pointerDataConverter, + super.keyboardConverter, ); final Set _pressedTouches = {}; @@ -775,13 +807,26 @@ class _TouchAdapter extends _BaseAdapter { void _pressTouch(int identifier) { _pressedTouches.add(identifier); } void _unpressTouch(int identifier) { _pressedTouches.remove(identifier); } - void _addTouchEventListener(DomEventTarget target, String eventName, _TouchEventListener handler) { + void _addTouchEventListener(DomEventTarget target, String eventName, _TouchEventListener handler, {bool checkModifiers = true,}) { addEventListener(target, eventName, (DomEvent event) { final DomTouchEvent touchEvent = event as DomTouchEvent; + if (checkModifiers) { + _checkModifiersState(event); + } handler(touchEvent); }); } + void _checkModifiersState(DomTouchEvent event) { + _keyboardConverter.synthesizeModifiersIfNeeded( + event.altKey, + event.ctrlKey, + event.metaKey, + event.shiftKey, + event.timeStamp!, + ); + } + @override void setup() { _addTouchEventListener(glassPaneElement, 'touchstart', (DomTouchEvent event) { @@ -910,7 +955,8 @@ class _MouseAdapter extends _BaseAdapter with _WheelEventListenerMixin { _MouseAdapter( super.callback, super.glassPaneElement, - super.pointerDataConverter + super.pointerDataConverter, + super.keyboardConverter, ); final _ButtonSanitizer _sanitizer = _ButtonSanitizer(); @@ -920,13 +966,27 @@ class _MouseAdapter extends _BaseAdapter with _WheelEventListenerMixin { String eventName, _MouseEventListener handler, { bool useCapture = true, + bool checkModifiers = true, }) { addEventListener(target, eventName, (DomEvent event) { final DomMouseEvent mouseEvent = event as DomMouseEvent; + if (checkModifiers) { + _checkModifiersState(event); + } handler(mouseEvent); }, useCapture: useCapture); } + void _checkModifiersState(DomMouseEvent event) { + _keyboardConverter.synthesizeModifiersIfNeeded( + event.getModifierState('Alt'), + event.getModifierState('Control'), + event.getModifierState('Meta'), + event.getModifierState('Shift'), + event.timeStamp!, + ); + } + @override void setup() { _addMouseEventListener(glassPaneElement, 'mousedown', (DomMouseEvent event) { diff --git a/lib/web_ui/test/engine/pointer_binding_test.dart b/lib/web_ui/test/engine/pointer_binding_test.dart index b713a93e19161..1d3317316a4ab 100644 --- a/lib/web_ui/test/engine/pointer_binding_test.dart +++ b/lib/web_ui/test/engine/pointer_binding_test.dart @@ -9,6 +9,8 @@ import 'package:test/test.dart'; import 'package:ui/src/engine.dart'; import 'package:ui/ui.dart' as ui; +import '../keyboard_converter_test.dart'; + const int _kNoButtonChange = -1; const PointerSupportDetector _defaultSupportDetector = PointerSupportDetector(); @@ -46,6 +48,13 @@ void testMain() { dpi = window.devicePixelRatio; }); + KeyboardConverter createKeyboardConverter(List keyDataList) { + return KeyboardConverter((ui.KeyData key) { + keyDataList.add(key); + return true; + }); + } + test('ios workaround', () { debugEmulateIosSafari = true; addTearDown(() { @@ -55,7 +64,9 @@ void testMain() { final MockSafariPointerEventWorkaround mockSafariPointer = MockSafariPointerEventWorkaround(); SafariPointerEventWorkaround.instance = mockSafariPointer; - final PointerBinding instance = PointerBinding(createDomHTMLDivElement()); + final List keyDataList = []; + final KeyboardConverter keyboardConverter = createKeyboardConverter(keyDataList); + final PointerBinding instance = PointerBinding(createDomHTMLDivElement(), keyboardConverter); expect(mockSafariPointer.workAroundInvoked, isIosSafari); instance.dispose(); }, skip: !isSafari); @@ -507,6 +518,248 @@ void testMain() { }, ); + _testEach<_BasicEventContext>( + <_BasicEventContext>[ + _PointerEventContext(), + _MouseEventContext(), + _TouchEventContext(), + ], + 'synthesize modifier keys left down event if left or right are not pressed', + (_BasicEventContext context) { + PointerBinding.instance!.debugOverrideDetector(context); + + // Should synthesize a modifier left key down event when DOM event indicates + // that the modifier key is pressed and known pressing state doesn't contain + // the modifier left key nor the modifier right key. + void shouldSynthesizeLeftDownIfNotPressed(String key) { + final int physicalLeft = kWebToPhysicalKey['${key}Left']!; + final int physicalRight = kWebToPhysicalKey['${key}Right']!; + final int logicalLeft = kWebLogicalLocationMap[key]![kLocationLeft]!; + + final List keyDataList = []; + final KeyboardConverter keyboardConverter = createKeyboardConverter(keyDataList); + PointerBinding.instance!.debugOverrideKeyboardConverter(keyboardConverter); + + expect(keyboardConverter.debugKeyIsPressed(physicalLeft), false); + expect(keyboardConverter.debugKeyIsPressed(physicalRight), false); + glassPane.dispatchEvent(context.primaryDown()); + expect(keyDataList.length, 1); + expectKeyData(keyDataList.last, + type: ui.KeyEventType.down, + physical: physicalLeft, + logical: logicalLeft, + character: null, + synthesized: true, + ); + } + + context.altPressed = true; + shouldSynthesizeLeftDownIfNotPressed('Alt'); + context.unpressAllModifiers(); + context.ctrlPressed = true; + shouldSynthesizeLeftDownIfNotPressed('Control'); + context.unpressAllModifiers(); + context.metaPressed = true; + shouldSynthesizeLeftDownIfNotPressed('Meta'); + context.unpressAllModifiers(); + context.shiftPressed = true; + shouldSynthesizeLeftDownIfNotPressed('Shift'); + context.unpressAllModifiers(); + }, + ); + + _testEach<_BasicEventContext>( + <_BasicEventContext>[ + _PointerEventContext(), + _MouseEventContext(), + _TouchEventContext(), + ], + 'should not synthesize modifier keys down event if left or right are pressed', + (_BasicEventContext context) { + PointerBinding.instance!.debugOverrideDetector(context); + + // Should not synthesize a modifier down event when DOM event indicates + // that the modifier key is pressed and known pressing state contains + // the modifier left key. + void shouldNotSynthesizeDownIfLeftPressed(String key, int modifiers) { + final int physicalLeft = kWebToPhysicalKey['${key}Left']!; + final int physicalRight = kWebToPhysicalKey['${key}Right']!; + + final List keyDataList = []; + final KeyboardConverter keyboardConverter = createKeyboardConverter(keyDataList); + PointerBinding.instance!.debugOverrideKeyboardConverter(keyboardConverter); + + keyboardConverter.handleEvent(keyDownEvent('${key}Left', key, modifiers, kLocationLeft)); + expect(keyboardConverter.debugKeyIsPressed(physicalLeft), true); + expect(keyboardConverter.debugKeyIsPressed(physicalRight), false); + keyDataList.clear(); // Remove key data generated by handleEvent + + glassPane.dispatchEvent(context.primaryDown()); + expect(keyDataList.length, 0); + } + + // Should not synthesize a modifier down event when DOM event indicates + // that the modifier key is pressed and known pressing state contains + // the modifier right key. + void shouldNotSynthesizeDownIfRightPressed(String key, int modifiers) { + final int physicalLeft = kWebToPhysicalKey['${key}Left']!; + final int physicalRight = kWebToPhysicalKey['${key}Right']!; + + final List keyDataList = []; + final KeyboardConverter keyboardConverter = createKeyboardConverter(keyDataList); + PointerBinding.instance!.debugOverrideKeyboardConverter(keyboardConverter); + + keyboardConverter.handleEvent(keyDownEvent('${key}Right', key, modifiers, kLocationRight)); + expect(keyboardConverter.debugKeyIsPressed(physicalLeft), false); + expect(keyboardConverter.debugKeyIsPressed(physicalRight), true); + keyDataList.clear(); // Remove key data generated by handleEvent + + glassPane.dispatchEvent(context.primaryDown()); + expect(keyDataList.length, 0); + } + + context.altPressed = true; + shouldNotSynthesizeDownIfLeftPressed('Alt', kAlt); + shouldNotSynthesizeDownIfRightPressed('Alt', kAlt); + context.unpressAllModifiers(); + context.ctrlPressed = true; + shouldNotSynthesizeDownIfLeftPressed('Control', kCtrl); + shouldNotSynthesizeDownIfRightPressed('Control', kCtrl); + context.unpressAllModifiers(); + context.metaPressed = true; + shouldNotSynthesizeDownIfLeftPressed('Meta', kMeta); + shouldNotSynthesizeDownIfRightPressed('Meta', kMeta); + context.unpressAllModifiers(); + context.shiftPressed = true; + shouldNotSynthesizeDownIfLeftPressed('Shift', kShift); + shouldNotSynthesizeDownIfRightPressed('Shift', kShift); + context.unpressAllModifiers(); + }, + ); + + _testEach<_BasicEventContext>( + <_BasicEventContext>[ + _PointerEventContext(), + _MouseEventContext(), + _TouchEventContext(), + ], + 'synthesize modifier keys up event if left or right are pressed', + (_BasicEventContext context) { + PointerBinding.instance!.debugOverrideDetector(context); + + // Should synthesize a modifier left key up event when DOM event indicates + // that the modifier key is not pressed and known pressing state contains + // the modifier left key. + void shouldSynthesizeLeftUpIfLeftPressed(String key, int modifiers) { + final int physicalLeft = kWebToPhysicalKey['${key}Left']!; + final int physicalRight = kWebToPhysicalKey['${key}Right']!; + final int logicalLeft = kWebLogicalLocationMap[key]![kLocationLeft]!; + + final List keyDataList = []; + final KeyboardConverter keyboardConverter = createKeyboardConverter(keyDataList); + PointerBinding.instance!.debugOverrideKeyboardConverter(keyboardConverter); + + keyboardConverter.handleEvent(keyDownEvent('${key}Left', key, modifiers, kLocationLeft)); + expect(keyboardConverter.debugKeyIsPressed(physicalLeft), true); + expect(keyboardConverter.debugKeyIsPressed(physicalRight), false); + keyDataList.clear(); // Remove key data generated by handleEvent + + glassPane.dispatchEvent(context.primaryDown()); + expect(keyDataList.length, 1); + expectKeyData(keyDataList.last, + type: ui.KeyEventType.up, + physical: physicalLeft, + logical: logicalLeft, + character: null, + synthesized: true, + ); + expect(keyboardConverter.debugKeyIsPressed(physicalLeft), false); + } + + // Should synthesize a modifier right key up event when DOM event indicates + // that the modifier key is not pressed and known pressing state contains + // the modifier right key. + void shouldSynthesizeRightUpIfRightPressed(String key, int modifiers) { + final int physicalLeft = kWebToPhysicalKey['${key}Left']!; + final int physicalRight = kWebToPhysicalKey['${key}Right']!; + final int logicalRight = kWebLogicalLocationMap[key]![kLocationRight]!; + + final List keyDataList = []; + final KeyboardConverter keyboardConverter = createKeyboardConverter(keyDataList); + PointerBinding.instance!.debugOverrideKeyboardConverter(keyboardConverter); + + keyboardConverter.handleEvent(keyDownEvent('${key}Right', key, modifiers, kLocationRight)); + expect(keyboardConverter.debugKeyIsPressed(physicalLeft), false); + expect(keyboardConverter.debugKeyIsPressed(physicalRight), true); + keyDataList.clear(); // Remove key data generated by handleEvent + + glassPane.dispatchEvent(context.primaryDown()); + expect(keyDataList.length, 1); + expectKeyData(keyDataList.last, + type: ui.KeyEventType.up, + physical: physicalRight, + logical: logicalRight, + character: null, + synthesized: true, + ); + expect(keyboardConverter.debugKeyIsPressed(physicalRight), false); + } + + context.altPressed = false; + shouldSynthesizeLeftUpIfLeftPressed('Alt', kAlt); + shouldSynthesizeRightUpIfRightPressed('Alt', kAlt); + context.ctrlPressed = false; + shouldSynthesizeLeftUpIfLeftPressed('Control', kCtrl); + shouldSynthesizeRightUpIfRightPressed('Control', kCtrl); + context.metaPressed = false; + shouldSynthesizeLeftUpIfLeftPressed('Meta', kMeta); + shouldSynthesizeRightUpIfRightPressed('Meta', kMeta); + context.shiftPressed = false; + shouldSynthesizeLeftUpIfLeftPressed('Shift', kShift); + shouldSynthesizeRightUpIfRightPressed('Shift', kShift); + }, + ); + + _testEach<_BasicEventContext>( + <_BasicEventContext>[ + _PointerEventContext(), + _MouseEventContext(), + _TouchEventContext(), + ], + 'should not synthesize modifier keys up event if left or right are not pressed', + (_BasicEventContext context) { + PointerBinding.instance!.debugOverrideDetector(context); + + // Should not synthesize a modifier up event when DOM event indicates + // that the modifier key is not pressed and known pressing state does + // not contain the modifier left key nor the modifier right key. + void shouldNotSynthesizeUpIfNotPressed(String key) { + final int physicalLeft = kWebToPhysicalKey['${key}Left']!; + final int physicalRight = kWebToPhysicalKey['${key}Right']!; + + final List keyDataList = []; + final KeyboardConverter keyboardConverter = createKeyboardConverter(keyDataList); + PointerBinding.instance!.debugOverrideKeyboardConverter(keyboardConverter); + + expect(keyboardConverter.debugKeyIsPressed(physicalLeft), false); + expect(keyboardConverter.debugKeyIsPressed(physicalRight), false); + keyDataList.clear(); // Remove key data generated by handleEvent + + glassPane.dispatchEvent(context.primaryDown()); + expect(keyDataList.length, 0); + } + + context.altPressed = false; + shouldNotSynthesizeUpIfNotPressed('Alt'); + context.ctrlPressed = false; + shouldNotSynthesizeUpIfNotPressed('Control'); + context.metaPressed = false; + shouldNotSynthesizeUpIfNotPressed('Meta'); + context.shiftPressed = false; + shouldNotSynthesizeUpIfNotPressed('Shift'); + }, + ); + _testEach<_ButtonedEventMixin>( <_ButtonedEventMixin>[ if (!isIosSafari) _PointerEventContext(), @@ -2466,7 +2719,7 @@ abstract class _BasicEventContext implements PointerSupportDetector { bool altPressed = false; bool ctrlPressed = false; bool metaPressed = false; - bool shitPressed = false; + bool shiftPressed = false; // Generate an event that is: // @@ -2490,14 +2743,14 @@ abstract class _BasicEventContext implements PointerSupportDetector { altPressed = true; ctrlPressed = true; metaPressed = true; - shitPressed = true; + shiftPressed = true; } void unpressAllModifiers() { altPressed = false; ctrlPressed = false; metaPressed = false; - shitPressed = false; + shiftPressed = false; } } @@ -2687,7 +2940,7 @@ class _TouchEventContext extends _BasicEventContext 'altKey': altPressed, 'ctrlKey': ctrlPressed, 'metaKey': metaPressed, - 'shiftKey': shitPressed, + 'shiftKey': shiftPressed, }, ); } @@ -2826,7 +3079,7 @@ class _MouseEventContext extends _BasicEventContext 'altKey': altPressed, 'ctrlKey': ctrlPressed, 'metaKey': metaPressed, - 'shiftKey': shitPressed, + 'shiftKey': shiftPressed, } ]; return js_util.callConstructor( @@ -2908,7 +3161,7 @@ class _PointerEventContext extends _BasicEventContext 'altKey': altPressed, 'ctrlKey': ctrlPressed, 'metaKey': metaPressed, - 'shiftKey': shitPressed, + 'shiftKey': shiftPressed, }); }