Skip to content

Commit 651521d

Browse files
authored
Merge pull request #1102 from lightpanda-io/readable_stream_uint8array
Better support for Uint8Array in ReadableStream
2 parents 20cb6cd + 2ecf901 commit 651521d

File tree

7 files changed

+156
-96
lines changed

7 files changed

+156
-96
lines changed

src/browser/fetch/Request.zig

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -181,7 +181,7 @@ pub fn constructor(input: RequestInput, _options: ?RequestInit, page: *Page) !Re
181181
pub fn get_body(self: *const Request, page: *Page) !?*ReadableStream {
182182
if (self.body) |body| {
183183
const stream = try ReadableStream.constructor(null, null, page);
184-
try stream.queue.append(page.arena, body);
184+
try stream.queue.append(page.arena, .{ .string = body });
185185
return stream;
186186
} else return null;
187187
}

src/browser/fetch/Response.zig

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ pub fn constructor(_input: ?ResponseBody, _options: ?ResponseOptions, page: *Pag
109109
pub fn get_body(self: *const Response, page: *Page) !*ReadableStream {
110110
const stream = try ReadableStream.constructor(null, null, page);
111111
if (self.body) |body| {
112-
try stream.queue.append(page.arena, body);
112+
try stream.queue.append(page.arena, .{ .string = body });
113113
}
114114
return stream;
115115
}

src/browser/streams/ReadableStream.zig

Lines changed: 34 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,9 @@
1919
const std = @import("std");
2020
const log = @import("../../log.zig");
2121

22-
const Page = @import("../page.zig").Page;
22+
const Allocator = std.mem.Allocator;
2323
const Env = @import("../env.zig").Env;
24+
const Page = @import("../page.zig").Page;
2425

2526
const ReadableStream = @This();
2627
const ReadableStreamDefaultReader = @import("ReadableStreamDefaultReader.zig");
@@ -45,16 +46,42 @@ cancel_fn: ?Env.Function = null,
4546
pull_fn: ?Env.Function = null,
4647

4748
strategy: QueueingStrategy,
48-
queue: std.ArrayListUnmanaged([]const u8) = .empty,
49+
queue: std.ArrayListUnmanaged(Chunk) = .empty,
50+
51+
pub const Chunk = union(enum) {
52+
// the order matters, sorry.
53+
uint8array: Env.TypedArray(u8),
54+
string: []const u8,
55+
56+
pub fn dupe(self: Chunk, allocator: Allocator) !Chunk {
57+
return switch (self) {
58+
.string => |str| .{ .string = try allocator.dupe(u8, str) },
59+
.uint8array => |arr| .{ .uint8array = try arr.dupe(allocator) },
60+
};
61+
}
62+
};
4963

5064
pub const ReadableStreamReadResult = struct {
51-
const ValueUnion =
52-
union(enum) { data: []const u8, empty: void };
53-
54-
value: ValueUnion,
5565
done: bool,
66+
value: Value = .empty,
67+
68+
const Value = union(enum) {
69+
empty,
70+
data: Chunk,
71+
};
72+
73+
pub fn init(chunk: Chunk, done: bool) ReadableStreamReadResult {
74+
if (done) {
75+
return .{ .done = true, .value = .empty };
76+
}
77+
78+
return .{
79+
.done = false,
80+
.value = .{ .data = chunk },
81+
};
82+
}
5683

57-
pub fn get_value(self: *const ReadableStreamReadResult) ValueUnion {
84+
pub fn get_value(self: *const ReadableStreamReadResult) Value {
5885
return self.value;
5986
}
6087

src/browser/streams/ReadableStreamDefaultController.zig

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -51,17 +51,17 @@ pub fn _close(self: *ReadableStreamDefaultController, _reason: ?[]const u8, page
5151
// to discard, must use cancel.
5252
}
5353

54-
pub fn _enqueue(self: *ReadableStreamDefaultController, chunk: []const u8, page: *Page) !void {
54+
pub fn _enqueue(self: *ReadableStreamDefaultController, chunk: ReadableStream.Chunk, page: *Page) !void {
5555
const stream = self.stream;
5656

5757
if (stream.state != .readable) {
5858
return error.TypeError;
5959
}
6060

61-
const duped_chunk = try page.arena.dupe(u8, chunk);
61+
const duped_chunk = try chunk.dupe(page.arena);
6262

6363
if (self.stream.reader_resolver) |*rr| {
64-
try rr.resolve(ReadableStreamReadResult{ .value = .{ .data = duped_chunk }, .done = false });
64+
try rr.resolve(ReadableStreamReadResult.init(duped_chunk, false));
6565
self.stream.reader_resolver = null;
6666
}
6767

src/browser/streams/ReadableStreamDefaultReader.zig

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ pub fn _read(self: *const ReadableStreamDefaultReader, page: *Page) !Env.Promise
4949
const data = self.stream.queue.orderedRemove(0);
5050
const resolver = page.main_context.createPromiseResolver();
5151

52-
try resolver.resolve(ReadableStreamReadResult{ .value = .{ .data = data }, .done = false });
52+
try resolver.resolve(ReadableStreamReadResult.init(data, false));
5353
try self.stream.pullIf();
5454
return resolver.promise();
5555
} else {
@@ -67,9 +67,9 @@ pub fn _read(self: *const ReadableStreamDefaultReader, page: *Page) !Env.Promise
6767

6868
if (stream.queue.items.len > 0) {
6969
const data = self.stream.queue.orderedRemove(0);
70-
try resolver.resolve(ReadableStreamReadResult{ .value = .{ .data = data }, .done = false });
70+
try resolver.resolve(ReadableStreamReadResult.init(data, false));
7171
} else {
72-
try resolver.resolve(ReadableStreamReadResult{ .value = .empty, .done = true });
72+
try resolver.resolve(ReadableStreamReadResult{ .done = true });
7373
}
7474

7575
return resolver.promise();

src/runtime/js.zig

Lines changed: 97 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -1094,88 +1094,10 @@ pub fn Env(comptime State: type, comptime WebApis: type) type {
10941094
}
10951095
},
10961096
.slice => {
1097-
var force_u8 = false;
1098-
var array_buffer: ?v8.ArrayBuffer = null;
1099-
if (js_value.isTypedArray()) {
1100-
const buffer_view = js_value.castTo(v8.ArrayBufferView);
1101-
array_buffer = buffer_view.getBuffer();
1102-
} else if (js_value.isArrayBufferView()) {
1103-
force_u8 = true;
1104-
const buffer_view = js_value.castTo(v8.ArrayBufferView);
1105-
array_buffer = buffer_view.getBuffer();
1106-
} else if (js_value.isArrayBuffer()) {
1107-
force_u8 = true;
1108-
array_buffer = js_value.castTo(v8.ArrayBuffer);
1109-
}
1110-
1111-
if (array_buffer) |buffer| {
1112-
const backing_store = v8.BackingStore.sharedPtrGet(&buffer.getBackingStore());
1113-
const data = backing_store.getData();
1114-
const byte_len = backing_store.getByteLength();
1115-
1116-
switch (ptr.child) {
1117-
u8 => {
1118-
// need this sentinel check to keep the compiler happy
1119-
if (ptr.sentinel() == null) {
1120-
if (force_u8 or js_value.isUint8Array() or js_value.isUint8ClampedArray()) {
1121-
if (byte_len == 0) return &[_]u8{};
1122-
const arr_ptr = @as([*]u8, @ptrCast(@alignCast(data)));
1123-
return arr_ptr[0..byte_len];
1124-
}
1125-
}
1126-
},
1127-
i8 => {
1128-
if (js_value.isInt8Array()) {
1129-
if (byte_len == 0) return &[_]i8{};
1130-
const arr_ptr = @as([*]i8, @ptrCast(@alignCast(data)));
1131-
return arr_ptr[0..byte_len];
1132-
}
1133-
},
1134-
u16 => {
1135-
if (js_value.isUint16Array()) {
1136-
if (byte_len == 0) return &[_]u16{};
1137-
const arr_ptr = @as([*]u16, @ptrCast(@alignCast(data)));
1138-
return arr_ptr[0 .. byte_len / 2];
1139-
}
1140-
},
1141-
i16 => {
1142-
if (js_value.isInt16Array()) {
1143-
if (byte_len == 0) return &[_]i16{};
1144-
const arr_ptr = @as([*]i16, @ptrCast(@alignCast(data)));
1145-
return arr_ptr[0 .. byte_len / 2];
1146-
}
1147-
},
1148-
u32 => {
1149-
if (js_value.isUint32Array()) {
1150-
if (byte_len == 0) return &[_]u32{};
1151-
const arr_ptr = @as([*]u32, @ptrCast(@alignCast(data)));
1152-
return arr_ptr[0 .. byte_len / 4];
1153-
}
1154-
},
1155-
i32 => {
1156-
if (js_value.isInt32Array()) {
1157-
if (byte_len == 0) return &[_]i32{};
1158-
const arr_ptr = @as([*]i32, @ptrCast(@alignCast(data)));
1159-
return arr_ptr[0 .. byte_len / 4];
1160-
}
1161-
},
1162-
u64 => {
1163-
if (js_value.isBigUint64Array()) {
1164-
if (byte_len == 0) return &[_]u64{};
1165-
const arr_ptr = @as([*]u64, @ptrCast(@alignCast(data)));
1166-
return arr_ptr[0 .. byte_len / 8];
1167-
}
1168-
},
1169-
i64 => {
1170-
if (js_value.isBigInt64Array()) {
1171-
if (byte_len == 0) return &[_]i64{};
1172-
const arr_ptr = @as([*]i64, @ptrCast(@alignCast(data)));
1173-
return arr_ptr[0 .. byte_len / 8];
1174-
}
1175-
},
1176-
else => {},
1097+
if (ptr.sentinel() == null) {
1098+
if (try self.jsValueToTypedArray(ptr.child, js_value)) |value| {
1099+
return value;
11771100
}
1178-
return error.InvalidArgument;
11791101
}
11801102

11811103
if (ptr.child == u8) {
@@ -1282,6 +1204,12 @@ pub fn Env(comptime State: type, comptime WebApis: type) type {
12821204
return try self.createFunction(js_value);
12831205
}
12841206

1207+
if (@hasDecl(T, "_TYPED_ARRAY_ID_KLUDGE")) {
1208+
const VT = @typeInfo(std.meta.fieldInfo(T, .values).type).pointer.child;
1209+
const arr = (try self.jsValueToTypedArray(VT, js_value)) orelse return null;
1210+
return .{ .values = arr };
1211+
}
1212+
12851213
if (T == String) {
12861214
return .{ .string = try valueToString(self.context_arena, js_value, self.isolate, self.v8_context) };
12871215
}
@@ -1320,6 +1248,90 @@ pub fn Env(comptime State: type, comptime WebApis: type) type {
13201248
return value;
13211249
}
13221250

1251+
fn jsValueToTypedArray(_: *JsContext, comptime T: type, js_value: v8.Value) !?[]T {
1252+
var force_u8 = false;
1253+
var array_buffer: ?v8.ArrayBuffer = null;
1254+
if (js_value.isTypedArray()) {
1255+
const buffer_view = js_value.castTo(v8.ArrayBufferView);
1256+
array_buffer = buffer_view.getBuffer();
1257+
} else if (js_value.isArrayBufferView()) {
1258+
force_u8 = true;
1259+
const buffer_view = js_value.castTo(v8.ArrayBufferView);
1260+
array_buffer = buffer_view.getBuffer();
1261+
} else if (js_value.isArrayBuffer()) {
1262+
force_u8 = true;
1263+
array_buffer = js_value.castTo(v8.ArrayBuffer);
1264+
}
1265+
1266+
const buffer = array_buffer orelse return null;
1267+
1268+
const backing_store = v8.BackingStore.sharedPtrGet(&buffer.getBackingStore());
1269+
const data = backing_store.getData();
1270+
const byte_len = backing_store.getByteLength();
1271+
1272+
switch (T) {
1273+
u8 => {
1274+
// need this sentinel check to keep the compiler happy
1275+
if (force_u8 or js_value.isUint8Array() or js_value.isUint8ClampedArray()) {
1276+
if (byte_len == 0) return &[_]u8{};
1277+
const arr_ptr = @as([*]u8, @ptrCast(@alignCast(data)));
1278+
return arr_ptr[0..byte_len];
1279+
}
1280+
},
1281+
i8 => {
1282+
if (js_value.isInt8Array()) {
1283+
if (byte_len == 0) return &[_]i8{};
1284+
const arr_ptr = @as([*]i8, @ptrCast(@alignCast(data)));
1285+
return arr_ptr[0..byte_len];
1286+
}
1287+
},
1288+
u16 => {
1289+
if (js_value.isUint16Array()) {
1290+
if (byte_len == 0) return &[_]u16{};
1291+
const arr_ptr = @as([*]u16, @ptrCast(@alignCast(data)));
1292+
return arr_ptr[0 .. byte_len / 2];
1293+
}
1294+
},
1295+
i16 => {
1296+
if (js_value.isInt16Array()) {
1297+
if (byte_len == 0) return &[_]i16{};
1298+
const arr_ptr = @as([*]i16, @ptrCast(@alignCast(data)));
1299+
return arr_ptr[0 .. byte_len / 2];
1300+
}
1301+
},
1302+
u32 => {
1303+
if (js_value.isUint32Array()) {
1304+
if (byte_len == 0) return &[_]u32{};
1305+
const arr_ptr = @as([*]u32, @ptrCast(@alignCast(data)));
1306+
return arr_ptr[0 .. byte_len / 4];
1307+
}
1308+
},
1309+
i32 => {
1310+
if (js_value.isInt32Array()) {
1311+
if (byte_len == 0) return &[_]i32{};
1312+
const arr_ptr = @as([*]i32, @ptrCast(@alignCast(data)));
1313+
return arr_ptr[0 .. byte_len / 4];
1314+
}
1315+
},
1316+
u64 => {
1317+
if (js_value.isBigUint64Array()) {
1318+
if (byte_len == 0) return &[_]u64{};
1319+
const arr_ptr = @as([*]u64, @ptrCast(@alignCast(data)));
1320+
return arr_ptr[0 .. byte_len / 8];
1321+
}
1322+
},
1323+
i64 => {
1324+
if (js_value.isBigInt64Array()) {
1325+
if (byte_len == 0) return &[_]i64{};
1326+
const arr_ptr = @as([*]i64, @ptrCast(@alignCast(data)));
1327+
return arr_ptr[0 .. byte_len / 8];
1328+
}
1329+
},
1330+
else => {},
1331+
}
1332+
return error.InvalidArgument;
1333+
}
1334+
13231335
fn createFunction(self: *JsContext, js_value: v8.Value) !Function {
13241336
// caller should have made sure this was a function
13251337
std.debug.assert(js_value.isFunction());
@@ -2387,6 +2399,10 @@ pub fn Env(comptime State: type, comptime WebApis: type) type {
23872399
const _TYPED_ARRAY_ID_KLUDGE = true;
23882400

23892401
values: []const T,
2402+
2403+
pub fn dupe(self: TypedArray(T), allocator: Allocator) !TypedArray(T) {
2404+
return .{ .values = try allocator.dupe(T, self.values) };
2405+
}
23902406
};
23912407
}
23922408

src/tests/streams/readable_stream.html

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
<!DOCTYPE html>
12
<script src="../testing.js"></script>
23

34
<script id=readable_stream>
@@ -17,6 +18,22 @@
1718
});
1819
</script>
1920

21+
<script id=readable_stream_binary>
22+
const input = new TextEncoder().encode('over 9000!');
23+
const binStream = new ReadableStream({
24+
start(controller) {
25+
controller.enqueue(input);
26+
controller.enqueue("world");
27+
controller.close();
28+
}
29+
});
30+
31+
testing.async(binStream.getReader().read(), (data) => {
32+
testing.expectEqual(input, data.value);
33+
testing.expectEqual(false, data.done);
34+
});
35+
</script>
36+
2037
<script id=readable_stream_close>
2138
var closeResult;
2239

0 commit comments

Comments
 (0)