[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,