fix(js): run string timer handlers as global-scope scripts at fire time
A string setTimeout/setInterval handler was compiled with new Function(fn) at scheduling time. That diverged from the HTML spec in two ways: top-level var/function declarations in the body stayed local to the compiled function instead of becoming globals, and a syntax error was caught eagerly at scheduling and turned into a silent no-op (that still returned a timer id) rather than surfacing when the timer fires. Defer string handlers to fire-time indirect eval ((0, eval)(src)), which runs in the true global scope like a classic script, so declarations become global and a SyntaxError is thrown when the timer elapses. Matches the dynamic-script path, which already uses indirect eval for the same reason. Function handlers are unchanged. Fixes #558
This commit is contained in:
1 parent
f0541f40a4
commit
452cc8554f
2 files changed
+29
-1
No files matched your search
Vendored
+9
-1
@@ -481,7 +481,15 @@ const _scheduleAfter = (delay, fn) => {
|
||||
// call silently no-ops and JS-triggered navigations (cookie → reload) never fire.
|
||||
const _coerceTimerFn = (fn) => {
|
||||
if (typeof fn === "string") {
|
||||
try { return new Function(fn); } catch (_) { return null; }
|
||||
// Per HTML, a string handler is compiled and run as a classic script in
|
||||
// global scope *at fire time*. Indirect eval ((0, eval)) runs in the true
|
||||
// global scope, so top-level var/function declarations become globals (a
|
||||
// `new Function(fn)` wrapper kept them local); deferring to fire time also
|
||||
// surfaces a SyntaxError when the timer elapses, matching a real browser,
|
||||
// instead of swallowing it eagerly at scheduling. The dynamic-script path
|
||||
// uses the same indirect eval for the same reason.
|
||||
const src = fn;
|
||||
return () => { (0, eval)(src); };
|
||||
}
|
||||
return typeof fn === "function" ? fn : null;
|
||||
};
|
||||
|
||||
Reference in new issue
Block a user