Skip to content

Commit 08d1f2a

Browse files
author
Nurhan Turgut
authored
[web] Calling platform message callback after copy (flutter#15950)
* Calling platform message callback after copy * addressing pr comments * adding unit tests to clipbpard.dart
1 parent bd8c955 commit 08d1f2a

File tree

3 files changed

+152
-16
lines changed

3 files changed

+152
-16
lines changed

lib/web_ui/lib/src/engine/clipboard.dart

Lines changed: 54 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -7,26 +7,51 @@ part of engine;
77
/// Handles clipboard related platform messages.
88
class ClipboardMessageHandler {
99
/// Helper to handle copy to clipboard functionality.
10-
final CopyToClipboardStrategy _copyToClipboardStrategy =
11-
CopyToClipboardStrategy();
10+
CopyToClipboardStrategy _copyToClipboardStrategy = CopyToClipboardStrategy();
1211

1312
/// Helper to handle copy to clipboard functionality.
14-
final PasteFromClipboardStrategy _pasteFromClipboardStrategy =
13+
PasteFromClipboardStrategy _pasteFromClipboardStrategy =
1514
PasteFromClipboardStrategy();
1615

1716
/// Handles the platform message which stores the given text to the clipboard.
18-
void setDataMethodCall(MethodCall methodCall) {
19-
_copyToClipboardStrategy.setData(methodCall.arguments['text']);
17+
void setDataMethodCall(
18+
MethodCall methodCall, ui.PlatformMessageResponseCallback callback) {
19+
const MethodCodec codec = JSONMethodCodec();
20+
_copyToClipboardStrategy
21+
.setData(methodCall.arguments['text'])
22+
.then((bool success) {
23+
if (success) {
24+
callback(codec.encodeSuccessEnvelope(true));
25+
} else {
26+
callback(codec.encodeErrorEnvelope(
27+
code: 'copy_fail', message: 'Clipboard.setData failed'));
28+
}
29+
}).catchError((_) {
30+
callback(codec.encodeErrorEnvelope(
31+
code: 'copy_fail', message: 'Clipboard.setData failed'));
32+
});
2033
}
2134

2235
/// Handles the platform message which retrieves text data from the clipboard.
2336
void getDataMethodCall(ui.PlatformMessageResponseCallback callback) {
37+
const MethodCodec codec = JSONMethodCodec();
2438
_pasteFromClipboardStrategy.getData().then((String data) {
25-
const MethodCodec codec = JSONMethodCodec();
2639
final Map<String, dynamic> map = {'text': data};
2740
callback(codec.encodeSuccessEnvelope(map));
28-
}).catchError(
29-
(error) => print('Could not get text from clipboard: $error'));
41+
}).catchError((error) {
42+
print('Could not get text from clipboard: $error');
43+
callback(codec.encodeErrorEnvelope(
44+
code: 'paste_fail', message: 'Clipboard.getData failed'));
45+
});
46+
}
47+
48+
/// Methods used by tests.
49+
set pasteFromClipboardStrategy(PasteFromClipboardStrategy strategy) {
50+
_pasteFromClipboardStrategy = strategy;
51+
}
52+
53+
set copyToClipboardStrategy(CopyToClipboardStrategy strategy) {
54+
_copyToClipboardStrategy = strategy;
3055
}
3156
}
3257

@@ -42,7 +67,11 @@ abstract class CopyToClipboardStrategy {
4267
}
4368

4469
/// Places the text onto the browser Clipboard.
45-
void setData(String text);
70+
///
71+
/// Returns `true` for a successful action.
72+
///
73+
/// Returns `false` for an uncessful action or when there is an excaption.
74+
Future<bool> setData(String text);
4675
}
4776

4877
/// Provides functionality for reading text from clipboard.
@@ -67,10 +96,14 @@ abstract class PasteFromClipboardStrategy {
6796
/// See: https://developer.mozilla.org/en-US/docs/Web/API/Clipboard_API
6897
class ClipboardAPICopyStrategy implements CopyToClipboardStrategy {
6998
@override
70-
void setData(String text) {
71-
html.window.navigator.clipboard
72-
.writeText(text)
73-
.catchError((error) => print('Could not copy text: $error'));
99+
Future<bool> setData(String text) async {
100+
try {
101+
await html.window.navigator.clipboard.writeText(text);
102+
} catch (e) {
103+
print('copy is not successful ${e.message}');
104+
return Future.value(false);
105+
}
106+
return Future.value(true);
74107
}
75108
}
76109

@@ -90,15 +123,20 @@ class ClipboardAPIPasteStrategy implements PasteFromClipboardStrategy {
90123
/// Provides a fallback strategy for browsers which does not support ClipboardAPI.
91124
class ExecCommandCopyStrategy implements CopyToClipboardStrategy {
92125
@override
93-
void setData(String text) {
126+
Future<bool> setData(String text) {
127+
return Future.value(_setDataSync(text));
128+
}
129+
130+
bool _setDataSync(String text) {
94131
// Copy content to clipboard with execCommand.
95132
// See: https://developers.google.com/web/updates/2015/04/cut-and-copy-commands
96133
final html.TextAreaElement tempTextArea = _appendTemporaryTextArea();
97134
tempTextArea.value = text;
98135
tempTextArea.focus();
99136
tempTextArea.select();
137+
bool result = false;
100138
try {
101-
final bool result = html.document.execCommand('copy');
139+
result = html.document.execCommand('copy');
102140
if (!result) {
103141
print('copy is not successful');
104142
}
@@ -107,6 +145,7 @@ class ExecCommandCopyStrategy implements CopyToClipboardStrategy {
107145
} finally {
108146
_removeTemporaryTextArea(tempTextArea);
109147
}
148+
return result;
110149
}
111150

112151
html.TextAreaElement _appendTemporaryTextArea() {

lib/web_ui/lib/src/engine/window.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -170,7 +170,7 @@ class EngineWindow extends ui.Window {
170170
// There are no default system sounds on web.
171171
return;
172172
case 'Clipboard.setData':
173-
ClipboardMessageHandler().setDataMethodCall(decoded);
173+
ClipboardMessageHandler().setDataMethodCall(decoded, callback);
174174
return;
175175
case 'Clipboard.getData':
176176
ClipboardMessageHandler().getDataMethodCall(callback);
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
// Copyright 2013 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
import 'dart:async';
6+
import 'dart:typed_data';
7+
8+
import 'package:mockito/mockito.dart';
9+
import 'package:test/test.dart';
10+
import 'package:ui/ui.dart' as ui;
11+
import 'package:ui/src/engine.dart';
12+
13+
Future<void> main() async {
14+
await ui.webOnlyInitializeTestDomRenderer();
15+
group('message handler', () {
16+
const String testText = 'test text';
17+
18+
final Future<bool> success = Future.value(true);
19+
final Future<bool> failure = Future.value(false);
20+
final Future<String> pasteTest = Future.value(testText);
21+
22+
ClipboardMessageHandler clipboardMessageHandler;
23+
ClipboardAPICopyStrategy clipboardAPICopyStrategy =
24+
MockClipboardAPICopyStrategy();
25+
ClipboardAPIPasteStrategy clipboardAPIPasteStrategy =
26+
MockClipboardAPIPasteStrategy();
27+
28+
setUp(() {
29+
clipboardMessageHandler = new ClipboardMessageHandler();
30+
clipboardAPICopyStrategy = MockClipboardAPICopyStrategy();
31+
clipboardAPIPasteStrategy = MockClipboardAPIPasteStrategy();
32+
clipboardMessageHandler.copyToClipboardStrategy =
33+
clipboardAPICopyStrategy;
34+
clipboardMessageHandler.pasteFromClipboardStrategy =
35+
clipboardAPIPasteStrategy;
36+
});
37+
38+
test('set data successful', () async {
39+
when(clipboardAPICopyStrategy.setData(testText))
40+
.thenAnswer((_) => success);
41+
const MethodCodec codec = JSONMethodCodec();
42+
bool result = false;
43+
ui.PlatformMessageResponseCallback callback = (ByteData data) {
44+
result = codec.decodeEnvelope(data);
45+
};
46+
47+
await clipboardMessageHandler.setDataMethodCall(
48+
const MethodCall('Clipboard.setData', <String, dynamic>{
49+
'text': testText,
50+
}),
51+
callback);
52+
53+
await expectLater(result, true);
54+
});
55+
56+
test('set data error', () async {
57+
when(clipboardAPICopyStrategy.setData(testText))
58+
.thenAnswer((_) => failure);
59+
const MethodCodec codec = JSONMethodCodec();
60+
ByteData result;
61+
ui.PlatformMessageResponseCallback callback = (ByteData data) {
62+
result = data;
63+
};
64+
65+
await clipboardMessageHandler.setDataMethodCall(
66+
const MethodCall('Clipboard.setData', <String, dynamic>{
67+
'text': testText,
68+
}),
69+
callback);
70+
71+
expect(() async {
72+
codec.decodeEnvelope(result);
73+
}, throwsA(TypeMatcher<PlatformException>()
74+
.having((e) => e.code, 'code', equals('copy_fail'))));
75+
});
76+
77+
test('get data successful', () async {
78+
when(clipboardAPIPasteStrategy.getData())
79+
.thenAnswer((_) => pasteTest);
80+
const MethodCodec codec = JSONMethodCodec();
81+
Map<String, dynamic> result;
82+
ui.PlatformMessageResponseCallback callback = (ByteData data) {
83+
result = codec.decodeEnvelope(data);
84+
};
85+
86+
await clipboardMessageHandler.getDataMethodCall(callback);
87+
88+
await expectLater(result['text'], testText);
89+
});
90+
});
91+
}
92+
93+
class MockClipboardAPICopyStrategy extends Mock
94+
implements ClipboardAPICopyStrategy {}
95+
96+
class MockClipboardAPIPasteStrategy extends Mock
97+
implements ClipboardAPIPasteStrategy {}

0 commit comments

Comments
 (0)