Merge pull request #1771 from lightpanda-io/nikneym/compile-function

Prefer `ScriptCompiler::CompileFunction` to compile attribute listeners
This commit is contained in:
Halil Durak
2026-03-11 11:38:16 +03:00
committed by GitHub
5 changed files with 62 additions and 35 deletions

View File

@@ -251,28 +251,18 @@ pub fn toLocal(self: *Context, global: anytype) js.Local.ToLocalReturnType(@Type
return l.toLocal(global); return l.toLocal(global);
} }
// This isn't expected to be called often. It's for converting attributes into pub fn stringToPersistedFunction(
// function calls, e.g. <body onload="doSomething"> will turn that "doSomething" self: *Context,
// string into a js.Function which looks like: function(e) { doSomething(e) } function_body: []const u8,
// There might be more efficient ways to do this, but doing it this way means comptime parameter_names: []const []const u8,
// our code only has to worry about js.Funtion, not some union of a js.Function extensions: []const v8.Object,
// or a string. ) !js.Function.Global {
pub fn stringToPersistedFunction(self: *Context, str: []const u8) !js.Function.Global {
var ls: js.Local.Scope = undefined; var ls: js.Local.Scope = undefined;
self.localScope(&ls); self.localScope(&ls);
defer ls.deinit(); defer ls.deinit();
var extra: []const u8 = ""; const js_function = try ls.local.compileFunction(function_body, parameter_names, extensions);
const normalized = std.mem.trim(u8, str, &std.ascii.whitespace); return js_function.persist();
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();
} }
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) { 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) {

View File

@@ -116,6 +116,49 @@ pub fn exec(self: *const Local, src: []const u8, name: ?[]const u8) !js.Value {
return self.compileAndRun(src, name); 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,
&parameter_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 { 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_name = self.isolate.initStringHandle(name orelse "anonymous");
const script_source = self.isolate.initStringHandle(src); const script_source = self.isolate.initStringHandle(src);

View File

@@ -1,5 +1,5 @@
<!DOCTYPE html> <!DOCTYPE html>
<body onload=func1></body> <body onload="func1(event)"></body>
<script src="../testing.js"></script> <script src="../testing.js"></script>
<script id=bodyOnLoad1> <script id=bodyOnLoad1>
@@ -14,4 +14,3 @@
testing.expectEqual(1, called); testing.expectEqual(1, called);
}); });
</script> </script>

View File

@@ -387,22 +387,17 @@ pub fn getAttributeFunction(
} }
const attr = element.getAttributeSafe(.wrap(@tagName(listener_type))) orelse return null; const attr = element.getAttributeSafe(.wrap(@tagName(listener_type))) orelse return null;
const callback = page.js.stringToPersistedFunction(attr) catch |err| switch (err) { const function = page.js.stringToPersistedFunction(attr, &.{"event"}, &.{}) catch |err| {
error.OutOfMemory => return err, // Not a valid expression; log this to find out if its something we should be supporting.
else => { log.warn(.js, "Html.getAttributeFunction", .{
// Not a valid expression; log this to find out if its something we should be supporting. .expression = attr,
log.warn(.js, "Html.getAttributeFunction", .{ .err = err,
.expression = attr, });
.err = err, return null;
});
return null;
},
}; };
try self.setAttributeListener(listener_type, callback, page); try self.setAttributeListener(listener_type, function, page);
return function;
return callback;
} }
pub fn hasAttributeFunction(self: *HtmlElement, listener_type: GlobalEventHandler, page: *const Page) bool { pub fn hasAttributeFunction(self: *HtmlElement, listener_type: GlobalEventHandler, page: *const Page) bool {

View File

@@ -50,7 +50,7 @@ pub const Build = struct {
pub fn complete(node: *Node, page: *Page) !void { pub fn complete(node: *Node, page: *Page) !void {
const el = node.as(Element); const el = node.as(Element);
const on_load = el.getAttributeSafe(comptime .wrap("onload")) orelse return; 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; page.window._on_load = func;
} else |err| { } else |err| {
log.err(.js, "body.onload", .{ .err = err, .str = on_load }); log.err(.js, "body.onload", .{ .err = err, .str = on_load });