const core = globalThis.Deno.core;
const ops = core.ops;
const primordials = globalThis.__bootstrap.primordials;
const {
ArrayPrototypePush,
ArrayPrototypeShift,
FunctionPrototypeCall,
MapPrototypeDelete,
MapPrototypeGet,
MapPrototypeHas,
MapPrototypeSet,
Uint8Array,
Uint32Array,
NumberPOSITIVE_INFINITY,
PromisePrototypeThen,
SafeArrayIterator,
SafeMap,
SymbolFor,
TypedArrayPrototypeGetBuffer,
TypeError,
indirectEval,
} = primordials;
import * as webidl from "ext:deno_webidl/00_webidl.js";
import { reportException } from "ext:deno_web/02_event.js";
import { assert } from "ext:deno_web/00_infra.js";
const { op_sleep } = core.generateAsyncOpHandler("op_sleep");
const hrU8 = new Uint8Array(8);
const hr = new Uint32Array(TypedArrayPrototypeGetBuffer(hrU8));
function opNow() {
ops.op_now(hrU8);
return (hr[0] * 1000 + hr[1] / 1e6);
}
const timerTasks = [];
let timerNestingLevel = 0;
function handleTimerMacrotask() {
if (timerTasks.length === 0) {
return true;
}
const task = ArrayPrototypeShift(timerTasks);
timerNestingLevel = task.nestingLevel;
try {
task.action();
} finally {
timerNestingLevel = 0;
}
return timerTasks.length === 0;
}
const activeTimers = new SafeMap();
let nextId = 1;
function initializeTimer(
callback,
timeout,
args,
repeat,
prevId,
) {
let id;
let timerInfo;
if (prevId !== undefined) {
assert(repeat);
id = prevId;
timerInfo = MapPrototypeGet(activeTimers, id);
} else {
id = nextId++;
const cancelRid = ops.op_timer_handle();
timerInfo = { cancelRid, isRef: true, promiseId: -1 };
MapPrototypeSet(activeTimers, id, timerInfo);
}
if (timeout < 0) timeout = 0;
if (timerNestingLevel > 5 && timeout < 4) timeout = 4;
const task = {
action: () => {
if (!MapPrototypeHas(activeTimers, id)) {
return;
}
if (typeof callback === "function") {
try {
FunctionPrototypeCall(
callback,
globalThis,
...new SafeArrayIterator(args),
);
} catch (error) {
reportException(error);
}
} else {
indirectEval(callback);
}
if (repeat) {
if (MapPrototypeHas(activeTimers, id)) {
initializeTimer(callback, timeout, args, true, id);
}
} else {
core.tryClose(timerInfo.cancelRid);
MapPrototypeDelete(activeTimers, id);
}
},
nestingLevel: timerNestingLevel + 1,
};
runAfterTimeout(
() => ArrayPrototypePush(timerTasks, task),
timeout,
timerInfo,
);
return id;
}
const scheduledTimers = { head: null, tail: null };
function runAfterTimeout(cb, millis, timerInfo) {
const cancelRid = timerInfo.cancelRid;
const sleepPromise = op_sleep(millis, cancelRid);
timerInfo.promiseId = sleepPromise[SymbolFor("Deno.core.internalPromiseId")];
if (!timerInfo.isRef) {
core.unrefOp(timerInfo.promiseId);
}
const timerObject = {
millis,
cb,
resolved: false,
prev: scheduledTimers.tail,
next: null,
};
if (scheduledTimers.tail === null) {
assert(scheduledTimers.head === null);
scheduledTimers.head = scheduledTimers.tail = timerObject;
} else {
scheduledTimers.tail.next = timerObject;
scheduledTimers.tail = timerObject;
}
PromisePrototypeThen(
sleepPromise,
(cancelled) => {
if (!cancelled) {
removeFromScheduledTimers(timerObject);
return;
}
timerObject.resolved = true;
let lowestUnresolvedTimeout = NumberPOSITIVE_INFINITY;
let currentEntry = scheduledTimers.head;
while (currentEntry !== null) {
if (currentEntry.millis < lowestUnresolvedTimeout) {
if (currentEntry.resolved) {
currentEntry.cb();
removeFromScheduledTimers(currentEntry);
} else {
lowestUnresolvedTimeout = currentEntry.millis;
}
}
currentEntry = currentEntry.next;
}
},
);
}
function removeFromScheduledTimers(timerObj) {
if (timerObj.prev !== null) {
timerObj.prev.next = timerObj.next;
} else {
assert(scheduledTimers.head === timerObj);
scheduledTimers.head = timerObj.next;
}
if (timerObj.next !== null) {
timerObj.next.prev = timerObj.prev;
} else {
assert(scheduledTimers.tail === timerObj);
scheduledTimers.tail = timerObj.prev;
}
}
function checkThis(thisArg) {
if (thisArg !== null && thisArg !== undefined && thisArg !== globalThis) {
throw new TypeError("Illegal invocation");
}
}
function setTimeout(callback, timeout = 0, ...args) {
checkThis(this);
if (typeof callback !== "function") {
callback = webidl.converters.DOMString(callback);
}
timeout = webidl.converters.long(timeout);
return initializeTimer(callback, timeout, args, false);
}
function setInterval(callback, timeout = 0, ...args) {
checkThis(this);
if (typeof callback !== "function") {
callback = webidl.converters.DOMString(callback);
}
timeout = webidl.converters.long(timeout);
return initializeTimer(callback, timeout, args, true);
}
function clearTimeout(id = 0) {
checkThis(this);
id = webidl.converters.long(id);
const timerInfo = MapPrototypeGet(activeTimers, id);
if (timerInfo !== undefined) {
core.tryClose(timerInfo.cancelRid);
MapPrototypeDelete(activeTimers, id);
}
}
function clearInterval(id = 0) {
checkThis(this);
clearTimeout(id);
}
function refTimer(id) {
const timerInfo = MapPrototypeGet(activeTimers, id);
if (timerInfo === undefined || timerInfo.isRef) {
return;
}
timerInfo.isRef = true;
core.refOp(timerInfo.promiseId);
}
function unrefTimer(id) {
const timerInfo = MapPrototypeGet(activeTimers, id);
if (timerInfo === undefined || !timerInfo.isRef) {
return;
}
timerInfo.isRef = false;
core.unrefOp(timerInfo.promiseId);
}
export {
clearInterval,
clearTimeout,
handleTimerMacrotask,
opNow,
refTimer,
setInterval,
setTimeout,
unrefTimer,
};