Commit 88cf68c8 authored by Adam Barth's avatar Adam Barth Committed by GitHub

Migrate Clipboard to platform messages (#6367)

The platform plugin now supports clipboard operations.
parent 50ebcd1d
......@@ -50,7 +50,7 @@ class _TextSelectionToolbar extends StatelessWidget {
}
void _handleCut() {
Clipboard.setClipboardData(new ClipboardData()..text = value.selection.textInside(value.text));
Clipboard.setData(new ClipboardData(text: value.selection.textInside(value.text)));
delegate.inputValue = new InputValue(
text: value.selection.textBefore(value.text) + value.selection.textAfter(value.text),
selection: new TextSelection.collapsed(offset: value.selection.start)
......@@ -59,7 +59,7 @@ class _TextSelectionToolbar extends StatelessWidget {
}
void _handleCopy() {
Clipboard.setClipboardData(new ClipboardData()..text = value.selection.textInside(value.text));
Clipboard.setData(new ClipboardData(text: value.selection.textInside(value.text)));
delegate.inputValue = new InputValue(
text: value.text,
selection: new TextSelection.collapsed(offset: value.selection.end)
......@@ -69,11 +69,11 @@ class _TextSelectionToolbar extends StatelessWidget {
Future<Null> _handlePaste() async {
InputValue value = this.value; // Snapshot the input before using `await`.
ClipboardData clip = await Clipboard.getClipboardData(Clipboard.kTextPlain);
if (clip != null) {
ClipboardData data = await Clipboard.getData(Clipboard.kTextPlain);
if (data != null) {
delegate.inputValue = new InputValue(
text: value.selection.textBefore(value.text) + clip.text + value.selection.textAfter(value.text),
selection: new TextSelection.collapsed(offset: value.selection.start + clip.text.length)
text: value.selection.textBefore(value.text) + data.text + value.selection.textAfter(value.text),
selection: new TextSelection.collapsed(offset: value.selection.start + data.text.length)
);
}
delegate.hideToolbar();
......
......@@ -4,38 +4,48 @@
import 'dart:async';
import 'package:flutter_services/editing.dart' as mojom;
import 'shell.dart';
export 'package:flutter_services/editing.dart' show ClipboardData;
mojom.ClipboardProxy _initClipboardProxy() {
return shell.connectToApplicationService('mojo:clipboard', mojom.Clipboard.connectToService);
import 'platform_messages.dart';
/// Data stored on the system clip board.
///
/// The system clip board can contain data of various media types. This data
/// structure currently supports only plain text data in the [text] property.
class ClipboardData {
/// Creates data for the system clipboard.
const ClipboardData({ this.text });
/// Plain text data on the clip board.
final String text;
}
final mojom.ClipboardProxy _clipboardProxy = _initClipboardProxy();
/// An interface to the system's clipboard. Wraps the mojo interface.
/// An interface to the system's clipboard.
class Clipboard {
/// Constants for common [getClipboardData] [format] types.
/// Constants for common [getData] [format] types.
static final String kTextPlain = 'text/plain';
Clipboard._();
/// Stores the given clipboard data on the clipboard.
static void setClipboardData(mojom.ClipboardData clip) {
_clipboardProxy.setClipboardData(clip);
static Future<Null> setData(ClipboardData data) async {
await PlatformMessages.sendJSON('flutter/platform', <String, dynamic>{
'method': 'Clipboard.setData',
'args': <Map<String, dynamic>>[<String, dynamic>{
'text': data.text,
}],
});
}
/// Retrieves data from the clipboard that matches the given format.
///
/// * `format` is a media type, such as `text/plain`.
static Future<mojom.ClipboardData> getClipboardData(String format) {
Completer<mojom.ClipboardData> completer = new Completer<mojom.ClipboardData>();
_clipboardProxy.getClipboardData(format, (mojom.ClipboardData clip) {
completer.complete(clip);
static Future<ClipboardData> getData(String format) async {
Map<String, dynamic> result =
await PlatformMessages.sendJSON('flutter/platform', <String, dynamic>{
'method': 'Clipboard.getData',
'args': <String>[format],
});
return completer.future;
if (result == null)
return null;
return new ClipboardData(text: result['text']);
}
}
......@@ -28,29 +28,35 @@ dynamic _decodeJSON(String message) {
return message != null ? JSON.decode(message) : null;
}
void _sendString(String name, String message, void callback(String reply)) {
Uint8List encoded = UTF8.encoder.convert(message);
ui.window.sendPlatformMessage(name, encoded.buffer.asByteData(), (ByteData reply) {
try {
callback(_decodeUTF8(reply));
} catch (exception, stack) {
FlutterError.reportError(new FlutterErrorDetails(
exception: exception,
stack: stack,
library: 'services library',
context: 'during a platform message response callback',
));
}
});
}
typedef Future<ByteData> _PlatformMessageHandler(ByteData message);
/// Sends message to and receives messages from the underlying platform.
class PlatformMessages {
/// Handlers for incoming platform messages.
static final Map<String, _PlatformMessageHandler> _handlers =
<String, _PlatformMessageHandler>{};
/// Mock handlers that intercept and respond to outgoing messages.
static final Map<String, _PlatformMessageHandler> _mockHandlers =
<String, _PlatformMessageHandler>{};
static Future<ByteData> _sendPlatformMessage(String name, ByteData message) {
final Completer<ByteData> completer = new Completer<ByteData>();
ui.window.sendPlatformMessage(name, message, (ByteData reply) {
try {
completer.complete(reply);
} catch (exception, stack) {
FlutterError.reportError(new FlutterErrorDetails(
exception: exception,
stack: stack,
library: 'services library',
context: 'during a platform message response callback',
));
}
});
return completer.future;
}
/// Calls the handler registered for the given name.
///
/// Typically called by [ServicesBinding] to handle platform messages received
......@@ -77,22 +83,22 @@ class PlatformMessages {
}
}
/// Send a binary message to the host application.
static Future<ByteData> sendBinary(String name, ByteData message) {
final _PlatformMessageHandler handler = _mockHandlers[name];
if (handler != null)
return handler(message);
return _sendPlatformMessage(name, message);
}
/// Send a string message to the host application.
static Future<String> sendString(String name, String message) {
Completer<String> completer = new Completer<String>();
_sendString(name, message, (String reply) {
completer.complete(reply);
});
return completer.future;
static Future<String> sendString(String name, String message) async {
return _decodeUTF8(await sendBinary(name, _encodeUTF8(message)));
}
/// Sends a JSON-encoded message to the host application and JSON-decodes the response.
static Future<dynamic> sendJSON(String name, dynamic json) {
Completer<dynamic> completer = new Completer<dynamic>();
_sendString(name, JSON.encode(json), (String reply) {
completer.complete(_decodeJSON(reply));
});
return completer.future;
static Future<dynamic> sendJSON(String name, dynamic json) async {
return _decodeJSON(await sendString(name, _encodeJSON(json)));
}
/// Set a callback for receiving binary messages from the platform.
......@@ -123,4 +129,35 @@ class PlatformMessages {
return _encodeJSON(await handler(_decodeJSON(message)));
});
}
/// Sets a message handler that intercepts outgoing messages in binary form.
///
/// The given callback will replace the currently registered callback (if any).
/// To remove the mock handler, pass `null` as the `handler` argument.
static void setMockBinaryMessageHandler(String name, Future<ByteData> handler(ByteData message)) {
if (handler == null)
_mockHandlers.remove(handler);
else
_mockHandlers[name] = handler;
}
/// Sets a message handler that intercepts outgoing messages in string form.
///
/// The given callback will replace the currently registered callback (if any).
/// To remove the mock handler, pass `null` as the `handler` argument.
static void setMockStringMessageHandler(String name, Future<String> handler(String message)) {
setMockBinaryMessageHandler(name, (ByteData message) async {
return _encodeUTF8(await handler(_decodeUTF8(message)));
});
}
/// Sets a message handler that intercepts outgoing messages in JSON form.
///
/// The given callback will replace the currently registered callback (if any).
/// To remove the mock handler, pass `null` as the `handler` argument.
static void setMockJSONMessageHandler(String name, Future<dynamic> handler(dynamic message)) {
setMockStringMessageHandler(name, (String message) async {
return _encodeJSON(await handler(_decodeJSON(message)));
});
}
}
......@@ -7,6 +7,7 @@ import 'dart:async';
import 'package:flutter_test/flutter_test.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/services.dart';
import 'package:flutter_services/editing.dart' as mojom;
import 'package:meta/meta.dart';
......@@ -30,29 +31,30 @@ class MockKeyboard extends mojom.KeyboardProxy {
void setEditingState(mojom.EditingState state) {}
}
class MockClipboard extends mojom.ClipboardProxy {
MockClipboard() : super.unbound();
mojom.ClipboardData _clip;
@override
void setClipboardData(mojom.ClipboardData clip) {
_clip = clip;
}
@override
void getClipboardData(String format, void callback(mojom.ClipboardData clip)) {
scheduleMicrotask(() {
callback(_clip);
});
class MockClipboard {
Object _clipboardData = <String, dynamic>{
'text': null
};
Future<dynamic> handleJSONMessage(dynamic json) async {
final String method = json['method'];
final List<dynamic> args= json['args'];
switch (method) {
case 'Clipboard.getData':
return _clipboardData;
case 'Clipboard.setData':
_clipboardData = args[0];
break;
}
}
}
void main() {
MockKeyboard mockKeyboard = new MockKeyboard();
serviceMocker.registerMockService(mockKeyboard);
MockClipboard mockClipboard = new MockClipboard();
serviceMocker.registerMockService(mockClipboard);
PlatformMessages.setMockJSONMessageHandler('flutter/platform', mockClipboard.handleJSONMessage);
const String kThreeLines =
'First line of text is here abcdef ghijkl mnopqrst. ' +
......
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