diff --git a/src/bun.js/bindings/ZigGlobalObject.cpp b/src/bun.js/bindings/ZigGlobalObject.cpp index e8ee749439ec69..da0b5fb2dbae93 100644 --- a/src/bun.js/bindings/ZigGlobalObject.cpp +++ b/src/bun.js/bindings/ZigGlobalObject.cpp @@ -740,9 +740,9 @@ static JSC_DEFINE_HOST_FUNCTION(functionSetTimeout, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)) { JSC::VM& vm = globalObject->vm(); - - if (callFrame->argumentCount() == 0) { - auto scope = DECLARE_THROW_SCOPE(globalObject->vm()); + auto argCount = callFrame->argumentCount(); + if (argCount == 0) { + auto scope = DECLARE_THROW_SCOPE(vm); JSC::throwTypeError(globalObject, scope, "setTimeout requires 1 argument (a function)"_s); return JSC::JSValue::encode(JSC::JSValue {}); } @@ -750,28 +750,47 @@ static JSC_DEFINE_HOST_FUNCTION(functionSetTimeout, JSC::JSValue job = callFrame->argument(0); if (!job.isObject() || !job.getObject()->isCallable()) { - auto scope = DECLARE_THROW_SCOPE(globalObject->vm()); + auto scope = DECLARE_THROW_SCOPE(vm); JSC::throwTypeError(globalObject, scope, "setTimeout expects a function"_s); return JSC::JSValue::encode(JSC::JSValue {}); } - if (callFrame->argumentCount() == 1) { + if (argCount == 1) { globalObject->queueMicrotask(job, JSC::JSValue {}, JSC::JSValue {}, JSC::JSValue {}, JSC::JSValue {}); return JSC::JSValue::encode(JSC::jsNumber(Bun__Timer__getNextID())); } JSC::JSValue num = callFrame->argument(1); - return Bun__Timer__setTimeout(globalObject, JSC::JSValue::encode(job), JSC::JSValue::encode(num)); + + if (argCount == 2) { + return Bun__Timer__setTimeout(globalObject, JSC::JSValue::encode(job), JSC::JSValue::encode(num)); + } + + JSC::JSArray* args = JSC::constructEmptyArray(globalObject, nullptr, argCount - 1); + if (UNLIKELY(!args)) { + auto scope = DECLARE_THROW_SCOPE(vm); + throwVMError(globalObject, scope, createOutOfMemoryError(globalObject)); + return JSC::JSValue::encode(JSC::JSValue {}); + } + args->putDirectIndex(globalObject, 0, JSC::jsNull()); + for (unsigned i = 2; i < argCount; i++) { + args->putDirectIndex(globalObject, i - 1, callFrame->uncheckedArgument(i)); + } + + JSImmutableButterfly* boundArgs = JSImmutableButterfly::createFromArray(globalObject, vm, args); + JSObject* obj = job.getObject(); + JSC::JSBoundFunction *bound = JSC::JSBoundFunction::create(vm, globalObject, obj, JSC::jsNull(), boundArgs, 0, nullptr); + return Bun__Timer__setTimeout(globalObject, JSC::JSValue::encode(bound), JSC::JSValue::encode(num)); } static JSC_DEFINE_HOST_FUNCTION(functionSetInterval, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)) { JSC::VM& vm = globalObject->vm(); - - if (callFrame->argumentCount() == 0) { - auto scope = DECLARE_THROW_SCOPE(globalObject->vm()); + auto argCount = callFrame->argumentCount(); + if (argCount == 0) { + auto scope = DECLARE_THROW_SCOPE(vm); JSC::throwTypeError(globalObject, scope, "setInterval requires 2 arguments (a function)"_s); return JSC::JSValue::encode(JSC::JSValue {}); } @@ -779,14 +798,32 @@ static JSC_DEFINE_HOST_FUNCTION(functionSetInterval, JSC::JSValue job = callFrame->argument(0); if (!job.isObject() || !job.getObject()->isCallable()) { - auto scope = DECLARE_THROW_SCOPE(globalObject->vm()); + auto scope = DECLARE_THROW_SCOPE(vm); JSC::throwTypeError(globalObject, scope, "setInterval expects a function"_s); return JSC::JSValue::encode(JSC::JSValue {}); } JSC::JSValue num = callFrame->argument(1); - return Bun__Timer__setInterval(globalObject, JSC::JSValue::encode(job), - JSC::JSValue::encode(num)); + + if (argCount <= 2) { + return Bun__Timer__setInterval(globalObject, JSC::JSValue::encode(job), JSC::JSValue::encode(num)); + } + + JSC::JSArray* args = JSC::constructEmptyArray(globalObject, nullptr, argCount - 1); + if (UNLIKELY(!args)) { + auto scope = DECLARE_THROW_SCOPE(vm); + throwVMError(globalObject, scope, createOutOfMemoryError(globalObject)); + return JSC::JSValue::encode(JSC::JSValue {}); + } + args->putDirectIndex(globalObject, 0, JSC::jsNull()); + for (unsigned i = 2; i < argCount; i++) { + args->putDirectIndex(globalObject, i - 1, callFrame->uncheckedArgument(i)); + } + + JSImmutableButterfly* boundArgs = JSImmutableButterfly::createFromArray(globalObject, vm, args); + JSObject* obj = job.getObject(); + JSC::JSBoundFunction *bound = JSC::JSBoundFunction::create(vm, globalObject, obj, JSC::jsNull(), boundArgs, 0, nullptr); + return Bun__Timer__setInterval(globalObject, JSC::JSValue::encode(bound), JSC::JSValue::encode(num)); } static JSC_DEFINE_HOST_FUNCTION(functionClearInterval, @@ -2740,7 +2777,7 @@ static JSC_DEFINE_HOST_FUNCTION(functionSetImmediate, JSC::VM& vm = globalObject->vm(); auto argCount = callFrame->argumentCount(); if (argCount == 0) { - auto scope = DECLARE_THROW_SCOPE(globalObject->vm()); + auto scope = DECLARE_THROW_SCOPE(vm); JSC::throwTypeError(globalObject, scope, "setImmediate requires 1 argument (a function)"_s); return JSC::JSValue::encode(JSC::JSValue {}); } @@ -2748,12 +2785,30 @@ static JSC_DEFINE_HOST_FUNCTION(functionSetImmediate, auto job = callFrame->argument(0); if (!job.isObject() || !job.getObject()->isCallable()) { - auto scope = DECLARE_THROW_SCOPE(globalObject->vm()); + auto scope = DECLARE_THROW_SCOPE(vm); JSC::throwTypeError(globalObject, scope, "setImmediate expects a function"_s); return JSC::JSValue::encode(JSC::JSValue {}); } - return Bun__Timer__setTimeout(globalObject, JSC::JSValue::encode(job), JSC::JSValue::encode(jsNumber(0))); + if (argCount == 1) { + return Bun__Timer__setTimeout(globalObject, JSC::JSValue::encode(job), JSC::JSValue::encode(jsNumber(0))); + } + + JSC::JSArray* args = JSC::constructEmptyArray(globalObject, nullptr, argCount); + if (UNLIKELY(!args)) { + auto scope = DECLARE_THROW_SCOPE(vm); + throwVMError(globalObject, scope, createOutOfMemoryError(globalObject)); + return JSC::JSValue::encode(JSC::JSValue {}); + } + args->putDirectIndex(globalObject, 0, JSC::jsNull()); + for (unsigned i = 1; i < argCount; i++) { + args->putDirectIndex(globalObject, i, callFrame->uncheckedArgument(i)); + } + + JSImmutableButterfly* boundArgs = JSImmutableButterfly::createFromArray(globalObject, vm, args); + JSObject* obj = job.getObject(); + JSC::JSBoundFunction *bound = JSC::JSBoundFunction::create(vm, globalObject, obj, JSC::jsNull(), boundArgs, 0, nullptr); + return Bun__Timer__setTimeout(globalObject, JSC::JSValue::encode(bound), JSC::JSValue::encode(jsNumber(0))); } JSC_DEFINE_CUSTOM_GETTER(JSModuleLoader_getter, (JSGlobalObject * globalObject, EncodedJSValue thisValue, PropertyName)) diff --git a/test/bun.js/setInterval.test.js b/test/bun.js/setInterval.test.js index de219b92f4e1e1..57ee54aa0891a3 100644 --- a/test/bun.js/setInterval.test.js +++ b/test/bun.js/setInterval.test.js @@ -6,13 +6,20 @@ it("setInterval", async () => { const result = await new Promise((resolve, reject) => { start = performance.now(); - var id = setInterval(() => { + var id = setInterval((...args) => { counter++; if (counter === 10) { resolve(counter); clearInterval(id); } - }, 1); + try { + expect(args.length).toBe(1); + expect(args[0]).toBe("foo"); + } catch (err) { + reject(err); + clearInterval(id); + } + }, 1, "foo"); }); expect(result).toBe(10); diff --git a/test/bun.js/setTimeout.test.js b/test/bun.js/setTimeout.test.js index a899867df75195..2b2e82d2efac20 100644 --- a/test/bun.js/setTimeout.test.js +++ b/test/bun.js/setTimeout.test.js @@ -5,22 +5,29 @@ it("setTimeout", async () => { const result = await new Promise((resolve, reject) => { var numbers = []; - for (let i = 1; i < 100; i++) { - const id = setTimeout(() => { + for (let i = 0; i < 10; i++) { + const id = setTimeout((...args) => { numbers.push(i); - if (i === 99) { + if (i === 9) { resolve(numbers); } - }, i); + try { + expect(args.length).toBe(1); + expect(args[0]).toBe("foo"); + } catch (err) { + reject(err); + clearInterval(id); + } + }, i, "foo"); expect(id > lastID).toBe(true); lastID = id; } }); for (let j = 0; j < result.length; j++) { - expect(result[j]).toBe(j + 1); + expect(result[j]).toBe(j); } - expect(result.length).toBe(99); + expect(result.length).toBe(10); }); it("clearTimeout", async () => {