From e610506df4860f3b943e438155c4356d6d3d915e Mon Sep 17 00:00:00 2001 From: Halil Durak Date: Tue, 10 Mar 2026 18:14:35 +0300 Subject: [PATCH 1/4] `Local`: initial `compileFunction` --- src/browser/js/Local.zig | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/src/browser/js/Local.zig b/src/browser/js/Local.zig index 6a68b332..4b38f3f8 100644 --- a/src/browser/js/Local.zig +++ b/src/browser/js/Local.zig @@ -115,6 +115,38 @@ pub fn exec(self: *const Local, src: []const u8, name: ?[]const u8) !js.Value { return self.compileAndRun(src, name); } +/// Compiles a function body as function. +/// +/// https://v8.github.io/api/head/classv8_1_1ScriptCompiler.html#a3a15bb5a7dfc3f998e6ac789e6b4646a +pub fn compileFunction(self: *const Local, function_body: []const u8) !js.Function { + // TODO: Make configurable. + const script_name = self.isolate.initStringHandle("anonymous"); + const script_source = self.isolate.initStringHandle(function_body); + + // Create ScriptOrigin. + var origin: v8.ScriptOrigin = undefined; + v8.v8__ScriptOrigin__CONSTRUCT(&origin, script_name); + + // Create ScriptCompilerSource. + var script_compiler_source: v8.ScriptCompilerSource = undefined; + v8.v8__ScriptCompiler__Source__CONSTRUCT2(script_source, &origin, null, &script_compiler_source); + defer v8.v8__ScriptCompiler__Source__DESTRUCT(&script_compiler_source); + + // Compile the function. + const result = v8.v8__ScriptCompiler__CompileFunction( + self.handle, + &script_compiler_source, + 0, + null, + 0, + null, + v8.kNoCompileOptions, + v8.kNoCacheNoReason, + ) orelse return error.CompilationError; + + return .{ .local = self, .handle = result }; +} + pub fn compileAndRun(self: *const Local, src: []const u8, name: ?[]const u8) !js.Value { const script_name = self.isolate.initStringHandle(name orelse "anonymous"); const script_source = self.isolate.initStringHandle(src); From 9d2ba52160f2ea5e0a7efff2f8411cbb8c71b567 Mon Sep 17 00:00:00 2001 From: Halil Durak Date: Tue, 10 Mar 2026 19:15:53 +0300 Subject: [PATCH 2/4] adapt `stringToPersistedFunction` to `compileFunction` This is just a thin wrapper around it now. --- src/browser/js/Context.zig | 26 ++++++++------------------ src/browser/js/Local.zig | 25 ++++++++++++++++++------- 2 files changed, 26 insertions(+), 25 deletions(-) diff --git a/src/browser/js/Context.zig b/src/browser/js/Context.zig index 9180223c..367a235a 100644 --- a/src/browser/js/Context.zig +++ b/src/browser/js/Context.zig @@ -336,28 +336,18 @@ pub fn toLocal(self: *Context, global: anytype) js.Local.ToLocalReturnType(@Type return l.toLocal(global); } -// This isn't expected to be called often. It's for converting attributes into -// function calls, e.g. will turn that "doSomething" -// string into a js.Function which looks like: function(e) { doSomething(e) } -// There might be more efficient ways to do this, but doing it this way means -// our code only has to worry about js.Funtion, not some union of a js.Function -// or a string. -pub fn stringToPersistedFunction(self: *Context, str: []const u8) !js.Function.Global { +pub fn stringToPersistedFunction( + self: *Context, + function_body: []const u8, + comptime parameter_names: []const []const u8, + extensions: []const v8.Object, +) !js.Function.Global { var ls: js.Local.Scope = undefined; self.localScope(&ls); defer ls.deinit(); - var extra: []const u8 = ""; - const normalized = std.mem.trim(u8, str, &std.ascii.whitespace); - if (normalized.len > 0 and normalized[normalized.len - 1] != ')') { - extra = "(e)"; - } - const full = try std.fmt.allocPrintSentinel(self.call_arena, "(function(e) {{ {s}{s} }})", .{ normalized, extra }, 0); - const js_val = try ls.local.compileAndRun(full, null); - if (!js_val.isFunction()) { - return error.StringFunctionError; - } - return try (js.Function{ .local = &ls.local, .handle = @ptrCast(js_val.handle) }).persist(); + const js_function = try ls.local.compileFunction(function_body, parameter_names, extensions); + return js_function.persist(); } pub fn module(self: *Context, comptime want_result: bool, local: *const js.Local, src: []const u8, url: []const u8, cacheable: bool) !(if (want_result) ModuleEntry else void) { diff --git a/src/browser/js/Local.zig b/src/browser/js/Local.zig index 4b38f3f8..c6b8e880 100644 --- a/src/browser/js/Local.zig +++ b/src/browser/js/Local.zig @@ -118,16 +118,27 @@ pub fn exec(self: *const Local, src: []const u8, name: ?[]const u8) !js.Value { /// Compiles a function body as function. /// /// https://v8.github.io/api/head/classv8_1_1ScriptCompiler.html#a3a15bb5a7dfc3f998e6ac789e6b4646a -pub fn compileFunction(self: *const Local, function_body: []const u8) !js.Function { +pub fn compileFunction( + self: *const Local, + function_body: []const u8, + /// We tend to know how many params we'll pass; can remove the comptime if necessary. + comptime parameter_names: []const []const u8, + extensions: []const v8.Object, +) !js.Function { // TODO: Make configurable. const script_name = self.isolate.initStringHandle("anonymous"); const script_source = self.isolate.initStringHandle(function_body); - // Create ScriptOrigin. + var parameter_list: [parameter_names.len]*const v8.String = undefined; + inline for (0..parameter_names.len) |i| { + parameter_list[i] = self.isolate.initStringHandle(parameter_names[i]); + } + + // Create `ScriptOrigin`. var origin: v8.ScriptOrigin = undefined; v8.v8__ScriptOrigin__CONSTRUCT(&origin, script_name); - // Create ScriptCompilerSource. + // Create `ScriptCompilerSource`. var script_compiler_source: v8.ScriptCompilerSource = undefined; v8.v8__ScriptCompiler__Source__CONSTRUCT2(script_source, &origin, null, &script_compiler_source); defer v8.v8__ScriptCompiler__Source__DESTRUCT(&script_compiler_source); @@ -136,10 +147,10 @@ pub fn compileFunction(self: *const Local, function_body: []const u8) !js.Functi const result = v8.v8__ScriptCompiler__CompileFunction( self.handle, &script_compiler_source, - 0, - null, - 0, - null, + parameter_list.len, + ¶meter_list, + extensions.len, + @ptrCast(&extensions), v8.kNoCompileOptions, v8.kNoCacheNoReason, ) orelse return error.CompilationError; From ba3da32ce67931e5c373a4ae6ee33ec816862477 Mon Sep 17 00:00:00 2001 From: Halil Durak Date: Tue, 10 Mar 2026 19:16:20 +0300 Subject: [PATCH 3/4] spread new `stringToPersistedFunction` --- src/browser/webapi/element/Html.zig | 23 +++++++++-------------- src/browser/webapi/element/html/Body.zig | 2 +- 2 files changed, 10 insertions(+), 15 deletions(-) diff --git a/src/browser/webapi/element/Html.zig b/src/browser/webapi/element/Html.zig index ea962c9e..40f70b67 100644 --- a/src/browser/webapi/element/Html.zig +++ b/src/browser/webapi/element/Html.zig @@ -387,22 +387,17 @@ pub fn getAttributeFunction( } const attr = element.getAttributeSafe(.wrap(@tagName(listener_type))) orelse return null; - const callback = page.js.stringToPersistedFunction(attr) catch |err| switch (err) { - error.OutOfMemory => return err, - else => { - // Not a valid expression; log this to find out if its something we should be supporting. - log.warn(.js, "Html.getAttributeFunction", .{ - .expression = attr, - .err = err, - }); - - return null; - }, + const function = page.js.stringToPersistedFunction(attr, &.{"event"}, &.{}) catch |err| { + // Not a valid expression; log this to find out if its something we should be supporting. + log.warn(.js, "Html.getAttributeFunction", .{ + .expression = attr, + .err = err, + }); + return null; }; - try self.setAttributeListener(listener_type, callback, page); - - return callback; + try self.setAttributeListener(listener_type, function, page); + return function; } pub fn hasAttributeFunction(self: *HtmlElement, listener_type: GlobalEventHandler, page: *const Page) bool { diff --git a/src/browser/webapi/element/html/Body.zig b/src/browser/webapi/element/html/Body.zig index 7b5b530e..dccb892d 100644 --- a/src/browser/webapi/element/html/Body.zig +++ b/src/browser/webapi/element/html/Body.zig @@ -50,7 +50,7 @@ pub const Build = struct { pub fn complete(node: *Node, page: *Page) !void { const el = node.as(Element); const on_load = el.getAttributeSafe(comptime .wrap("onload")) orelse return; - if (page.js.stringToPersistedFunction(on_load)) |func| { + if (page.js.stringToPersistedFunction(on_load, &.{"event"}, &.{})) |func| { page.window._on_load = func; } else |err| { log.err(.js, "body.onload", .{ .err = err, .str = on_load }); From a22040efa9fedc680c7cf5850b8e1c9c9c70fc7f Mon Sep 17 00:00:00 2001 From: Halil Durak Date: Tue, 10 Mar 2026 19:16:35 +0300 Subject: [PATCH 4/4] update `body.onload` test --- src/browser/tests/window/body_onload1.html | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/browser/tests/window/body_onload1.html b/src/browser/tests/window/body_onload1.html index 7eb7ee61..3858810e 100644 --- a/src/browser/tests/window/body_onload1.html +++ b/src/browser/tests/window/body_onload1.html @@ -1,5 +1,5 @@ - + -