diff --git a/lib/web_ui/lib/src/engine/clipboard.dart b/lib/web_ui/lib/src/engine/clipboard.dart index 1fbe4a0be1052..36aea3e414476 100644 --- a/lib/web_ui/lib/src/engine/clipboard.dart +++ b/lib/web_ui/lib/src/engine/clipboard.dart @@ -7,26 +7,51 @@ 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. - void setDataMethodCall(MethodCall methodCall) { - _copyToClipboardStrategy.setData(methodCall.arguments['text']); + void setDataMethodCall( + MethodCall methodCall, ui.PlatformMessageResponseCallback callback) { + 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')); + }); + } + + /// Methods used by tests. + set pasteFromClipboardStrategy(PasteFromClipboardStrategy strategy) { + _pasteFromClipboardStrategy = strategy; + } + + set copyToClipboardStrategy(CopyToClipboardStrategy strategy) { + _copyToClipboardStrategy = strategy; } } @@ -42,7 +67,11 @@ abstract class CopyToClipboardStrategy { } /// Places the text onto the browser Clipboard. - void 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. @@ -67,10 +96,14 @@ 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 - .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); } } @@ -90,15 +123,20 @@ class ClipboardAPIPasteStrategy implements PasteFromClipboardStrategy { /// Provides a fallback strategy for browsers which does not support ClipboardAPI. class ExecCommandCopyStrategy implements CopyToClipboardStrategy { @override - void setData(String text) { + 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 { - final bool result = html.document.execCommand('copy'); + result = html.document.execCommand('copy'); if (!result) { print('copy is not successful'); } @@ -107,6 +145,7 @@ class ExecCommandCopyStrategy implements CopyToClipboardStrategy { } finally { _removeTemporaryTextArea(tempTextArea); } + return result; } 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); 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 {}