@sebastianwessel/quickjs
Version:
A typescript package to execute JavaScript and TypeScript code in a WebAssembly QuickJS sandbox
198 lines (197 loc) • 7.36 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.provideTimingFunctions = void 0;
const quickjs_emscripten_core_1 = require("quickjs-emscripten-core");
const disposeHandles = (handles) => {
for (const handle of handles) {
handle.dispose();
}
};
const provideTimingFunctions = (ctx, max) => {
const scope = new quickjs_emscripten_core_1.Scope();
const timeouts = new Map();
let timeoutCounter = 0;
const immediates = new Map();
let immediateCounter = 0;
const intervals = new Map();
let intervalCounter = 0;
const disposeTimeoutEntry = (entry) => {
clearTimeout(entry.id);
entry.callback.dispose();
disposeHandles(entry.args);
};
const disposeIntervalEntry = (entry) => {
clearInterval(entry.id);
entry.callback.dispose();
disposeHandles(entry.args);
};
const callTimerCallback = (callback, args) => {
const result = ctx.callFunction(callback, ctx.undefined, ...args);
if (result.error) {
result.error.dispose();
return;
}
result.value.dispose();
};
const copyTimerArgs = (args) => args.map(arg => arg.dup());
const _setTimeout = ctx.newFunction('setTimeout', (vmFnHandle, timeoutHandle, ...argHandles) => {
const currentCounter = timeoutCounter++;
if (timeouts.size + 1 > max.maxTimeoutCount) {
vmFnHandle.dispose();
timeoutHandle?.dispose();
disposeHandles(argHandles);
throw new Error(`Client tries to use setTimeout, which exceeds the limit of max ${max.maxTimeoutCount} concurrent running timeout functions`);
}
const vmFnHandleCopy = vmFnHandle.dup();
const timeout = timeoutHandle ? ctx.dump(timeoutHandle) : undefined;
const args = copyTimerArgs(argHandles);
vmFnHandle.dispose();
timeoutHandle?.dispose();
disposeHandles(argHandles);
const timeoutID = setTimeout(() => {
const entry = timeouts.get(currentCounter);
if (!entry) {
return;
}
timeouts.delete(currentCounter);
try {
callTimerCallback(entry.callback, entry.args);
}
finally {
disposeTimeoutEntry(entry);
}
}, timeout);
timeouts.set(currentCounter, { id: timeoutID, callback: vmFnHandleCopy, args });
return ctx.newNumber(currentCounter);
});
scope.manage(_setTimeout);
ctx.setProp(ctx.global, 'setTimeout', _setTimeout);
const _clearTimeout = ctx.newFunction('clearTimeout', timeoutHandle => {
const id = ctx.dump(timeoutHandle);
timeoutHandle.dispose();
const t = timeouts.get(id);
if (t) {
timeouts.delete(id);
disposeTimeoutEntry(t);
}
});
scope.manage(_clearTimeout);
ctx.setProp(ctx.global, 'clearTimeout', _clearTimeout);
const _setImmediate = ctx.newFunction('setImmediate', (vmFnHandle, ...argHandles) => {
const currentCounter = immediateCounter++;
if (immediates.size + 1 > max.maxTimeoutCount) {
vmFnHandle.dispose();
disposeHandles(argHandles);
throw new Error(`Client tries to use setImmediate, which exceeds the limit of max ${max.maxTimeoutCount} concurrent running timeout functions`);
}
const vmFnHandleCopy = vmFnHandle.dup();
const args = copyTimerArgs(argHandles);
vmFnHandle.dispose();
disposeHandles(argHandles);
const timeoutID = setTimeout(() => {
const entry = immediates.get(currentCounter);
if (!entry) {
return;
}
immediates.delete(currentCounter);
try {
callTimerCallback(entry.callback, entry.args);
}
finally {
disposeTimeoutEntry(entry);
}
}, 0);
immediates.set(currentCounter, { id: timeoutID, callback: vmFnHandleCopy, args });
return ctx.newNumber(currentCounter);
});
scope.manage(_setImmediate);
ctx.setProp(ctx.global, 'setImmediate', _setImmediate);
const _clearImmediate = ctx.newFunction('clearImmediate', idHandle => {
const id = ctx.dump(idHandle);
idHandle.dispose();
const t = immediates.get(id);
if (t) {
immediates.delete(id);
disposeTimeoutEntry(t);
}
});
scope.manage(_clearImmediate);
ctx.setProp(ctx.global, 'clearImmediate', _clearImmediate);
const _setInterval = ctx.newFunction('setInterval', (vmFnHandle, intervalHandle, ...argHandles) => {
const currentCounter = intervalCounter++;
if (intervals.size + 1 > max.maxIntervalCount) {
vmFnHandle.dispose();
intervalHandle?.dispose();
disposeHandles(argHandles);
throw new Error(`Client tries to use setInterval, which exceeds the limit of max ${max.maxIntervalCount} concurrent running interval functions`);
}
const vmFnHandleCopy = vmFnHandle.dup();
const args = copyTimerArgs(argHandles);
const interval = ctx.dump(intervalHandle);
vmFnHandle.dispose();
intervalHandle.dispose();
disposeHandles(argHandles);
const intervalID = setInterval(() => {
const entry = intervals.get(currentCounter);
if (!entry) {
return;
}
entry.running = true;
try {
callTimerCallback(entry.callback, entry.args);
}
finally {
entry.running = false;
if (entry.cancelled) {
disposeIntervalEntry(entry);
}
}
}, interval);
intervals.set(currentCounter, {
id: intervalID,
callback: vmFnHandleCopy,
args,
cancelled: false,
running: false,
});
return ctx.newNumber(currentCounter);
});
scope.manage(_setInterval);
ctx.setProp(ctx.global, 'setInterval', _setInterval);
const _clearInterval = ctx.newFunction('clearInterval', intervalHandle => {
const id = ctx.dump(intervalHandle);
intervalHandle.dispose();
const t = intervals.get(id);
if (t) {
intervals.delete(id);
if (t.running) {
clearInterval(t.id);
t.cancelled = true;
return;
}
disposeIntervalEntry(t);
}
});
scope.manage(_clearInterval);
ctx.setProp(ctx.global, 'clearInterval', _clearInterval);
const dispose = () => {
for (const [_key, value] of timeouts) {
disposeTimeoutEntry(value);
}
timeouts.clear();
timeoutCounter = 0;
for (const [_key, value] of immediates) {
disposeTimeoutEntry(value);
}
immediates.clear();
immediateCounter = 0;
for (const [_key, value] of intervals) {
disposeIntervalEntry(value);
}
intervals.clear();
intervalCounter = 0;
scope.dispose();
};
return { dispose };
};
exports.provideTimingFunctions = provideTimingFunctions;