diff --git a/lib/web_ui/lib/src/engine/platform_dispatcher.dart b/lib/web_ui/lib/src/engine/platform_dispatcher.dart index 10faac0b9af5f..a1b5305b86663 100644 --- a/lib/web_ui/lib/src/engine/platform_dispatcher.dart +++ b/lib/web_ui/lib/src/engine/platform_dispatcher.dart @@ -194,10 +194,15 @@ class EnginePlatformDispatcher extends ui.PlatformDispatcher { /// Engine code should use this method instead of the callback directly. /// Otherwise zones won't work properly. void invokeOnKeyData(ui.KeyData data, _KeyDataResponseCallback callback) { - invoke( - () { callback(onKeyData == null ? false : onKeyData!(data)); }, - _onKeyDataZone, - ); + final ui.KeyDataCallback? onKeyData = _onKeyData; + if (onKeyData != null) { + invoke( + () => callback(onKeyData(data)), + _onKeyDataZone, + ); + } else { + callback(false); + } } /// A callback that is invoked to report the [FrameTiming] of recently diff --git a/lib/web_ui/test/engine/window_test.dart b/lib/web_ui/test/engine/window_test.dart index 6dc9497c04c54..82325c8f60451 100644 --- a/lib/web_ui/test/engine/window_test.dart +++ b/lib/web_ui/test/engine/window_test.dart @@ -13,6 +13,9 @@ import 'package:test/test.dart'; import 'package:ui/ui.dart' as ui; import 'package:ui/src/engine.dart'; +const int kPhysicalKeyA = 0x00070004; +const int kLogicalKeyA = 0x00000000061; + void main() { internalBootstrapBrowserTest(() => testMain); } @@ -146,6 +149,52 @@ void testMain() { EnginePlatformDispatcher.instance.invokeOnPointerDataPacket(null); }); + test('invokeOnKeyData returns normally when onKeyData is null', () { + final ui.KeyData keyData = ui.KeyData( + timeStamp: Duration(milliseconds: 1), + type: ui.KeyEventType.repeat, + physical: kPhysicalKeyA, + logical: kLogicalKeyA, + character: 'a', + synthesized: true, + ); + expect(() { + EnginePlatformDispatcher.instance.invokeOnKeyData(keyData, (bool result) { + expect(result, isFalse); + }); + }, returnsNormally); + }); + + test('onKeyData preserves the zone', () { + final Zone innerZone = Zone.current.fork(); + + innerZone.runGuarded(() { + final ui.KeyDataCallback onKeyData = (_) { + expect(Zone.current, innerZone); + return false; + }; + window.onKeyData = onKeyData; + + // Test that the getter returns the exact same onKeyData, e.g. it doesn't + // wrap it. + expect(window.onKeyData, same(onKeyData)); + }); + + final ui.KeyData keyData = ui.KeyData( + timeStamp: Duration(milliseconds: 1), + type: ui.KeyEventType.repeat, + physical: kPhysicalKeyA, + logical: kLogicalKeyA, + character: 'a', + synthesized: true, + ); + EnginePlatformDispatcher.instance.invokeOnKeyData(keyData, (bool result) { + expect(result, isFalse); + }); + + window.onKeyData = null; + }); + test('onSemanticsEnabledChanged preserves the zone', () { final Zone innerZone = Zone.current.fork();