From e9a54654034b1bbe8572094e07e6004e4bfb44bf Mon Sep 17 00:00:00 2001 From: Nurhan Turgut Date: Thu, 23 Jan 2020 14:27:56 -0800 Subject: [PATCH 1/3] Calling platform message callback after copy --- lib/web_ui/lib/src/engine/clipboard.dart | 51 ++++++++++++++---------- lib/web_ui/lib/src/engine/window.dart | 2 +- 2 files changed, 31 insertions(+), 22 deletions(-) diff --git a/lib/web_ui/lib/src/engine/clipboard.dart b/lib/web_ui/lib/src/engine/clipboard.dart index 1fbe4a0be1052..06900e66fcac6 100644 --- a/lib/web_ui/lib/src/engine/clipboard.dart +++ b/lib/web_ui/lib/src/engine/clipboard.dart @@ -15,8 +15,12 @@ class ClipboardMessageHandler { PasteFromClipboardStrategy(); /// Handles the platform message which stores the given text to the clipboard. - void setDataMethodCall(MethodCall methodCall) { - _copyToClipboardStrategy.setData(methodCall.arguments['text']); + void setDataMethodCall( + MethodCall methodCall, ui.PlatformMessageResponseCallback callback) { + _copyToClipboardStrategy.setData(methodCall.arguments['text']).then((_) { + const MethodCodec codec = JSONMethodCodec(); + callback(codec.encodeSuccessEnvelope(true)); + }).catchError((error) => print('Could not copy text: $error')); } /// Handles the platform message which retrieves text data from the clipboard. @@ -42,7 +46,7 @@ abstract class CopyToClipboardStrategy { } /// Places the text onto the browser Clipboard. - void setData(String text); + Future setData(String text); } /// Provides functionality for reading text from clipboard. @@ -67,8 +71,8 @@ abstract class PasteFromClipboardStrategy { /// See: https://developer.mozilla.org/en-US/docs/Web/API/Clipboard_API class ClipboardAPICopyStrategy implements CopyToClipboardStrategy { @override - void setData(String text) { - html.window.navigator.clipboard + Future setData(String text) async { + await html.window.navigator.clipboard .writeText(text) .catchError((error) => print('Could not copy text: $error')); } @@ -90,23 +94,28 @@ class ClipboardAPIPasteStrategy implements PasteFromClipboardStrategy { /// Provides a fallback strategy for browsers which does not support ClipboardAPI. class ExecCommandCopyStrategy implements CopyToClipboardStrategy { @override - void setData(String text) { - // Copy content to clipboard with execCommand. - // See: https://developers.google.com/web/updates/2015/04/cut-and-copy-commands - final html.TextAreaElement tempTextArea = _appendTemporaryTextArea(); - tempTextArea.value = text; - tempTextArea.focus(); - tempTextArea.select(); - try { - final bool result = html.document.execCommand('copy'); - if (!result) { - print('copy is not successful'); + Future setData(String text) { + // In Flutter, platform messages are exchanged between threads so the + // messages and responses have to be exchanged asynchronously. We simulate + // that by adding a zero-length delay to the reply. + return Future.delayed(Duration.zero).then((_) { + // Copy content to clipboard with execCommand. + // See: https://developers.google.com/web/updates/2015/04/cut-and-copy-commands + final html.TextAreaElement tempTextArea = _appendTemporaryTextArea(); + tempTextArea.value = text; + tempTextArea.focus(); + tempTextArea.select(); + try { + final bool result = html.document.execCommand('copy'); + if (!result) { + print('copy is not successful'); + } + } catch (e) { + print('copy is not successful ${e.message}'); + } finally { + _removeTemporaryTextArea(tempTextArea); } - } catch (e) { - print('copy is not successful ${e.message}'); - } finally { - _removeTemporaryTextArea(tempTextArea); - } + }); } html.TextAreaElement _appendTemporaryTextArea() { diff --git a/lib/web_ui/lib/src/engine/window.dart b/lib/web_ui/lib/src/engine/window.dart index 557dbf21b85f6..0ecdc15de99e4 100644 --- a/lib/web_ui/lib/src/engine/window.dart +++ b/lib/web_ui/lib/src/engine/window.dart @@ -165,7 +165,7 @@ class EngineWindow extends ui.Window { // There are no default system sounds on web. return; case 'Clipboard.setData': - ClipboardMessageHandler().setDataMethodCall(decoded); + ClipboardMessageHandler().setDataMethodCall(decoded, callback); return; case 'Clipboard.getData': ClipboardMessageHandler().getDataMethodCall(callback); From 2a62321bf254d7b7ac748c30c8899aadaabc3ac3 Mon Sep 17 00:00:00 2001 From: Nurhan Turgut Date: Fri, 24 Jan 2020 09:35:07 -0800 Subject: [PATCH 2/3] addressing pr comments --- lib/web_ui/lib/src/engine/clipboard.dart | 88 +++++++++++++++--------- 1 file changed, 55 insertions(+), 33 deletions(-) diff --git a/lib/web_ui/lib/src/engine/clipboard.dart b/lib/web_ui/lib/src/engine/clipboard.dart index 06900e66fcac6..5a3cf047985ca 100644 --- a/lib/web_ui/lib/src/engine/clipboard.dart +++ b/lib/web_ui/lib/src/engine/clipboard.dart @@ -17,20 +17,33 @@ class ClipboardMessageHandler { /// Handles the platform message which stores the given text to the clipboard. void setDataMethodCall( MethodCall methodCall, ui.PlatformMessageResponseCallback callback) { - _copyToClipboardStrategy.setData(methodCall.arguments['text']).then((_) { - const MethodCodec codec = JSONMethodCodec(); - callback(codec.encodeSuccessEnvelope(true)); - }).catchError((error) => print('Could not copy text: $error')); + const MethodCodec codec = JSONMethodCodec(); + _copyToClipboardStrategy + .setData(methodCall.arguments['text']) + .then((bool success) { + if (success) { + callback(codec.encodeSuccessEnvelope(true)); + } else { + callback(codec.encodeErrorEnvelope( + code: 'copy_fail', message: 'Clipboard.setData failed')); + } + }).catchError((_) { + callback(codec.encodeErrorEnvelope( + code: 'copy_fail', message: 'Clipboard.setData failed')); + }); } /// Handles the platform message which retrieves text data from the clipboard. void getDataMethodCall(ui.PlatformMessageResponseCallback callback) { + const MethodCodec codec = JSONMethodCodec(); _pasteFromClipboardStrategy.getData().then((String data) { - const MethodCodec codec = JSONMethodCodec(); final Map map = {'text': data}; callback(codec.encodeSuccessEnvelope(map)); - }).catchError( - (error) => print('Could not get text from clipboard: $error')); + }).catchError((error) { + print('Could not get text from clipboard: $error'); + callback(codec.encodeErrorEnvelope( + code: 'paste_fail', message: 'Clipboard.getData failed')); + }); } } @@ -46,7 +59,11 @@ abstract class CopyToClipboardStrategy { } /// Places the text onto the browser Clipboard. - Future setData(String text); + /// + /// Returns `true` for a successful action. + /// + /// Returns `false` for an uncessful action or when there is an excaption. + Future setData(String text); } /// Provides functionality for reading text from clipboard. @@ -71,10 +88,14 @@ abstract class PasteFromClipboardStrategy { /// See: https://developer.mozilla.org/en-US/docs/Web/API/Clipboard_API class ClipboardAPICopyStrategy implements CopyToClipboardStrategy { @override - Future setData(String text) async { - await html.window.navigator.clipboard - .writeText(text) - .catchError((error) => print('Could not copy text: $error')); + Future setData(String text) async { + try { + await html.window.navigator.clipboard.writeText(text); + } catch (e) { + print('copy is not successful ${e.message}'); + return Future.value(false); + } + return Future.value(true); } } @@ -94,28 +115,29 @@ class ClipboardAPIPasteStrategy implements PasteFromClipboardStrategy { /// Provides a fallback strategy for browsers which does not support ClipboardAPI. class ExecCommandCopyStrategy implements CopyToClipboardStrategy { @override - Future setData(String text) { - // In Flutter, platform messages are exchanged between threads so the - // messages and responses have to be exchanged asynchronously. We simulate - // that by adding a zero-length delay to the reply. - return Future.delayed(Duration.zero).then((_) { - // Copy content to clipboard with execCommand. - // See: https://developers.google.com/web/updates/2015/04/cut-and-copy-commands - final html.TextAreaElement tempTextArea = _appendTemporaryTextArea(); - tempTextArea.value = text; - tempTextArea.focus(); - tempTextArea.select(); - try { - final bool result = html.document.execCommand('copy'); - if (!result) { - print('copy is not successful'); - } - } catch (e) { - print('copy is not successful ${e.message}'); - } finally { - _removeTemporaryTextArea(tempTextArea); + Future setData(String text) { + return Future.value(_setDataSync(text)); + } + + bool _setDataSync(String text) { + // Copy content to clipboard with execCommand. + // See: https://developers.google.com/web/updates/2015/04/cut-and-copy-commands + final html.TextAreaElement tempTextArea = _appendTemporaryTextArea(); + tempTextArea.value = text; + tempTextArea.focus(); + tempTextArea.select(); + bool result = false; + try { + result = html.document.execCommand('copy'); + if (!result) { + print('copy is not successful'); } - }); + } catch (e) { + print('copy is not successful ${e.message}'); + } finally { + _removeTemporaryTextArea(tempTextArea); + } + return result; } html.TextAreaElement _appendTemporaryTextArea() { From ec2ca6f4d306d6bd273b2329051748efc76e2d99 Mon Sep 17 00:00:00 2001 From: Nurhan Turgut Date: Fri, 24 Jan 2020 14:52:59 -0800 Subject: [PATCH 3/3] adding unit tests to clipbpard.dart --- lib/web_ui/lib/src/engine/clipboard.dart | 14 +++- lib/web_ui/test/clipboard_test.dart | 97 ++++++++++++++++++++++++ 2 files changed, 108 insertions(+), 3 deletions(-) create mode 100644 lib/web_ui/test/clipboard_test.dart diff --git a/lib/web_ui/lib/src/engine/clipboard.dart b/lib/web_ui/lib/src/engine/clipboard.dart index 5a3cf047985ca..36aea3e414476 100644 --- a/lib/web_ui/lib/src/engine/clipboard.dart +++ b/lib/web_ui/lib/src/engine/clipboard.dart @@ -7,11 +7,10 @@ part of engine; /// Handles clipboard related platform messages. class ClipboardMessageHandler { /// Helper to handle copy to clipboard functionality. - final CopyToClipboardStrategy _copyToClipboardStrategy = - CopyToClipboardStrategy(); + CopyToClipboardStrategy _copyToClipboardStrategy = CopyToClipboardStrategy(); /// Helper to handle copy to clipboard functionality. - final PasteFromClipboardStrategy _pasteFromClipboardStrategy = + PasteFromClipboardStrategy _pasteFromClipboardStrategy = PasteFromClipboardStrategy(); /// Handles the platform message which stores the given text to the clipboard. @@ -45,6 +44,15 @@ class ClipboardMessageHandler { code: 'paste_fail', message: 'Clipboard.getData failed')); }); } + + /// Methods used by tests. + set pasteFromClipboardStrategy(PasteFromClipboardStrategy strategy) { + _pasteFromClipboardStrategy = strategy; + } + + set copyToClipboardStrategy(CopyToClipboardStrategy strategy) { + _copyToClipboardStrategy = strategy; + } } /// Provides functionality for writing text to clipboard. diff --git a/lib/web_ui/test/clipboard_test.dart b/lib/web_ui/test/clipboard_test.dart new file mode 100644 index 0000000000000..b31ed423f143d --- /dev/null +++ b/lib/web_ui/test/clipboard_test.dart @@ -0,0 +1,97 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'dart:async'; +import 'dart:typed_data'; + +import 'package:mockito/mockito.dart'; +import 'package:test/test.dart'; +import 'package:ui/ui.dart' as ui; +import 'package:ui/src/engine.dart'; + +Future main() async { + await ui.webOnlyInitializeTestDomRenderer(); + group('message handler', () { + const String testText = 'test text'; + + final Future success = Future.value(true); + final Future failure = Future.value(false); + final Future pasteTest = Future.value(testText); + + ClipboardMessageHandler clipboardMessageHandler; + ClipboardAPICopyStrategy clipboardAPICopyStrategy = + MockClipboardAPICopyStrategy(); + ClipboardAPIPasteStrategy clipboardAPIPasteStrategy = + MockClipboardAPIPasteStrategy(); + + setUp(() { + clipboardMessageHandler = new ClipboardMessageHandler(); + clipboardAPICopyStrategy = MockClipboardAPICopyStrategy(); + clipboardAPIPasteStrategy = MockClipboardAPIPasteStrategy(); + clipboardMessageHandler.copyToClipboardStrategy = + clipboardAPICopyStrategy; + clipboardMessageHandler.pasteFromClipboardStrategy = + clipboardAPIPasteStrategy; + }); + + test('set data successful', () async { + when(clipboardAPICopyStrategy.setData(testText)) + .thenAnswer((_) => success); + const MethodCodec codec = JSONMethodCodec(); + bool result = false; + ui.PlatformMessageResponseCallback callback = (ByteData data) { + result = codec.decodeEnvelope(data); + }; + + await clipboardMessageHandler.setDataMethodCall( + const MethodCall('Clipboard.setData', { + 'text': testText, + }), + callback); + + await expectLater(result, true); + }); + + test('set data error', () async { + when(clipboardAPICopyStrategy.setData(testText)) + .thenAnswer((_) => failure); + const MethodCodec codec = JSONMethodCodec(); + ByteData result; + ui.PlatformMessageResponseCallback callback = (ByteData data) { + result = data; + }; + + await clipboardMessageHandler.setDataMethodCall( + const MethodCall('Clipboard.setData', { + 'text': testText, + }), + callback); + + expect(() async { + codec.decodeEnvelope(result); + }, throwsA(TypeMatcher() + .having((e) => e.code, 'code', equals('copy_fail')))); + }); + + test('get data successful', () async { + when(clipboardAPIPasteStrategy.getData()) + .thenAnswer((_) => pasteTest); + const MethodCodec codec = JSONMethodCodec(); + Map result; + ui.PlatformMessageResponseCallback callback = (ByteData data) { + result = codec.decodeEnvelope(data); + }; + + await clipboardMessageHandler.getDataMethodCall(callback); + + await expectLater(result['text'], testText); + }); + }); +} + +class MockClipboardAPICopyStrategy extends Mock + implements ClipboardAPICopyStrategy {} + +class MockClipboardAPIPasteStrategy extends Mock + implements ClipboardAPIPasteStrategy {}