fix message codecs for the Web (#36549)
diff --git a/packages/flutter/lib/src/services/message_codecs.dart b/packages/flutter/lib/src/services/message_codecs.dart
index 8a658a4..cac9267 100644
--- a/packages/flutter/lib/src/services/message_codecs.dart
+++ b/packages/flutter/lib/src/services/message_codecs.dart
@@ -338,6 +338,14 @@
buffer.putUint8(_valueNull);
} else if (value is bool) {
buffer.putUint8(value ? _valueTrue : _valueFalse);
+ } else if (value is double) { // Double precedes int because in JS everything is a double.
+ // Therefore in JS, both `is int` and `is double` always
+ // return `true`. If we check int first, we'll end up treating
+ // all numbers as ints and attempt the int32/int64 conversion,
+ // which is wrong. This precedence rule is irrelevant when
+ // decoding because we use tags to detect the type of value.
+ buffer.putUint8(_valueFloat64);
+ buffer.putFloat64(value);
} else if (value is int) {
if (-0x7fffffff - 1 <= value && value <= 0x7fffffff) {
buffer.putUint8(_valueInt32);
@@ -346,9 +354,6 @@
buffer.putUint8(_valueInt64);
buffer.putInt64(value);
}
- } else if (value is double) {
- buffer.putUint8(_valueFloat64);
- buffer.putFloat64(value);
} else if (value is String) {
buffer.putUint8(_valueString);
final List<int> bytes = utf8.encoder.convert(value);
diff --git a/packages/flutter/test/services/message_codecs_test.dart b/packages/flutter/test/services/message_codecs_test.dart
index c69d0d6..b1aa34c 100644
--- a/packages/flutter/test/services/message_codecs_test.dart
+++ b/packages/flutter/test/services/message_codecs_test.dart
@@ -2,28 +2,31 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-@TestOn('!chrome')
+// This files contains message codec tests that are supported both on the Web
+// and in the VM. For VM-only tests see message_codecs_vm_test.dart.
+
import 'dart:typed_data';
import 'package:flutter/services.dart';
import '../flutter_test_alternative.dart';
+import 'message_codecs_testing.dart';
void main() {
group('Binary codec', () {
const MessageCodec<ByteData> binary = BinaryCodec();
test('should encode and decode simple messages', () {
- _checkEncodeDecode<ByteData>(binary, null);
- _checkEncodeDecode<ByteData>(binary, ByteData(0));
- _checkEncodeDecode<ByteData>(binary, ByteData(4)..setInt32(0, -7));
+ checkEncodeDecode<ByteData>(binary, null);
+ checkEncodeDecode<ByteData>(binary, ByteData(0));
+ checkEncodeDecode<ByteData>(binary, ByteData(4)..setInt32(0, -7));
});
});
group('String codec', () {
const MessageCodec<String> string = StringCodec();
test('should encode and decode simple messages', () {
- _checkEncodeDecode<String>(string, null);
- _checkEncodeDecode<String>(string, '');
- _checkEncodeDecode<String>(string, 'hello');
- _checkEncodeDecode<String>(string, 'special chars >\u263A\u{1F602}<');
+ checkEncodeDecode<String>(string, null);
+ checkEncodeDecode<String>(string, '');
+ checkEncodeDecode<String>(string, 'hello');
+ checkEncodeDecode<String>(string, 'special chars >\u263A\u{1F602}<');
});
test('ByteData with offset', () {
const MessageCodec<String> string = StringCodec();
@@ -42,19 +45,17 @@
group('JSON message codec', () {
const MessageCodec<dynamic> json = JSONMessageCodec();
test('should encode and decode simple messages', () {
- _checkEncodeDecode<dynamic>(json, null);
- _checkEncodeDecode<dynamic>(json, true);
- _checkEncodeDecode<dynamic>(json, false);
- _checkEncodeDecode<dynamic>(json, 7);
- _checkEncodeDecode<dynamic>(json, -7);
- _checkEncodeDecode<dynamic>(json, 98742923489);
- _checkEncodeDecode<dynamic>(json, -98742923489);
- _checkEncodeDecode<dynamic>(json, 9223372036854775807);
- _checkEncodeDecode<dynamic>(json, -9223372036854775807);
- _checkEncodeDecode<dynamic>(json, 3.14);
- _checkEncodeDecode<dynamic>(json, '');
- _checkEncodeDecode<dynamic>(json, 'hello');
- _checkEncodeDecode<dynamic>(json, 'special chars >\u263A\u{1F602}<');
+ checkEncodeDecode<dynamic>(json, null);
+ checkEncodeDecode<dynamic>(json, true);
+ checkEncodeDecode<dynamic>(json, false);
+ checkEncodeDecode<dynamic>(json, 7);
+ checkEncodeDecode<dynamic>(json, -7);
+ checkEncodeDecode<dynamic>(json, 98742923489);
+ checkEncodeDecode<dynamic>(json, -98742923489);
+ checkEncodeDecode<dynamic>(json, 3.14);
+ checkEncodeDecode<dynamic>(json, '');
+ checkEncodeDecode<dynamic>(json, 'hello');
+ checkEncodeDecode<dynamic>(json, 'special chars >\u263A\u{1F602}<');
});
test('should encode and decode composite message', () {
final List<dynamic> message = <dynamic>[
@@ -63,7 +64,6 @@
false,
-707,
-7000000007,
- -7000000000000000007,
-3.14,
'',
'hello',
@@ -71,91 +71,47 @@
<dynamic, dynamic>{'a': 'nested', 'b': <dynamic, dynamic>{}},
'world',
];
- _checkEncodeDecode<dynamic>(json, message);
+ checkEncodeDecode<dynamic>(json, message);
});
});
group('Standard message codec', () {
const MessageCodec<dynamic> standard = StandardMessageCodec();
- test('should encode integers correctly at boundary cases', () {
- _checkEncoding<dynamic>(
- standard,
- -0x7fffffff - 1,
- <int>[3, 0x00, 0x00, 0x00, 0x80],
- );
- _checkEncoding<dynamic>(
- standard,
- -0x7fffffff - 2,
- <int>[4, 0xff, 0xff, 0xff, 0x7f, 0xff, 0xff, 0xff, 0xff],
- );
- _checkEncoding<dynamic>(
- standard,
- 0x7fffffff,
- <int>[3, 0xff, 0xff, 0xff, 0x7f],
- );
- _checkEncoding<dynamic>(
- standard,
- 0x7fffffff + 1,
- <int>[4, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00],
- );
- _checkEncoding<dynamic>(
- standard,
- -0x7fffffffffffffff - 1,
- <int>[4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80],
- );
- _checkEncoding<dynamic>(
- standard,
- -0x7fffffffffffffff - 2,
- <int>[4, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f],
- );
- _checkEncoding<dynamic>(
- standard,
- 0x7fffffffffffffff,
- <int>[4, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f],
- );
- _checkEncoding<dynamic>(
- standard,
- 0x7fffffffffffffff + 1,
- <int>[4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80],
- );
- });
test('should encode sizes correctly at boundary cases', () {
- _checkEncoding<dynamic>(
+ checkEncoding<dynamic>(
standard,
Uint8List(253),
<int>[8, 253, ...List<int>.filled(253, 0)],
);
- _checkEncoding<dynamic>(
+ checkEncoding<dynamic>(
standard,
Uint8List(254),
<int>[8, 254, 254, 0, ...List<int>.filled(254, 0)],
);
- _checkEncoding<dynamic>(
+ checkEncoding<dynamic>(
standard,
Uint8List(0xffff),
<int>[8, 254, 0xff, 0xff, ...List<int>.filled(0xffff, 0)],
);
- _checkEncoding<dynamic>(
+ checkEncoding<dynamic>(
standard,
Uint8List(0xffff + 1),
<int>[8, 255, 0, 0, 1, 0, ...List<int>.filled(0xffff + 1, 0)],
);
});
test('should encode and decode simple messages', () {
- _checkEncodeDecode<dynamic>(standard, null);
- _checkEncodeDecode<dynamic>(standard, true);
- _checkEncodeDecode<dynamic>(standard, false);
- _checkEncodeDecode<dynamic>(standard, 7);
- _checkEncodeDecode<dynamic>(standard, -7);
- _checkEncodeDecode<dynamic>(standard, 98742923489);
- _checkEncodeDecode<dynamic>(standard, -98742923489);
- _checkEncodeDecode<dynamic>(standard, 9223372036854775807);
- _checkEncodeDecode<dynamic>(standard, -9223372036854775807);
- _checkEncodeDecode<dynamic>(standard, 3.14);
- _checkEncodeDecode<dynamic>(standard, double.infinity);
- _checkEncodeDecode<dynamic>(standard, double.nan);
- _checkEncodeDecode<dynamic>(standard, '');
- _checkEncodeDecode<dynamic>(standard, 'hello');
- _checkEncodeDecode<dynamic>(standard, 'special chars >\u263A\u{1F602}<');
+ checkEncodeDecode<dynamic>(standard, null);
+ checkEncodeDecode<dynamic>(standard, true);
+ checkEncodeDecode<dynamic>(standard, false);
+ checkEncodeDecode<dynamic>(standard, 7);
+ checkEncodeDecode<dynamic>(standard, -7);
+ checkEncodeDecode<dynamic>(standard, 98742923489);
+ checkEncodeDecode<dynamic>(standard, -98742923489);
+ checkEncodeDecode<dynamic>(standard, 3.14);
+ checkEncodeDecode<dynamic>(standard, double.infinity);
+ checkEncodeDecode<dynamic>(standard, double.nan);
+ checkEncodeDecode<dynamic>(standard, '');
+ checkEncodeDecode<dynamic>(standard, 'hello');
+ checkEncodeDecode<dynamic>(standard, 'special chars >\u263A\u{1F602}<');
});
test('should encode and decode composite message', () {
final List<dynamic> message = <dynamic>[
@@ -164,15 +120,12 @@
false,
-707,
-7000000007,
- -7000000000000000007,
-3.14,
'',
'hello',
Uint8List.fromList(<int>[0xBA, 0x5E, 0xBA, 0x11]),
Int32List.fromList(<int>[-0x7fffffff - 1, 0, 0x7fffffff]),
null, // ensures the offset of the following list is unaligned.
- Int64List.fromList(
- <int>[-0x7fffffffffffffff - 1, 0, 0x7fffffffffffffff]),
null, // ensures the offset of the following list is unaligned.
Float64List.fromList(<double>[
double.negativeInfinity,
@@ -189,10 +142,10 @@
<dynamic, dynamic>{'a': 'nested', null: <dynamic, dynamic>{}},
'world',
];
- _checkEncodeDecode<dynamic>(standard, message);
+ checkEncodeDecode<dynamic>(standard, message);
});
test('should align doubles to 8 bytes', () {
- _checkEncoding<dynamic>(
+ checkEncoding<dynamic>(
standard,
1.0,
<int>[6, 0, 0, 0, 0, 0, 0, 0,
@@ -201,76 +154,3 @@
});
});
}
-
-void _checkEncoding<T>(MessageCodec<T> codec, T message, List<int> expectedBytes) {
- final ByteData encoded = codec.encodeMessage(message);
- expect(
- encoded.buffer.asUint8List(0, encoded.lengthInBytes),
- orderedEquals(expectedBytes),
- );
-}
-
-void _checkEncodeDecode<T>(MessageCodec<T> codec, T message) {
- final ByteData encoded = codec.encodeMessage(message);
- final T decoded = codec.decodeMessage(encoded);
- if (message == null) {
- expect(encoded, isNull);
- expect(decoded, isNull);
- } else {
- expect(_deepEquals(message, decoded), isTrue);
- final ByteData encodedAgain = codec.encodeMessage(decoded);
- expect(
- encodedAgain.buffer.asUint8List(),
- orderedEquals(encoded.buffer.asUint8List()),
- );
- }
-}
-
-bool _deepEquals(dynamic valueA, dynamic valueB) {
- if (valueA is TypedData)
- return valueB is TypedData && _deepEqualsTypedData(valueA, valueB);
- if (valueA is List)
- return valueB is List && _deepEqualsList(valueA, valueB);
- if (valueA is Map)
- return valueB is Map && _deepEqualsMap(valueA, valueB);
- if (valueA is double && valueA.isNaN)
- return valueB is double && valueB.isNaN;
- return valueA == valueB;
-}
-
-bool _deepEqualsTypedData(TypedData valueA, TypedData valueB) {
- if (valueA is ByteData) {
- return valueB is ByteData
- && _deepEqualsList(
- valueA.buffer.asUint8List(), valueB.buffer.asUint8List());
- }
- if (valueA is Uint8List)
- return valueB is Uint8List && _deepEqualsList(valueA, valueB);
- if (valueA is Int32List)
- return valueB is Int32List && _deepEqualsList(valueA, valueB);
- if (valueA is Int64List)
- return valueB is Int64List && _deepEqualsList(valueA, valueB);
- if (valueA is Float64List)
- return valueB is Float64List && _deepEqualsList(valueA, valueB);
- throw 'Unexpected typed data: $valueA';
-}
-
-bool _deepEqualsList(List<dynamic> valueA, List<dynamic> valueB) {
- if (valueA.length != valueB.length)
- return false;
- for (int i = 0; i < valueA.length; i++) {
- if (!_deepEquals(valueA[i], valueB[i]))
- return false;
- }
- return true;
-}
-
-bool _deepEqualsMap(Map<dynamic, dynamic> valueA, Map<dynamic, dynamic> valueB) {
- if (valueA.length != valueB.length)
- return false;
- for (final dynamic key in valueA.keys) {
- if (!valueB.containsKey(key) || !_deepEquals(valueA[key], valueB[key]))
- return false;
- }
- return true;
-}
diff --git a/packages/flutter/test/services/message_codecs_testing.dart b/packages/flutter/test/services/message_codecs_testing.dart
new file mode 100644
index 0000000..adf3ffc
--- /dev/null
+++ b/packages/flutter/test/services/message_codecs_testing.dart
@@ -0,0 +1,81 @@
+// Copyright 2019 The Chromium 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:typed_data';
+
+import 'package:flutter/services.dart';
+import '../flutter_test_alternative.dart';
+
+void checkEncoding<T>(MessageCodec<T> codec, T message, List<int> expectedBytes) {
+ final ByteData encoded = codec.encodeMessage(message);
+ expect(
+ encoded.buffer.asUint8List(0, encoded.lengthInBytes),
+ orderedEquals(expectedBytes),
+ );
+}
+
+void checkEncodeDecode<T>(MessageCodec<T> codec, T message) {
+ final ByteData encoded = codec.encodeMessage(message);
+ final T decoded = codec.decodeMessage(encoded);
+ if (message == null) {
+ expect(encoded, isNull);
+ expect(decoded, isNull);
+ } else {
+ expect(deepEquals(message, decoded), isTrue);
+ final ByteData encodedAgain = codec.encodeMessage(decoded);
+ expect(
+ encodedAgain.buffer.asUint8List(),
+ orderedEquals(encoded.buffer.asUint8List()),
+ );
+ }
+}
+
+bool deepEquals(dynamic valueA, dynamic valueB) {
+ if (valueA is TypedData)
+ return valueB is TypedData && deepEqualsTypedData(valueA, valueB);
+ if (valueA is List)
+ return valueB is List && deepEqualsList(valueA, valueB);
+ if (valueA is Map)
+ return valueB is Map && deepEqualsMap(valueA, valueB);
+ if (valueA is double && valueA.isNaN)
+ return valueB is double && valueB.isNaN;
+ return valueA == valueB;
+}
+
+bool deepEqualsTypedData(TypedData valueA, TypedData valueB) {
+ if (valueA is ByteData) {
+ return valueB is ByteData
+ && deepEqualsList(
+ valueA.buffer.asUint8List(), valueB.buffer.asUint8List());
+ }
+ if (valueA is Uint8List)
+ return valueB is Uint8List && deepEqualsList(valueA, valueB);
+ if (valueA is Int32List)
+ return valueB is Int32List && deepEqualsList(valueA, valueB);
+ if (valueA is Int64List)
+ return valueB is Int64List && deepEqualsList(valueA, valueB);
+ if (valueA is Float64List)
+ return valueB is Float64List && deepEqualsList(valueA, valueB);
+ throw 'Unexpected typed data: $valueA';
+}
+
+bool deepEqualsList(List<dynamic> valueA, List<dynamic> valueB) {
+ if (valueA.length != valueB.length)
+ return false;
+ for (int i = 0; i < valueA.length; i++) {
+ if (!deepEquals(valueA[i], valueB[i]))
+ return false;
+ }
+ return true;
+}
+
+bool deepEqualsMap(Map<dynamic, dynamic> valueA, Map<dynamic, dynamic> valueB) {
+ if (valueA.length != valueB.length)
+ return false;
+ for (final dynamic key in valueA.keys) {
+ if (!valueB.containsKey(key) || !deepEquals(valueA[key], valueB[key]))
+ return false;
+ }
+ return true;
+}
diff --git a/packages/flutter/test/services/message_codecs_vm_test.dart b/packages/flutter/test/services/message_codecs_vm_test.dart
new file mode 100644
index 0000000..6c20a71
--- /dev/null
+++ b/packages/flutter/test/services/message_codecs_vm_test.dart
@@ -0,0 +1,84 @@
+// Copyright 2019 The Chromium 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 file contains tests that are only supported by the Dart VM. For
+// example, on the Web there's no way to express large integers.
+@TestOn('!chrome')
+
+import 'dart:typed_data';
+
+import 'package:flutter/services.dart';
+import '../flutter_test_alternative.dart';
+import 'message_codecs_testing.dart';
+
+void main() {
+ group('JSON message codec', () {
+ const MessageCodec<dynamic> json = JSONMessageCodec();
+ test('should encode and decode big numbers', () {
+ checkEncodeDecode<dynamic>(json, 9223372036854775807);
+ checkEncodeDecode<dynamic>(json, -9223372036854775807);
+ });
+ test('should encode and decode list with a big number', () {
+ final List<dynamic> message = <dynamic>[-7000000000000000007];
+ checkEncodeDecode<dynamic>(json, message);
+ });
+ });
+ group('Standard message codec', () {
+ const MessageCodec<dynamic> standard = StandardMessageCodec();
+ test('should encode integers correctly at boundary cases', () {
+ checkEncoding<dynamic>(
+ standard,
+ -0x7fffffff - 1,
+ <int>[3, 0x00, 0x00, 0x00, 0x80],
+ );
+ checkEncoding<dynamic>(
+ standard,
+ -0x7fffffff - 2,
+ <int>[4, 0xff, 0xff, 0xff, 0x7f, 0xff, 0xff, 0xff, 0xff],
+ );
+ checkEncoding<dynamic>(
+ standard,
+ 0x7fffffff,
+ <int>[3, 0xff, 0xff, 0xff, 0x7f],
+ );
+ checkEncoding<dynamic>(
+ standard,
+ 0x7fffffff + 1,
+ <int>[4, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00],
+ );
+ checkEncoding<dynamic>(
+ standard,
+ -0x7fffffffffffffff - 1,
+ <int>[4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80],
+ );
+ checkEncoding<dynamic>(
+ standard,
+ -0x7fffffffffffffff - 2,
+ <int>[4, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f],
+ );
+ checkEncoding<dynamic>(
+ standard,
+ 0x7fffffffffffffff,
+ <int>[4, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f],
+ );
+ checkEncoding<dynamic>(
+ standard,
+ 0x7fffffffffffffff + 1,
+ <int>[4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80],
+ );
+ });
+ test('should encode and decode big numbers', () {
+ checkEncodeDecode<dynamic>(standard, 9223372036854775807);
+ checkEncodeDecode<dynamic>(standard, -9223372036854775807);
+ });
+ test('should encode and decode a list containing big numbers', () {
+ final List<dynamic> message = <dynamic>[
+ -7000000000000000007,
+ Int64List.fromList(
+ <int>[-0x7fffffffffffffff - 1, 0, 0x7fffffffffffffff]),
+ ];
+ checkEncodeDecode<dynamic>(standard, message);
+ });
+ });
+}