Move V8 pipe callback helpers into js/ layer

ReadableStream.zig was the only webapi file importing v8 directly.
Extract the repeated newFunctionWithData / callback boilerplate into
js/Local (newFunctionWithData) and js/Caller (initFromHandle,
FunctionCallbackInfo.getData), and update ReadableStream and Context
to use them.
This commit is contained in:
Pierre Tachoire
2026-03-02 17:33:56 +01:00
parent c121dbbd67
commit 7d0548406e
4 changed files with 40 additions and 51 deletions

View File

@@ -60,6 +60,11 @@ fn initWithContext(self: *Caller, ctx: *Context, v8_context: *const v8.Context)
ctx.local = &self.local;
}
pub fn initFromHandle(self: *Caller, handle: ?*const v8.FunctionCallbackInfo) void {
const isolate = v8.v8__FunctionCallbackInfo__GetIsolate(handle).?;
self.init(isolate);
}
pub fn deinit(self: *Caller) void {
const ctx = self.local.ctx;
const call_depth = ctx.call_depth - 1;
@@ -441,6 +446,11 @@ pub const FunctionCallbackInfo = struct {
return .{ .local = local, .handle = v8.v8__FunctionCallbackInfo__INDEX(self.handle, @intCast(index)).? };
}
pub fn getData(self: FunctionCallbackInfo) ?*anyopaque {
const data = v8.v8__FunctionCallbackInfo__Data(self.handle) orelse return null;
return v8.v8__External__Value(@ptrCast(data));
}
pub fn getThis(self: FunctionCallbackInfo) *const v8.Object {
return v8.v8__FunctionCallbackInfo__This(self.handle).?;
}

View File

@@ -539,15 +539,6 @@ fn postCompileModule(self: *Context, mod: js.Module, url: [:0]const u8, local: *
}
}
fn newFunctionWithData(local: *const js.Local, comptime callback: *const fn (?*const v8.FunctionCallbackInfo) callconv(.c) void, data: *anyopaque) js.Function {
const external = local.isolate.createExternal(data);
const handle = v8.v8__Function__New__DEFAULT2(local.handle, callback, @ptrCast(external)).?;
return .{
.local = local,
.handle = handle,
};
}
// == Callbacks ==
// Callback from V8, asking us to load a module. The "specifier" is
// the src of the module to load.
@@ -866,15 +857,14 @@ fn resolveDynamicModule(self: *Context, state: *DynamicModuleResolveState, modul
// last value of the module. But, for module loading, we need to
// resolve to the module's namespace.
const then_callback = newFunctionWithData(local, struct {
const then_callback = local.newFunctionWithData(struct {
pub fn callback(callback_handle: ?*const v8.FunctionCallbackInfo) callconv(.c) void {
const isolate = v8.v8__FunctionCallbackInfo__GetIsolate(callback_handle).?;
var c: Caller = undefined;
c.init(isolate);
c.initFromHandle(callback_handle);
defer c.deinit();
const info_data = v8.v8__FunctionCallbackInfo__Data(callback_handle).?;
const s: *DynamicModuleResolveState = @ptrCast(@alignCast(v8.v8__External__Value(@ptrCast(info_data))));
const info = Caller.FunctionCallbackInfo{ .handle = callback_handle.? };
const s: *DynamicModuleResolveState = @ptrCast(@alignCast(info.getData() orelse return));
if (s.context_id != c.local.ctx.id) {
// The microtask is tied to the isolate, not the context
@@ -891,19 +881,17 @@ fn resolveDynamicModule(self: *Context, state: *DynamicModuleResolveState, modul
}
}.callback, @ptrCast(state));
const catch_callback = newFunctionWithData(local, struct {
const catch_callback = local.newFunctionWithData(struct {
pub fn callback(callback_handle: ?*const v8.FunctionCallbackInfo) callconv(.c) void {
const isolate = v8.v8__FunctionCallbackInfo__GetIsolate(callback_handle).?;
var c: Caller = undefined;
c.init(isolate);
c.initFromHandle(callback_handle);
defer c.deinit();
const info_data = v8.v8__FunctionCallbackInfo__Data(callback_handle).?;
const s: *DynamicModuleResolveState = @ptrCast(@alignCast(v8.v8__External__Value(@ptrCast(info_data))));
const info = Caller.FunctionCallbackInfo{ .handle = callback_handle.? };
const s: *DynamicModuleResolveState = @ptrCast(@alignCast(info.getData() orelse return));
const l = &c.local;
const ctx = l.ctx;
if (s.context_id != ctx.id) {
if (s.context_id != l.ctx.id) {
return;
}

View File

@@ -82,6 +82,16 @@ pub fn createTypedArray(self: *const Local, comptime array_type: js.ArrayType, s
return .init(self, size);
}
pub fn newFunctionWithData(
self: *const Local,
comptime callback: *const fn (?*const v8.FunctionCallbackInfo) callconv(.c) void,
data: *anyopaque,
) js.Function {
const external = self.isolate.createExternal(data);
const handle = v8.v8__Function__New__DEFAULT2(self.handle, callback, @ptrCast(external)).?;
return .{ .local = self, .handle = handle };
}
pub fn runMacrotasks(self: *const Local) void {
const env = self.ctx.env;
env.pumpMessageLoop();

View File

@@ -20,7 +20,6 @@ const std = @import("std");
const log = @import("../../../log.zig");
const js = @import("../../js/js.zig");
const v8 = js.v8;
const Page = @import("../../Page.zig");
const ReadableStreamDefaultReader = @import("ReadableStreamDefaultReader.zig");
@@ -310,22 +309,21 @@ const PipeState = struct {
const read_promise = try state.reader.read(state.page);
// Create JS callback functions for .then() and .catch()
const then_fn = newFunctionWithData(local, &onReadFulfilled, state);
const catch_fn = newFunctionWithData(local, &onReadRejected, state);
const then_fn = local.newFunctionWithData(&onReadFulfilled, state);
const catch_fn = local.newFunctionWithData(&onReadRejected, state);
_ = read_promise.thenAndCatch(then_fn, catch_fn) catch {
state.finish(local);
};
}
fn onReadFulfilled(callback_handle: ?*const v8.FunctionCallbackInfo) callconv(.c) void {
const isolate = v8.v8__FunctionCallbackInfo__GetIsolate(callback_handle).?;
fn onReadFulfilled(callback_handle: ?*const js.v8.FunctionCallbackInfo) callconv(.c) void {
var c: js.Caller = undefined;
c.init(isolate);
c.initFromHandle(callback_handle);
defer c.deinit();
const info_data = v8.v8__FunctionCallbackInfo__Data(callback_handle).?;
const state: *PipeState = @ptrCast(@alignCast(v8.v8__External__Value(@ptrCast(info_data))));
const info = js.Caller.FunctionCallbackInfo{ .handle = callback_handle.? };
const state: *PipeState = @ptrCast(@alignCast(info.getData() orelse return));
if (state.context_id != c.local.ctx.id) return;
@@ -333,10 +331,7 @@ const PipeState = struct {
defer l.runMicrotasks();
// Get the read result argument {done, value}
const result_val = js.Value{
.local = l,
.handle = v8.v8__FunctionCallbackInfo__INDEX(callback_handle, 0) orelse return,
};
const result_val = info.getArg(0, l);
if (!result_val.isObject()) {
state.finish(l);
@@ -374,14 +369,13 @@ const PipeState = struct {
};
}
fn onReadRejected(callback_handle: ?*const v8.FunctionCallbackInfo) callconv(.c) void {
const isolate = v8.v8__FunctionCallbackInfo__GetIsolate(callback_handle).?;
fn onReadRejected(callback_handle: ?*const js.v8.FunctionCallbackInfo) callconv(.c) void {
var c: js.Caller = undefined;
c.init(isolate);
c.initFromHandle(callback_handle);
defer c.deinit();
const info_data = v8.v8__FunctionCallbackInfo__Data(callback_handle).?;
const state: *PipeState = @ptrCast(@alignCast(v8.v8__External__Value(@ptrCast(info_data))));
const info = js.Caller.FunctionCallbackInfo{ .handle = callback_handle.? };
const state: *PipeState = @ptrCast(@alignCast(info.getData() orelse return));
if (state.context_id != c.local.ctx.id) return;
@@ -404,19 +398,6 @@ const PipeState = struct {
local.toLocal(r).resolve("pipe finished", {});
}
}
fn newFunctionWithData(
local: *const js.Local,
comptime callback: *const fn (?*const v8.FunctionCallbackInfo) callconv(.c) void,
data: *anyopaque,
) js.Function {
const external = local.isolate.createExternal(data);
const handle = v8.v8__Function__New__DEFAULT2(local.handle, callback, @ptrCast(external)).?;
return .{
.local = local,
.handle = handle,
};
}
};
const Cancel = struct {