| // 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. |
| |
| |
| // This is identical to ../../../../ui/channel_buffers.dart with the |
| // following exceptions: |
| // |
| // * All comments except this one are removed. |
| // * _invokeX is replaced with engine.invokeX (X=1,2) |
| // * _printDebug is replaced with print in an assert. |
| |
| part of ui; |
| |
| typedef DrainChannelCallback = Future<void> Function(ByteData? data, PlatformMessageResponseCallback callback); |
| |
| typedef ChannelCallback = void Function(ByteData? data, PlatformMessageResponseCallback callback); |
| |
| class _ChannelCallbackRecord { |
| _ChannelCallbackRecord(this._callback) : _zone = Zone.current; |
| final ChannelCallback _callback; |
| final Zone _zone; |
| |
| void invoke(ByteData? dataArg, PlatformMessageResponseCallback callbackArg) { |
| engine.invoke2<ByteData?, PlatformMessageResponseCallback>(_callback, _zone, dataArg, callbackArg); |
| } |
| } |
| |
| class _StoredMessage { |
| _StoredMessage(this.data, this._callback) : _zone = Zone.current; |
| |
| final ByteData? data; |
| |
| final PlatformMessageResponseCallback _callback; |
| |
| final Zone _zone; |
| |
| void invoke(ByteData? dataArg) { |
| engine.invoke1(_callback, _zone, dataArg); |
| } |
| } |
| |
| class _Channel { |
| _Channel([ this._capacity = ChannelBuffers.kDefaultBufferSize ]) |
| : _queue = collection.ListQueue<_StoredMessage>(_capacity); |
| |
| final collection.ListQueue<_StoredMessage> _queue; |
| |
| int get length => _queue.length; |
| |
| bool debugEnableDiscardWarnings = true; |
| |
| int get capacity => _capacity; |
| int _capacity; |
| set capacity(int newSize) { |
| _capacity = newSize; |
| _dropOverflowMessages(newSize); |
| } |
| |
| bool _draining = false; |
| |
| bool push(_StoredMessage message) { |
| if (!_draining && _channelCallbackRecord != null) { |
| assert(_queue.isEmpty); |
| _channelCallbackRecord!.invoke(message.data, message.invoke); |
| return false; |
| } |
| if (_capacity <= 0) { |
| return debugEnableDiscardWarnings; |
| } |
| final bool result = _dropOverflowMessages(_capacity - 1); |
| _queue.addLast(message); |
| return result; |
| } |
| |
| _StoredMessage pop() => _queue.removeFirst(); |
| |
| bool _dropOverflowMessages(int lengthLimit) { |
| bool result = false; |
| while (_queue.length > lengthLimit) { |
| final _StoredMessage message = _queue.removeFirst(); |
| message.invoke(null); // send empty reply to the plugin side |
| result = true; |
| } |
| return result; |
| } |
| |
| _ChannelCallbackRecord? _channelCallbackRecord; |
| |
| void setListener(ChannelCallback callback) { |
| final bool needDrain = _channelCallbackRecord == null; |
| _channelCallbackRecord = _ChannelCallbackRecord(callback); |
| if (needDrain && !_draining) { |
| _drain(); |
| } |
| } |
| |
| void clearListener() { |
| _channelCallbackRecord = null; |
| } |
| |
| void _drain() { |
| assert(!_draining); |
| _draining = true; |
| scheduleMicrotask(_drainStep); |
| } |
| |
| void _drainStep() { |
| assert(_draining); |
| if (_queue.isNotEmpty && _channelCallbackRecord != null) { |
| final _StoredMessage message = pop(); |
| _channelCallbackRecord!.invoke(message.data, message.invoke); |
| scheduleMicrotask(_drainStep); |
| } else { |
| _draining = false; |
| } |
| } |
| } |
| |
| class ChannelBuffers { |
| ChannelBuffers(); |
| |
| static const int kDefaultBufferSize = 1; |
| |
| static const String kControlChannelName = 'dev.flutter/channel-buffers'; |
| |
| final Map<String, _Channel> _channels = <String, _Channel>{}; |
| |
| void push(String name, ByteData? data, PlatformMessageResponseCallback callback) { |
| final _Channel channel = _channels.putIfAbsent(name, () => _Channel()); |
| if (channel.push(_StoredMessage(data, callback))) { |
| assert(() { |
| print( |
| 'A message on the $name channel was discarded before it could be handled.\n' |
| 'This happens when a plugin sends messages to the framework side before the ' |
| 'framework has had an opportunity to register a listener. See the ChannelBuffers ' |
| 'API documentation for details on how to configure the channel to expect more ' |
| 'messages, or to expect messages to get discarded:\n' |
| ' https://api.flutter.dev/flutter/dart-ui/ChannelBuffers-class.html' |
| ); |
| return true; |
| }()); |
| } |
| } |
| |
| void setListener(String name, ChannelCallback callback) { |
| final _Channel channel = _channels.putIfAbsent(name, () => _Channel()); |
| channel.setListener(callback); |
| } |
| |
| void clearListener(String name) { |
| final _Channel? channel = _channels[name]; |
| if (channel != null) { |
| channel.clearListener(); |
| } |
| } |
| |
| Future<void> drain(String name, DrainChannelCallback callback) async { |
| final _Channel? channel = _channels[name]; |
| while (channel != null && !channel._queue.isEmpty) { |
| final _StoredMessage message = channel.pop(); |
| await callback(message.data, message.invoke); |
| } |
| } |
| |
| void handleMessage(ByteData data) { |
| final Uint8List bytes = data.buffer.asUint8List(data.offsetInBytes, data.lengthInBytes); |
| if (bytes[0] == 0x07) { // 7 = value code for string |
| final int methodNameLength = bytes[1]; |
| if (methodNameLength >= 254) { |
| throw Exception('Unrecognized message sent to $kControlChannelName (method name too long)'); |
| } |
| int index = 2; // where we are in reading the bytes |
| final String methodName = utf8.decode(bytes.sublist(index, index + methodNameLength)); |
| index += methodNameLength; |
| switch (methodName) { |
| case 'resize': |
| if (bytes[index] != 0x0C) { |
| throw Exception("Invalid arguments for 'resize' method sent to $kControlChannelName (arguments must be a two-element list, channel name and new capacity)"); |
| } |
| index += 1; |
| if (bytes[index] < 0x02) { |
| throw Exception("Invalid arguments for 'resize' method sent to $kControlChannelName (arguments must be a two-element list, channel name and new capacity)"); |
| } |
| index += 1; |
| if (bytes[index] != 0x07) { |
| throw Exception("Invalid arguments for 'resize' method sent to $kControlChannelName (first argument must be a string)"); |
| } |
| index += 1; |
| final int channelNameLength = bytes[index]; |
| if (channelNameLength >= 254) { |
| throw Exception("Invalid arguments for 'resize' method sent to $kControlChannelName (channel name must be less than 254 characters long)"); |
| } |
| index += 1; |
| final String channelName = utf8.decode(bytes.sublist(index, index + channelNameLength)); |
| index += channelNameLength; |
| if (bytes[index] != 0x03) { |
| throw Exception("Invalid arguments for 'resize' method sent to $kControlChannelName (second argument must be an integer in the range 0 to 2147483647)"); |
| } |
| index += 1; |
| resize(channelName, data.getUint32(index, Endian.host)); |
| case 'overflow': |
| if (bytes[index] != 0x0C) { |
| throw Exception("Invalid arguments for 'overflow' method sent to $kControlChannelName (arguments must be a two-element list, channel name and flag state)"); |
| } |
| index += 1; |
| if (bytes[index] < 0x02) { |
| throw Exception("Invalid arguments for 'overflow' method sent to $kControlChannelName (arguments must be a two-element list, channel name and flag state)"); |
| } |
| index += 1; |
| if (bytes[index] != 0x07) { |
| throw Exception("Invalid arguments for 'overflow' method sent to $kControlChannelName (first argument must be a string)"); |
| } |
| index += 1; |
| final int channelNameLength = bytes[index]; |
| if (channelNameLength >= 254) { |
| throw Exception("Invalid arguments for 'overflow' method sent to $kControlChannelName (channel name must be less than 254 characters long)"); |
| } |
| index += 1; |
| final String channelName = utf8.decode(bytes.sublist(index, index + channelNameLength)); |
| index += channelNameLength; |
| if (bytes[index] != 0x01 && bytes[index] != 0x02) { |
| throw Exception("Invalid arguments for 'overflow' method sent to $kControlChannelName (second argument must be a boolean)"); |
| } |
| allowOverflow(channelName, bytes[index] == 0x01); |
| default: |
| throw Exception("Unrecognized method '$methodName' sent to $kControlChannelName"); |
| } |
| } else { |
| final List<String> parts = utf8.decode(bytes).split('\r'); |
| if (parts.length == 1 + /*arity=*/2 && parts[0] == 'resize') { |
| resize(parts[1], int.parse(parts[2])); |
| } else { |
| throw Exception('Unrecognized message $parts sent to $kControlChannelName.'); |
| } |
| } |
| } |
| |
| void resize(String name, int newSize) { |
| _Channel? channel = _channels[name]; |
| if (channel == null) { |
| channel = _Channel(newSize); |
| _channels[name] = channel; |
| } else { |
| channel.capacity = newSize; |
| } |
| } |
| |
| void allowOverflow(String name, bool allowed) { |
| assert(() { |
| _Channel? channel = _channels[name]; |
| if (channel == null && allowed) { |
| channel = _Channel(); |
| _channels[name] = channel; |
| } |
| channel?.debugEnableDiscardWarnings = !allowed; |
| return true; |
| }()); |
| } |
| |
| void sendChannelUpdate(String name, {required bool listening}) {} |
| } |
| |
| final ChannelBuffers channelBuffers = ChannelBuffers(); |