diff --git a/src/browser/streams/ReadableStream.zig b/src/browser/streams/ReadableStream.zig index 4b27ba3c..216152da 100644 --- a/src/browser/streams/ReadableStream.zig +++ b/src/browser/streams/ReadableStream.zig @@ -173,166 +173,5 @@ pub fn _getReader(self: *ReadableStream, _options: ?GetReaderOptions) !ReadableS const testing = @import("../../testing.zig"); test "streams: ReadableStream" { - var runner = try testing.jsRunner(testing.tracking_allocator, .{ .url = "https://lightpanda.io" }); - defer runner.deinit(); - - try runner.testCases(&.{ - .{ "var readResult;", "undefined" }, - .{ - \\ const stream = new ReadableStream({ - \\ start(controller) { - \\ controller.enqueue("hello"); - \\ controller.enqueue("world"); - \\ controller.close(); - \\ } - \\ }); - , - undefined, - }, - .{ - \\ const reader = stream.getReader(); - \\ (async function () { readResult = await reader.read() }()); - \\ false; - , - "false", - }, - .{ "reader", "[object ReadableStreamDefaultReader]" }, - .{ "readResult.value", "hello" }, - .{ "readResult.done", "false" }, - }, .{}); -} - -test "streams: ReadableStream cancel and close" { - var runner = try testing.jsRunner(testing.tracking_allocator, .{ .url = "https://lightpanda.io" }); - defer runner.deinit(); - try runner.testCases(&.{ - .{ "var readResult; var cancelResult; var closeResult;", "undefined" }, - - // Test 1: Stream with controller.close() - .{ - \\ const stream1 = new ReadableStream({ - \\ start(controller) { - \\ controller.enqueue("first"); - \\ controller.enqueue("second"); - \\ controller.close(); - \\ } - \\ }); - , - undefined, - }, - .{ "const reader1 = stream1.getReader();", undefined }, - .{ - \\ (async function () { - \\ readResult = await reader1.read(); - \\ }()); - \\ false; - , - "false", - }, - .{ "readResult.value", "first" }, - .{ "readResult.done", "false" }, - - // Read second chunk - .{ - \\ (async function () { - \\ readResult = await reader1.read(); - \\ }()); - \\ false; - , - "false", - }, - .{ "readResult.value", "second" }, - .{ "readResult.done", "false" }, - - // Read after close - should get done: true - .{ - \\ (async function () { - \\ readResult = await reader1.read(); - \\ }()); - \\ false; - , - "false", - }, - .{ "readResult.value", "undefined" }, - .{ "readResult.done", "true" }, - - // Test 2: Stream with reader.cancel() - .{ - \\ const stream2 = new ReadableStream({ - \\ start(controller) { - \\ controller.enqueue("data1"); - \\ controller.enqueue("data2"); - \\ controller.enqueue("data3"); - \\ }, - \\ cancel(reason) { - \\ closeResult = `Stream cancelled: ${reason}`; - \\ } - \\ }); - , - undefined, - }, - .{ "const reader2 = stream2.getReader();", undefined }, - - // Read one chunk before canceling - .{ - \\ (async function () { - \\ readResult = await reader2.read(); - \\ }()); - \\ false; - , - "false", - }, - .{ "readResult.value", "data1" }, - .{ "readResult.done", "false" }, - - // Cancel the stream - .{ - \\ (async function () { - \\ cancelResult = await reader2.cancel("user requested"); - \\ }()); - \\ false; - , - "false", - }, - .{ "cancelResult", "undefined" }, - .{ "closeResult", "Stream cancelled: user requested" }, - - // Try to read after cancel - should throw or return done - .{ - \\ try { - \\ (async function () { - \\ readResult = await reader2.read(); - \\ }()); - \\ } catch(e) { - \\ readResult = { error: e.name }; - \\ } - \\ false; - , - "false", - }, - - // Test 3: Cancel without reason - .{ - \\ const stream3 = new ReadableStream({ - \\ start(controller) { - \\ controller.enqueue("test"); - \\ }, - \\ cancel(reason) { - \\ closeResult = reason === undefined ? "no reason" : reason; - \\ } - \\ }); - , - undefined, - }, - .{ "const reader3 = stream3.getReader();", undefined }, - .{ - \\ (async function () { - \\ await reader3.cancel(); - \\ }()); - \\ false; - , - "false", - }, - .{ "closeResult", "no reason" }, - }, .{}); + try testing.htmlRunner("streams/readable_stream.html"); } diff --git a/src/browser/streams/ReadableStreamDefaultController.zig b/src/browser/streams/ReadableStreamDefaultController.zig index 84bb5073..c83a13a5 100644 --- a/src/browser/streams/ReadableStreamDefaultController.zig +++ b/src/browser/streams/ReadableStreamDefaultController.zig @@ -59,13 +59,15 @@ pub fn _enqueue(self: *ReadableStreamDefaultController, chunk: []const u8, page: return error.TypeError; } + const duped_chunk = try page.arena.dupe(u8, chunk); + if (self.stream.reader_resolver) |*rr| { defer rr.deinit(); - try rr.resolve(ReadableStreamReadResult{ .value = .{ .data = chunk }, .done = false }); + try rr.resolve(ReadableStreamReadResult{ .value = .{ .data = duped_chunk }, .done = false }); self.stream.reader_resolver = null; } - try self.stream.queue.append(page.arena, chunk); + try self.stream.queue.append(page.arena, duped_chunk); try self.stream.pullIf(); } diff --git a/src/browser/streams/ReadableStreamDefaultReader.zig b/src/browser/streams/ReadableStreamDefaultReader.zig index 5ad863ec..0708eff2 100644 --- a/src/browser/streams/ReadableStreamDefaultReader.zig +++ b/src/browser/streams/ReadableStreamDefaultReader.zig @@ -48,6 +48,7 @@ pub fn _read(self: *const ReadableStreamDefaultReader, page: *Page) !Env.Promise if (stream.queue.items.len > 0) { const data = self.stream.queue.orderedRemove(0); const resolver = page.main_context.createPromiseResolver(); + try resolver.resolve(ReadableStreamReadResult{ .value = .{ .data = data }, .done = false }); try self.stream.pullIf(); return resolver.promise(); diff --git a/src/tests/streams/readable_stream.html b/src/tests/streams/readable_stream.html new file mode 100644 index 00000000..a8d71d66 --- /dev/null +++ b/src/tests/streams/readable_stream.html @@ -0,0 +1,117 @@ + + + + + + + + + + +