diff options
Diffstat (limited to '')
-rw-r--r-- | packages/bun-types/globals.d.ts | 15 | ||||
-rw-r--r-- | src/bun.js/api/bun.zig | 142 | ||||
-rw-r--r-- | src/bun.js/bindings/ZigGeneratedClasses+DOMClientIsoSubspaces.h | 3 | ||||
-rw-r--r-- | src/bun.js/bindings/ZigGeneratedClasses+DOMIsoSubspaces.h | 3 | ||||
-rw-r--r-- | src/bun.js/bindings/ZigGeneratedClasses+lazyStructureHeader.h | 6 | ||||
-rw-r--r-- | src/bun.js/bindings/ZigGeneratedClasses+lazyStructureImpl.h | 7 | ||||
-rw-r--r-- | src/bun.js/bindings/ZigGeneratedClasses.cpp | 203 | ||||
-rw-r--r-- | src/bun.js/bindings/ZigGeneratedClasses.h | 50 | ||||
-rw-r--r-- | src/bun.js/bindings/ZigGlobalObject.cpp | 106 | ||||
-rw-r--r-- | src/bun.js/bindings/exports.zig | 14 | ||||
-rw-r--r-- | src/bun.js/bindings/generated_classes.zig | 69 | ||||
-rw-r--r-- | src/bun.js/bindings/generated_classes_list.zig | 1 | ||||
-rw-r--r-- | src/bun.js/bindings/headers-cpp.h | 2 | ||||
-rw-r--r-- | src/bun.js/node/node.classes.ts | 27 | ||||
-rw-r--r-- | src/bun.js/node_timers.exports.js | 105 | ||||
-rw-r--r-- | test/bun.js/inspect.test.js | 9 | ||||
-rw-r--r-- | test/bun.js/setInterval.test.js | 3 | ||||
-rw-r--r-- | test/bun.js/setTimeout.test.js | 58 |
18 files changed, 616 insertions, 207 deletions
diff --git a/packages/bun-types/globals.d.ts b/packages/bun-types/globals.d.ts index 8848f59e6..69c100c05 100644 --- a/packages/bun-types/globals.d.ts +++ b/packages/bun-types/globals.d.ts @@ -1368,6 +1368,15 @@ declare function queueMicrotask(callback: (...args: any[]) => void): void; * @param error Error or string */ declare function reportError(error: any): void; + +interface Timer { + ref(): void; + unref(): void; + hasRef(): boolean; + + [Symbol.toPrimitive](): number; +} + /** * Run a function immediately after main event loop is vacant * @param handler function to call @@ -1375,7 +1384,7 @@ declare function reportError(error: any): void; declare function setImmediate( handler: TimerHandler, ...arguments: any[] -): number; +): Timer; /** * Run a function every `interval` milliseconds * @param handler function to call @@ -1385,7 +1394,7 @@ declare function setInterval( handler: TimerHandler, interval?: number, ...arguments: any[] -): number; +): Timer; /** * Run a function after `timeout` (milliseconds) * @param handler function to call @@ -1395,7 +1404,7 @@ declare function setTimeout( handler: TimerHandler, timeout?: number, ...arguments: any[] -): number; +): Timer; declare function addEventListener<K extends keyof EventMap>( type: K, listener: (this: object, ev: EventMap[K]) => any, diff --git a/src/bun.js/api/bun.zig b/src/bun.js/api/bun.zig index 4dcbad374..78927f85a 100644 --- a/src/bun.js/api/bun.zig +++ b/src/bun.js/api/bun.zig @@ -2777,12 +2777,19 @@ pub const Timer = struct { warned: bool = false, // We split up the map here to avoid storing an extra "repeat" boolean - - /// Used by setTimeout() - timeout_map: TimeoutMap = TimeoutMap{}, - - /// Used by setInterval() - interval_map: TimeoutMap = TimeoutMap{}, + maps: struct { + setTimeout: TimeoutMap = .{}, + setInterval: TimeoutMap = .{}, + setImmediate: TimeoutMap = .{}, + + pub inline fn get(this: *@This(), kind: Timeout.Kind) *TimeoutMap { + return switch (kind) { + .setTimeout => &this.setTimeout, + .setInterval => &this.setInterval, + .setImmediate => &this.setImmediate, + }; + } + } = .{}, /// TimeoutMap is map of i32 to nullable Timeout structs /// i32 is exposed to JavaScript and can be used with clearTimeout, clearInterval, etc. @@ -2812,7 +2819,7 @@ pub const Timer = struct { globalThis: *JSC.JSGlobalObject, callback: JSC.Strong = .{}, arguments: JSC.Strong = .{}, - repeat: bool = false, + kind: Timeout.Kind = .setTimeout, pub const Task = JSC.AnyTask.New(CallbackJob, perform); @@ -2849,12 +2856,13 @@ pub const Timer = struct { pub fn perform(this: *CallbackJob) void { var globalThis = this.globalThis; var vm = globalThis.bunVM(); - var map: *TimeoutMap = if (this.repeat) &vm.timer.interval_map else &vm.timer.timeout_map; + const kind = this.kind; + var map: *TimeoutMap = vm.timer.maps.get(kind); // This doesn't deinit the timer // Timers are deinit'd separately // We do need to handle when the timer is cancelled after the job has been enqueued - if (!this.repeat) { + if (kind != .setInterval) { if (map.fetchSwapRemove(this.id) == null) { // if the timeout was cancelled, don't run the callback this.deinit(); @@ -2934,6 +2942,47 @@ pub const Timer = struct { } }; + pub const TimerObject = struct { + id: i32 = -1, + kind: Timeout.Kind = .setTimeout, + ref_count: u16 = 1, + + pub usingnamespace JSC.Codegen.JSTimeout; + + pub fn doRef(this: *TimerObject, _: *JSC.JSGlobalObject, _: *JSC.CallFrame) callconv(.C) JSValue { + if (this.ref_count > 0) + this.ref_count +|= 1; + + return JSValue.jsUndefined(); + } + + pub fn doUnref(this: *TimerObject, globalObject: *JSC.JSGlobalObject, _: *JSC.CallFrame) callconv(.C) JSValue { + this.ref_count -|= 1; + if (this.ref_count == 0) { + switch (this.kind) { + .setTimeout, .setImmediate => { + _ = clearTimeout(globalObject, JSValue.jsNumber(this.id)); + }, + .setInterval => { + _ = clearInterval(globalObject, JSValue.jsNumber(this.id)); + }, + } + } + + return JSValue.jsUndefined(); + } + pub fn hasRef(this: *TimerObject, globalObject: *JSC.JSGlobalObject, _: *JSC.CallFrame) callconv(.C) JSValue { + return JSValue.jsBoolean(this.ref_count > 0 and globalObject.bunVM().timer.maps.get(this.kind).contains(this.id)); + } + pub fn toPrimitive(this: *TimerObject, _: *JSC.JSGlobalObject, _: *JSC.CallFrame) callconv(.C) JSValue { + return JSValue.jsNumber(this.id); + } + + pub fn finalize(this: *TimerObject) callconv(.C) void { + bun.default_allocator.destroy(this); + } + }; + pub const Timeout = struct { callback: JSC.Strong = .{}, globalThis: *JSC.JSGlobalObject, @@ -2941,11 +2990,21 @@ pub const Timer = struct { poll_ref: JSC.PollRef = JSC.PollRef.init(), arguments: JSC.Strong = .{}, + pub const Kind = enum(u32) { + setTimeout, + setInterval, + setImmediate, + }; + // this is sized to be the same as one pointer pub const ID = extern struct { id: i32, - repeat: u32 = 0, + kind: Kind = Kind.setTimeout, + + pub fn repeats(this: ID) bool { + return this.kind == .setInterval; + } }; pub fn run(timer: *uws.Timer) callconv(.C) void { @@ -2955,9 +3014,9 @@ pub const Timer = struct { // to handle the timeout being cancelled after already enqueued var vm = JSC.VirtualMachine.get(); - const repeats = timer_id.repeat > 0; + const repeats = timer_id.repeats(); - var map = if (repeats) &vm.timer.interval_map else &vm.timer.timeout_map; + var map = vm.timer.maps.get(timer_id.kind); var this_: ?Timeout = map.get( timer_id.id, @@ -3000,7 +3059,7 @@ pub const Timer = struct { this.arguments, .globalThis = globalThis, .id = timer_id.id, - .repeat = timer_id.repeat > 0, + .kind = timer_id.kind, }; // This allows us to: @@ -3054,19 +3113,18 @@ pub const Timer = struct { if (repeat) @as(i32, 1) else 0, ); - var map = if (repeat) - &vm.timer.interval_map - else - &vm.timer.timeout_map; + const kind: Timeout.Kind = if (repeat) .setInterval else .setTimeout; + + var map = vm.timer.maps.get(kind); // setImmediate(foo) // setTimeout(foo, 0) - if (interval == 0) { + if (kind == .setTimeout and interval == 0) { var cb: CallbackJob = .{ .callback = JSC.Strong.create(callback, globalThis), .globalThis = globalThis, .id = id, - .repeat = false, + .kind = kind, }; if (arguments_array_or_zero != .zero) { @@ -3093,7 +3151,7 @@ pub const Timer = struct { vm.uws_event_loop.?, Timeout.ID{ .id = id, - .repeat = @as(u32, @boolToInt(repeat)), + .kind = kind, }, ), }; @@ -3108,11 +3166,11 @@ pub const Timer = struct { timeout.timer.set( Timeout.ID{ .id = id, - .repeat = if (repeat) 1 else 0, + .kind = kind, }, Timeout.run, interval, - @as(i32, @boolToInt(repeat)) * interval, + @as(i32, @boolToInt(kind == .setInterval)) * interval, ); } @@ -3129,7 +3187,13 @@ pub const Timer = struct { Timer.set(id, globalThis, callback, countdown, arguments, false) catch return JSValue.jsUndefined(); - return JSValue.jsNumberWithType(i32, id); + var timer = globalThis.allocator().create(TimerObject) catch unreachable; + timer.* = .{ + .id = id, + .kind = .setTimeout, + }; + + return timer.toJS(globalThis); } pub fn setInterval( globalThis: *JSGlobalObject, @@ -3144,21 +3208,37 @@ pub const Timer = struct { Timer.set(id, globalThis, callback, countdown, arguments, true) catch return JSValue.jsUndefined(); - return JSValue.jsNumberWithType(i32, id); + var timer = globalThis.allocator().create(TimerObject) catch unreachable; + timer.* = .{ + .id = id, + .kind = .setInterval, + }; + + return timer.toJS(globalThis); } - pub fn clearTimer(timer_id: JSValue, globalThis: *JSGlobalObject, repeats: bool) void { + pub fn clearTimer(timer_id_value: JSValue, globalThis: *JSGlobalObject, repeats: bool) void { JSC.markBinding(@src()); - var map = if (repeats) &VirtualMachine.get().timer.interval_map else &VirtualMachine.get().timer.timeout_map; - if (!timer_id.isAnyInt()) { - return; - } + const kind: Timeout.Kind = if (repeats) .setInterval else .setTimeout; + + var map = globalThis.bunVM().timer.maps.get(kind); const id: Timeout.ID = .{ - .id = timer_id.coerce(i32, globalThis), - .repeat = @as(u32, @boolToInt(repeats)), + .id = brk: { + if (timer_id_value.isAnyInt()) { + break :brk timer_id_value.coerce(i32, globalThis); + } + + if (TimerObject.fromJS(timer_id_value)) |timer_obj| { + break :brk timer_obj.id; + } + + return; + }, + .kind = kind, }; + var timer = map.fetchSwapRemove(id.id) orelse return; if (timer.value == null) { // this timer was scheduled to run but was cancelled before it was run diff --git a/src/bun.js/bindings/ZigGeneratedClasses+DOMClientIsoSubspaces.h b/src/bun.js/bindings/ZigGeneratedClasses+DOMClientIsoSubspaces.h index 8de2f11f8..d642ae40f 100644 --- a/src/bun.js/bindings/ZigGeneratedClasses+DOMClientIsoSubspaces.h +++ b/src/bun.js/bindings/ZigGeneratedClasses+DOMClientIsoSubspaces.h @@ -22,5 +22,6 @@ std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForStatsConstructor;std:: std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForTCPSocket; std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForTLSSocket; std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForTextDecoder; -std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForTextDecoderConstructor;std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForTranspiler; +std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForTextDecoderConstructor;std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForTimeout; +std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForTranspiler; std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForTranspilerConstructor;
\ No newline at end of file diff --git a/src/bun.js/bindings/ZigGeneratedClasses+DOMIsoSubspaces.h b/src/bun.js/bindings/ZigGeneratedClasses+DOMIsoSubspaces.h index 7084bd710..93cb9bf5e 100644 --- a/src/bun.js/bindings/ZigGeneratedClasses+DOMIsoSubspaces.h +++ b/src/bun.js/bindings/ZigGeneratedClasses+DOMIsoSubspaces.h @@ -22,5 +22,6 @@ std::unique_ptr<IsoSubspace> m_subspaceForStatsConstructor;std::unique_ptr<IsoSu std::unique_ptr<IsoSubspace> m_subspaceForTCPSocket; std::unique_ptr<IsoSubspace> m_subspaceForTLSSocket; std::unique_ptr<IsoSubspace> m_subspaceForTextDecoder; -std::unique_ptr<IsoSubspace> m_subspaceForTextDecoderConstructor;std::unique_ptr<IsoSubspace> m_subspaceForTranspiler; +std::unique_ptr<IsoSubspace> m_subspaceForTextDecoderConstructor;std::unique_ptr<IsoSubspace> m_subspaceForTimeout; +std::unique_ptr<IsoSubspace> m_subspaceForTranspiler; std::unique_ptr<IsoSubspace> m_subspaceForTranspilerConstructor;
\ No newline at end of file diff --git a/src/bun.js/bindings/ZigGeneratedClasses+lazyStructureHeader.h b/src/bun.js/bindings/ZigGeneratedClasses+lazyStructureHeader.h index f03a3faa3..ab75a9949 100644 --- a/src/bun.js/bindings/ZigGeneratedClasses+lazyStructureHeader.h +++ b/src/bun.js/bindings/ZigGeneratedClasses+lazyStructureHeader.h @@ -142,6 +142,12 @@ JSC::Structure* JSTextDecoderStructure() { return m_JSTextDecoder.getInitialized JSC::LazyClassStructure m_JSTextDecoder; bool hasJSTextDecoderSetterValue { false }; mutable JSC::WriteBarrier<JSC::Unknown> m_JSTextDecoderSetterValue; +JSC::Structure* JSTimeoutStructure() { return m_JSTimeout.getInitializedOnMainThread(this); } + JSC::JSObject* JSTimeoutConstructor() { return m_JSTimeout.constructorInitializedOnMainThread(this); } + JSC::JSValue JSTimeoutPrototype() { return m_JSTimeout.prototypeInitializedOnMainThread(this); } + JSC::LazyClassStructure m_JSTimeout; + bool hasJSTimeoutSetterValue { false }; + mutable JSC::WriteBarrier<JSC::Unknown> m_JSTimeoutSetterValue; JSC::Structure* JSTranspilerStructure() { return m_JSTranspiler.getInitializedOnMainThread(this); } JSC::JSObject* JSTranspilerConstructor() { return m_JSTranspiler.constructorInitializedOnMainThread(this); } JSC::JSValue JSTranspilerPrototype() { return m_JSTranspiler.prototypeInitializedOnMainThread(this); } diff --git a/src/bun.js/bindings/ZigGeneratedClasses+lazyStructureImpl.h b/src/bun.js/bindings/ZigGeneratedClasses+lazyStructureImpl.h index 388b1f467..aed941a2e 100644 --- a/src/bun.js/bindings/ZigGeneratedClasses+lazyStructureImpl.h +++ b/src/bun.js/bindings/ZigGeneratedClasses+lazyStructureImpl.h @@ -143,6 +143,12 @@ void GlobalObject::initGeneratedLazyClasses() { init.setStructure(WebCore::JSTextDecoder::createStructure(init.vm, init.global, init.prototype)); init.setConstructor(WebCore::JSTextDecoder::createConstructor(init.vm, init.global, init.prototype)); }); + m_JSTimeout.initLater( + [](LazyClassStructure::Initializer& init) { + init.setPrototype(WebCore::JSTimeout::createPrototype(init.vm, reinterpret_cast<Zig::GlobalObject*>(init.global))); + init.setStructure(WebCore::JSTimeout::createStructure(init.vm, init.global, init.prototype)); + + }); m_JSTranspiler.initLater( [](LazyClassStructure::Initializer& init) { init.setPrototype(WebCore::JSTranspiler::createPrototype(init.vm, reinterpret_cast<Zig::GlobalObject*>(init.global))); @@ -177,5 +183,6 @@ void GlobalObject::visitGeneratedLazyClasses(GlobalObject *thisObject, Visitor& thisObject->m_JSTCPSocket.visit(visitor); visitor.append(thisObject->m_JSTCPSocketSetterValue); thisObject->m_JSTLSSocket.visit(visitor); visitor.append(thisObject->m_JSTLSSocketSetterValue); thisObject->m_JSTextDecoder.visit(visitor); visitor.append(thisObject->m_JSTextDecoderSetterValue); + thisObject->m_JSTimeout.visit(visitor); visitor.append(thisObject->m_JSTimeoutSetterValue); thisObject->m_JSTranspiler.visit(visitor); visitor.append(thisObject->m_JSTranspilerSetterValue); }
\ No newline at end of file diff --git a/src/bun.js/bindings/ZigGeneratedClasses.cpp b/src/bun.js/bindings/ZigGeneratedClasses.cpp index c1e7f674f..805c4c929 100644 --- a/src/bun.js/bindings/ZigGeneratedClasses.cpp +++ b/src/bun.js/bindings/ZigGeneratedClasses.cpp @@ -12628,6 +12628,209 @@ void JSTextDecoder::visitOutputConstraintsImpl(JSCell* cell, Visitor& visitor) } DEFINE_VISIT_OUTPUT_CONSTRAINTS(JSTextDecoder); +class JSTimeoutPrototype final : public JSC::JSNonFinalObject { +public: + using Base = JSC::JSNonFinalObject; + + static JSTimeoutPrototype* create(JSC::VM& vm, JSGlobalObject* globalObject, JSC::Structure* structure) + { + JSTimeoutPrototype* ptr = new (NotNull, JSC::allocateCell<JSTimeoutPrototype>(vm)) JSTimeoutPrototype(vm, globalObject, structure); + ptr->finishCreation(vm, globalObject); + return ptr; + } + + DECLARE_INFO; + template<typename CellType, JSC::SubspaceAccess> + static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm) + { + return &vm.plainObjectSpace(); + } + static JSC::Structure* createStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::JSValue prototype) + { + return JSC::Structure::create(vm, globalObject, prototype, JSC::TypeInfo(JSC::ObjectType, StructureFlags), info()); + } + +private: + JSTimeoutPrototype(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::Structure* structure) + : Base(vm, structure) + { + } + + void finishCreation(JSC::VM&, JSC::JSGlobalObject*); +}; + +extern "C" void TimeoutClass__finalize(void*); + +extern "C" EncodedJSValue TimeoutPrototype__toPrimitive(void* ptr, JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame); +JSC_DECLARE_HOST_FUNCTION(TimeoutPrototype__toPrimitiveCallback); + +extern "C" EncodedJSValue TimeoutPrototype__hasRef(void* ptr, JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame); +JSC_DECLARE_HOST_FUNCTION(TimeoutPrototype__hasRefCallback); + +extern "C" EncodedJSValue TimeoutPrototype__doRef(void* ptr, JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame); +JSC_DECLARE_HOST_FUNCTION(TimeoutPrototype__refCallback); + +extern "C" EncodedJSValue TimeoutPrototype__doUnref(void* ptr, JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame); +JSC_DECLARE_HOST_FUNCTION(TimeoutPrototype__unrefCallback); + +STATIC_ASSERT_ISO_SUBSPACE_SHARABLE(JSTimeoutPrototype, JSTimeoutPrototype::Base); + +static const HashTableValue JSTimeoutPrototypeTableValues[] = { + { "hasRef"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function | PropertyAttribute::DontDelete), NoIntrinsic, { HashTableValue::NativeFunctionType, TimeoutPrototype__hasRefCallback, 0 } }, + { "ref"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function | PropertyAttribute::DontDelete), NoIntrinsic, { HashTableValue::NativeFunctionType, TimeoutPrototype__refCallback, 0 } }, + { "unref"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function | PropertyAttribute::DontDelete), NoIntrinsic, { HashTableValue::NativeFunctionType, TimeoutPrototype__unrefCallback, 0 } } +}; + +const ClassInfo JSTimeoutPrototype::s_info = { "Timeout"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSTimeoutPrototype) }; + +JSC_DEFINE_HOST_FUNCTION(TimeoutPrototype__toPrimitiveCallback, (JSGlobalObject * lexicalGlobalObject, CallFrame* callFrame)) +{ + auto& vm = lexicalGlobalObject->vm(); + + JSTimeout* thisObject = jsDynamicCast<JSTimeout*>(callFrame->thisValue()); + + if (UNLIKELY(!thisObject)) { + auto throwScope = DECLARE_THROW_SCOPE(vm); + return throwVMTypeError(lexicalGlobalObject, throwScope); + } + + JSC::EnsureStillAliveScope thisArg = JSC::EnsureStillAliveScope(thisObject); + + return TimeoutPrototype__toPrimitive(thisObject->wrapped(), lexicalGlobalObject, callFrame); +} + +JSC_DEFINE_HOST_FUNCTION(TimeoutPrototype__hasRefCallback, (JSGlobalObject * lexicalGlobalObject, CallFrame* callFrame)) +{ + auto& vm = lexicalGlobalObject->vm(); + + JSTimeout* thisObject = jsDynamicCast<JSTimeout*>(callFrame->thisValue()); + + if (UNLIKELY(!thisObject)) { + auto throwScope = DECLARE_THROW_SCOPE(vm); + return throwVMTypeError(lexicalGlobalObject, throwScope); + } + + JSC::EnsureStillAliveScope thisArg = JSC::EnsureStillAliveScope(thisObject); + + return TimeoutPrototype__hasRef(thisObject->wrapped(), lexicalGlobalObject, callFrame); +} + +JSC_DEFINE_HOST_FUNCTION(TimeoutPrototype__refCallback, (JSGlobalObject * lexicalGlobalObject, CallFrame* callFrame)) +{ + auto& vm = lexicalGlobalObject->vm(); + + JSTimeout* thisObject = jsDynamicCast<JSTimeout*>(callFrame->thisValue()); + + if (UNLIKELY(!thisObject)) { + auto throwScope = DECLARE_THROW_SCOPE(vm); + return throwVMTypeError(lexicalGlobalObject, throwScope); + } + + JSC::EnsureStillAliveScope thisArg = JSC::EnsureStillAliveScope(thisObject); + + return TimeoutPrototype__doRef(thisObject->wrapped(), lexicalGlobalObject, callFrame); +} + +JSC_DEFINE_HOST_FUNCTION(TimeoutPrototype__unrefCallback, (JSGlobalObject * lexicalGlobalObject, CallFrame* callFrame)) +{ + auto& vm = lexicalGlobalObject->vm(); + + JSTimeout* thisObject = jsDynamicCast<JSTimeout*>(callFrame->thisValue()); + + if (UNLIKELY(!thisObject)) { + auto throwScope = DECLARE_THROW_SCOPE(vm); + return throwVMTypeError(lexicalGlobalObject, throwScope); + } + + JSC::EnsureStillAliveScope thisArg = JSC::EnsureStillAliveScope(thisObject); + + return TimeoutPrototype__doUnref(thisObject->wrapped(), lexicalGlobalObject, callFrame); +} + +void JSTimeoutPrototype::finishCreation(JSC::VM& vm, JSC::JSGlobalObject* globalObject) +{ + Base::finishCreation(vm); + reifyStaticProperties(vm, JSTimeout::info(), JSTimeoutPrototypeTableValues, *this); + this->putDirect(vm, vm.propertyNames->toPrimitiveSymbol, JSFunction::create(vm, globalObject, 1, String("toPrimitive"_s), TimeoutPrototype__toPrimitiveCallback, ImplementationVisibility::Public), PropertyAttribute::Function | PropertyAttribute::ReadOnly | PropertyAttribute::DontEnum | 0); + JSC_TO_STRING_TAG_WITHOUT_TRANSITION(); +} + +JSTimeout::~JSTimeout() +{ + if (m_ctx) { + TimeoutClass__finalize(m_ctx); + } +} +void JSTimeout::destroy(JSCell* cell) +{ + static_cast<JSTimeout*>(cell)->JSTimeout::~JSTimeout(); +} + +const ClassInfo JSTimeout::s_info = { "Timeout"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSTimeout) }; + +void JSTimeout::finishCreation(VM& vm) +{ + Base::finishCreation(vm); + ASSERT(inherits(info())); +} + +JSTimeout* JSTimeout::create(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::Structure* structure, void* ctx) +{ + JSTimeout* ptr = new (NotNull, JSC::allocateCell<JSTimeout>(vm)) JSTimeout(vm, structure, ctx); + ptr->finishCreation(vm); + return ptr; +} + +extern "C" void* Timeout__fromJS(JSC::EncodedJSValue value) +{ + JSC::JSValue decodedValue = JSC::JSValue::decode(value); + if (decodedValue.isEmpty() || !decodedValue.isCell()) + return nullptr; + + JSC::JSCell* cell = decodedValue.asCell(); + JSTimeout* object = JSC::jsDynamicCast<JSTimeout*>(cell); + + if (!object) + return nullptr; + + return object->wrapped(); +} + +extern "C" bool Timeout__dangerouslySetPtr(JSC::EncodedJSValue value, void* ptr) +{ + JSTimeout* object = JSC::jsDynamicCast<JSTimeout*>(JSValue::decode(value)); + if (!object) + return false; + + object->m_ctx = ptr; + return true; +} + +extern "C" const size_t Timeout__ptrOffset = JSTimeout::offsetOfWrapped(); + +void JSTimeout::analyzeHeap(JSCell* cell, HeapAnalyzer& analyzer) +{ + auto* thisObject = jsCast<JSTimeout*>(cell); + if (void* wrapped = thisObject->wrapped()) { + // if (thisObject->scriptExecutionContext()) + // analyzer.setLabelForCell(cell, "url " + thisObject->scriptExecutionContext()->url().string()); + } + Base::analyzeHeap(cell, analyzer); +} + +JSObject* JSTimeout::createPrototype(VM& vm, JSDOMGlobalObject* globalObject) +{ + return JSTimeoutPrototype::create(vm, globalObject, JSTimeoutPrototype::createStructure(vm, globalObject, globalObject->objectPrototype())); +} + +extern "C" EncodedJSValue Timeout__create(Zig::GlobalObject* globalObject, void* ptr) +{ + auto& vm = globalObject->vm(); + JSC::Structure* structure = globalObject->JSTimeoutStructure(); + JSTimeout* instance = JSTimeout::create(vm, globalObject, structure, ptr); + + return JSValue::encode(instance); +} class JSTranspilerPrototype final : public JSC::JSNonFinalObject { public: using Base = JSC::JSNonFinalObject; diff --git a/src/bun.js/bindings/ZigGeneratedClasses.h b/src/bun.js/bindings/ZigGeneratedClasses.h index d65a687e0..3cd3e8a1a 100644 --- a/src/bun.js/bindings/ZigGeneratedClasses.h +++ b/src/bun.js/bindings/ZigGeneratedClasses.h @@ -1404,6 +1404,56 @@ public: mutable JSC::WriteBarrier<JSC::Unknown> m_encoding; }; +class JSTimeout final : public JSC::JSDestructibleObject { +public: + using Base = JSC::JSDestructibleObject; + static JSTimeout* create(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::Structure* structure, void* ctx); + + DECLARE_EXPORT_INFO; + template<typename, JSC::SubspaceAccess mode> static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm) + { + if constexpr (mode == JSC::SubspaceAccess::Concurrently) + return nullptr; + return WebCore::subspaceForImpl<JSTimeout, WebCore::UseCustomHeapCellType::No>( + vm, + [](auto& spaces) { return spaces.m_clientSubspaceForTimeout.get(); }, + [](auto& spaces, auto&& space) { spaces.m_clientSubspaceForTimeout = std::forward<decltype(space)>(space); }, + [](auto& spaces) { return spaces.m_subspaceForTimeout.get(); }, + [](auto& spaces, auto&& space) { spaces.m_subspaceForTimeout = std::forward<decltype(space)>(space); }); + } + + static void destroy(JSC::JSCell*); + static JSC::Structure* createStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::JSValue prototype) + { + return JSC::Structure::create(vm, globalObject, prototype, JSC::TypeInfo(static_cast<JSC::JSType>(0b11101110), StructureFlags), info()); + } + + static JSObject* createPrototype(VM& vm, JSDOMGlobalObject* globalObject); + ; + + ~JSTimeout(); + + void* wrapped() const { return m_ctx; } + + void detach() + { + m_ctx = nullptr; + } + + static void analyzeHeap(JSCell*, JSC::HeapAnalyzer&); + static ptrdiff_t offsetOfWrapped() { return OBJECT_OFFSETOF(JSTimeout, m_ctx); } + + void* m_ctx { nullptr }; + + JSTimeout(JSC::VM& vm, JSC::Structure* structure, void* sinkPtr) + : Base(vm, structure) + { + m_ctx = sinkPtr; + } + + void finishCreation(JSC::VM&); +}; + class JSTranspiler final : public JSC::JSDestructibleObject { public: using Base = JSC::JSDestructibleObject; diff --git a/src/bun.js/bindings/ZigGlobalObject.cpp b/src/bun.js/bindings/ZigGlobalObject.cpp index feabdd61d..dced9dbdd 100644 --- a/src/bun.js/bindings/ZigGlobalObject.cpp +++ b/src/bun.js/bindings/ZigGlobalObject.cpp @@ -751,6 +751,45 @@ JSC_DEFINE_HOST_FUNCTION(functionQueueMicrotask, return JSC::JSValue::encode(JSC::jsUndefined()); } +JSC_DEFINE_HOST_FUNCTION(functionBunSleepThenCallback, + (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)) +{ + JSC::VM& vm = globalObject->vm(); + + RELEASE_ASSERT(callFrame->argumentCount() == 1); + JSPromise* promise = jsCast<JSC::JSPromise*>(callFrame->argument(0)); + RELEASE_ASSERT(promise); + + promise->resolve(globalObject, JSC::jsUndefined()); + + return JSC::JSValue::encode(promise); +} + +JSC_DEFINE_HOST_FUNCTION(functionBunSleep, + (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)) +{ + JSC::VM& vm = globalObject->vm(); + + JSC::JSValue millisecondsValue = callFrame->argument(0); + + if (millisecondsValue.inherits<JSC::DateInstance>()) { + auto now = MonotonicTime::now(); + auto milliseconds = jsCast<JSC::DateInstance*>(millisecondsValue)->internalNumber() - now.approximateWallTime().secondsSinceEpoch().milliseconds(); + millisecondsValue = JSC::jsNumber(milliseconds > 0 ? milliseconds : 0); + } + + if (!millisecondsValue.isNumber()) { + auto scope = DECLARE_THROW_SCOPE(globalObject->vm()); + JSC::throwTypeError(globalObject, scope, "sleep expects a number (milliseconds)"_s); + return JSC::JSValue::encode(JSC::JSValue {}); + } + + Zig::GlobalObject* global = JSC::jsCast<Zig::GlobalObject*>(globalObject); + JSC::JSPromise* promise = JSC::JSPromise::create(vm, globalObject->promiseStructure()); + Bun__Timer__setTimeout(globalObject, JSC::JSValue::encode(global->bunSleepThenCallback()), JSC::JSValue::encode(millisecondsValue), JSValue::encode(promise)); + return JSC::JSValue::encode(promise); +} + JSC_DEFINE_HOST_FUNCTION(functionSetTimeout, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)) { @@ -778,7 +817,7 @@ JSC_DEFINE_HOST_FUNCTION(functionSetTimeout, JSC::JSArray* argumentsArray = JSC::JSArray::tryCreateUninitializedRestricted( initializationScope, nullptr, globalObject->arrayStructureForIndexingTypeDuringAllocation(JSC::ArrayWithContiguous), - argumentCount); + argumentCount - 2); if (UNLIKELY(!argumentsArray)) { auto scope = DECLARE_THROW_SCOPE(globalObject->vm()); @@ -786,8 +825,8 @@ JSC_DEFINE_HOST_FUNCTION(functionSetTimeout, return JSC::JSValue::encode(JSC::JSValue {}); } - for (size_t i = 0; i < argumentCount; i++) { - argumentsArray->putDirectIndex(globalObject, i, callFrame->uncheckedArgument(i + 2)); + for (size_t i = 2; i < argumentCount; i++) { + argumentsArray->putDirectIndex(globalObject, i - 2, callFrame->uncheckedArgument(i)); } arguments = JSValue(argumentsArray); } @@ -802,46 +841,7 @@ JSC_DEFINE_HOST_FUNCTION(functionSetTimeout, return Bun__Timer__setTimeout(globalObject, JSC::JSValue::encode(job), JSC::JSValue::encode(num), JSValue::encode(arguments)); } -JSC_DEFINE_HOST_FUNCTION(functionBunSleepThenCallback, - (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)) -{ - JSC::VM& vm = globalObject->vm(); - - RELEASE_ASSERT(callFrame->argumentCount() == 1); - JSPromise* promise = jsCast<JSC::JSPromise*>(callFrame->argument(0)); - RELEASE_ASSERT(promise); - - promise->resolve(globalObject, JSC::jsUndefined()); - - return JSC::JSValue::encode(promise); -} - -JSC_DEFINE_HOST_FUNCTION(functionBunSleep, - (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)) -{ - JSC::VM& vm = globalObject->vm(); - - JSC::JSValue millisecondsValue = callFrame->argument(0); - - if (millisecondsValue.inherits<JSC::DateInstance>()) { - auto now = MonotonicTime::now(); - auto milliseconds = jsCast<JSC::DateInstance*>(millisecondsValue)->internalNumber() - now.approximateWallTime().secondsSinceEpoch().milliseconds(); - millisecondsValue = JSC::jsNumber(milliseconds > 0 ? milliseconds : 0); - } - - if (!millisecondsValue.isNumber()) { - auto scope = DECLARE_THROW_SCOPE(globalObject->vm()); - JSC::throwTypeError(globalObject, scope, "sleep expects a number (milliseconds)"_s); - return JSC::JSValue::encode(JSC::JSValue {}); - } - - Zig::GlobalObject* global = JSC::jsCast<Zig::GlobalObject*>(globalObject); - JSC::JSPromise* promise = JSC::JSPromise::create(vm, globalObject->promiseStructure()); - Bun__Timer__setTimeout(globalObject, JSC::JSValue::encode(global->bunSleepThenCallback()), JSC::JSValue::encode(millisecondsValue), JSValue::encode(promise)); - return JSC::JSValue::encode(promise); -} - -static JSC_DEFINE_HOST_FUNCTION(functionSetInterval, +JSC_DEFINE_HOST_FUNCTION(functionSetInterval, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)) { JSC::VM& vm = globalObject->vm(); @@ -868,7 +868,7 @@ static JSC_DEFINE_HOST_FUNCTION(functionSetInterval, JSC::JSArray* argumentsArray = JSC::JSArray::tryCreateUninitializedRestricted( initializationScope, nullptr, globalObject->arrayStructureForIndexingTypeDuringAllocation(JSC::ArrayWithContiguous), - argumentCount); + argumentCount - 2); if (UNLIKELY(!argumentsArray)) { auto scope = DECLARE_THROW_SCOPE(globalObject->vm()); @@ -876,8 +876,8 @@ static JSC_DEFINE_HOST_FUNCTION(functionSetInterval, return JSC::JSValue::encode(JSC::JSValue {}); } - for (size_t i = 0; i < argumentCount; i++) { - argumentsArray->putDirectIndex(globalObject, i, callFrame->uncheckedArgument(i + 2)); + for (size_t i = 2; i < argumentCount; i++) { + argumentsArray->putDirectIndex(globalObject, i - 2, callFrame->uncheckedArgument(i)); } arguments = JSValue(argumentsArray); } @@ -892,7 +892,7 @@ static JSC_DEFINE_HOST_FUNCTION(functionSetInterval, return Bun__Timer__setInterval(globalObject, JSC::JSValue::encode(job), JSC::JSValue::encode(num), JSValue::encode(arguments)); } -static JSC_DEFINE_HOST_FUNCTION(functionClearInterval, +JSC_DEFINE_HOST_FUNCTION(functionClearInterval, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)) { JSC::VM& vm = globalObject->vm(); @@ -908,7 +908,7 @@ static JSC_DEFINE_HOST_FUNCTION(functionClearInterval, return Bun__Timer__clearInterval(globalObject, JSC::JSValue::encode(num)); } -static JSC_DEFINE_HOST_FUNCTION(functionClearTimeout, +JSC_DEFINE_HOST_FUNCTION(functionClearTimeout, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)) { JSC::VM& vm = globalObject->vm(); @@ -924,7 +924,7 @@ static JSC_DEFINE_HOST_FUNCTION(functionClearTimeout, return Bun__Timer__clearTimeout(globalObject, JSC::JSValue::encode(num)); } -static JSC_DEFINE_HOST_FUNCTION(functionBTOA, +JSC_DEFINE_HOST_FUNCTION(functionBTOA, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)) { JSC::VM& vm = globalObject->vm(); @@ -2954,12 +2954,12 @@ static JSC_DEFINE_HOST_FUNCTION(functionSetImmediate, JSC::JSValue arguments = {}; size_t argumentCount = callFrame->argumentCount() - 1; - if (argumentCount > 0) { + if (argumentCount > 1) { JSC::ObjectInitializationScope initializationScope(globalObject->vm()); JSC::JSArray* argumentsArray = JSC::JSArray::tryCreateUninitializedRestricted( initializationScope, nullptr, globalObject->arrayStructureForIndexingTypeDuringAllocation(JSC::ArrayWithContiguous), - argumentCount); + argumentCount - 1); if (UNLIKELY(!argumentsArray)) { auto scope = DECLARE_THROW_SCOPE(globalObject->vm()); @@ -2967,8 +2967,8 @@ static JSC_DEFINE_HOST_FUNCTION(functionSetImmediate, return JSC::JSValue::encode(JSC::JSValue {}); } - for (size_t i = 0; i < argumentCount; i++) { - argumentsArray->putDirectIndex(globalObject, i, callFrame->uncheckedArgument(i + 1)); + for (size_t i = 1; i < argumentCount; i++) { + argumentsArray->putDirectIndex(globalObject, i - 1, callFrame->uncheckedArgument(i)); } arguments = JSValue(argumentsArray); } diff --git a/src/bun.js/bindings/exports.zig b/src/bun.js/bindings/exports.zig index 9e1f03592..15ee53176 100644 --- a/src/bun.js/bindings/exports.zig +++ b/src/bun.js/bindings/exports.zig @@ -2113,6 +2113,20 @@ pub const ZigConsoleClient = struct { .Object, enable_ansi_colors, ); + } else if (value.as(JSC.API.Bun.Timer.TimerObject)) |timer| { + this.addForNewLine("Timeout(# ) ".len + bun.fmt.fastDigitCount(@intCast(u64, @max(timer.id, 0)))); + if (timer.kind == .setInterval) { + this.addForNewLine("repeats ".len + bun.fmt.fastDigitCount(@intCast(u64, @max(timer.id, 0)))); + writer.print(comptime Output.prettyFmt("<r><blue>Timeout<r> <d>(#<yellow>{d}<r><d>, repeats)<r>", enable_ansi_colors), .{ + timer.id, + }); + } else { + writer.print(comptime Output.prettyFmt("<r><blue>Timeout<r> <d>(#<yellow>{d}<r><d>)<r>", enable_ansi_colors), .{ + timer.id, + }); + } + + return; } else if (jsType != .DOMWrapper) { if (CAPI.JSObjectGetPrivate(value.asRef())) |private_data_ptr| { const priv_data = JSPrivateDataPtr.from(private_data_ptr); diff --git a/src/bun.js/bindings/generated_classes.zig b/src/bun.js/bindings/generated_classes.zig index 599d05d7e..dec2d6543 100644 --- a/src/bun.js/bindings/generated_classes.zig +++ b/src/bun.js/bindings/generated_classes.zig @@ -3585,6 +3585,74 @@ pub const JSTextDecoder = struct { } } }; +pub const JSTimeout = struct { + const Timeout = Classes.Timeout; + const GetterType = fn (*Timeout, *JSC.JSGlobalObject) callconv(.C) JSC.JSValue; + const GetterTypeWithThisValue = fn (*Timeout, JSC.JSValue, *JSC.JSGlobalObject) callconv(.C) JSC.JSValue; + const SetterType = fn (*Timeout, *JSC.JSGlobalObject, JSC.JSValue) callconv(.C) bool; + const SetterTypeWithThisValue = fn (*Timeout, JSC.JSValue, *JSC.JSGlobalObject, JSC.JSValue) callconv(.C) bool; + const CallbackType = fn (*Timeout, *JSC.JSGlobalObject, *JSC.CallFrame) callconv(.C) JSC.JSValue; + + /// Return the pointer to the wrapped object. + /// If the object does not match the type, return null. + pub fn fromJS(value: JSC.JSValue) ?*Timeout { + JSC.markBinding(@src()); + return Timeout__fromJS(value); + } + + /// Create a new instance of Timeout + pub fn toJS(this: *Timeout, globalObject: *JSC.JSGlobalObject) JSC.JSValue { + JSC.markBinding(@src()); + if (comptime Environment.allow_assert) { + const value__ = Timeout__create(globalObject, this); + std.debug.assert(value__.as(Timeout).? == this); // If this fails, likely a C ABI issue. + return value__; + } else { + return Timeout__create(globalObject, this); + } + } + + /// Modify the internal ptr to point to a new instance of Timeout. + pub fn dangerouslySetPtr(value: JSC.JSValue, ptr: ?*Timeout) bool { + JSC.markBinding(@src()); + return Timeout__dangerouslySetPtr(value, ptr); + } + + /// Detach the ptr from the thisValue + pub fn detachPtr(_: *Timeout, value: JSC.JSValue) void { + JSC.markBinding(@src()); + std.debug.assert(Timeout__dangerouslySetPtr(value, null)); + } + + extern fn Timeout__fromJS(JSC.JSValue) ?*Timeout; + extern fn Timeout__getConstructor(*JSC.JSGlobalObject) JSC.JSValue; + + extern fn Timeout__create(globalObject: *JSC.JSGlobalObject, ptr: ?*Timeout) JSC.JSValue; + + extern fn Timeout__dangerouslySetPtr(JSC.JSValue, ?*Timeout) bool; + + comptime { + if (@TypeOf(Timeout.finalize) != (fn (*Timeout) callconv(.C) void)) { + @compileLog("Timeout.finalize is not a finalizer"); + } + + if (@TypeOf(Timeout.toPrimitive) != CallbackType) + @compileLog("Expected Timeout.toPrimitive to be a callback but received " ++ @typeName(@TypeOf(Timeout.toPrimitive))); + if (@TypeOf(Timeout.hasRef) != CallbackType) + @compileLog("Expected Timeout.hasRef to be a callback but received " ++ @typeName(@TypeOf(Timeout.hasRef))); + if (@TypeOf(Timeout.doRef) != CallbackType) + @compileLog("Expected Timeout.doRef to be a callback but received " ++ @typeName(@TypeOf(Timeout.doRef))); + if (@TypeOf(Timeout.doUnref) != CallbackType) + @compileLog("Expected Timeout.doUnref to be a callback but received " ++ @typeName(@TypeOf(Timeout.doUnref))); + if (!JSC.is_bindgen) { + @export(Timeout.doRef, .{ .name = "TimeoutPrototype__doRef" }); + @export(Timeout.doUnref, .{ .name = "TimeoutPrototype__doUnref" }); + @export(Timeout.finalize, .{ .name = "TimeoutClass__finalize" }); + @export(Timeout.hasRef, .{ .name = "TimeoutPrototype__hasRef" }); + @export(Timeout.toPrimitive, .{ .name = "TimeoutPrototype__toPrimitive" }); + } + } +}; pub const JSTranspiler = struct { const Transpiler = Classes.Transpiler; const GetterType = fn (*Transpiler, *JSC.JSGlobalObject) callconv(.C) JSC.JSValue; @@ -3691,5 +3759,6 @@ comptime { _ = JSTCPSocket; _ = JSTLSSocket; _ = JSTextDecoder; + _ = JSTimeout; _ = JSTranspiler; } diff --git a/src/bun.js/bindings/generated_classes_list.zig b/src/bun.js/bindings/generated_classes_list.zig index 446416a3e..d779a4ff1 100644 --- a/src/bun.js/bindings/generated_classes_list.zig +++ b/src/bun.js/bindings/generated_classes_list.zig @@ -26,4 +26,5 @@ pub const Classes = struct { pub const NodeJSFS = JSC.Node.NodeJSFS; pub const Transpiler = JSC.API.Transpiler; pub const Stats = JSC.Node.Stats; + pub const Timeout = JSC.API.Bun.Timer.TimerObject; }; diff --git a/src/bun.js/bindings/headers-cpp.h b/src/bun.js/bindings/headers-cpp.h index c57c65e18..45ef8dbb7 100644 --- a/src/bun.js/bindings/headers-cpp.h +++ b/src/bun.js/bindings/headers-cpp.h @@ -1,4 +1,4 @@ -//-- AUTOGENERATED FILE -- 1676656020 +//-- AUTOGENERATED FILE -- 1676701449 // clang-format off #pragma once diff --git a/src/bun.js/node/node.classes.ts b/src/bun.js/node/node.classes.ts index 14a9ebbc5..5bebabe72 100644 --- a/src/bun.js/node/node.classes.ts +++ b/src/bun.js/node/node.classes.ts @@ -2,6 +2,33 @@ import { define } from "../scripts/class-definitions"; export default [ define({ + name: "Timeout", + construct: false, + noConstructor: true, + finalize: true, + configurable: false, + klass: {}, + JSType: "0b11101110", + proto: { + ref: { + fn: "doRef", + length: 0, + }, + unref: { + fn: "doUnref", + length: 0, + }, + hasRef: { + fn: "hasRef", + length: 0, + }, + ["@@toPrimitive"]: { + fn: "toPrimitive", + length: 1, + }, + }, + }), + define({ name: "Stats", construct: true, finalize: true, diff --git a/src/bun.js/node_timers.exports.js b/src/bun.js/node_timers.exports.js index d1f3e81e7..52dec5baa 100644 --- a/src/bun.js/node_timers.exports.js +++ b/src/bun.js/node_timers.exports.js @@ -1,110 +1,7 @@ // This implementation isn't 100% correct // Ref/unref does not impact whether the process is kept alive -var clear = Symbol("clear"); -class Timeout { - #id; - #refCount = 1; - #clearFunction; - - constructor(id, clearFunction) { - this.#id = id; - this.#refCount = 1; - this.#clearFunction = clearFunction; - } - - ref() { - this.#refCount += 1; - } - - hasRef() { - return this.#refCount > 0; - } - - [clear]() { - this.#refCount = 0; - var clearFunction = this.#clearFunction; - if (clearFunction) { - this.#clearFunction = null; - clearFunction(this.#id); - } - } - - unref() { - this.#refCount -= 1; - var clearFunction = this.#clearFunction; - if (clearFunction && this.#refCount === 0) { - this.#clearFunction = null; - clearFunction(this.#id); - } - } -} -var { - setTimeout: setTimeout_, - setImmediate: setImmediate_, - clearTimeout: clearTimeout_, - setInterval: setInterval_, - clearInterval: clearInterval_, -} = globalThis; - -export function setImmediate(callback, ...args) { - if (typeof callback !== "function") { - throw new TypeError("callback must be a function"); - } - var cleared = false; - function clearImmediate(id) { - cleared = true; - } - - const wrapped = function (callback, args) { - if (cleared) { - return; - } - cleared = true; - try { - callback(...args); - } catch (e) { - reportError(e); - } finally { - } - }; - - return new Timeout(setImmediate_(wrapped, callback, args), clearImmediate); -} - -export function setTimeout(callback, delay, ...args) { - if (typeof callback !== "function") { - throw new TypeError("callback must be a function"); - } - - return new Timeout(setTimeout_.apply(globalThis, arguments), clearTimeout_); -} - -export function setInterval(callback, delay, ...args) { - if (typeof callback !== "function") { - throw new TypeError("callback must be a function"); - } - - return new Timeout(setInterval_.apply(globalThis, arguments), clearInterval_); -} - -export function clearTimeout(id) { - if (id && typeof id === "object" && id[clear]) { - id[clear](); - return; - } - - clearTimeout_(id); -} - -export function clearInterval(id) { - if (id && typeof id === "object" && id[clear]) { - id[clear](); - return; - } - - clearInterval_(id); -} +export var { setTimeout, clearTimeout, setInterval, setImmediate, clearInterval, clearImmediate } = globalThis; export default { setInterval, diff --git a/test/bun.js/inspect.test.js b/test/bun.js/inspect.test.js index 229f80548..243f23cdc 100644 --- a/test/bun.js/inspect.test.js +++ b/test/bun.js/inspect.test.js @@ -1,5 +1,14 @@ import { it, expect, describe } from "bun:test"; +it("Timeout", () => { + const id = setTimeout(() => {}, 0); + expect(Bun.inspect(id)).toBe(`Timeout (#${+id})`); + + const id2 = setInterval(() => {}, 1); + id2.unref(); + expect(Bun.inspect(id2)).toBe(`Timeout (#${+id2}, repeats)`); +}); + it("when prototype defines the same property, don't print the same property twice", () => { var base = { foo: "123", diff --git a/test/bun.js/setInterval.test.js b/test/bun.js/setInterval.test.js index 0981df8b1..7b03afba5 100644 --- a/test/bun.js/setInterval.test.js +++ b/test/bun.js/setInterval.test.js @@ -14,8 +14,7 @@ it("setInterval", async () => { clearInterval(id); } try { - expect(args.length).toBe(1); - expect(args[0]).toBe("foo"); + expect(args).toStrictEqual(["foo"]); } catch (err) { reject(err); clearInterval(id); diff --git a/test/bun.js/setTimeout.test.js b/test/bun.js/setTimeout.test.js index 393a32bbe..88472adc7 100644 --- a/test/bun.js/setTimeout.test.js +++ b/test/bun.js/setTimeout.test.js @@ -13,8 +13,7 @@ it("setTimeout", async () => { resolve(numbers); } try { - expect(args.length).toBe(1); - expect(args[0]).toBe("foo"); + expect(args).toStrictEqual(["foo"]); } catch (err) { reject(err); } @@ -22,7 +21,7 @@ it("setTimeout", async () => { i, "foo", ); - expect(id > lastID).toBe(true); + expect(+id > lastID).toBe(true); lastID = id; } }); @@ -35,14 +34,30 @@ it("setTimeout", async () => { it("clearTimeout", async () => { var called = false; - const id = setTimeout(() => { - called = true; - expect(false).toBe(true); - }, 1); - clearTimeout(id); - // assert it doesn't crash if you call clearTimeout twice - clearTimeout(id); + // as object + { + const id = setTimeout(() => { + called = true; + expect(false).toBe(true); + }, 0); + clearTimeout(id); + + // assert it doesn't crash if you call clearTimeout twice + clearTimeout(id); + } + + // as number + { + const id = setTimeout(() => { + called = true; + expect(false).toBe(true); + }, 0); + clearTimeout(+id); + + // assert it doesn't crash if you call clearTimeout twice + clearTimeout(+id); + } await new Promise((resolve, reject) => { setTimeout(resolve, 10); @@ -118,7 +133,7 @@ it("Bun.sleep propagates exceptions", async () => { it("Bun.sleep works with a Date object", async () => { var ten_ms = new Date(); - ten_ms.setMilliseconds(ten_ms.getMilliseconds() + 10); + ten_ms.setMilliseconds(ten_ms.getMilliseconds() + 12); const now = performance.now(); await Bun.sleep(ten_ms); expect(performance.now() - now).toBeGreaterThanOrEqual(10); @@ -135,3 +150,24 @@ it("node.js timers/promises setTimeout propagates exceptions", async () => { expect(err.message).toBe("TestPassed"); } }); + +it.skip("order of setTimeouts", done => { + var nums = []; + var maybeDone = cb => { + return () => { + cb(); + if (nums.length === 4) { + try { + expect(nums).toEqual([1, 2, 3, 4]); + done(); + } catch (e) { + done(e); + } + } + }; + }; + setTimeout(maybeDone(() => nums.push(2))); + setTimeout(maybeDone(() => nums.push(3), 0)); + setTimeout(maybeDone(() => nums.push(4), 1)); + Promise.resolve().then(maybeDone(() => nums.push(1))); +}); |