diff --git a/src/browser/js/Context.zig b/src/browser/js/Context.zig index ac6ed3c1..787527b6 100644 --- a/src/browser/js/Context.zig +++ b/src/browser/js/Context.zig @@ -251,28 +251,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 78ab9d15..ee2ca5cd 100644 --- a/src/browser/js/Local.zig +++ b/src/browser/js/Local.zig @@ -116,6 +116,49 @@ 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, + /// 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); + + 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`. + 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, + parameter_list.len, + ¶meter_list, + extensions.len, + @ptrCast(&extensions), + 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); 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 @@ - + - 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 });