summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--cli/tests/unit/timers_test.ts43
-rw-r--r--runtime/js/11_timers.js44
2 files changed, 67 insertions, 20 deletions
diff --git a/cli/tests/unit/timers_test.ts b/cli/tests/unit/timers_test.ts
index e315d018a..55bc11f76 100644
--- a/cli/tests/unit/timers_test.ts
+++ b/cli/tests/unit/timers_test.ts
@@ -7,10 +7,51 @@ import {
unitTest,
} from "./test_util.ts";
-function waitForMs(ms: number): Promise<number> {
+function waitForMs(ms: number): Promise<void> {
return new Promise((resolve): number => setTimeout(resolve, ms));
}
+unitTest(async function functionParameterBindingSuccess(): Promise<void> {
+ const promise = deferred();
+ let count = 0;
+
+ const nullProto = (newCount: number): void => {
+ count = newCount;
+ promise.resolve();
+ };
+
+ Reflect.setPrototypeOf(nullProto, null);
+
+ setTimeout(nullProto, 500, 1);
+ await promise;
+ // count should be reassigned
+ assertEquals(count, 1);
+});
+
+unitTest(async function stringifyAndEvalNonFunctions(): Promise<void> {
+ // eval can only access global scope
+ const global = globalThis as unknown as {
+ globalPromise: ReturnType<typeof deferred>;
+ globalCount: number;
+ };
+ global.globalPromise = deferred();
+ global.globalCount = 0;
+
+ const notAFunction =
+ "globalThis.globalCount++; globalThis.globalPromise.resolve();" as unknown as () =>
+ void;
+
+ setTimeout(notAFunction, 500);
+
+ await global.globalPromise;
+
+ // count should be incremented
+ assertEquals(global.globalCount, 1);
+
+ Reflect.deleteProperty(global, "globalPromise");
+ Reflect.deleteProperty(global, "globalCount");
+});
+
unitTest(async function timeoutSuccess(): Promise<void> {
const promise = deferred();
let count = 0;
diff --git a/runtime/js/11_timers.js b/runtime/js/11_timers.js
index 5a59844a3..166694358 100644
--- a/runtime/js/11_timers.js
+++ b/runtime/js/11_timers.js
@@ -274,7 +274,7 @@
}
const { console } = globalThis;
- const OriginalDate = Date;
+ const OriginalDateNow = Date.now;
// Timeout values > TIMEOUT_MAX are set to 1.
const TIMEOUT_MAX = 2 ** 31 - 1;
@@ -333,7 +333,7 @@
}
function prepareReadyTimers() {
- const now = OriginalDate.now();
+ const now = OriginalDateNow();
// Bail out if we're not expecting the global timer to fire.
if (globalTimeoutDue === null || pendingEvents > 0) {
return;
@@ -409,7 +409,7 @@
const nextDueNode = dueTree.min();
setOrClearGlobalTimeout(
nextDueNode && nextDueNode.due,
- OriginalDate.now(),
+ OriginalDateNow(),
);
}
} else {
@@ -434,14 +434,18 @@
} else {
// Interval timer: compute when timer was supposed to fire next.
// However make sure to never schedule the next interval in the past.
- const now = OriginalDate.now();
+ const now = OriginalDateNow();
timer.due = Math.max(now, timer.due + timer.delay);
schedule(timer, now);
}
// Call the user callback. Intermediate assignment is to avoid leaking `this`
// to it, while also keeping the stack trace neat when it shows up in there.
const callback = timer.callback;
- callback();
+ if ("function" === typeof callback) {
+ callback();
+ } else {
+ eval(callback);
+ }
}
function checkThis(thisArg) {
@@ -450,24 +454,26 @@
}
}
- function checkBigInt(n) {
- if (typeof n === "bigint") {
- throw new TypeError("Cannot convert a BigInt value to a number");
- }
- }
-
function setTimer(
cb,
delay,
args,
repeat,
) {
- // Bind `args` to the callback and bind `this` to globalThis(global).
- const callback = cb.bind(globalThis, ...args);
+ // If the callack is a function, bind `args` to the callback and bind `this` to globalThis(global).
+ // otherwise call `String` on it, and `eval` it on calls; do not pass variardic args to the string
+ let callback;
+
+ if ("function" === typeof cb) {
+ callback = Function.prototype.bind.call(cb, globalThis, ...args);
+ } else {
+ callback = String(cb);
+ args = []; // args are ignored
+ }
// In the browser, the delay value must be coercible to an integer between 0
// and INT32_MAX. Any other value will cause the timer to fire immediately.
// We emulate this behavior.
- const now = OriginalDate.now();
+ const now = OriginalDateNow();
if (delay > TIMEOUT_MAX) {
console.warn(
`${delay} does not fit into` +
@@ -500,7 +506,7 @@
delay = 0,
...args
) {
- checkBigInt(delay);
+ delay >>>= 0;
checkThis(this);
return setTimer(cb, delay, args, false);
}
@@ -510,13 +516,13 @@
delay = 0,
...args
) {
- checkBigInt(delay);
+ delay >>>= 0;
checkThis(this);
return setTimer(cb, delay, args, true);
}
function clearTimer(id) {
- id = Number(id);
+ id >>>= 0;
const timer = idMap.get(id);
if (timer === undefined) {
// Timer doesn't exist any more or never existed. This is not an error.
@@ -528,7 +534,7 @@
}
function clearTimeout(id = 0) {
- checkBigInt(id);
+ id >>>= 0;
if (id === 0) {
return;
}
@@ -536,7 +542,7 @@
}
function clearInterval(id = 0) {
- checkBigInt(id);
+ id >>>= 0;
if (id === 0) {
return;
}