| // 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. |
| |
| // KEEP THIS SYNCHRONIZED WITH ../../lib/web_ui/test/channel_buffers_test.dart |
| |
| import 'dart:async'; |
| import 'dart:convert'; |
| import 'dart:typed_data'; |
| import 'dart:ui' as ui; |
| |
| import 'package:litetest/litetest.dart'; |
| |
| ByteData _makeByteData(String str) { |
| final Uint8List list = utf8.encode(str) as Uint8List; |
| final ByteBuffer buffer = list.buffer; |
| return ByteData.view(buffer); |
| } |
| |
| void _resize(ui.ChannelBuffers buffers, String name, int newSize) { |
| buffers.handleMessage(_makeByteData('resize\r$name\r$newSize')); |
| } |
| |
| void main() { |
| test('push drain', () async { |
| const String channel = 'foo'; |
| final ByteData data = _makeByteData('bar'); |
| final ui.ChannelBuffers buffers = ui.ChannelBuffers(); |
| bool called = false; |
| void callback(ByteData? responseData) { |
| called = true; |
| } |
| buffers.push(channel, data, callback); |
| await buffers.drain(channel, (ByteData? drainedData, ui.PlatformMessageResponseCallback drainedCallback) async { |
| expect(drainedData, equals(data)); |
| assert(!called); |
| drainedCallback(drainedData); |
| assert(called); |
| }); |
| }); |
| |
| test('drain is sync', () async { |
| const String channel = 'foo'; |
| final ByteData data = _makeByteData('message'); |
| final ui.ChannelBuffers buffers = ui.ChannelBuffers(); |
| void callback(ByteData? responseData) {} |
| buffers.push(channel, data, callback); |
| final List<String> log = <String>[]; |
| final Completer<void> completer = Completer<void>(); |
| scheduleMicrotask(() { log.add('before drain, microtask'); }); |
| log.add('before drain'); |
| |
| // Ignoring the returned future because the completion of the drain is |
| // communicated using the `completer`. |
| // ignore: unawaited_futures |
| buffers.drain(channel, (ByteData? drainedData, ui.PlatformMessageResponseCallback drainedCallback) async { |
| log.add('callback'); |
| completer.complete(); |
| }); |
| log.add('after drain, before await'); |
| await completer.future; |
| log.add('after await'); |
| expect(log, <String>[ |
| 'before drain', |
| 'callback', |
| 'after drain, before await', |
| 'before drain, microtask', |
| 'after await' |
| ]); |
| }); |
| |
| test('push drain zero', () async { |
| const String channel = 'foo'; |
| final ByteData data = _makeByteData('bar'); |
| final |
| ui.ChannelBuffers buffers = ui.ChannelBuffers(); |
| void callback(ByteData? responseData) {} |
| _resize(buffers, channel, 0); |
| buffers.push(channel, data, callback); |
| bool didCall = false; |
| await buffers.drain(channel, (ByteData? drainedData, ui.PlatformMessageResponseCallback drainedCallback) async { |
| didCall = true; |
| }); |
| expect(didCall, equals(false)); |
| }); |
| |
| test('drain when empty', () async { |
| const String channel = 'foo'; |
| final ui.ChannelBuffers buffers = ui.ChannelBuffers(); |
| bool didCall = false; |
| await buffers.drain(channel, (ByteData? drainedData, ui.PlatformMessageResponseCallback drainedCallback) async { |
| didCall = true; |
| }); |
| expect(didCall, equals(false)); |
| }); |
| |
| test('overflow', () async { |
| const String channel = 'foo'; |
| final ByteData one = _makeByteData('one'); |
| final ByteData two = _makeByteData('two'); |
| final ByteData three = _makeByteData('three'); |
| final ByteData four = _makeByteData('four'); |
| final ui.ChannelBuffers buffers = ui.ChannelBuffers(); |
| void callback(ByteData? responseData) {} |
| _resize(buffers, channel, 3); |
| buffers.push(channel, one, callback); |
| buffers.push(channel, two, callback); |
| buffers.push(channel, three, callback); |
| buffers.push(channel, four, callback); |
| int counter = 0; |
| await buffers.drain(channel, (ByteData? drainedData, ui.PlatformMessageResponseCallback drainedCallback) async { |
| switch (counter) { |
| case 0: |
| expect(drainedData, equals(two)); |
| break; |
| case 1: |
| expect(drainedData, equals(three)); |
| break; |
| case 2: |
| expect(drainedData, equals(four)); |
| break; |
| } |
| counter += 1; |
| }); |
| expect(counter, equals(3)); |
| }); |
| |
| test('resize drop', () async { |
| const String channel = 'foo'; |
| final ByteData one = _makeByteData('one'); |
| final ByteData two = _makeByteData('two'); |
| final ui.ChannelBuffers buffers = ui.ChannelBuffers(); |
| _resize(buffers, channel, 100); |
| void callback(ByteData? responseData) {} |
| buffers.push(channel, one, callback); |
| buffers.push(channel, two, callback); |
| _resize(buffers, channel, 1); |
| int counter = 0; |
| await buffers.drain(channel, (ByteData? drainedData, ui.PlatformMessageResponseCallback drainedCallback) async { |
| switch (counter) { |
| case 0: |
| expect(drainedData, equals(two)); |
| } |
| counter += 1; |
| }); |
| expect(counter, equals(1)); |
| }); |
| |
| test('resize dropping calls callback', () async { |
| const String channel = 'foo'; |
| final ByteData one = _makeByteData('one'); |
| final ByteData two = _makeByteData('two'); |
| final ui.ChannelBuffers buffers = ui.ChannelBuffers(); |
| bool didCallCallback = false; |
| void oneCallback(ByteData? responseData) { |
| expect(responseData, isNull); |
| didCallCallback = true; |
| } |
| void twoCallback(ByteData? responseData) { |
| fail('wrong callback called'); |
| } |
| _resize(buffers, channel, 100); |
| buffers.push(channel, one, oneCallback); |
| buffers.push(channel, two, twoCallback); |
| expect(didCallCallback, equals(false)); |
| _resize(buffers, channel, 1); |
| expect(didCallCallback, equals(true)); |
| }); |
| |
| test('overflow calls callback', () async { |
| const String channel = 'foo'; |
| final ByteData one = _makeByteData('one'); |
| final ByteData two = _makeByteData('two'); |
| final ui.ChannelBuffers buffers = ui.ChannelBuffers(); |
| bool didCallCallback = false; |
| void oneCallback(ByteData? responseData) { |
| expect(responseData, isNull); |
| didCallCallback = true; |
| } |
| void twoCallback(ByteData? responseData) { |
| fail('wrong callback called'); |
| } |
| _resize(buffers, channel, 1); |
| buffers.push(channel, one, oneCallback); |
| buffers.push(channel, two, twoCallback); |
| expect(didCallCallback, equals(true)); |
| }); |
| |
| test('handle garbage', () async { |
| final ui.ChannelBuffers buffers = ui.ChannelBuffers(); |
| expect(() => buffers.handleMessage(_makeByteData('asdfasdf')), |
| throwsException); |
| }); |
| |
| test('handle resize garbage', () async { |
| final ui.ChannelBuffers buffers = ui.ChannelBuffers(); |
| expect(() => buffers.handleMessage(_makeByteData('resize\rfoo\rbar')), |
| throwsException); |
| }); |
| |
| test('ChannelBuffers.setListener', () async { |
| final List<String> log = <String>[]; |
| final ui.ChannelBuffers buffers = ui.ChannelBuffers(); |
| final ByteData one = _makeByteData('one'); |
| final ByteData two = _makeByteData('two'); |
| final ByteData three = _makeByteData('three'); |
| final ByteData four = _makeByteData('four'); |
| final ByteData five = _makeByteData('five'); |
| final ByteData six = _makeByteData('six'); |
| final ByteData seven = _makeByteData('seven'); |
| buffers.push('a', one, (ByteData? data) { }); |
| buffers.push('b', two, (ByteData? data) { }); |
| buffers.push('a', three, (ByteData? data) { }); |
| log.add('top'); |
| buffers.setListener('a', (ByteData? data, ui.PlatformMessageResponseCallback callback) { |
| assert(data != null); |
| log.add('a1: ${utf8.decode(data!.buffer.asUint8List())}'); |
| }); |
| log.add('-1'); |
| await null; |
| log.add('-2'); |
| buffers.setListener('a', (ByteData? data, ui.PlatformMessageResponseCallback callback) { |
| assert(data != null); |
| log.add('a2: ${utf8.decode(data!.buffer.asUint8List())}'); |
| }); |
| log.add('-3'); |
| await null; |
| log.add('-4'); |
| buffers.setListener('b', (ByteData? data, ui.PlatformMessageResponseCallback callback) { |
| assert(data != null); |
| log.add('b: ${utf8.decode(data!.buffer.asUint8List())}'); |
| }); |
| log.add('-5'); |
| await null; // first microtask after setting listener drains the first message |
| await null; // second microtask ends the draining. |
| log.add('-6'); |
| buffers.push('b', four, (ByteData? data) { }); |
| buffers.push('a', five, (ByteData? data) { }); |
| log.add('-7'); |
| await null; |
| log.add('-8'); |
| buffers.clearListener('a'); |
| buffers.push('a', six, (ByteData? data) { }); |
| buffers.push('b', seven, (ByteData? data) { }); |
| await null; |
| log.add('-9'); |
| expect(log, <String>[ |
| 'top', |
| '-1', |
| 'a1: three', |
| '-2', |
| '-3', |
| '-4', |
| '-5', |
| 'b: two', |
| '-6', |
| 'b: four', |
| 'a2: five', |
| '-7', |
| '-8', |
| 'b: seven', |
| '-9', |
| ]); |
| }); |
| |
| test('ChannelBuffers.clearListener', () async { |
| final List<String> log = <String>[]; |
| final ui.ChannelBuffers buffers = ui.ChannelBuffers(); |
| final ByteData one = _makeByteData('one'); |
| final ByteData two = _makeByteData('two'); |
| final ByteData three = _makeByteData('three'); |
| final ByteData four = _makeByteData('four'); |
| buffers.handleMessage(_makeByteData('resize\ra\r10')); |
| buffers.push('a', one, (ByteData? data) { }); |
| buffers.push('a', two, (ByteData? data) { }); |
| buffers.push('a', three, (ByteData? data) { }); |
| log.add('-1'); |
| buffers.setListener('a', (ByteData? data, ui.PlatformMessageResponseCallback callback) { |
| assert(data != null); |
| log.add('a1: ${utf8.decode(data!.buffer.asUint8List())}'); |
| }); |
| await null; // handles one |
| log.add('-2'); |
| buffers.clearListener('a'); |
| await null; |
| log.add('-3'); |
| buffers.setListener('a', (ByteData? data, ui.PlatformMessageResponseCallback callback) { |
| assert(data != null); |
| log.add('a2: ${utf8.decode(data!.buffer.asUint8List())}'); |
| }); |
| log.add('-4'); |
| await null; |
| buffers.push('a', four, (ByteData? data) { }); |
| log.add('-5'); |
| await null; |
| log.add('-6'); |
| await null; |
| log.add('-7'); |
| await null; |
| expect(log, <String>[ |
| '-1', |
| 'a1: one', |
| '-2', |
| '-3', |
| '-4', |
| 'a2: two', |
| '-5', |
| 'a2: three', |
| '-6', |
| 'a2: four', |
| '-7', |
| ]); |
| }); |
| |
| test('ChannelBuffers.handleMessage for resize', () async { |
| final List<String> log = <String>[]; |
| final ui.ChannelBuffers buffers = _TestChannelBuffers(log); |
| // Created as follows: |
| // print(StandardMethodCodec().encodeMethodCall(MethodCall('resize', ['abcdef', 12345])).buffer.asUint8List()); |
| // ...with three 0xFF bytes on either side to ensure the method works with an offset on the underlying buffer. |
| buffers.handleMessage(ByteData.sublistView(Uint8List.fromList(<int>[255, 255, 255, 7, 6, 114, 101, 115, 105, 122, 101, 12, 2, 7, 6, 97, 98, 99, 100, 101, 102, 3, 57, 48, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255]), 3, 27)); |
| expect(log, const <String>['resize abcdef 12345']); |
| }); |
| |
| test('ChannelBuffers.handleMessage for overflow', () async { |
| final List<String> log = <String>[]; |
| final ui.ChannelBuffers buffers = _TestChannelBuffers(log); |
| // Created as follows: |
| // print(StandardMethodCodec().encodeMethodCall(MethodCall('overflow', ['abcdef', false])).buffer.asUint8List()); |
| // ...with three 0xFF bytes on either side to ensure the method works with an offset on the underlying buffer. |
| buffers.handleMessage(ByteData.sublistView(Uint8List.fromList(<int>[255, 255, 255, 7, 8, 111, 118, 101, 114, 102, 108, 111, 119, 12, 2, 7, 6, 97, 98, 99, 100, 101, 102, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255]), 3, 24)); |
| expect(log, const <String>['allowOverflow abcdef false']); |
| }); |
| |
| test('ChannelBuffers uses the right zones', () async { |
| final List<String> log = <String>[]; |
| final ui.ChannelBuffers buffers = ui.ChannelBuffers(); |
| final Zone zone1 = Zone.current.fork(); |
| final Zone zone2 = Zone.current.fork(); |
| zone1.run(() { |
| log.add('first zone run: ${Zone.current == zone1}'); |
| buffers.setListener('a', (ByteData? data, ui.PlatformMessageResponseCallback callback) { |
| log.add('callback1: ${Zone.current == zone1}'); |
| callback(data); |
| }); |
| }); |
| zone2.run(() { |
| log.add('second zone run: ${Zone.current == zone2}'); |
| buffers.push('a', ByteData.sublistView(Uint8List.fromList(<int>[]), 0, 0), (ByteData? data) { |
| log.add('callback2: ${Zone.current == zone2}'); |
| }); |
| }); |
| await null; |
| expect(log, <String>[ |
| 'first zone run: true', |
| 'second zone run: true', |
| 'callback1: true', |
| 'callback2: true', |
| ]); |
| }); |
| } |
| |
| class _TestChannelBuffers extends ui.ChannelBuffers { |
| _TestChannelBuffers(this.log); |
| |
| final List<String> log; |
| |
| @override |
| void resize(String name, int newSize) { |
| log.add('resize $name $newSize'); |
| } |
| |
| @override |
| void allowOverflow(String name, bool allowed) { |
| log.add('allowOverflow $name $allowed'); |
| } |
| } |