diff --git a/src/browser/fetch/Request.zig b/src/browser/fetch/Request.zig
index 0674bcea6..5881b427f 100644
--- a/src/browser/fetch/Request.zig
+++ b/src/browser/fetch/Request.zig
@@ -181,7 +181,7 @@ pub fn constructor(input: RequestInput, _options: ?RequestInit, page: *Page) !Re
pub fn get_body(self: *const Request, page: *Page) !?*ReadableStream {
if (self.body) |body| {
const stream = try ReadableStream.constructor(null, null, page);
- try stream.queue.append(page.arena, body);
+ try stream.queue.append(page.arena, .{ .string = body });
return stream;
} else return null;
}
diff --git a/src/browser/fetch/Response.zig b/src/browser/fetch/Response.zig
index cf67b462e..d9530fb14 100644
--- a/src/browser/fetch/Response.zig
+++ b/src/browser/fetch/Response.zig
@@ -109,7 +109,7 @@ pub fn constructor(_input: ?ResponseBody, _options: ?ResponseOptions, page: *Pag
pub fn get_body(self: *const Response, page: *Page) !*ReadableStream {
const stream = try ReadableStream.constructor(null, null, page);
if (self.body) |body| {
- try stream.queue.append(page.arena, body);
+ try stream.queue.append(page.arena, .{ .string = body });
}
return stream;
}
diff --git a/src/browser/streams/ReadableStream.zig b/src/browser/streams/ReadableStream.zig
index 762050a2e..6cd676550 100644
--- a/src/browser/streams/ReadableStream.zig
+++ b/src/browser/streams/ReadableStream.zig
@@ -19,8 +19,9 @@
const std = @import("std");
const log = @import("../../log.zig");
-const Page = @import("../page.zig").Page;
+const Allocator = std.mem.Allocator;
const Env = @import("../env.zig").Env;
+const Page = @import("../page.zig").Page;
const ReadableStream = @This();
const ReadableStreamDefaultReader = @import("ReadableStreamDefaultReader.zig");
@@ -45,16 +46,42 @@ cancel_fn: ?Env.Function = null,
pull_fn: ?Env.Function = null,
strategy: QueueingStrategy,
-queue: std.ArrayListUnmanaged([]const u8) = .empty,
+queue: std.ArrayListUnmanaged(Chunk) = .empty,
+
+pub const Chunk = union(enum) {
+ // the order matters, sorry.
+ uint8array: Env.TypedArray(u8),
+ string: []const u8,
+
+ pub fn dupe(self: Chunk, allocator: Allocator) !Chunk {
+ return switch (self) {
+ .string => |str| .{ .string = try allocator.dupe(u8, str) },
+ .uint8array => |arr| .{ .uint8array = try arr.dupe(allocator) },
+ };
+ }
+};
pub const ReadableStreamReadResult = struct {
- const ValueUnion =
- union(enum) { data: []const u8, empty: void };
-
- value: ValueUnion,
done: bool,
+ value: Value = .empty,
+
+ const Value = union(enum) {
+ empty,
+ data: Chunk,
+ };
+
+ pub fn init(chunk: Chunk, done: bool) ReadableStreamReadResult {
+ if (done) {
+ return .{ .done = true, .value = .empty };
+ }
+
+ return .{
+ .done = false,
+ .value = .{ .data = chunk },
+ };
+ }
- pub fn get_value(self: *const ReadableStreamReadResult) ValueUnion {
+ pub fn get_value(self: *const ReadableStreamReadResult) Value {
return self.value;
}
diff --git a/src/browser/streams/ReadableStreamDefaultController.zig b/src/browser/streams/ReadableStreamDefaultController.zig
index 59ee5fb9e..a6a6345d1 100644
--- a/src/browser/streams/ReadableStreamDefaultController.zig
+++ b/src/browser/streams/ReadableStreamDefaultController.zig
@@ -51,17 +51,17 @@ pub fn _close(self: *ReadableStreamDefaultController, _reason: ?[]const u8, page
// to discard, must use cancel.
}
-pub fn _enqueue(self: *ReadableStreamDefaultController, chunk: []const u8, page: *Page) !void {
+pub fn _enqueue(self: *ReadableStreamDefaultController, chunk: ReadableStream.Chunk, page: *Page) !void {
const stream = self.stream;
if (stream.state != .readable) {
return error.TypeError;
}
- const duped_chunk = try page.arena.dupe(u8, chunk);
+ const duped_chunk = try chunk.dupe(page.arena);
if (self.stream.reader_resolver) |*rr| {
- try rr.resolve(ReadableStreamReadResult{ .value = .{ .data = duped_chunk }, .done = false });
+ try rr.resolve(ReadableStreamReadResult.init(duped_chunk, false));
self.stream.reader_resolver = null;
}
diff --git a/src/browser/streams/ReadableStreamDefaultReader.zig b/src/browser/streams/ReadableStreamDefaultReader.zig
index c64c2d8d0..9d1d7d478 100644
--- a/src/browser/streams/ReadableStreamDefaultReader.zig
+++ b/src/browser/streams/ReadableStreamDefaultReader.zig
@@ -49,7 +49,7 @@ pub fn _read(self: *const ReadableStreamDefaultReader, page: *Page) !Env.Promise
const data = self.stream.queue.orderedRemove(0);
const resolver = page.main_context.createPromiseResolver();
- try resolver.resolve(ReadableStreamReadResult{ .value = .{ .data = data }, .done = false });
+ try resolver.resolve(ReadableStreamReadResult.init(data, false));
try self.stream.pullIf();
return resolver.promise();
} else {
@@ -67,9 +67,9 @@ pub fn _read(self: *const ReadableStreamDefaultReader, page: *Page) !Env.Promise
if (stream.queue.items.len > 0) {
const data = self.stream.queue.orderedRemove(0);
- try resolver.resolve(ReadableStreamReadResult{ .value = .{ .data = data }, .done = false });
+ try resolver.resolve(ReadableStreamReadResult.init(data, false));
} else {
- try resolver.resolve(ReadableStreamReadResult{ .value = .empty, .done = true });
+ try resolver.resolve(ReadableStreamReadResult{ .done = true });
}
return resolver.promise();
diff --git a/src/runtime/js.zig b/src/runtime/js.zig
index 62f82886d..7d4c79742 100644
--- a/src/runtime/js.zig
+++ b/src/runtime/js.zig
@@ -1094,88 +1094,10 @@ pub fn Env(comptime State: type, comptime WebApis: type) type {
}
},
.slice => {
- var force_u8 = false;
- var array_buffer: ?v8.ArrayBuffer = null;
- if (js_value.isTypedArray()) {
- const buffer_view = js_value.castTo(v8.ArrayBufferView);
- array_buffer = buffer_view.getBuffer();
- } else if (js_value.isArrayBufferView()) {
- force_u8 = true;
- const buffer_view = js_value.castTo(v8.ArrayBufferView);
- array_buffer = buffer_view.getBuffer();
- } else if (js_value.isArrayBuffer()) {
- force_u8 = true;
- array_buffer = js_value.castTo(v8.ArrayBuffer);
- }
-
- if (array_buffer) |buffer| {
- const backing_store = v8.BackingStore.sharedPtrGet(&buffer.getBackingStore());
- const data = backing_store.getData();
- const byte_len = backing_store.getByteLength();
-
- switch (ptr.child) {
- u8 => {
- // need this sentinel check to keep the compiler happy
- if (ptr.sentinel() == null) {
- if (force_u8 or js_value.isUint8Array() or js_value.isUint8ClampedArray()) {
- if (byte_len == 0) return &[_]u8{};
- const arr_ptr = @as([*]u8, @ptrCast(@alignCast(data)));
- return arr_ptr[0..byte_len];
- }
- }
- },
- i8 => {
- if (js_value.isInt8Array()) {
- if (byte_len == 0) return &[_]i8{};
- const arr_ptr = @as([*]i8, @ptrCast(@alignCast(data)));
- return arr_ptr[0..byte_len];
- }
- },
- u16 => {
- if (js_value.isUint16Array()) {
- if (byte_len == 0) return &[_]u16{};
- const arr_ptr = @as([*]u16, @ptrCast(@alignCast(data)));
- return arr_ptr[0 .. byte_len / 2];
- }
- },
- i16 => {
- if (js_value.isInt16Array()) {
- if (byte_len == 0) return &[_]i16{};
- const arr_ptr = @as([*]i16, @ptrCast(@alignCast(data)));
- return arr_ptr[0 .. byte_len / 2];
- }
- },
- u32 => {
- if (js_value.isUint32Array()) {
- if (byte_len == 0) return &[_]u32{};
- const arr_ptr = @as([*]u32, @ptrCast(@alignCast(data)));
- return arr_ptr[0 .. byte_len / 4];
- }
- },
- i32 => {
- if (js_value.isInt32Array()) {
- if (byte_len == 0) return &[_]i32{};
- const arr_ptr = @as([*]i32, @ptrCast(@alignCast(data)));
- return arr_ptr[0 .. byte_len / 4];
- }
- },
- u64 => {
- if (js_value.isBigUint64Array()) {
- if (byte_len == 0) return &[_]u64{};
- const arr_ptr = @as([*]u64, @ptrCast(@alignCast(data)));
- return arr_ptr[0 .. byte_len / 8];
- }
- },
- i64 => {
- if (js_value.isBigInt64Array()) {
- if (byte_len == 0) return &[_]i64{};
- const arr_ptr = @as([*]i64, @ptrCast(@alignCast(data)));
- return arr_ptr[0 .. byte_len / 8];
- }
- },
- else => {},
+ if (ptr.sentinel() == null) {
+ if (try self.jsValueToTypedArray(ptr.child, js_value)) |value| {
+ return value;
}
- return error.InvalidArgument;
}
if (ptr.child == u8) {
@@ -1282,6 +1204,12 @@ pub fn Env(comptime State: type, comptime WebApis: type) type {
return try self.createFunction(js_value);
}
+ if (@hasDecl(T, "_TYPED_ARRAY_ID_KLUDGE")) {
+ const VT = @typeInfo(std.meta.fieldInfo(T, .values).type).pointer.child;
+ const arr = (try self.jsValueToTypedArray(VT, js_value)) orelse return null;
+ return .{ .values = arr };
+ }
+
if (T == String) {
return .{ .string = try valueToString(self.context_arena, js_value, self.isolate, self.v8_context) };
}
@@ -1320,6 +1248,90 @@ pub fn Env(comptime State: type, comptime WebApis: type) type {
return value;
}
+ fn jsValueToTypedArray(_: *JsContext, comptime T: type, js_value: v8.Value) !?[]T {
+ var force_u8 = false;
+ var array_buffer: ?v8.ArrayBuffer = null;
+ if (js_value.isTypedArray()) {
+ const buffer_view = js_value.castTo(v8.ArrayBufferView);
+ array_buffer = buffer_view.getBuffer();
+ } else if (js_value.isArrayBufferView()) {
+ force_u8 = true;
+ const buffer_view = js_value.castTo(v8.ArrayBufferView);
+ array_buffer = buffer_view.getBuffer();
+ } else if (js_value.isArrayBuffer()) {
+ force_u8 = true;
+ array_buffer = js_value.castTo(v8.ArrayBuffer);
+ }
+
+ const buffer = array_buffer orelse return null;
+
+ const backing_store = v8.BackingStore.sharedPtrGet(&buffer.getBackingStore());
+ const data = backing_store.getData();
+ const byte_len = backing_store.getByteLength();
+
+ switch (T) {
+ u8 => {
+ // need this sentinel check to keep the compiler happy
+ if (force_u8 or js_value.isUint8Array() or js_value.isUint8ClampedArray()) {
+ if (byte_len == 0) return &[_]u8{};
+ const arr_ptr = @as([*]u8, @ptrCast(@alignCast(data)));
+ return arr_ptr[0..byte_len];
+ }
+ },
+ i8 => {
+ if (js_value.isInt8Array()) {
+ if (byte_len == 0) return &[_]i8{};
+ const arr_ptr = @as([*]i8, @ptrCast(@alignCast(data)));
+ return arr_ptr[0..byte_len];
+ }
+ },
+ u16 => {
+ if (js_value.isUint16Array()) {
+ if (byte_len == 0) return &[_]u16{};
+ const arr_ptr = @as([*]u16, @ptrCast(@alignCast(data)));
+ return arr_ptr[0 .. byte_len / 2];
+ }
+ },
+ i16 => {
+ if (js_value.isInt16Array()) {
+ if (byte_len == 0) return &[_]i16{};
+ const arr_ptr = @as([*]i16, @ptrCast(@alignCast(data)));
+ return arr_ptr[0 .. byte_len / 2];
+ }
+ },
+ u32 => {
+ if (js_value.isUint32Array()) {
+ if (byte_len == 0) return &[_]u32{};
+ const arr_ptr = @as([*]u32, @ptrCast(@alignCast(data)));
+ return arr_ptr[0 .. byte_len / 4];
+ }
+ },
+ i32 => {
+ if (js_value.isInt32Array()) {
+ if (byte_len == 0) return &[_]i32{};
+ const arr_ptr = @as([*]i32, @ptrCast(@alignCast(data)));
+ return arr_ptr[0 .. byte_len / 4];
+ }
+ },
+ u64 => {
+ if (js_value.isBigUint64Array()) {
+ if (byte_len == 0) return &[_]u64{};
+ const arr_ptr = @as([*]u64, @ptrCast(@alignCast(data)));
+ return arr_ptr[0 .. byte_len / 8];
+ }
+ },
+ i64 => {
+ if (js_value.isBigInt64Array()) {
+ if (byte_len == 0) return &[_]i64{};
+ const arr_ptr = @as([*]i64, @ptrCast(@alignCast(data)));
+ return arr_ptr[0 .. byte_len / 8];
+ }
+ },
+ else => {},
+ }
+ return error.InvalidArgument;
+ }
+
fn createFunction(self: *JsContext, js_value: v8.Value) !Function {
// caller should have made sure this was a function
std.debug.assert(js_value.isFunction());
@@ -2387,6 +2399,10 @@ pub fn Env(comptime State: type, comptime WebApis: type) type {
const _TYPED_ARRAY_ID_KLUDGE = true;
values: []const T,
+
+ pub fn dupe(self: TypedArray(T), allocator: Allocator) !TypedArray(T) {
+ return .{ .values = try allocator.dupe(T, self.values) };
+ }
};
}
diff --git a/src/tests/streams/readable_stream.html b/src/tests/streams/readable_stream.html
index a8d71d666..a8339cc50 100644
--- a/src/tests/streams/readable_stream.html
+++ b/src/tests/streams/readable_stream.html
@@ -1,3 +1,4 @@
+
+
+