web
web copied to clipboard
Allow converting `List<int>` to a `JSArray` / `JSTypedArray`
Currently, there is no easy way to pass a List<int>/Uint8List
to the Blob
constructor.
We have to do stuff like:
final List<int> bytes = ...;
final Blob blob = Blob(bytes.map((int byte) => byte.toJS).toList().toJS);
Per https://developer.mozilla.org/en-US/docs/Web/API/Blob/Blob#array the constructor's field for the array supports TypedArray
.
There is also no extension to convert a List<int>
(which Uint8List implements) to a JSArray
, since we only have that for List<JSAny?>
. But int
is a primitive that is allowed.
I propose we do either of (or both?)
a) add an extension that converts a List<int>
to a JSArray
b) update the Blob constructor to also allow a JSTypedArray
~Personally I would just add option a, since it is easy and we don't need to change the Blob constructor.~
Context: https://github.com/flutter/flutter/issues/137374
a) This one is complex and we've discussed it several times. We can definitely do the simple route of exposing an extension member that would be essentially the code you wrote above. The semantics there are consistent across all backends.
It's a bit more complicated if we want to take advantage of the type representation for performance benefits on the JS backends. On the JS backends, a List<int>
is a subtype of the runtime type of JSArray
, which is List<Object?>
, so we can just cast. On dart2wasm, this isn't the case and we need to do the iterative copy conversion. We could make this an implementation detail, but we come across the same "liveness" issue as we do with List<JSAny?>
. The resulting JSArray
may or may not be the same object depending on the backend (since dart2wasm will need a copy or a proxy). This already is an issue with List<JSAny?>.toJS
(and why we have toJSProxyOrRef
), and so we're introducing more pitfalls here by having an inconsistent conversion.
Note that while int
is allowed, int
as a type parameter/within a higher-order type gets more complicated (moreso coming from JS rather than going in).
If we were to add this extension method, I propose we do the consistent semantics.
b) The IDL defines blobParts
as a sequence
, which we treat as JSArray
, so that's likely where the gap here is. https://webidl.spec.whatwg.org/#idl-sequence
We could instead generate JSObject
as that's the least upper bound here whenever we see sequence
, but this might be too permissive. We could also add a new abstract type into our JS type hierarchy e.g. JSListLike
that can be the supertype of JSArray
and the typed array types and then use that here for sequence
. This is also useful for types like NodeList
.
cc @sigmundch
cc @rakudrama
For (b), I wonder if we should be exposing something like JavaScriptIndexingBehavior
(see https://github.com/dart-lang/sdk/blob/8b3d93234d1df69c3db5428fa02bb6b39c671ed8/sdk/lib/_internal/js_runtime/lib/js_helper.dart#L2719),
For (a) - a lot of our concerns with the List<int> => JSArray
conversion came from supporting the List<int>
type in general. Would this be any different if we provided the extension directly on typed arrays instead? Would that address the need we have here for Blob
?
Currently, there is no easy way to pass a
List<int>/Uint8List
to theBlob
constructor. We have to do stuff like:final List<int> bytes = ...; final Blob blob = Blob(bytes.map((int byte) => byte.toJS).toList().toJS);
Per https://developer.mozilla.org/en-US/docs/Web/API/Blob/Blob#array the constructor's field for the array supports
TypedArray
.
The Blob
constructor takes an iterable. If you pass a typed array that is not wrapped in another Iterable (like a JavaScript Array), it is seen as an iterable of numbers, and the numbers are converted to strings. Consider
var a = new Blob(new Int32Array(3)); // Blob {size: 3}
var b = new Blob([new Int32Array(3)]); // Blob {size: 12}
a
contains the byte sequence 48,48,48 (three zero characters, i.e. "000"
)
b
contains twelve bytes, all zero.
So... I think it is a bad idea to enabling the Blob
constructor in Dart to directly take any TypedData types.
Note @rakudrama that this is referring to the Blob API in package:web
here: https://github.com/dart-lang/web/blob/e0564a4cc3cb96c88d15def1c545aa8dff375d94/lib/src/dom/fileapi.dart#L17
So the request is whether we can provide a zero-cost conversion between Int32Array
and dart:js_interop
's JSArray
type.
I wonder if we should be exposing something like JavaScriptIndexingBehavior (see https://github.com/dart-lang/sdk/blob/8b3d93234d1df69c3db5428fa02bb6b39c671ed8/sdk/lib/_internal/js_runtime/lib/js_helper.dart#L2719).
You mean as the representation type for the JS type interface? I think that would only work if we have an interceptor for every possible indexable JS type. I was imagining it to be interceptors.JSObject
still, but merely having a static separation from js_interop.JSObject
.
If you pass a typed array that is not wrapped in another Iterable (like a JavaScript Array), it is seen as an iterable of numbers, and the numbers are converted to strings.
That's...pretty confusing. I'm torn because while it is confusing, it's also what the API allows you to do.