Files
browser/src/browser/webapi/streams/ReadableStream.zig
2025-12-01 15:16:33 +08:00

141 lines
4.3 KiB
Zig

// Copyright (C) 2023-2025 Lightpanda (Selecy SAS)
//
// Francis Bouvier <francis@lightpanda.io>
// Pierre Tachoire <pierre@lightpanda.io>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
const std = @import("std");
const js = @import("../../js/js.zig");
const Page = @import("../../Page.zig");
const ReadableStreamDefaultReader = @import("ReadableStreamDefaultReader.zig");
const ReadableStreamDefaultController = @import("ReadableStreamDefaultController.zig");
pub fn registerTypes() []const type {
return &.{
ReadableStream,
AsyncIterator,
};
}
const ReadableStream = @This();
pub const State = enum {
readable,
closed,
errored,
};
_page: *Page,
_state: State,
_reader: ?*ReadableStreamDefaultReader,
_controller: *ReadableStreamDefaultController,
_stored_error: ?[]const u8,
pub fn init(page: *Page) !*ReadableStream {
const stream = try page._factory.create(ReadableStream{
._page = page,
._state = .readable,
._reader = null,
._controller = undefined,
._stored_error = null,
});
stream._controller = try ReadableStreamDefaultController.init(stream, page);
return stream;
}
pub fn initWithData(data: []const u8, page: *Page) !*ReadableStream {
const stream = try init(page);
// For Phase 1: immediately enqueue all data and close
try stream._controller.enqueue(data);
try stream._controller.close();
return stream;
}
pub fn getReader(self: *ReadableStream, page: *Page) !*ReadableStreamDefaultReader {
if (self._reader != null) {
return error.ReaderLocked;
}
const reader = try ReadableStreamDefaultReader.init(self, page);
self._reader = reader;
return reader;
}
pub fn releaseReader(self: *ReadableStream) void {
self._reader = null;
}
pub fn getAsyncIterator(self: *ReadableStream, page: *Page) !*AsyncIterator {
return AsyncIterator.init(self, page);
}
pub const JsApi = struct {
pub const bridge = js.Bridge(ReadableStream);
pub const Meta = struct {
pub const name = "ReadableStream";
pub const prototype_chain = bridge.prototypeChain();
pub var class_id: bridge.ClassId = undefined;
};
pub const constructor = bridge.constructor(ReadableStream.init, .{});
pub const getReader = bridge.function(ReadableStream.getReader, .{});
pub const symbol_async_iterator = bridge.iterator(ReadableStream.getAsyncIterator, .{ .async = true });
};
pub const AsyncIterator = struct {
_stream: *ReadableStream,
_reader: *ReadableStreamDefaultReader,
pub fn init(stream: *ReadableStream, page: *Page) !*AsyncIterator {
const reader = try stream.getReader(page);
return page._factory.create(AsyncIterator{
._reader = reader,
._stream = stream,
});
}
pub fn next(self: *AsyncIterator, page: *Page) !js.Promise {
return self._reader.read(page);
}
pub fn @"return"(self: *AsyncIterator, page: *Page) !js.Promise {
self._reader.releaseLock();
return page.js.resolvePromise(.{ .done = true, .value = null });
}
pub const JsApi = struct {
pub const bridge = js.Bridge(ReadableStream.AsyncIterator);
pub const Meta = struct {
pub const name = "ReadableStreamAsyncIterator";
pub const prototype_chain = bridge.prototypeChain();
pub var class_id: bridge.ClassId = undefined;
};
pub const next = bridge.function(ReadableStream.AsyncIterator.next, .{});
pub const @"return" = bridge.function(ReadableStream.AsyncIterator.@"return", .{});
};
};
const testing = @import("../../../testing.zig");
test "WebApi: ReadableStream" {
try testing.htmlRunner("streams/readable_stream.html", .{});
}