Skip to content

Commit

Permalink
Fix JS value to Dart conversion when receiving from a web socket (#298)
Browse files Browse the repository at this point in the history
`MessageEvent` is a `package:web` type and `data` field is a JS value of type
`JSAny?`.

The receiving end of the `sink` is here:
https://github.com/dart-lang/sdk/blob/26107a319a7503deafee404e3462644a873e2920/pkg/vm_service/lib/src/vm_service.dart#L1795

This code currently does not expect to see JS objects, so passing a `JSAny?` to
the sink breaks it.

The fix should be in the generating end rather than the receiving end: `JSAny?`
is supposed to be an unboxed value, not a boxed Dart value. In an ideal world we
shouldn't be able to pass it as a Dart object. So we call `dartify` and convert
it to a Dart object.

This fixes DevTools when compiled to Wasm.

Thanks to @eyebrowsoffire and @mkustermann for help with debugging.

Co-authored-by: Kevin Moore <[email protected]>
  • Loading branch information
osa1 and kevmoo authored Dec 9, 2023
1 parent df096a9 commit 969bc6c
Show file tree
Hide file tree
Showing 2 changed files with 16 additions and 5 deletions.
10 changes: 7 additions & 3 deletions lib/html.dart
Original file line number Diff line number Diff line change
Expand Up @@ -127,13 +127,17 @@ class HtmlWebSocketChannel extends StreamChannelMixin
}

void _innerListen(MessageEvent event) {
// Event data will be ArrayBuffer, Blob, or String.
final eventData = event.data;
Object? data;
if (eventData.typeofEquals('object') &&
final Object? data;
if (eventData.typeofEquals('string')) {
data = (eventData as JSString).toDart;
} else if (eventData.typeofEquals('object') &&
(eventData as JSObject).instanceOfString('ArrayBuffer')) {
data = (eventData as JSArrayBuffer).toDart.asUint8List();
} else {
data = event.data;
// Blobs are passed directly.
data = eventData;
}
_controller.local.sink.add(data);
}
Expand Down
11 changes: 9 additions & 2 deletions test/html_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,19 @@ import 'dart:js_interop';
import 'dart:typed_data';

import 'package:async/async.dart';
import 'package:stream_channel/stream_channel.dart';
import 'package:test/test.dart';
import 'package:web/helpers.dart' hide BinaryType;
import 'package:web_socket_channel/html.dart';
import 'package:web_socket_channel/src/web_helpers.dart';
import 'package:web_socket_channel/web_socket_channel.dart';

extension on StreamChannel {
/// Handles the Wasm case where the runtime type is actually [double] instead
/// of the JS case where its [int].
Future<int> get firstAsInt async => ((await stream.first) as num).toInt();
}

void main() {
late int port;
setUpAll(() async {
Expand All @@ -35,7 +42,7 @@ void main() {
}
''', stayAlive: true);

port = await channel.stream.first as int;
port = await channel.firstAsInt;
});

test('communicates using an existing WebSocket', () async {
Expand Down Expand Up @@ -169,7 +176,7 @@ void main() {
// TODO(nweiz): Make this channel use a port number that's guaranteed to be
// invalid.
final channel = HtmlWebSocketChannel.connect(
'ws://localhost:${await serverChannel.stream.first}');
'ws://localhost:${await serverChannel.firstAsInt}');
expect(channel.ready, throwsA(isA<WebSocketChannelException>()));
expect(channel.stream.toList(), throwsA(isA<WebSocketChannelException>()));
});
Expand Down

0 comments on commit 969bc6c

Please sign in to comment.