Merge pull request #2191 from lightpanda-io/feat/timer-string-handler

Feat/timer string handler
This commit is contained in:
Karl Seguin
2026-04-20 10:11:34 +08:00
committed by GitHub
3 changed files with 57 additions and 4 deletions

View File

@@ -120,14 +120,14 @@ pub fn exec(self: *const Local, src: []const u8, name: ?[]const u8) !js.Value {
/// https://v8.github.io/api/head/classv8_1_1ScriptCompiler.html#a3a15bb5a7dfc3f998e6ac789e6b4646a
pub fn compileFunction(
self: *const Local,
function_body: []const u8,
src: anytype,
/// 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);
const script_source = if (@TypeOf(src) == js.String) src.handle else self.isolate.initStringHandle(src);
var parameter_list: [parameter_names.len]*const v8.String = undefined;
inline for (0..parameter_names.len) |i| {
@@ -742,6 +742,7 @@ fn jsValueToStruct(self: *const Local, comptime T: type, js_val: js.Value) !?T {
else => unreachable,
};
},
js.String => return js_val.isString(),
string.String => {
const js_str = js_val.isString() orelse return null;
return try js_str.toSSO(false);

View File

@@ -51,3 +51,33 @@
clearImmediate(-3);
testing.expectEqual(true, true);
</script>
<script id=setTimeout-string-handler>
// Per HTML spec, TimerHandler = Function or DOMString. The string form
// is legacy but still required by the spec, and many sites (especially
// legacy or server-rendered pages) rely on it.
window.__st_string_ran = 0;
const st_id = window.setTimeout("window.__st_string_ran = 42;", 1);
testing.expectEqual('number', typeof st_id);
testing.onload(() => testing.expectEqual(42, window.__st_string_ran));
</script>
<script id=setInterval-string-handler>
window.__si_string_ran = 0;
const si_id = window.setInterval("window.__si_string_ran += 1;", 1);
testing.expectEqual('number', typeof si_id);
window.setTimeout(() => window.clearInterval(si_id), 5);
testing.onload(() => testing.expectEqual(true, window.__si_string_ran >= 1));
</script>
<script id=setTimeout-invalid-handler>
// Non-function, non-string handlers must throw a TypeError.
let threw = false;
try {
window.setTimeout(123, 1);
} catch (e) {
threw = true;
}
testing.expectEqual(true, threw);
</script>

View File

@@ -253,7 +253,13 @@ pub fn fetch(_: *const Window, input: Fetch.Input, options: ?Fetch.InitOpts, pag
return Fetch.init(input, options, page);
}
pub fn setTimeout(self: *Window, cb: js.Function.Temp, delay_ms: ?u32, params: []js.Value.Temp, page: *Page) !u32 {
const LegacyHandler = union(enum) {
function: js.Function.Temp,
string: js.String,
};
pub fn setTimeout(self: *Window, handler: LegacyHandler, delay_ms: ?u32, params: []js.Value.Temp, page: *Page) !u32 {
const cb = try resolveTimerHandler(handler, page);
return self.scheduleCallback(cb, delay_ms orelse 0, .{
.repeat = false,
.params = params,
@@ -262,7 +268,8 @@ pub fn setTimeout(self: *Window, cb: js.Function.Temp, delay_ms: ?u32, params: [
}, page);
}
pub fn setInterval(self: *Window, cb: js.Function.Temp, delay_ms: ?u32, params: []js.Value.Temp, page: *Page) !u32 {
pub fn setInterval(self: *Window, handler: LegacyHandler, delay_ms: ?u32, params: []js.Value.Temp, page: *Page) !u32 {
const cb = try resolveTimerHandler(handler, page);
return self.scheduleCallback(cb, delay_ms orelse 0, .{
.repeat = true,
.params = params,
@@ -271,6 +278,21 @@ pub fn setInterval(self: *Window, cb: js.Function.Temp, delay_ms: ?u32, params:
}, page);
}
// https://html.spec.whatwg.org/multipage/timers-and-user-prompts.html#dom-settimeout
// https://html.spec.whatwg.org/multipage/timers-and-user-prompts.html#timerhandler
// TimerHandler = Function or DOMString. When a string is passed, it is
// compiled into an anonymous function body, matching how legacy browsers
// (and all current UAs) interpret `setTimeout("foo()", 100)`.
fn resolveTimerHandler(handler: LegacyHandler, page: *Page) !js.Function.Temp {
switch (handler) {
.function => |fun| return fun,
.string => |str| {
const fun = try page.js.local.?.compileFunction(str, &.{}, &.{});
return fun.temp();
},
}
}
pub fn setImmediate(self: *Window, cb: js.Function.Temp, params: []js.Value.Temp, page: *Page) !u32 {
return self.scheduleCallback(cb, 0, .{
.repeat = false,