Explicitly creates LocalScope in hard-to-reason callsites

In some cases, it's straightforward to know whether or not there's an implicit
local scope available. But both Navigation and ReadableStream* have more complex
flows. Both could, in theory, be initiated from non-V8 calls, so relying on
the implicit scope isn't safe.

This adds an explicit scope to most callbacks in Navigation and ReadbleStream*.
However, I still don't quite understand how / if these are being initiated from
Zig currently (I could see how they would be, in the future). Therefore, in
debug mode, this will still panic if there's no implicit scope, because I want
to understand what's going on.
This commit is contained in:
Karl Seguin
2026-01-23 09:58:36 +08:00
parent 065ca39d60
commit 70625c86c3
3 changed files with 73 additions and 11 deletions

View File

@@ -17,9 +17,13 @@
// along with this program. If not, see <https://www.gnu.org/licenses/>.
const std = @import("std");
const log = @import("../../../log.zig");
const js = @import("../../js/js.zig");
const Page = @import("../../Page.zig");
const IS_DEBUG = @import("builtin").mode == .Debug;
const EventTarget = @import("../EventTarget.zig");
const NavigationCurrentEntryChangeEvent = @import("../event/NavigationCurrentEntryChangeEvent.zig");
@@ -43,10 +47,23 @@ pub fn dispatch(self: *NavigationEventTarget, event_type: DispatchType, page: *P
};
};
if (comptime IS_DEBUG) {
if (page.js.local == null) {
log.fatal(.bug, "null context scope", .{.src = "NavigationEventTarget.dispatch", .url = page.url});
std.debug.assert(page.js.local != null);
}
}
const func = @field(self, field) orelse return;
var ls: js.Local.Scope = undefined;
page.js.localScope(&ls);
defer ls.deinit();
return page._event_manager.dispatchWithFunction(
self.asEventTarget(),
event,
page.js.toLocal(@field(self, field)),
ls.toLocal(func),
.{ .context = "Navigation" },
);
}

View File

@@ -17,12 +17,16 @@
// along with this program. If not, see <https://www.gnu.org/licenses/>.
const std = @import("std");
const js = @import("../../js/js.zig");
const log = @import("../../../log.zig");
const js = @import("../../js/js.zig");
const Page = @import("../../Page.zig");
const ReadableStreamDefaultReader = @import("ReadableStreamDefaultReader.zig");
const ReadableStreamDefaultController = @import("ReadableStreamDefaultController.zig");
const IS_DEBUG = @import("builtin").mode == .Debug;
pub fn registerTypes() []const type {
return &.{
ReadableStream,
@@ -137,12 +141,25 @@ pub fn callPullIfNeeded(self: *ReadableStream) !void {
self._pulling = true;
const pull_fn = self._page.js.toLocal(self._pull_fn) orelse return;
if (comptime IS_DEBUG) {
if (self._page.js.local == null) {
log.fatal(.bug, "null context scope", .{.src = "ReadableStream.callPullIfNeeded", .url = self._page.url});
std.debug.assert(self._page.js.local != null);
}
}
// Call the pull function
// Note: In a complete implementation, we'd handle the promise returned by pull
// and set _pulling = false when it resolves
try pull_fn.call(void, .{self._controller});
{
const func = self._pull_fn orelse return;
var ls: js.Local.Scope = undefined;
self._page.js.localScope(&ls);
defer ls.deinit();
// Call the pull function
// Note: In a complete implementation, we'd handle the promise returned by pull
// and set _pulling = false when it resolves
try ls.toLocal(func).call(void, .{self._controller});
}
self._pulling = false;

View File

@@ -17,12 +17,16 @@
// along with this program. If not, see <https://www.gnu.org/licenses/>.
const std = @import("std");
const js = @import("../../js/js.zig");
const log = @import("../../../log.zig");
const js = @import("../../js/js.zig");
const Page = @import("../../Page.zig");
const ReadableStream = @import("ReadableStream.zig");
const ReadableStreamDefaultReader = @import("ReadableStreamDefaultReader.zig");
const IS_DEBUG = @import("builtin").mode == .Debug;
const ReadableStreamDefaultController = @This();
pub const Chunk = union(enum) {
@@ -79,7 +83,19 @@ pub fn enqueue(self: *ReadableStreamDefaultController, chunk: Chunk) !void {
.done = false,
.value = .fromChunk(chunk),
};
self._page.js.toLocal(resolver).resolve("stream enqueue", result);
if (comptime IS_DEBUG) {
if (self._page.js.local == null) {
log.fatal(.bug, "null context scope", .{.src = "ReadableStreamDefaultController.enqueue", .url = self._page.url});
std.debug.assert(self._page.js.local != null);
}
}
var ls: js.Local.Scope = undefined;
self._page.js.localScope(&ls);
defer ls.deinit();
ls.toLocal(resolver).resolve("stream enqueue", result);
}
pub fn close(self: *ReadableStreamDefaultController) !void {
@@ -94,9 +110,21 @@ pub fn close(self: *ReadableStreamDefaultController) !void {
.done = true,
.value = .empty,
};
for (self._pending_reads.items) |resolver| {
self._page.js.toLocal(resolver).resolve("stream close", result);
if (comptime IS_DEBUG) {
if (self._page.js.local == null) {
log.fatal(.bug, "null context scope", .{.src = "ReadableStreamDefaultController.close", .url = self._page.url});
std.debug.assert(self._page.js.local != null);
}
}
for (self._pending_reads.items) |resolver| {
var ls: js.Local.Scope = undefined;
self._page.js.localScope(&ls);
defer ls.deinit();
ls.toLocal(resolver).resolve("stream close", result);
}
self._pending_reads.clearRetainingCapacity();
}