mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
[web] Calling platform message callback after copy (flutter/engine#15950)
* Calling platform message callback after copy * addressing pr comments * adding unit tests to clipbpard.dart
This commit is contained in:
parent
024a753078
commit
076cf25e37
@ -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<String, dynamic> 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<bool> 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<bool> 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<bool> 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() {
|
||||
|
||||
@ -170,7 +170,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);
|
||||
|
||||
97
engine/src/flutter/lib/web_ui/test/clipboard_test.dart
Normal file
97
engine/src/flutter/lib/web_ui/test/clipboard_test.dart
Normal file
@ -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<void> main() async {
|
||||
await ui.webOnlyInitializeTestDomRenderer();
|
||||
group('message handler', () {
|
||||
const String testText = 'test text';
|
||||
|
||||
final Future<bool> success = Future.value(true);
|
||||
final Future<bool> failure = Future.value(false);
|
||||
final Future<String> 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', <String, dynamic>{
|
||||
'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', <String, dynamic>{
|
||||
'text': testText,
|
||||
}),
|
||||
callback);
|
||||
|
||||
expect(() async {
|
||||
codec.decodeEnvelope(result);
|
||||
}, throwsA(TypeMatcher<PlatformException>()
|
||||
.having((e) => e.code, 'code', equals('copy_fail'))));
|
||||
});
|
||||
|
||||
test('get data successful', () async {
|
||||
when(clipboardAPIPasteStrategy.getData())
|
||||
.thenAnswer((_) => pasteTest);
|
||||
const MethodCodec codec = JSONMethodCodec();
|
||||
Map<String, dynamic> 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 {}
|
||||
Loading…
x
Reference in New Issue
Block a user