From c553a2cd386d6fadee9ae64caf6db9eccf0a1e29 Mon Sep 17 00:00:00 2001 From: Muki Kiboigo Date: Tue, 16 Sep 2025 12:09:54 -0700 Subject: [PATCH] use Env.PersistentPromiseResolver --- src/browser/fetch/fetch.zig | 44 ++++++--------- src/browser/streams/ReadableStream.zig | 27 +++------- .../ReadableStreamDefaultController.zig | 47 ++++------------ .../streams/ReadableStreamDefaultReader.zig | 53 +++++++------------ src/runtime/js.zig | 44 +++++++++++++++ 5 files changed, 97 insertions(+), 118 deletions(-) diff --git a/src/browser/fetch/fetch.zig b/src/browser/fetch/fetch.zig index 036dc9ee..0b80d5f7 100644 --- a/src/browser/fetch/fetch.zig +++ b/src/browser/fetch/fetch.zig @@ -19,7 +19,6 @@ const std = @import("std"); const log = @import("../../log.zig"); -const v8 = @import("v8"); const Env = @import("../env.zig").Env; const Page = @import("../page.zig").Page; @@ -46,7 +45,7 @@ pub const Interfaces = .{ pub const FetchContext = struct { arena: std.mem.Allocator, js_ctx: *Env.JsContext, - promise_resolver: v8.Persistent(v8.PromiseResolver), + promise_resolver: Env.PersistentPromiseResolver, method: Http.Method, url: []const u8, @@ -82,13 +81,9 @@ pub const FetchContext = struct { pub fn destructor(self: *FetchContext) void { if (self.transfer) |_| { - const resolver = Env.PromiseResolver{ - .js_context = self.js_ctx, - .resolver = self.promise_resolver.castToPromiseResolver(), - }; - - resolver.reject("TypeError") catch unreachable; + self.promise_resolver.reject("TypeError") catch unreachable; self.promise_resolver.deinit(); + self.transfer = null; } } }; @@ -99,10 +94,7 @@ pub fn fetch(input: RequestInput, options: ?RequestInit, page: *Page) !Env.Promi const req = try Request.constructor(input, options, page); - const resolver = Env.PromiseResolver{ - .js_context = page.main_context, - .resolver = v8.PromiseResolver.init(page.main_context.v8_context), - }; + const resolver = page.main_context.createPersistentPromiseResolver(); var headers = try Http.Headers.init(); @@ -125,10 +117,7 @@ pub fn fetch(input: RequestInput, options: ?RequestInit, page: *Page) !Env.Promi fetch_ctx.* = .{ .arena = arena, .js_ctx = page.main_context, - .promise_resolver = v8.Persistent(v8.PromiseResolver).init( - page.main_context.isolate, - resolver.resolver, - ), + .promise_resolver = resolver, .method = req.method, .url = req.url, }; @@ -205,24 +194,23 @@ pub fn fetch(input: RequestInput, options: ?RequestInit, page: *Page) !Env.Promi }); const response = try self.toResponse(); - const promise_resolver: Env.PromiseResolver = .{ - .js_context = self.js_ctx, - .resolver = self.promise_resolver.castToPromiseResolver(), - }; - - try promise_resolver.resolve(response); + try self.promise_resolver.resolve(response); } }.doneCallback, .error_callback = struct { fn errorCallback(ctx: *anyopaque, err: anyerror) void { const self: *FetchContext = @ptrCast(@alignCast(ctx)); - self.transfer = null; + if (self.transfer != null) { + self.transfer = null; - log.err(.http, "error", .{ - .url = self.url, - .err = err, - .source = "fetch error", - }); + log.err(.http, "error", .{ + .url = self.url, + .err = err, + .source = "fetch error", + }); + + self.promise_resolver.reject(@errorName(err)) catch unreachable; + } } }.errorCallback, }); diff --git a/src/browser/streams/ReadableStream.zig b/src/browser/streams/ReadableStream.zig index 8660123b..4b27ba3c 100644 --- a/src/browser/streams/ReadableStream.zig +++ b/src/browser/streams/ReadableStream.zig @@ -19,7 +19,6 @@ const std = @import("std"); const log = @import("../../log.zig"); -const v8 = @import("v8"); const Page = @import("../page.zig").Page; const Env = @import("../env.zig").Env; @@ -35,9 +34,9 @@ const State = union(enum) { }; // This promise resolves when a stream is canceled. -cancel_resolver: v8.Persistent(v8.PromiseResolver), -closed_resolver: v8.Persistent(v8.PromiseResolver), -reader_resolver: ?v8.Persistent(v8.PromiseResolver) = null, +cancel_resolver: Env.PersistentPromiseResolver, +closed_resolver: Env.PersistentPromiseResolver, +reader_resolver: ?Env.PersistentPromiseResolver = null, locked: bool = false, state: State = .readable, @@ -79,15 +78,8 @@ const QueueingStrategy = struct { pub fn constructor(underlying: ?UnderlyingSource, _strategy: ?QueueingStrategy, page: *Page) !*ReadableStream { const strategy: QueueingStrategy = _strategy orelse .{}; - const cancel_resolver = v8.Persistent(v8.PromiseResolver).init( - page.main_context.isolate, - v8.PromiseResolver.init(page.main_context.v8_context), - ); - - const closed_resolver = v8.Persistent(v8.PromiseResolver).init( - page.main_context.isolate, - v8.PromiseResolver.init(page.main_context.v8_context), - ); + const cancel_resolver = page.main_context.createPersistentPromiseResolver(); + const closed_resolver = page.main_context.createPersistentPromiseResolver(); const stream = try page.arena.create(ReadableStream); stream.* = ReadableStream{ .cancel_resolver = cancel_resolver, .closed_resolver = closed_resolver, .strategy = strategy }; @@ -131,11 +123,6 @@ pub fn _cancel(self: *ReadableStream, reason: ?[]const u8, page: *Page) !Env.Pro return error.TypeError; } - const resolver = Env.PromiseResolver{ - .js_context = page.main_context, - .resolver = self.cancel_resolver.castToPromiseResolver(), - }; - self.state = .{ .cancelled = if (reason) |r| try page.arena.dupe(u8, r) else null }; // Call cancel callback. @@ -147,8 +134,8 @@ pub fn _cancel(self: *ReadableStream, reason: ?[]const u8, page: *Page) !Env.Pro } } - try resolver.resolve({}); - return resolver.promise(); + try self.cancel_resolver.resolve({}); + return self.cancel_resolver.promise(); } pub fn pullIf(self: *ReadableStream) !void { diff --git a/src/browser/streams/ReadableStreamDefaultController.zig b/src/browser/streams/ReadableStreamDefaultController.zig index af5057a7..84bb5073 100644 --- a/src/browser/streams/ReadableStreamDefaultController.zig +++ b/src/browser/streams/ReadableStreamDefaultController.zig @@ -21,7 +21,6 @@ const log = @import("../../log.zig"); const Page = @import("../page.zig").Page; const Env = @import("../env.zig").Env; -const v8 = @import("v8"); const ReadableStream = @import("./ReadableStream.zig"); const ReadableStreamReadResult = @import("./ReadableStream.zig").ReadableStreamReadResult; @@ -40,23 +39,14 @@ pub fn _close(self: *ReadableStreamDefaultController, _reason: ?[]const u8, page self.stream.state = .{ .closed = reason }; // Resolve the Reader Promise - if (self.stream.reader_resolver) |rr| { - const resolver = Env.PromiseResolver{ - .js_context = page.main_context, - .resolver = rr.castToPromiseResolver(), - }; - - try resolver.resolve(ReadableStreamReadResult{ .value = .empty, .done = true }); + if (self.stream.reader_resolver) |*rr| { + defer rr.deinit(); + try rr.resolve(ReadableStreamReadResult{ .value = .empty, .done = true }); self.stream.reader_resolver = null; } // Resolve the Closed promise. - const closed_resolver = Env.PromiseResolver{ - .js_context = page.main_context, - .resolver = self.stream.closed_resolver.castToPromiseResolver(), - }; - - try closed_resolver.resolve({}); + try self.stream.closed_resolver.resolve({}); // close just sets as closed meaning it wont READ any more but anything in the queue is fine to read. // to discard, must use cancel. @@ -69,37 +59,22 @@ pub fn _enqueue(self: *ReadableStreamDefaultController, chunk: []const u8, page: return error.TypeError; } - if (self.stream.reader_resolver) |rr| { - const resolver = Env.PromiseResolver{ - .js_context = page.main_context, - .resolver = rr.castToPromiseResolver(), - }; - - try resolver.resolve(ReadableStreamReadResult{ .value = .{ .data = chunk }, .done = false }); + if (self.stream.reader_resolver) |*rr| { + defer rr.deinit(); + try rr.resolve(ReadableStreamReadResult{ .value = .{ .data = chunk }, .done = false }); self.stream.reader_resolver = null; - - // rr.setWeakFinalizer(@ptrCast(self.stream), struct { - // fn callback(info: ?*v8.c.WeakCallbackInfo) void { - // const inner_stream: *ReadableStream = @ptrCast(@alignCast(v8.c.v8__WeakCallbackInfo__GetParameter(info).?)); - // inner_stream.reader_resolver = null; - // } - // }.callback, .kParameter); } try self.stream.queue.append(page.arena, chunk); try self.stream.pullIf(); } -pub fn _error(self: *ReadableStreamDefaultController, err: Env.JsObject, page: *Page) !void { +pub fn _error(self: *ReadableStreamDefaultController, err: Env.JsObject) !void { self.stream.state = .{ .errored = err }; - if (self.stream.reader_resolver) |rr| { - const resolver = Env.PromiseResolver{ - .js_context = page.main_context, - .resolver = rr.castToPromiseResolver(), - }; - - try resolver.reject(err); + if (self.stream.reader_resolver) |*rr| { + defer rr.deinit(); + try rr.reject(err); self.stream.reader_resolver = null; } } diff --git a/src/browser/streams/ReadableStreamDefaultReader.zig b/src/browser/streams/ReadableStreamDefaultReader.zig index ac7bcf68..5ad863ec 100644 --- a/src/browser/streams/ReadableStreamDefaultReader.zig +++ b/src/browser/streams/ReadableStreamDefaultReader.zig @@ -18,8 +18,6 @@ const std = @import("std"); -const v8 = @import("v8"); - const log = @import("../../log.zig"); const Env = @import("../env.zig").Env; const Page = @import("../page.zig").Page; @@ -34,13 +32,8 @@ pub fn constructor(stream: *ReadableStream) ReadableStreamDefaultReader { return .{ .stream = stream }; } -pub fn get_closed(self: *const ReadableStreamDefaultReader, page: *Page) Env.Promise { - const resolver = Env.PromiseResolver{ - .js_context = page.main_context, - .resolver = self.stream.closed_resolver.castToPromiseResolver(), - }; - - return resolver.promise(); +pub fn get_closed(self: *const ReadableStreamDefaultReader) Env.Promise { + return self.stream.closed_resolver.promise(); } pub fn _cancel(self: *ReadableStreamDefaultReader, reason: ?[]const u8, page: *Page) !Env.Promise { @@ -50,61 +43,53 @@ pub fn _cancel(self: *ReadableStreamDefaultReader, reason: ?[]const u8, page: *P pub fn _read(self: *const ReadableStreamDefaultReader, page: *Page) !Env.Promise { const stream = self.stream; - const resolver = Env.PromiseResolver{ - .js_context = page.main_context, - .resolver = v8.PromiseResolver.init(page.main_context.v8_context), - }; - switch (stream.state) { .readable => { 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(); } else { if (self.stream.reader_resolver) |rr| { - const r_resolver = Env.PromiseResolver{ - .js_context = page.main_context, - .resolver = rr.castToPromiseResolver(), - }; - - return r_resolver.promise(); + return rr.promise(); } else { - const p_resolver = v8.Persistent(v8.PromiseResolver).init(page.main_context.isolate, resolver.resolver); - self.stream.reader_resolver = p_resolver; - return resolver.promise(); + const persistent_resolver = page.main_context.createPersistentPromiseResolver(); + self.stream.reader_resolver = persistent_resolver; + return persistent_resolver.promise(); } - - try self.stream.pullIf(); } }, .closed => |_| { + const resolver = page.main_context.createPromiseResolver(); + if (stream.queue.items.len > 0) { const data = self.stream.queue.orderedRemove(0); try resolver.resolve(ReadableStreamReadResult{ .value = .{ .data = data }, .done = false }); } else { try resolver.resolve(ReadableStreamReadResult{ .value = .empty, .done = true }); } + + return resolver.promise(); }, .cancelled => |_| { + const resolver = page.main_context.createPromiseResolver(); try resolver.resolve(ReadableStreamReadResult{ .value = .empty, .done = true }); + return resolver.promise(); }, .errored => |err| { + const resolver = page.main_context.createPromiseResolver(); try resolver.reject(err); + return resolver.promise(); }, } - - return resolver.promise(); } -pub fn _releaseLock(self: *const ReadableStreamDefaultReader, page: *Page) !void { +pub fn _releaseLock(self: *const ReadableStreamDefaultReader) !void { self.stream.locked = false; if (self.stream.reader_resolver) |rr| { - const resolver = Env.PromiseResolver{ - .js_context = page.main_context, - .resolver = rr.castToPromiseResolver(), - }; - - try resolver.reject("TypeError"); + try rr.reject("TypeError"); } } diff --git a/src/runtime/js.zig b/src/runtime/js.zig index 6eec9e61..f09c7cdd 100644 --- a/src/runtime/js.zig +++ b/src/runtime/js.zig @@ -1261,6 +1261,13 @@ pub fn Env(comptime State: type, comptime WebApis: type) type { }; } + pub fn createPersistentPromiseResolver(self: *JsContext) PersistentPromiseResolver { + return .{ + .js_context = self, + .resolver = v8.Persistent(v8.PromiseResolver).init(self.isolate, v8.PromiseResolver.init(self.v8_context)), + }; + } + // Probing is part of trying to map a JS value to a Zig union. There's // a lot of ambiguity in this process, in part because some JS values // can almost always be coerced. For example, anything can be coerced @@ -2231,6 +2238,43 @@ pub fn Env(comptime State: type, comptime WebApis: type) type { } }; + pub const PersistentPromiseResolver = struct { + js_context: *JsContext, + resolver: v8.Persistent(v8.PromiseResolver), + + pub fn deinit(self: *PersistentPromiseResolver) void { + self.resolver.deinit(); + } + + pub fn promise(self: PersistentPromiseResolver) Promise { + return .{ + .promise = self.resolver.castToPromiseResolver().getPromise(), + }; + } + + pub fn resolve(self: PersistentPromiseResolver, value: anytype) !void { + const js_context = self.js_context; + const js_value = try js_context.zigValueToJs(value); + + // resolver.resolve will return null if the promise isn't pending + const ok = self.resolver.castToPromiseResolver().resolve(js_context.v8_context, js_value) orelse return; + if (!ok) { + return error.FailedToResolvePromise; + } + } + + pub fn reject(self: PersistentPromiseResolver, value: anytype) !void { + const js_context = self.js_context; + const js_value = try js_context.zigValueToJs(value); + + // resolver.reject will return null if the promise isn't pending + const ok = self.resolver.castToPromiseResolver().reject(js_context.v8_context, js_value) orelse return; + if (!ok) { + return error.FailedToRejectPromise; + } + } + }; + pub const Promise = struct { promise: v8.Promise, };