Skip to content

Allow converting List<int> to a JSArray / JSTypedArray #91

New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Open
navaronbracke opened this issue Oct 28, 2023 · 5 comments
Open

Allow converting List<int> to a JSArray / JSTypedArray #91

navaronbracke opened this issue Oct 28, 2023 · 5 comments

Comments

@navaronbracke
Copy link

navaronbracke commented Oct 28, 2023

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: flutter/flutter#137374

@srujzs
Copy link
Contributor

srujzs commented Oct 31, 2023

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

@sigmundch
Copy link
Member

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?

@rakudrama
Copy link
Member

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.

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.

@sigmundch
Copy link
Member

Note @rakudrama that this is referring to the Blob API in package:web here:

class Blob implements JSObject {

So the request is whether we can provide a zero-cost conversion between Int32Array and dart:js_interop's JSArray type.

@srujzs
Copy link
Contributor

srujzs commented Nov 2, 2023

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.

# for free to join this conversation on GitHub. Already have an account? # to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants