[cross_file] Migrate to pkg:web, bump min SDK to Dart 3.2 (#5520)

* Migrates the web implementation of `cross_file` from `dart:html` to `package:web`, so it can be compiled with `dart2wasm`.
* Bumps minimum sdk version.

Part of https://github.com/flutter/flutter/issues/117022
diff --git a/packages/cross_file/CHANGELOG.md b/packages/cross_file/CHANGELOG.md
index b10acb4..2591d2e 100644
--- a/packages/cross_file/CHANGELOG.md
+++ b/packages/cross_file/CHANGELOG.md
@@ -1,3 +1,8 @@
+## 0.3.3+8
+
+* Now supports `dart2wasm` compilation.
+* Updates minimum supported SDK version to Flutter 3.16/Dart 3.2.
+
 ## 0.3.3+7
 
 * Updates README to improve example of instantiating an XFile.
diff --git a/packages/cross_file/example/test/readme_excerpts_test.dart b/packages/cross_file/example/test/readme_excerpts_test.dart
index e9a4bd3..93f1adc 100644
--- a/packages/cross_file/example/test/readme_excerpts_test.dart
+++ b/packages/cross_file/example/test/readme_excerpts_test.dart
@@ -6,7 +6,7 @@
 import 'package:cross_file_example/readme_excerpts.dart';
 import 'package:test/test.dart';
 
-const bool kIsWeb = bool.fromEnvironment('dart.library.js_util');
+const bool kIsWeb = bool.fromEnvironment('dart.library.js_interop');
 
 void main() {
   test('instantiateXFile loads asset file', () async {
diff --git a/packages/cross_file/lib/src/types/html.dart b/packages/cross_file/lib/src/types/html.dart
index 7f5a1e5..9eb95b4 100644
--- a/packages/cross_file/lib/src/types/html.dart
+++ b/packages/cross_file/lib/src/types/html.dart
@@ -4,10 +4,11 @@
 
 import 'dart:async';
 import 'dart:convert';
-import 'dart:html';
+import 'dart:js_interop';
 import 'dart:typed_data';
 
 import 'package:meta/meta.dart';
+import 'package:web/helpers.dart';
 
 import '../web_helpers/web_helpers.dart';
 import 'base.dart';
@@ -65,7 +66,9 @@
         super(path) {
     if (path == null) {
       _browserBlob = _createBlobFromBytes(bytes, mimeType);
-      _path = Url.createObjectUrl(_browserBlob);
+      // TODO(kevmoo): drop ignore when pkg:web constraint excludes v0.3
+      // ignore: unnecessary_cast
+      _path = URL.createObjectURL(_browserBlob! as JSObject);
     } else {
       _path = path;
     }
@@ -74,8 +77,9 @@
   // Initializes a Blob from a bunch of `bytes` and an optional `mimeType`.
   Blob _createBlobFromBytes(Uint8List bytes, String? mimeType) {
     return (mimeType == null)
-        ? Blob(<dynamic>[bytes])
-        : Blob(<dynamic>[bytes], mimeType);
+        ? Blob(<JSUint8Array>[bytes.toJS].toJS)
+        : Blob(
+            <JSUint8Array>[bytes.toJS].toJS, BlobPropertyBag(type: mimeType));
   }
 
   // Overridable (meta) data that can be specified by the constructors.
@@ -127,11 +131,13 @@
 
     // Attempt to re-hydrate the blob from the `path` via a (local) HttpRequest.
     // Note that safari hangs if the Blob is >=4GB, so bail out in that case.
+    // TODO(kevmoo): Remove ignore and fix when the MIN Dart SDK is 3.3
+    // ignore: unnecessary_non_null_assertion
     if (isSafari() && _length != null && _length! >= _fourGigabytes) {
       throw Exception('Safari cannot handle XFiles larger than 4GB.');
     }
 
-    late HttpRequest request;
+    late XMLHttpRequest request;
     try {
       request = await HttpRequest.request(path, responseType: 'blob');
     } on ProgressEvent catch (e) {
@@ -181,7 +187,8 @@
 
     await reader.onLoadEnd.first;
 
-    final Uint8List? result = reader.result as Uint8List?;
+    final Uint8List? result =
+        (reader.result as JSArrayBuffer?)?.toDart.asUint8List();
 
     if (result == null) {
       throw Exception('Cannot read bytes from Blob. Is it still available?');
@@ -201,12 +208,14 @@
 
     // Create an <a> tag with the appropriate download attributes and click it
     // May be overridden with CrossFileTestOverrides
-    final AnchorElement element = _hasTestOverrides
-        ? _overrides!.createAnchorElement(this.path, name) as AnchorElement
+    final HTMLAnchorElement element = _hasTestOverrides
+        ? _overrides!.createAnchorElement(this.path, name) as HTMLAnchorElement
         : createAnchorElement(this.path, name);
 
     // Clear the children in _target and add an element to click
-    _target.children.clear();
+    while (_target.children.length > 0) {
+      _target.removeChild(_target.children.item(0)!);
+    }
     addElementToContainerAndClick(_target, element);
   }
 }
diff --git a/packages/cross_file/lib/src/types/io.dart b/packages/cross_file/lib/src/types/io.dart
index fc34d49..a6979e5 100644
--- a/packages/cross_file/lib/src/types/io.dart
+++ b/packages/cross_file/lib/src/types/io.dart
@@ -82,6 +82,8 @@
       await _file.copy(path);
     } else {
       final File fileToSave = File(path);
+      // TODO(kevmoo): Remove ignore and fix when the MIN Dart SDK is 3.3
+      // ignore: unnecessary_non_null_assertion
       await fileToSave.writeAsBytes(_bytes!);
     }
   }
@@ -106,6 +108,8 @@
   @override
   Future<String> readAsString({Encoding encoding = utf8}) {
     if (_bytes != null) {
+      // TODO(kevmoo): Remove ignore and fix when the MIN Dart SDK is 3.3
+      // ignore: unnecessary_non_null_assertion
       return Future<String>.value(String.fromCharCodes(_bytes!));
     }
     return _file.readAsString(encoding: encoding);
diff --git a/packages/cross_file/lib/src/web_helpers/web_helpers.dart b/packages/cross_file/lib/src/web_helpers/web_helpers.dart
index da023b6..ec9a2e8 100644
--- a/packages/cross_file/lib/src/web_helpers/web_helpers.dart
+++ b/packages/cross_file/lib/src/web_helpers/web_helpers.dart
@@ -2,26 +2,19 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-import 'dart:html';
+import 'package:web/helpers.dart';
 
 /// Create anchor element with download attribute
-AnchorElement createAnchorElement(String href, String? suggestedName) {
-  final AnchorElement element = AnchorElement(href: href);
-
-  if (suggestedName == null) {
-    element.download = 'download';
-  } else {
-    element.download = suggestedName;
-  }
-
-  return element;
-}
+HTMLAnchorElement createAnchorElement(String href, String? suggestedName) =>
+    (document.createElement('a') as HTMLAnchorElement)
+      ..href = href
+      ..download = suggestedName ?? 'download';
 
 /// Add an element to a container and click it
-void addElementToContainerAndClick(Element container, Element element) {
+void addElementToContainerAndClick(Element container, HTMLElement element) {
   // Add the element and click it
   // All previous elements will be removed before adding the new one
-  container.children.add(element);
+  container.appendChild(element);
   element.click();
 }
 
@@ -29,9 +22,9 @@
 Element ensureInitialized(String id) {
   Element? target = querySelector('#$id');
   if (target == null) {
-    final Element targetElement = Element.tag('flt-x-file')..id = id;
+    final Element targetElement = document.createElement('flt-x-file')..id = id;
 
-    querySelector('body')!.children.add(targetElement);
+    querySelector('body')!.appendChild(targetElement);
     target = targetElement;
   }
   return target;
diff --git a/packages/cross_file/lib/src/x_file.dart b/packages/cross_file/lib/src/x_file.dart
index a6022e7..00dda82 100644
--- a/packages/cross_file/lib/src/x_file.dart
+++ b/packages/cross_file/lib/src/x_file.dart
@@ -3,5 +3,5 @@
 // found in the LICENSE file.
 
 export 'types/interface.dart'
-    if (dart.library.html) 'types/html.dart'
+    if (dart.library.js_interop) 'types/html.dart'
     if (dart.library.io) 'types/io.dart';
diff --git a/packages/cross_file/pubspec.yaml b/packages/cross_file/pubspec.yaml
index f1216d3..c2b8f2f 100644
--- a/packages/cross_file/pubspec.yaml
+++ b/packages/cross_file/pubspec.yaml
@@ -2,14 +2,14 @@
 description: An abstraction to allow working with files across multiple platforms.
 repository: https://github.com/flutter/packages/tree/main/packages/cross_file
 issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+cross_file%22
-version: 0.3.3+7
+version: 0.3.3+8
 
 environment:
-  sdk: ">=3.0.0 <4.0.0"
+  sdk: ^3.2.0
 
 dependencies:
-  js: ^0.6.3
   meta: ^1.3.0
+  web: '>=0.3.0 <0.5.0'
 
 dev_dependencies:
   path: ^1.8.1
diff --git a/packages/cross_file/test/x_file_html_test.dart b/packages/cross_file/test/x_file_html_test.dart
index a19e509..4e1cac6 100644
--- a/packages/cross_file/test/x_file_html_test.dart
+++ b/packages/cross_file/test/x_file_html_test.dart
@@ -5,17 +5,21 @@
 @TestOn('chrome') // Uses web-only Flutter SDK
 
 import 'dart:convert';
-import 'dart:html' as html;
+import 'dart:js_interop';
 import 'dart:typed_data';
 
 import 'package:cross_file/cross_file.dart';
-import 'package:js/js_util.dart' as js_util;
 import 'package:test/test.dart';
+import 'package:web/helpers.dart' as html;
 
 const String expectedStringContents = 'Hello, world! I ❤ ñ! 空手';
 final Uint8List bytes = Uint8List.fromList(utf8.encode(expectedStringContents));
-final html.File textFile = html.File(<Object>[bytes], 'hello.txt');
-final String textFileUrl = html.Url.createObjectUrl(textFile);
+final html.File textFile =
+    html.File(<JSUint8Array>[bytes.toJS].toJS, 'hello.txt');
+final String textFileUrl =
+    // TODO(kevmoo): drop ignore when pkg:web constraint excludes v0.3
+    // ignore: unnecessary_cast
+    html.URL.createObjectURL(textFile as JSObject);
 
 void main() {
   group('Create with an objectUrl', () {
@@ -63,16 +67,16 @@
 
     test('Stores data as a Blob', () async {
       // Read the blob from its path 'natively'
-      final Object response = await html.window.fetch(file.path) as Object;
-      // Call '.arrayBuffer()' on the fetch response object to look at its bytes.
-      final ByteBuffer data = await js_util.promiseToFuture(
-        js_util.callMethod(response, 'arrayBuffer', <Object?>[]),
-      );
+      final html.Response response =
+          (await html.window.fetch(file.path.toJS).toDart)! as html.Response;
+
+      final JSAny? arrayBuffer = await response.arrayBuffer().toDart;
+      final ByteBuffer data = (arrayBuffer! as JSArrayBuffer).toDart;
       expect(data.asUint8List(), equals(bytes));
     });
 
     test('Data may be purged from the blob!', () async {
-      html.Url.revokeObjectUrl(file.path);
+      html.URL.revokeObjectURL(file.path);
 
       expect(() async {
         await file.readAsBytes();
@@ -102,9 +106,15 @@
 
         final html.Element container =
             html.querySelector('#$crossFileDomElementId')!;
-        final html.AnchorElement element = container.children
-                .firstWhere((html.Element element) => element.tagName == 'A')
-            as html.AnchorElement;
+
+        late html.HTMLAnchorElement element;
+        for (int i = 0; i < container.childNodes.length; i++) {
+          final html.Element test = container.children.item(i)!;
+          if (test.tagName == 'A') {
+            element = test as html.HTMLAnchorElement;
+            break;
+          }
+        }
 
         // if element is not found, the `firstWhere` call will throw StateError.
         expect(element.href, file.path);
@@ -112,7 +122,8 @@
       });
 
       test('anchor element is clicked', () async {
-        final html.AnchorElement mockAnchor = html.AnchorElement();
+        final html.HTMLAnchorElement mockAnchor =
+            html.document.createElement('a') as html.HTMLAnchorElement;
 
         final CrossFileTestOverrides overrides = CrossFileTestOverrides(
           createAnchorElement: (_, __) => mockAnchor,