Skip to content

Commit 7a11e2d

Browse files
authored
[cross_file] Migrate to pkg:web, bump min SDK to Dart 3.2 (flutter#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 flutter#117022
1 parent eeecbd4 commit 7a11e2d

File tree

8 files changed

+66
-44
lines changed

8 files changed

+66
-44
lines changed

packages/cross_file/CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
## 0.3.3+8
2+
3+
* Now supports `dart2wasm` compilation.
4+
* Updates minimum supported SDK version to Flutter 3.16/Dart 3.2.
5+
16
## 0.3.3+7
27

38
* Updates README to improve example of instantiating an XFile.

packages/cross_file/example/test/readme_excerpts_test.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import 'package:cross_file/cross_file.dart';
66
import 'package:cross_file_example/readme_excerpts.dart';
77
import 'package:test/test.dart';
88

9-
const bool kIsWeb = bool.fromEnvironment('dart.library.js_util');
9+
const bool kIsWeb = bool.fromEnvironment('dart.library.js_interop');
1010

1111
void main() {
1212
test('instantiateXFile loads asset file', () async {

packages/cross_file/lib/src/types/html.dart

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,11 @@
44

55
import 'dart:async';
66
import 'dart:convert';
7-
import 'dart:html';
7+
import 'dart:js_interop';
88
import 'dart:typed_data';
99

1010
import 'package:meta/meta.dart';
11+
import 'package:web/helpers.dart';
1112

1213
import '../web_helpers/web_helpers.dart';
1314
import 'base.dart';
@@ -65,7 +66,9 @@ class XFile extends XFileBase {
6566
super(path) {
6667
if (path == null) {
6768
_browserBlob = _createBlobFromBytes(bytes, mimeType);
68-
_path = Url.createObjectUrl(_browserBlob);
69+
// TODO(kevmoo): drop ignore when pkg:web constraint excludes v0.3
70+
// ignore: unnecessary_cast
71+
_path = URL.createObjectURL(_browserBlob! as JSObject);
6972
} else {
7073
_path = path;
7174
}
@@ -74,8 +77,9 @@ class XFile extends XFileBase {
7477
// Initializes a Blob from a bunch of `bytes` and an optional `mimeType`.
7578
Blob _createBlobFromBytes(Uint8List bytes, String? mimeType) {
7679
return (mimeType == null)
77-
? Blob(<dynamic>[bytes])
78-
: Blob(<dynamic>[bytes], mimeType);
80+
? Blob(<JSUint8Array>[bytes.toJS].toJS)
81+
: Blob(
82+
<JSUint8Array>[bytes.toJS].toJS, BlobPropertyBag(type: mimeType));
7983
}
8084

8185
// Overridable (meta) data that can be specified by the constructors.
@@ -127,11 +131,13 @@ class XFile extends XFileBase {
127131

128132
// Attempt to re-hydrate the blob from the `path` via a (local) HttpRequest.
129133
// Note that safari hangs if the Blob is >=4GB, so bail out in that case.
134+
// TODO(kevmoo): Remove ignore and fix when the MIN Dart SDK is 3.3
135+
// ignore: unnecessary_non_null_assertion
130136
if (isSafari() && _length != null && _length! >= _fourGigabytes) {
131137
throw Exception('Safari cannot handle XFiles larger than 4GB.');
132138
}
133139

134-
late HttpRequest request;
140+
late XMLHttpRequest request;
135141
try {
136142
request = await HttpRequest.request(path, responseType: 'blob');
137143
} on ProgressEvent catch (e) {
@@ -181,7 +187,8 @@ class XFile extends XFileBase {
181187

182188
await reader.onLoadEnd.first;
183189

184-
final Uint8List? result = reader.result as Uint8List?;
190+
final Uint8List? result =
191+
(reader.result as JSArrayBuffer?)?.toDart.asUint8List();
185192

186193
if (result == null) {
187194
throw Exception('Cannot read bytes from Blob. Is it still available?');
@@ -201,12 +208,14 @@ class XFile extends XFileBase {
201208

202209
// Create an <a> tag with the appropriate download attributes and click it
203210
// May be overridden with CrossFileTestOverrides
204-
final AnchorElement element = _hasTestOverrides
205-
? _overrides!.createAnchorElement(this.path, name) as AnchorElement
211+
final HTMLAnchorElement element = _hasTestOverrides
212+
? _overrides!.createAnchorElement(this.path, name) as HTMLAnchorElement
206213
: createAnchorElement(this.path, name);
207214

208215
// Clear the children in _target and add an element to click
209-
_target.children.clear();
216+
while (_target.children.length > 0) {
217+
_target.removeChild(_target.children.item(0)!);
218+
}
210219
addElementToContainerAndClick(_target, element);
211220
}
212221
}

packages/cross_file/lib/src/types/io.dart

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,8 @@ class XFile extends XFileBase {
8282
await _file.copy(path);
8383
} else {
8484
final File fileToSave = File(path);
85+
// TODO(kevmoo): Remove ignore and fix when the MIN Dart SDK is 3.3
86+
// ignore: unnecessary_non_null_assertion
8587
await fileToSave.writeAsBytes(_bytes!);
8688
}
8789
}
@@ -106,6 +108,8 @@ class XFile extends XFileBase {
106108
@override
107109
Future<String> readAsString({Encoding encoding = utf8}) {
108110
if (_bytes != null) {
111+
// TODO(kevmoo): Remove ignore and fix when the MIN Dart SDK is 3.3
112+
// ignore: unnecessary_non_null_assertion
109113
return Future<String>.value(String.fromCharCodes(_bytes!));
110114
}
111115
return _file.readAsString(encoding: encoding);

packages/cross_file/lib/src/web_helpers/web_helpers.dart

Lines changed: 9 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2,36 +2,29 @@
22
// Use of this source code is governed by a BSD-style license that can be
33
// found in the LICENSE file.
44

5-
import 'dart:html';
5+
import 'package:web/helpers.dart';
66

77
/// Create anchor element with download attribute
8-
AnchorElement createAnchorElement(String href, String? suggestedName) {
9-
final AnchorElement element = AnchorElement(href: href);
10-
11-
if (suggestedName == null) {
12-
element.download = 'download';
13-
} else {
14-
element.download = suggestedName;
15-
}
16-
17-
return element;
18-
}
8+
HTMLAnchorElement createAnchorElement(String href, String? suggestedName) =>
9+
(document.createElement('a') as HTMLAnchorElement)
10+
..href = href
11+
..download = suggestedName ?? 'download';
1912

2013
/// Add an element to a container and click it
21-
void addElementToContainerAndClick(Element container, Element element) {
14+
void addElementToContainerAndClick(Element container, HTMLElement element) {
2215
// Add the element and click it
2316
// All previous elements will be removed before adding the new one
24-
container.children.add(element);
17+
container.appendChild(element);
2518
element.click();
2619
}
2720

2821
/// Initializes a DOM container where elements can be injected.
2922
Element ensureInitialized(String id) {
3023
Element? target = querySelector('#$id');
3124
if (target == null) {
32-
final Element targetElement = Element.tag('flt-x-file')..id = id;
25+
final Element targetElement = document.createElement('flt-x-file')..id = id;
3326

34-
querySelector('body')!.children.add(targetElement);
27+
querySelector('body')!.appendChild(targetElement);
3528
target = targetElement;
3629
}
3730
return target;

packages/cross_file/lib/src/x_file.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,5 @@
33
// found in the LICENSE file.
44

55
export 'types/interface.dart'
6-
if (dart.library.html) 'types/html.dart'
6+
if (dart.library.js_interop) 'types/html.dart'
77
if (dart.library.io) 'types/io.dart';

packages/cross_file/pubspec.yaml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,14 @@ name: cross_file
22
description: An abstraction to allow working with files across multiple platforms.
33
repository: https://github.com/flutter/packages/tree/main/packages/cross_file
44
issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+cross_file%22
5-
version: 0.3.3+7
5+
version: 0.3.3+8
66

77
environment:
8-
sdk: ">=3.0.0 <4.0.0"
8+
sdk: ^3.2.0
99

1010
dependencies:
11-
js: ^0.6.3
1211
meta: ^1.3.0
12+
web: '>=0.3.0 <0.5.0'
1313

1414
dev_dependencies:
1515
path: ^1.8.1

packages/cross_file/test/x_file_html_test.dart

Lines changed: 25 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,21 @@
55
@TestOn('chrome') // Uses web-only Flutter SDK
66

77
import 'dart:convert';
8-
import 'dart:html' as html;
8+
import 'dart:js_interop';
99
import 'dart:typed_data';
1010

1111
import 'package:cross_file/cross_file.dart';
12-
import 'package:js/js_util.dart' as js_util;
1312
import 'package:test/test.dart';
13+
import 'package:web/helpers.dart' as html;
1414

1515
const String expectedStringContents = 'Hello, world! I ❤ ñ! 空手';
1616
final Uint8List bytes = Uint8List.fromList(utf8.encode(expectedStringContents));
17-
final html.File textFile = html.File(<Object>[bytes], 'hello.txt');
18-
final String textFileUrl = html.Url.createObjectUrl(textFile);
17+
final html.File textFile =
18+
html.File(<JSUint8Array>[bytes.toJS].toJS, 'hello.txt');
19+
final String textFileUrl =
20+
// TODO(kevmoo): drop ignore when pkg:web constraint excludes v0.3
21+
// ignore: unnecessary_cast
22+
html.URL.createObjectURL(textFile as JSObject);
1923

2024
void main() {
2125
group('Create with an objectUrl', () {
@@ -63,16 +67,16 @@ void main() {
6367

6468
test('Stores data as a Blob', () async {
6569
// Read the blob from its path 'natively'
66-
final Object response = await html.window.fetch(file.path) as Object;
67-
// Call '.arrayBuffer()' on the fetch response object to look at its bytes.
68-
final ByteBuffer data = await js_util.promiseToFuture(
69-
js_util.callMethod(response, 'arrayBuffer', <Object?>[]),
70-
);
70+
final html.Response response =
71+
(await html.window.fetch(file.path.toJS).toDart)! as html.Response;
72+
73+
final JSAny? arrayBuffer = await response.arrayBuffer().toDart;
74+
final ByteBuffer data = (arrayBuffer! as JSArrayBuffer).toDart;
7175
expect(data.asUint8List(), equals(bytes));
7276
});
7377

7478
test('Data may be purged from the blob!', () async {
75-
html.Url.revokeObjectUrl(file.path);
79+
html.URL.revokeObjectURL(file.path);
7680

7781
expect(() async {
7882
await file.readAsBytes();
@@ -102,17 +106,24 @@ void main() {
102106

103107
final html.Element container =
104108
html.querySelector('#$crossFileDomElementId')!;
105-
final html.AnchorElement element = container.children
106-
.firstWhere((html.Element element) => element.tagName == 'A')
107-
as html.AnchorElement;
109+
110+
late html.HTMLAnchorElement element;
111+
for (int i = 0; i < container.childNodes.length; i++) {
112+
final html.Element test = container.children.item(i)!;
113+
if (test.tagName == 'A') {
114+
element = test as html.HTMLAnchorElement;
115+
break;
116+
}
117+
}
108118

109119
// if element is not found, the `firstWhere` call will throw StateError.
110120
expect(element.href, file.path);
111121
expect(element.download, file.name);
112122
});
113123

114124
test('anchor element is clicked', () async {
115-
final html.AnchorElement mockAnchor = html.AnchorElement();
125+
final html.HTMLAnchorElement mockAnchor =
126+
html.document.createElement('a') as html.HTMLAnchorElement;
116127

117128
final CrossFileTestOverrides overrides = CrossFileTestOverrides(
118129
createAnchorElement: (_, __) => mockAnchor,

0 commit comments

Comments
 (0)