Unverified Commit ec24a819 authored by Justin McCandless's avatar Justin McCandless Committed by GitHub

ClipboardStatusNotifier should handle errors in Clipboard.getData (#64012)

parent d981fafe
...@@ -1512,7 +1512,7 @@ class ClipboardStatusNotifier extends ValueNotifier<ClipboardStatus> with Widget ...@@ -1512,7 +1512,7 @@ class ClipboardStatusNotifier extends ValueNotifier<ClipboardStatus> with Widget
bool get disposed => _disposed; bool get disposed => _disposed;
/// Check the [Clipboard] and update [value] if needed. /// Check the [Clipboard] and update [value] if needed.
void update() { Future<void> update() async {
// iOS 14 added a notification that appears when an app accesses the // iOS 14 added a notification that appears when an app accesses the
// clipboard. To avoid the notification, don't access the clipboard on iOS, // clipboard. To avoid the notification, don't access the clipboard on iOS,
// and instead always shown the paste button, even when the clipboard is // and instead always shown the paste button, even when the clipboard is
...@@ -1532,15 +1532,26 @@ class ClipboardStatusNotifier extends ValueNotifier<ClipboardStatus> with Widget ...@@ -1532,15 +1532,26 @@ class ClipboardStatusNotifier extends ValueNotifier<ClipboardStatus> with Widget
break; break;
} }
Clipboard.getData(Clipboard.kTextPlain).then((ClipboardData data) { ClipboardData data;
final ClipboardStatus clipboardStatus = data != null && data.text != null && data.text.isNotEmpty try {
? ClipboardStatus.pasteable data = await Clipboard.getData(Clipboard.kTextPlain);
: ClipboardStatus.notPasteable; } catch (stacktrace) {
if (_disposed || clipboardStatus == value) { // In the case of an error from the Clipboard API, set the value to
// unknown so that it will try to update again later.
if (_disposed || value == ClipboardStatus.unknown) {
return; return;
} }
value = clipboardStatus; value = ClipboardStatus.unknown;
}); return;
}
final ClipboardStatus clipboardStatus = data != null && data.text != null && data.text.isNotEmpty
? ClipboardStatus.pasteable
: ClipboardStatus.notPasteable;
if (_disposed || clipboardStatus == value) {
return;
}
value = clipboardStatus;
} }
@override @override
......
...@@ -6,9 +6,34 @@ ...@@ -6,9 +6,34 @@
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import 'package:flutter/gestures.dart' show PointerDeviceKind; import 'package:flutter/gestures.dart' show PointerDeviceKind;
import 'package:flutter/widgets.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/services.dart';
import 'package:flutter/widgets.dart';
class MockClipboard {
MockClipboard({
this.getDataThrows = false,
});
final bool getDataThrows;
Object _clipboardData = <String, dynamic>{
'text': null,
};
Future<dynamic> handleMethodCall(MethodCall methodCall) async {
switch (methodCall.method) {
case 'Clipboard.getData':
if (getDataThrows) {
throw Exception();
}
return _clipboardData;
case 'Clipboard.setData':
_clipboardData = methodCall.arguments;
}
}
}
void main() { void main() {
int tapCount; int tapCount;
...@@ -608,6 +633,56 @@ void main() { ...@@ -608,6 +633,56 @@ void main() {
expect(hitRect.size.width, lessThan(textFieldRect.size.width)); expect(hitRect.size.width, lessThan(textFieldRect.size.width));
expect(hitRect.size.height, lessThan(textFieldRect.size.height)); expect(hitRect.size.height, lessThan(textFieldRect.size.height));
}, variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.iOS, TargetPlatform.macOS })); }, variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.iOS, TargetPlatform.macOS }));
group('ClipboardStatusNotifier', () {
group('when Clipboard fails', () {
setUp(() {
final MockClipboard mockClipboard = MockClipboard(getDataThrows: true);
SystemChannels.platform.setMockMethodCallHandler(mockClipboard.handleMethodCall);
});
tearDown(() {
SystemChannels.platform.setMockMethodCallHandler(null);
});
test('Clipboard API failure is gracefully recovered from', () async {
final ClipboardStatusNotifier notifier = ClipboardStatusNotifier();
expect(notifier.value, ClipboardStatus.unknown);
await expectLater(notifier.update(), completes);
expect(notifier.value, ClipboardStatus.unknown);
});
});
group('when Clipboard succeeds', () {
final MockClipboard mockClipboard = MockClipboard();
setUp(() {
SystemChannels.platform.setMockMethodCallHandler(mockClipboard.handleMethodCall);
});
tearDown(() {
SystemChannels.platform.setMockMethodCallHandler(null);
});
test('update sets value based on clipboard contents', () async {
final ClipboardStatusNotifier notifier = ClipboardStatusNotifier();
expect(notifier.value, ClipboardStatus.unknown);
await expectLater(notifier.update(), completes);
expect(notifier.value, ClipboardStatus.notPasteable);
mockClipboard.handleMethodCall(const MethodCall(
'Clipboard.setData',
<String, dynamic>{
'text': 'pasteablestring',
},
));
await expectLater(notifier.update(), completes);
expect(notifier.value, ClipboardStatus.pasteable);
});
});
});
} }
class FakeTextSelectionGestureDetectorBuilderDelegate implements TextSelectionGestureDetectorBuilderDelegate { class FakeTextSelectionGestureDetectorBuilderDelegate implements TextSelectionGestureDetectorBuilderDelegate {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment