diff --git a/engine/src/flutter/lib/web_ui/lib/src/engine/clipboard.dart b/engine/src/flutter/lib/web_ui/lib/src/engine/clipboard.dart index 22fff343c94..8486197a406 100644 --- a/engine/src/flutter/lib/web_ui/lib/src/engine/clipboard.dart +++ b/engine/src/flutter/lib/web_ui/lib/src/engine/clipboard.dart @@ -65,6 +65,31 @@ class ClipboardMessageHandler { }); } + /// Handles the platform message which asks if the clipboard contains + /// pasteable strings. + void hasStringsMethodCall(ui.PlatformMessageResponseCallback? callback) { + const MethodCodec codec = JSONMethodCodec(); + _pasteFromClipboardStrategy.getData().then((String data) { + final Map map = {'value': data.isNotEmpty}; + callback!(codec.encodeSuccessEnvelope(map)); + }).catchError((dynamic error) { + if (error is UnimplementedError) { + // Clipboard.hasStrings not supported. + // Passing [null] to [callback] indicates that the platform message isn't + // implemented. Look at [MethodChannel.invokeMethod] to see how [null] is + // handled. + Future.delayed(Duration.zero).then((_) { + if (callback != null) { + callback(null); + } + }); + return; + } + final Map map = {'value': false}; + callback!(codec.encodeSuccessEnvelope(map)); + }); + } + void _reportGetDataFailure(ui.PlatformMessageResponseCallback? callback, MethodCodec codec, dynamic error) { print('Could not get text from clipboard: $error'); diff --git a/engine/src/flutter/lib/web_ui/lib/src/engine/platform_dispatcher.dart b/engine/src/flutter/lib/web_ui/lib/src/engine/platform_dispatcher.dart index 71e56b69ecd..33a7b5fb382 100644 --- a/engine/src/flutter/lib/web_ui/lib/src/engine/platform_dispatcher.dart +++ b/engine/src/flutter/lib/web_ui/lib/src/engine/platform_dispatcher.dart @@ -580,6 +580,9 @@ class EnginePlatformDispatcher extends ui.PlatformDispatcher { case 'Clipboard.getData': ClipboardMessageHandler().getDataMethodCall(callback); return; + case 'Clipboard.hasStrings': + ClipboardMessageHandler().hasStringsMethodCall(callback); + return; } // Dispatched by the bindings to delay service worker initialization. diff --git a/engine/src/flutter/lib/web_ui/test/engine/clipboard_test.dart b/engine/src/flutter/lib/web_ui/test/engine/clipboard_test.dart index baeca91df6c..097cf855d1e 100644 --- a/engine/src/flutter/lib/web_ui/test/engine/clipboard_test.dart +++ b/engine/src/flutter/lib/web_ui/test/engine/clipboard_test.dart @@ -87,6 +87,48 @@ Future testMain() async { final Map result = await completer.future; expect(result['text'], testText); }); + + test('has strings true', () async { + clipboardAPIPasteStrategy.testResult = testText; + const MethodCodec codec = JSONMethodCodec(); + final Completer> completer = Completer>(); + void callback(ByteData? data) { + completer.complete(codec.decodeEnvelope(data!) as Map); + } + + clipboardMessageHandler.hasStringsMethodCall(callback); + + final Map result = await completer.future; + expect(result['value'], isTrue); + }); + + test('has strings false', () async { + clipboardAPIPasteStrategy.testResult = ''; + const MethodCodec codec = JSONMethodCodec(); + final Completer> completer = Completer>(); + void callback(ByteData? data) { + completer.complete(codec.decodeEnvelope(data!) as Map); + } + + clipboardMessageHandler.hasStringsMethodCall(callback); + + final Map result = await completer.future; + expect(result['value'], isFalse); + }); + + test('has strings error', () async { + clipboardAPIPasteStrategy.errors = true; + const MethodCodec codec = JSONMethodCodec(); + final Completer> completer = Completer>(); + void callback(ByteData? data) { + completer.complete(codec.decodeEnvelope(data!) as Map); + } + + clipboardMessageHandler.hasStringsMethodCall(callback); + + final Map result = await completer.future; + expect(result['value'], isFalse); + }); }); } @@ -102,8 +144,14 @@ class MockClipboardAPICopyStrategy implements ClipboardAPICopyStrategy { class MockClipboardAPIPasteStrategy implements ClipboardAPIPasteStrategy { String testResult = ''; + // Whether getData's Future will resolve with an error. + bool errors = false; + @override Future getData() { + if (errors) { + return Future.error(Error()); + } return Future.value(testResult); } }