Change implementations to return Uint8List rather than List<int> (#123)

This is in preparation for https://github.com/dart-lang/sdk/issues/36900
diff --git a/packages/file/CHANGELOG.md b/packages/file/CHANGELOG.md
index 740dfdf..5c28d0d 100644
--- a/packages/file/CHANGELOG.md
+++ b/packages/file/CHANGELOG.md
@@ -1,3 +1,7 @@
+#### 5.0.8
+
+* Return Uint8List rather than List<int>.
+
 #### 5.0.7
 
 * Dart 2 fixes for `RecordingProxyMixin` and `ReplayProxyMixin`.
diff --git a/packages/file/lib/src/backends/chroot.dart b/packages/file/lib/src/backends/chroot.dart
index e4355be..72ce3ca 100644
--- a/packages/file/lib/src/backends/chroot.dart
+++ b/packages/file/lib/src/backends/chroot.dart
@@ -6,6 +6,7 @@
 
 import 'dart:async';
 import 'dart:convert';
+import 'dart:typed_data';
 
 import 'package:file/file.dart';
 import 'package:file/src/common.dart' as common;
diff --git a/packages/file/lib/src/backends/chroot/chroot_file.dart b/packages/file/lib/src/backends/chroot/chroot_file.dart
index 79b361e..ea65c38 100644
--- a/packages/file/lib/src/backends/chroot/chroot_file.dart
+++ b/packages/file/lib/src/backends/chroot/chroot_file.dart
@@ -251,7 +251,7 @@
       getDelegate(followLinks: true).openSync(mode: mode);
 
   @override
-  Stream<List<int>> openRead([int start, int end]) =>
+  Stream<Uint8List> openRead([int start, int end]) =>
       getDelegate(followLinks: true).openRead(start, end);
 
   @override
@@ -262,11 +262,11 @@
       getDelegate(followLinks: true).openWrite(mode: mode, encoding: encoding);
 
   @override
-  Future<List<int>> readAsBytes() =>
+  Future<Uint8List> readAsBytes() =>
       getDelegate(followLinks: true).readAsBytes();
 
   @override
-  List<int> readAsBytesSync() =>
+  Uint8List readAsBytesSync() =>
       getDelegate(followLinks: true).readAsBytesSync();
 
   @override
diff --git a/packages/file/lib/src/backends/memory/memory_file.dart b/packages/file/lib/src/backends/memory/memory_file.dart
index 73899dd..019c4be 100644
--- a/packages/file/lib/src/backends/memory/memory_file.dart
+++ b/packages/file/lib/src/backends/memory/memory_file.dart
@@ -5,6 +5,7 @@
 import 'dart:async';
 import 'dart:convert';
 import 'dart:math' show min;
+import 'dart:typed_data';
 
 import 'package:file/file.dart';
 import 'package:file/src/common.dart' as common;
@@ -176,18 +177,18 @@
       throw new UnimplementedError('TODO');
 
   @override
-  Stream<List<int>> openRead([int start, int end]) {
+  Stream<Uint8List> openRead([int start, int end]) {
     try {
       FileNode node = resolvedBacking;
-      List<int> content = node.content;
+      Uint8List content = node.content;
       if (start != null) {
         content = end == null
             ? content.sublist(start)
             : content.sublist(start, min(end, content.length));
       }
-      return new Stream<List<int>>.fromIterable(<List<int>>[content]);
+      return new Stream<Uint8List>.fromIterable(<Uint8List>[content]);
     } catch (e) {
-      return new Stream<List<int>>.fromFuture(new Future<List<int>>.error(e));
+      return new Stream<Uint8List>.fromFuture(new Future<Uint8List>.error(e));
     }
   }
 
@@ -204,10 +205,11 @@
   }
 
   @override
-  Future<List<int>> readAsBytes() async => readAsBytesSync();
+  Future<Uint8List> readAsBytes() async => readAsBytesSync();
 
   @override
-  List<int> readAsBytesSync() => (resolvedBacking as FileNode).content;
+  Uint8List readAsBytesSync() =>
+      Uint8List.fromList((resolvedBacking as FileNode).content);
 
   @override
   Future<String> readAsString({Encoding encoding: utf8}) async =>
@@ -248,7 +250,7 @@
     }
     FileNode node = _resolvedBackingOrCreate;
     _truncateIfNecessary(node, mode);
-    node.content.addAll(bytes);
+    node.write(bytes);
     node.touch();
   }
 
@@ -278,7 +280,7 @@
 
   void _truncateIfNecessary(FileNode node, io.FileMode mode) {
     if (mode == io.FileMode.write || mode == io.FileMode.writeOnly) {
-      node.content.clear();
+      node.clear();
     }
   }
 
@@ -402,7 +404,7 @@
 
   void _addData(List<int> data) {
     _pendingWrites = _pendingWrites.then((FileNode node) {
-      node.content.addAll(data);
+      node.write(data);
       return node;
     });
   }
diff --git a/packages/file/lib/src/backends/memory/node.dart b/packages/file/lib/src/backends/memory/node.dart
index b4073a5..65a2539 100644
--- a/packages/file/lib/src/backends/memory/node.dart
+++ b/packages/file/lib/src/backends/memory/node.dart
@@ -2,6 +2,8 @@
 // for details. 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:file/file.dart';
 import 'package:file/src/io.dart' as io;
 
@@ -222,7 +224,8 @@
 /// Class that represents the backing for an in-memory regular file.
 class FileNode extends RealNode {
   /// File contents in bytes.
-  List<int> content = <int>[];
+  Uint8List get content => _content;
+  Uint8List _content = Uint8List(0);
 
   /// Constructs a new [FileNode] as a child of the specified [parent].
   FileNode(DirectoryNode parent) : super(parent);
@@ -231,7 +234,20 @@
   io.FileSystemEntityType get type => io.FileSystemEntityType.file;
 
   @override
-  int get size => content.length;
+  int get size => _content.length;
+
+  /// Appends the specified bytes to the end of this node's [content].
+  void write(List<int> bytes) {
+    Uint8List existing = _content;
+    _content = Uint8List(existing.length + bytes.length);
+    _content.setRange(0, existing.length, existing);
+    _content.setRange(existing.length, _content.length, bytes);
+  }
+
+  /// Clears the [content] of the node.
+  void clear() {
+    _content = Uint8List(0);
+  }
 
   /// Copies data from [source] into this node. The [modified] and [changed]
   /// fields will be reset as opposed to copied to indicate that this file
@@ -240,7 +256,7 @@
     modified = changed = new DateTime.now().millisecondsSinceEpoch;
     accessed = source.accessed;
     mode = source.mode;
-    content = new List<int>.from(source.content);
+    _content = Uint8List.fromList(source.content);
   }
 }
 
diff --git a/packages/file/lib/src/backends/record_replay/codecs.dart b/packages/file/lib/src/backends/record_replay/codecs.dart
index 67236e7..bc1b30a 100644
--- a/packages/file/lib/src/backends/record_replay/codecs.dart
+++ b/packages/file/lib/src/backends/record_replay/codecs.dart
@@ -5,6 +5,7 @@
 import 'dart:async';
 import 'dart:convert';
 import 'dart:io' show systemEncoding;
+import 'dart:typed_data';
 
 import 'package:file/file.dart';
 import 'package:path/path.dart' as path;
@@ -455,6 +456,14 @@
   List<T> convert(T input) => <T>[input];
 }
 
+class Uint8ListToPlainList extends Converter<Uint8List, List<int>> {
+  /// Creates a new [Uint8ListToPlainList]
+  const Uint8ListToPlainList();
+
+  @override
+  List<int> convert(Uint8List list) => List<int>.from(list);
+}
+
 /// Revives a [Directory] entity reference into a [ReplayDirectory].
 class ReviveDirectory extends Converter<String, Directory> {
   final ReplayFileSystemImpl _fileSystem;
@@ -571,7 +580,7 @@
 
 /// Converts a blob reference (serialized as a [String] of the form
 /// `!<filename>`) into a byte list.
-class BlobToBytes extends Converter<String, List<int>> {
+class BlobToBytes extends Converter<String, Uint8List> {
   final ReplayFileSystemImpl _fileSystem;
 
   /// Creates a new [BlobToBytes] that will use the specified file system's
@@ -579,7 +588,7 @@
   const BlobToBytes(this._fileSystem);
 
   @override
-  List<int> convert(String input) {
+  Uint8List convert(String input) {
     assert(input.startsWith('!'));
     String basename = input.substring(1);
     String dirname = _fileSystem.recording.path;
diff --git a/packages/file/lib/src/backends/record_replay/recording_file.dart b/packages/file/lib/src/backends/record_replay/recording_file.dart
index 4400a96..03ccd56 100644
--- a/packages/file/lib/src/backends/record_replay/recording_file.dart
+++ b/packages/file/lib/src/backends/record_replay/recording_file.dart
@@ -4,6 +4,7 @@
 
 import 'dart:async';
 import 'dart:convert';
+import 'dart:typed_data';
 
 import 'package:meta/meta.dart';
 import 'package:file/file.dart';
@@ -89,11 +90,11 @@
   RandomAccessFile _openSync({FileMode mode: FileMode.read}) =>
       _wrapRandomAccessFile(delegate.openSync(mode: mode));
 
-  StreamReference<List<int>> _openRead([int start, int end]) {
-    return new _BlobStreamReference<List<int>>(
+  StreamReference<Uint8List> _openRead([int start, int end]) {
+    return new _BlobStreamReference<Uint8List>(
       file: _newRecordingFile(),
       stream: delegate.openRead(start, end),
-      writer: (File file, List<int> bytes) {
+      writer: (File file, Uint8List bytes) {
         file.writeAsBytesSync(bytes, mode: FileMode.append, flush: true);
       },
     );
@@ -106,21 +107,21 @@
     );
   }
 
-  FutureReference<List<int>> _readAsBytes() {
-    return new _BlobFutureReference<List<int>>(
+  FutureReference<Uint8List> _readAsBytes() {
+    return new _BlobFutureReference<Uint8List>(
       file: _newRecordingFile(),
       future: delegate.readAsBytes(),
-      writer: (File file, List<int> bytes) async {
+      writer: (File file, Uint8List bytes) async {
         await file.writeAsBytes(bytes, flush: true);
       },
     );
   }
 
-  ResultReference<List<int>> _readAsBytesSync() {
-    return new _BlobReference<List<int>>(
+  ResultReference<Uint8List> _readAsBytesSync() {
+    return new _BlobReference<Uint8List>(
       file: _newRecordingFile(),
       value: delegate.readAsBytesSync(),
-      writer: (File file, List<int> bytes) {
+      writer: (File file, Uint8List bytes) {
         file.writeAsBytesSync(bytes, flush: true);
       },
     );
diff --git a/packages/file/lib/src/backends/record_replay/replay_file.dart b/packages/file/lib/src/backends/record_replay/replay_file.dart
index 9a21416..d40e046 100644
--- a/packages/file/lib/src/backends/record_replay/replay_file.dart
+++ b/packages/file/lib/src/backends/record_replay/replay_file.dart
@@ -4,6 +4,7 @@
 
 import 'dart:async';
 import 'dart:convert';
+import 'dart:typed_data';
 
 import 'package:file/file.dart';
 
@@ -20,10 +21,11 @@
     Converter<String, File> reviveFile = new ReviveFile(fileSystem);
     Converter<String, Future<File>> reviveFileAsFuture =
         reviveFile.fuse(const ToFuture<File>());
-    Converter<String, List<int>> blobToBytes = new BlobToBytes(fileSystem);
-    Converter<String, Future<List<int>>> blobToBytesFuture =
-        blobToBytes.fuse(const ToFuture<List<int>>());
-    Converter<String, String> blobToString = blobToBytes.fuse(utf8.decoder);
+    Converter<String, Uint8List> blobToBytes = new BlobToBytes(fileSystem);
+    Converter<String, Future<Uint8List>> blobToBytesFuture =
+        blobToBytes.fuse(const ToFuture<Uint8List>());
+    Converter<String, String> blobToString =
+        blobToBytes.fuse(const Uint8ListToPlainList()).fuse(utf8.decoder);
     Converter<String, Future<String>> blobToStringFuture =
         blobToString.fuse(const ToFuture<String>());
     Converter<String, RandomAccessFile> reviveRandomAccessFile =
@@ -36,9 +38,9 @@
         blobToString.fuse(lineSplitter);
     Converter<String, Future<List<String>>> blobToLinesFuture =
         blobToLines.fuse(const ToFuture<List<String>>());
-    Converter<String, Stream<List<int>>> blobToByteStream = blobToBytes
-        .fuse(const Listify<List<int>>())
-        .fuse(const ToStream<List<int>>());
+    Converter<String, Stream<Uint8List>> blobToByteStream = blobToBytes
+        .fuse(const Listify<Uint8List>())
+        .fuse(const ToStream<Uint8List>());
     Converter<int, Future<DateTime>> reviveDateTime =
         DateTimeCodec.deserialize.fuse(const ToFuture<DateTime>());
 
diff --git a/packages/file/lib/src/backends/record_replay/replay_random_access_file.dart b/packages/file/lib/src/backends/record_replay/replay_random_access_file.dart
index c1219b4..9bfb4b3 100644
--- a/packages/file/lib/src/backends/record_replay/replay_random_access_file.dart
+++ b/packages/file/lib/src/backends/record_replay/replay_random_access_file.dart
@@ -4,6 +4,7 @@
 
 import 'dart:async';
 import 'dart:convert';
+import 'dart:typed_data';
 
 import 'package:file/file.dart';
 
@@ -29,8 +30,8 @@
       #closeSync: const Passthrough<Null>(),
       #readByte: const ToFuture<int>(),
       #readByteSync: const Passthrough<int>(),
-      #read: const ToFuture<List<int>>(),
-      #readSync: const Passthrough<List<int>>(),
+      #read: const ToFuture<Uint8List>(),
+      #readSync: const Passthrough<Uint8List>(),
       #readInto: const ToFuture<int>(),
       #readIntoSync: const Passthrough<int>(),
       #writeByte: reviveRandomAccessFileAsFuture,
diff --git a/packages/file/lib/src/forwarding/forwarding_file.dart b/packages/file/lib/src/forwarding/forwarding_file.dart
index ef99d92..3d76af0 100644
--- a/packages/file/lib/src/forwarding/forwarding_file.dart
+++ b/packages/file/lib/src/forwarding/forwarding_file.dart
@@ -4,6 +4,7 @@
 
 import 'dart:async';
 import 'dart:convert';
+import 'dart:typed_data';
 
 import 'package:file/src/io.dart' as io;
 import 'package:file/file.dart';
@@ -71,8 +72,8 @@
       delegate.openSync(mode: mode);
 
   @override
-  Stream<List<int>> openRead([int start, int end]) =>
-      delegate.openRead(start, end);
+  Stream<Uint8List> openRead([int start, int end]) =>
+      delegate.openRead(start, end).transform(const _ToUint8List());
 
   @override
   IOSink openWrite({
@@ -82,10 +83,14 @@
       delegate.openWrite(mode: mode, encoding: encoding);
 
   @override
-  Future<List<int>> readAsBytes() => delegate.readAsBytes();
+  Future<Uint8List> readAsBytes() {
+    return delegate.readAsBytes().then<Uint8List>((List<int> bytes) {
+      return Uint8List.fromList(bytes);
+    });
+  }
 
   @override
-  List<int> readAsBytesSync() => delegate.readAsBytesSync();
+  Uint8List readAsBytesSync() => Uint8List.fromList(delegate.readAsBytesSync());
 
   @override
   Future<String> readAsString({Encoding encoding: utf8}) =>
@@ -151,3 +156,31 @@
         flush: flush,
       );
 }
+
+class _ToUint8List extends Converter<List<int>, Uint8List> {
+  const _ToUint8List();
+
+  @override
+  Uint8List convert(List<int> input) => Uint8List.fromList(input);
+
+  @override
+  Sink<List<int>> startChunkedConversion(Sink<Uint8List> sink) {
+    return _Uint8ListConversionSink(sink);
+  }
+}
+
+class _Uint8ListConversionSink implements Sink<List<int>> {
+  const _Uint8ListConversionSink(this._target);
+
+  final Sink<Uint8List> _target;
+
+  @override
+  void add(List<int> data) {
+    _target.add(Uint8List.fromList(data));
+  }
+
+  @override
+  void close() {
+    _target.close();
+  }
+}
diff --git a/packages/file/pubspec.yaml b/packages/file/pubspec.yaml
index 2402f8c..dc7ccdd 100644
--- a/packages/file/pubspec.yaml
+++ b/packages/file/pubspec.yaml
@@ -1,5 +1,5 @@
 name: file
-version: 5.0.7
+version: 5.0.8
 authors:
 - Matan Lurey <matanl@google.com>
 - Yegor Jbanov <yjbanov@google.com>
diff --git a/packages/file/test/common_tests.dart b/packages/file/test/common_tests.dart
index 23923eb..281ac13 100644
--- a/packages/file/test/common_tests.dart
+++ b/packages/file/test/common_tests.dart
@@ -6,6 +6,7 @@
 import 'dart:async';
 import 'dart:convert';
 import 'dart:io' as io;
+import 'dart:typed_data';
 
 import 'package:file/file.dart';
 import 'package:file_testing/file_testing.dart';
@@ -2394,6 +2395,15 @@
           File f = fs.file(ns('/foo'))..createSync();
           expect(f.readAsBytesSync(), isEmpty);
         });
+
+        test('returns a copy, not a view, of the file content', () {
+          File f = fs.file(ns('/foo'))..createSync();
+          f.writeAsBytesSync(<int>[1, 2, 3, 4]);
+          List<int> result = f.readAsBytesSync();
+          expect(result, <int>[1, 2, 3, 4]);
+          result[0] = 10;
+          expect(f.readAsBytesSync(), <int>[1, 2, 3, 4]);
+        });
       });
 
       group('readAsString', () {
diff --git a/packages/file/test/record_replay_matchers.dart b/packages/file/test/record_replay_matchers.dart
index 34c5387..952e1e8 100644
--- a/packages/file/test/record_replay_matchers.dart
+++ b/packages/file/test/record_replay_matchers.dart
@@ -14,9 +14,9 @@
 };
 
 const Map<Type, Matcher> _kTypeMatchers = const <Type, Matcher>{
-  MethodEvent: const isInstanceOf<MethodEvent<dynamic>>(),
-  PropertyGetEvent: const isInstanceOf<PropertyGetEvent<dynamic>>(),
-  PropertySetEvent: const isInstanceOf<PropertySetEvent<dynamic>>(),
+  MethodEvent: const TypeMatcher<MethodEvent<dynamic>>(),
+  PropertyGetEvent: const TypeMatcher<PropertyGetEvent<dynamic>>(),
+  PropertySetEvent: const TypeMatcher<PropertySetEvent<dynamic>>(),
 };
 
 /// Returns a matcher that will match against a [MethodEvent].
@@ -49,13 +49,10 @@
 /// scope of the match (e.g. by property value, target object, etc).
 PropertySet setsProperty([dynamic name]) => new PropertySet._(name);
 
-/// A matcher that successfully matches against an instance of
-/// [NoMatchingInvocationError].
-const Matcher isNoMatchingInvocationError = const _NoMatchingInvocationError();
-
 /// A matcher that successfully matches against a future or function
 /// that throws a [NoMatchingInvocationError].
-Matcher throwsNoMatchingInvocationError = throwsA(isNoMatchingInvocationError);
+Matcher throwsNoMatchingInvocationError =
+    throwsA(const TypeMatcher<NoMatchingInvocationError>());
 
 /// Base class for matchers that match against generic [InvocationEvent]
 /// instances.
@@ -583,11 +580,3 @@
     return _matcher.describe(description);
   }
 }
-
-class _NoMatchingInvocationError extends TypeMatcher<dynamic> {
-  const _NoMatchingInvocationError() : super("NoMatchingInvocationError");
-
-  @override
-  bool matches(dynamic item, Map<dynamic, dynamic> matchState) =>
-      item is NoMatchingInvocationError;
-}
diff --git a/packages/file/test/recording_test.dart b/packages/file/test/recording_test.dart
index 03286fe..cb9dc46 100644
--- a/packages/file/test/recording_test.dart
+++ b/packages/file/test/recording_test.dart
@@ -9,7 +9,7 @@
 import 'package:file/memory.dart';
 import 'package:file/record_replay.dart';
 import 'package:file/src/backends/record_replay/codecs.dart';
-import 'package:file/src/backends/record_replay/common.dart';
+import 'package:file/src/backends/record_replay/common.dart' hide TypeMatcher;
 import 'package:file/src/backends/record_replay/events.dart';
 import 'package:file/src/backends/record_replay/mutable_recording.dart';
 import 'package:file/src/backends/record_replay/recording_proxy_mixin.dart';
@@ -157,7 +157,7 @@
         test('awaitsPendingResultsIndefinitelyByDefault', () async {
           rc.veryLongFutureMethod(); // ignore: unawaited_futures
           expect(recording.flush().timeout(const Duration(milliseconds: 50)),
-              throwsTimeoutException);
+              throwsA(const TypeMatcher<TimeoutException>()));
         });
 
         test('succeedsIfAwaitPendingResultsThatComplete', () async {
@@ -398,7 +398,7 @@
             events[0],
             getsProperty('path')
                 .on(fs)
-                .withResult(const isInstanceOf<p.Context>()),
+                .withResult(const TypeMatcher<p.Context>()),
           );
         });
 
@@ -613,7 +613,7 @@
               recording.events,
               contains(invokesMethod('openRead')
                   .on(isFile)
-                  .withPositionalArguments([null, null])
+                  .withPositionalArguments(<int>[null, null])
                   .withNoNamedArguments()
                   .withResult(isList)));
           await recording.flush();
@@ -625,7 +625,7 @@
                 containsPair('type', 'invoke'),
                 containsPair('method', 'openRead'),
                 containsPair('object', matches(r'^RecordingFile@[0-9]+$')),
-                containsPair('positionalArguments', [null, null]),
+                containsPair('positionalArguments', <int>[null, null]),
                 containsPair('namedArguments', isEmpty),
                 containsPair('result', matches(r'^![0-9]+.foo$')),
               ));
@@ -783,7 +783,8 @@
                 containsPair('object', matches(r'^RecordingFile@[0-9]+$')),
                 containsPair('positionalArguments', isEmpty),
                 containsPair('result', matches(r'^![0-9]+.foo$')),
-                containsPair('namedArguments', {'encoding': 'utf-8'}),
+                containsPair(
+                    'namedArguments', <String, String>{'encoding': 'utf-8'}),
               ));
           File file = _getRecordingFile(recording, manifest[1]['result']);
           expect(file, exists);
@@ -932,7 +933,3 @@
   @override
   dynamic noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation);
 }
-
-/// Successfully matches against a function that throws a [TimeoutException].
-Matcher throwsTimeoutException =
-    throwsA(const isInstanceOf<TimeoutException>());
diff --git a/packages/file/test/replay_test.dart b/packages/file/test/replay_test.dart
index b059356..37cb261 100644
--- a/packages/file/test/replay_test.dart
+++ b/packages/file/test/replay_test.dart
@@ -204,4 +204,4 @@
 }
 
 /// Successfully matches against an instance of [Future].
-const Matcher isFuture = const isInstanceOf<Future<dynamic>>();
+const Matcher isFuture = const TypeMatcher<Future<dynamic>>();