Determine encoding of source files used for coverage (#1691)

Restore behavior that used to come from `package:http`,
check the headers to choose an encoding.
diff --git a/pkgs/test/CHANGELOG.md b/pkgs/test/CHANGELOG.md
index 170b6bb..5c15d42 100644
--- a/pkgs/test/CHANGELOG.md
+++ b/pkgs/test/CHANGELOG.md
@@ -1,3 +1,8 @@
+## 1.21.1
+
+* Fix a bug loading JS sources with non-utf8 content while parsing coverage
+  information from chrome.
+
 ## 1.21.0
 
 * Allow analyzer version `4.x`.
diff --git a/pkgs/test/lib/src/runner/browser/chrome.dart b/pkgs/test/lib/src/runner/browser/chrome.dart
index d68fc11..0feb397 100644
--- a/pkgs/test/lib/src/runner/browser/chrome.dart
+++ b/pkgs/test/lib/src/runner/browser/chrome.dart
@@ -194,11 +194,23 @@
 }
 
 extension on HttpClient {
+  Encoding determineEncoding(HttpHeaders headers) {
+    final contentType = headers.contentType?.charset;
+
+    /// Using the `charset` property of the `contentType` if available.
+    /// If it's unavailable or if the encoding name is unknown, [latin1] is used by default,
+    /// as per [RFC 2616][].
+    ///
+    /// [RFC 2616]: http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html
+    return Encoding.getByName(contentType) ?? latin1;
+  }
+
   Future<String?> getString(String url) async {
     final request = await getUrl(Uri.parse(url));
     final response = await request.close();
     if (response.statusCode != HttpStatus.ok) return null;
     var bytes = [await for (var chunk in response) ...chunk];
-    return utf8.decode(bytes);
+    final encoding = determineEncoding(response.headers);
+    return encoding.decode(bytes);
   }
 }
diff --git a/pkgs/test/pubspec.yaml b/pkgs/test/pubspec.yaml
index b837eb3..42053e8 100644
--- a/pkgs/test/pubspec.yaml
+++ b/pkgs/test/pubspec.yaml
@@ -1,5 +1,5 @@
 name: test
-version: 1.21.0
+version: 1.21.1
 description: >-
   A full featured library for writing and running Dart tests across platforms.
 repository: https://github.com/dart-lang/test/tree/master/pkgs/test
diff --git a/pkgs/test/test/runner/coverage_test.dart b/pkgs/test/test/runner/coverage_test.dart
index a9005db..26eea45 100644
--- a/pkgs/test/test/runner/coverage_test.dart
+++ b/pkgs/test/test/runner/coverage_test.dart
@@ -62,5 +62,70 @@
           ['--coverage', coverageDirectory.path, 'test.dart', '-p', 'chrome']);
       await _validateCoverage(test, 'test.dart.chrome.json');
     });
+
+    test(
+        'gathers coverage for Chrome tests when JS files contain unicode characters',
+        () async {
+      final sourceMapFileContent =
+          '{"version":3,"file":"","sources":[],"names":[],"mappings":""}';
+      final jsContent = '''
+        (function() {
+          '© '
+          window.foo = function foo() {
+            return 'foo';
+          };
+        })({
+        
+          '© ': ''
+          });
+      ''';
+      await d.file('file_with_unicode.js', jsContent).create();
+      await d.file('file_with_unicode.js.map', sourceMapFileContent).create();
+
+      await d.file('js_with_unicode_test.dart', '''
+        import 'dart:html';
+        import 'dart:js';
+        
+        import 'package:test/test.dart';
+        
+        Future<void> loadScript(String src) async {
+          final script = ScriptElement()..src = src;
+          final scriptLoaded = script.onLoad.first;
+          document.body!.append(script);
+          await scriptLoaded.timeout(Duration(seconds: 1));
+        }
+
+        void main() {
+          test("test 1", () async {
+            await loadScript('file_with_unicode.js');
+            expect(context['foo'], isNotNull);
+            context.callMethod('foo', []);
+            expect(true, isTrue);
+          });
+        }
+      ''').create();
+
+      final jsBytes = utf8.encode(jsContent);
+      final jsLatin1 = latin1.decode(jsBytes);
+      final jsUtf8 = utf8.decode(jsBytes);
+      expect(jsLatin1, isNot(jsUtf8),
+          reason: 'test setup: should have decoded differently');
+
+      const functionPattern = 'function foo';
+      expect([jsLatin1, jsUtf8], everyElement(contains(functionPattern)));
+      expect(jsLatin1.indexOf(functionPattern),
+          isNot(jsUtf8.indexOf(functionPattern)),
+          reason:
+              'test setup: decoding should have shifted the position of the function');
+
+      var test = await runTest([
+        '--coverage',
+        coverageDirectory.path,
+        'js_with_unicode_test.dart',
+        '-p',
+        'chrome'
+      ]);
+      await _validateCoverage(test, 'js_with_unicode_test.dart.chrome.json');
+    });
   });
 }