diff --git a/src/bun.js/bindings/ErrorCode.ts b/src/bun.js/bindings/ErrorCode.ts index 0a690e4b22..23e6b0b35f 100644 --- a/src/bun.js/bindings/ErrorCode.ts +++ b/src/bun.js/bindings/ErrorCode.ts @@ -16,6 +16,7 @@ const errors: ErrorCodeMapping = [ ["ABORT_ERR", Error, "AbortError"], ["ERR_ACCESS_DENIED", Error], ["ERR_AMBIGUOUS_ARGUMENT", TypeError], + ["ERR_ARG_NOT_ITERABLE", TypeError], ["ERR_ASSERTION", Error], ["ERR_ASYNC_CALLBACK", TypeError], ["ERR_ASYNC_TYPE", TypeError], diff --git a/src/bun.js/bindings/JSDOMExceptionHandling.cpp b/src/bun.js/bindings/JSDOMExceptionHandling.cpp index cfa87ce0a4..7580eed5d9 100644 --- a/src/bun.js/bindings/JSDOMExceptionHandling.cpp +++ b/src/bun.js/bindings/JSDOMExceptionHandling.cpp @@ -278,9 +278,13 @@ JSC::EncodedJSValue throwConstructorScriptExecutionContextUnavailableError(JSC:: return throwVMError(&lexicalGlobalObject, scope, createReferenceError(&lexicalGlobalObject, makeString(interfaceName, " constructor associated execution context is unavailable"_s))); } -void throwSequenceTypeError(JSC::JSGlobalObject& lexicalGlobalObject, JSC::ThrowScope& scope) +void throwSequenceTypeError(JSC::JSGlobalObject& lexicalGlobalObject, JSC::ThrowScope& scope, ASCIILiteral functionName, ASCIILiteral argumentName) { - Bun::throwError(&lexicalGlobalObject, scope, Bun::ErrorCode::ERR_INVALID_ARG_TYPE, "Value is not a sequence"_s); + if (functionName && argumentName) { + Bun::throwError(&lexicalGlobalObject, scope, Bun::ErrorCode::ERR_ARG_NOT_ITERABLE, makeString(functionName, ": "_s, argumentName, " is not iterable."_s)); + } else { + Bun::throwError(&lexicalGlobalObject, scope, Bun::ErrorCode::ERR_INVALID_ARG_TYPE, "Value is not a sequence"_s); + } } void throwNonFiniteTypeError(JSGlobalObject& lexicalGlobalObject, JSC::ThrowScope& scope) diff --git a/src/bun.js/bindings/JSDOMExceptionHandling.h b/src/bun.js/bindings/JSDOMExceptionHandling.h index 20e284a71d..e208b48800 100644 --- a/src/bun.js/bindings/JSDOMExceptionHandling.h +++ b/src/bun.js/bindings/JSDOMExceptionHandling.h @@ -45,7 +45,7 @@ void throwInvalidStateError(JSC::JSGlobalObject&, JSC::ThrowScope&, ASCIILiteral WEBCORE_EXPORT void throwNonFiniteTypeError(JSC::JSGlobalObject&, JSC::ThrowScope&); void throwNotSupportedError(JSC::JSGlobalObject&, JSC::ThrowScope&, ASCIILiteral); void throwSecurityError(JSC::JSGlobalObject&, JSC::ThrowScope&, const String& message); -WEBCORE_EXPORT void throwSequenceTypeError(JSC::JSGlobalObject&, JSC::ThrowScope&); +WEBCORE_EXPORT void throwSequenceTypeError(JSC::JSGlobalObject&, JSC::ThrowScope&, ASCIILiteral functionName = {}, ASCIILiteral argumentName = {}); WEBCORE_EXPORT JSC::EncodedJSValue throwArgumentMustBeEnumError(JSC::JSGlobalObject&, JSC::ThrowScope&, unsigned argumentIndex, ASCIILiteral argumentName, ASCIILiteral functionInterfaceName, ASCIILiteral functionName, ASCIILiteral expectedValues); WEBCORE_EXPORT JSC::EncodedJSValue throwArgumentMustBeFunctionError(JSC::JSGlobalObject&, JSC::ThrowScope&, unsigned argumentIndex, ASCIILiteral argumentName, ASCIILiteral functionInterfaceName, ASCIILiteral functionName); diff --git a/src/bun.js/bindings/webcore/BroadcastChannel.cpp b/src/bun.js/bindings/webcore/BroadcastChannel.cpp index 67fcd6d330..37fffd0400 100644 --- a/src/bun.js/bindings/webcore/BroadcastChannel.cpp +++ b/src/bun.js/bindings/webcore/BroadcastChannel.cpp @@ -267,7 +267,7 @@ void BroadcastChannel::dispatchMessage(Ref&& message) auto& vm = JSC::getVM(globalObject); auto scope = DECLARE_CATCH_SCOPE(vm); Vector> dummyPorts; - auto event = MessageEvent::create(*globalObject, WTFMove(message), {}, {}, std::nullopt, WTFMove(dummyPorts)); + auto event = MessageEvent::create(*globalObject, WTFMove(message), {}, {}, nullptr, WTFMove(dummyPorts)); if (UNLIKELY(scope.exception())) { // Currently, we assume that the only way we can get here is if we have a termination. RELEASE_ASSERT(vm.hasPendingTerminationException()); diff --git a/src/bun.js/bindings/webcore/JSDOMConvertBase.h b/src/bun.js/bindings/webcore/JSDOMConvertBase.h index 737b65bc12..a60e301d11 100644 --- a/src/bun.js/bindings/webcore/JSDOMConvertBase.h +++ b/src/bun.js/bindings/webcore/JSDOMConvertBase.h @@ -56,6 +56,8 @@ template typename Converter::ReturnType convert(JSC::JSGlobalObje template typename Converter::ReturnType convert(JSC::JSGlobalObject&, JSC::JSValue, JSC::JSObject&); template typename Converter::ReturnType convert(JSC::JSGlobalObject&, JSC::JSValue, JSDOMGlobalObject&); template typename Converter::ReturnType convert(JSC::JSGlobalObject&, JSC::JSValue, ExceptionThrower&&); +template typename Converter::ReturnType convert(JSC::JSGlobalObject&, JSC::JSValue, ExceptionThrower&&, ASCIILiteral functionName, ASCIILiteral argumentName); + template typename Converter::ReturnType convert(JSC::JSGlobalObject&, JSC::JSValue, JSC::JSObject&, ExceptionThrower&&); template typename Converter::ReturnType convert(JSC::JSGlobalObject&, JSC::JSValue, JSDOMGlobalObject&, ExceptionThrower&&); @@ -79,6 +81,11 @@ template inline typename Converter::Re return Converter::convert(lexicalGlobalObject, value, std::forward(exceptionThrower)); } +template inline typename Converter::ReturnType convert(JSC::JSGlobalObject& lexicalGlobalObject, JSC::JSValue value, ExceptionThrower&& exceptionThrower, ASCIILiteral functionName, ASCIILiteral argumentName) +{ + return Converter::convert(lexicalGlobalObject, value, std::forward(exceptionThrower), functionName, argumentName); +} + template inline typename Converter::ReturnType convert(JSC::JSGlobalObject& lexicalGlobalObject, JSC::JSValue value, JSC::JSObject& thisObject, ExceptionThrower&& exceptionThrower) { return Converter::convert(lexicalGlobalObject, value, thisObject, std::forward(exceptionThrower)); diff --git a/src/bun.js/bindings/webcore/JSDOMConvertSequences.h b/src/bun.js/bindings/webcore/JSDOMConvertSequences.h index 3eada58623..28ed89119d 100644 --- a/src/bun.js/bindings/webcore/JSDOMConvertSequences.h +++ b/src/bun.js/bindings/webcore/JSDOMConvertSequences.h @@ -60,6 +60,21 @@ struct GenericSequenceConverter { return WTFMove(result); } + template + static ReturnType convert(JSC::JSGlobalObject& lexicalGlobalObject, JSC::JSObject* object, ExceptionThrower&& exceptionThrower = ExceptionThrower()) + { + ReturnType result; + forEachInIterable(&lexicalGlobalObject, object, [&result, &exceptionThrower](JSC::VM& vm, JSC::JSGlobalObject* lexicalGlobalObject, JSC::JSValue nextValue) { + auto scope = DECLARE_THROW_SCOPE(vm); + + auto convertedValue = Converter::convert(*lexicalGlobalObject, nextValue, std::forward(exceptionThrower)); + if (UNLIKELY(scope.exception())) + return; + result.append(WTFMove(convertedValue)); + }); + return WTFMove(result); + } + static ReturnType convert(JSC::JSGlobalObject& lexicalGlobalObject, JSC::JSObject* object, JSC::JSValue method) { return convert(lexicalGlobalObject, object, method, ReturnType()); @@ -240,13 +255,58 @@ struct SequenceConverter { return result; } - static ReturnType convert(JSC::JSGlobalObject& lexicalGlobalObject, JSC::JSValue value) + template + static ReturnType convertArray(JSC::JSGlobalObject& lexicalGlobalObject, JSC::JSArray* array, ExceptionThrower&& exceptionThrower = ExceptionThrower()) + { + auto& vm = lexicalGlobalObject.vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + unsigned length = array->length(); + + ReturnType result; + if (!result.tryReserveCapacity(length)) { + // FIXME: Is the right exception to throw? + throwTypeError(&lexicalGlobalObject, scope); + return {}; + } + + JSC::IndexingType indexingType = array->indexingType() & JSC::IndexingShapeMask; + + if (indexingType == JSC::ContiguousShape) { + for (unsigned i = 0; i < length; i++) { + auto indexValue = array->butterfly()->contiguous().at(array, i).get(); + if (!indexValue) + indexValue = JSC::jsUndefined(); + + auto convertedValue = Converter::convert(lexicalGlobalObject, indexValue, std::forward(exceptionThrower)); + RETURN_IF_EXCEPTION(scope, {}); + + result.append(convertedValue); + } + return result; + } + + for (unsigned i = 0; i < length; i++) { + auto indexValue = array->getDirectIndex(&lexicalGlobalObject, i); + RETURN_IF_EXCEPTION(scope, {}); + + if (!indexValue) + indexValue = JSC::jsUndefined(); + + auto convertedValue = Converter::convert(lexicalGlobalObject, indexValue, std::forward(exceptionThrower)); + RETURN_IF_EXCEPTION(scope, {}); + + result.append(convertedValue); + } + return result; + } + + static ReturnType convert(JSC::JSGlobalObject& lexicalGlobalObject, JSC::JSValue value, ASCIILiteral functionName = {}, ASCIILiteral argumentName = {}) { auto& vm = JSC::getVM(&lexicalGlobalObject); auto scope = DECLARE_THROW_SCOPE(vm); if (!value.isObject()) { - throwSequenceTypeError(lexicalGlobalObject, scope); + throwSequenceTypeError(lexicalGlobalObject, scope, functionName, argumentName); return {}; } @@ -264,6 +324,34 @@ struct SequenceConverter { RELEASE_AND_RETURN(scope, (convertArray(lexicalGlobalObject, array))); } + template + static ReturnType convert(JSC::JSGlobalObject& lexicalGlobalObject, + JSC::JSValue value, + ExceptionThrower&& exceptionThrower = ExceptionThrower(), + ASCIILiteral functionName = {}, ASCIILiteral argumentName = {}) + { + auto& vm = JSC::getVM(&lexicalGlobalObject); + auto scope = DECLARE_THROW_SCOPE(vm); + + if (!value.isObject()) { + throwSequenceTypeError(lexicalGlobalObject, scope, functionName, argumentName); + return {}; + } + + JSC::JSObject* object = JSC::asObject(value); + if (Converter::conversionHasSideEffects) + RELEASE_AND_RETURN(scope, (GenericConverter::convert(lexicalGlobalObject, object, std::forward(exceptionThrower)))); + + if (!JSC::isJSArray(object)) + RELEASE_AND_RETURN(scope, (GenericConverter::convert(lexicalGlobalObject, object, std::forward(exceptionThrower)))); + + JSC::JSArray* array = JSC::asArray(object); + if (!array->isIteratorProtocolFastAndNonObservable()) + RELEASE_AND_RETURN(scope, (GenericConverter::convert(lexicalGlobalObject, object, std::forward(exceptionThrower)))); + + RELEASE_AND_RETURN(scope, (convertArray(lexicalGlobalObject, array, std::forward(exceptionThrower)))); + } + static ReturnType convert(JSC::JSGlobalObject& lexicalGlobalObject, JSC::JSObject* object, JSC::JSValue method) { if (Converter::conversionHasSideEffects) @@ -360,15 +448,20 @@ struct SequenceConverter { template struct Converter> : DefaultConverter> { using ReturnType = typename Detail::SequenceConverter::ReturnType; - static ReturnType convert(JSC::JSGlobalObject& lexicalGlobalObject, JSC::JSValue value) + static ReturnType convert(JSC::JSGlobalObject& lexicalGlobalObject, JSC::JSValue value, ASCIILiteral functionName = {}, ASCIILiteral argumentName = {}) { - return Detail::SequenceConverter::convert(lexicalGlobalObject, value); + return Detail::SequenceConverter::convert(lexicalGlobalObject, value, functionName, argumentName); } static ReturnType convert(JSC::JSGlobalObject& lexicalGlobalObject, JSC::JSObject* object, JSC::JSValue method) { return Detail::SequenceConverter::convert(lexicalGlobalObject, object, method); } + + template static ReturnType convert(JSC::JSGlobalObject& lexicalGlobalObject, JSC::JSValue value, ExceptionThrower&& exceptionThrower, ASCIILiteral functionName = {}, ASCIILiteral argumentName = {}) + { + return Detail::SequenceConverter::convert(lexicalGlobalObject, value, std::forward(exceptionThrower), functionName, argumentName); + } }; template struct JSConverter> { diff --git a/src/bun.js/bindings/webcore/JSMessageEvent.cpp b/src/bun.js/bindings/webcore/JSMessageEvent.cpp index 9186e193e9..015b153de8 100644 --- a/src/bun.js/bindings/webcore/JSMessageEvent.cpp +++ b/src/bun.js/bindings/webcore/JSMessageEvent.cpp @@ -56,6 +56,7 @@ #include #include #include +#include "ErrorCode.h" namespace WebCore { using namespace JSC; @@ -151,7 +152,16 @@ template<> MessageEvent::Init convertDictionary(JSGlobalObje RETURN_IF_EXCEPTION(throwScope, {}); } if (!portsValue.isUndefined()) { - result.ports = convert>>(lexicalGlobalObject, portsValue); + result.ports = convert>>( + lexicalGlobalObject, + portsValue, + [](JSGlobalObject& lexicalGlobalObject, ThrowScope& throwScope) { + Bun::ERR::INVALID_ARG_TYPE(throwScope, + &lexicalGlobalObject, + "MessageEvent constructor: Expected every item of eventInitDict.ports to be an instance of MessagePort."_s); + }, + "MessageEvent constructor"_s, + "eventInitDict.ports"_s); RETURN_IF_EXCEPTION(throwScope, {}); } else result.ports = Converter>>::ReturnType {}; @@ -162,11 +172,14 @@ template<> MessageEvent::Init convertDictionary(JSGlobalObje sourceValue = object->get(&lexicalGlobalObject, Identifier::fromString(vm, "source"_s)); RETURN_IF_EXCEPTION(throwScope, {}); } - // if (!sourceValue.isUndefined()) { - // result.source = convert, IDLInterface, IDLInterface>>>(lexicalGlobalObject, sourceValue); - // RETURN_IF_EXCEPTION(throwScope, {}); - // } else - result.source = std::nullopt; + if (!sourceValue.isUndefinedOrNull()) { + result.source = convert>>(lexicalGlobalObject, sourceValue, [&sourceValue](JSGlobalObject& lexicalGlobalObject, ThrowScope& throwScope) { + Bun::ERR::INVALID_ARG_TYPE(throwScope, &lexicalGlobalObject, "eventInitDict.source"_s, "MessagePort"_s, sourceValue); + }); + RETURN_IF_EXCEPTION(throwScope, {}); + } else { + result.source = nullptr; + } return result; } @@ -351,10 +364,10 @@ JSC_DEFINE_CUSTOM_GETTER(jsMessageEvent_lastEventId, (JSGlobalObject * lexicalGl static inline JSValue jsMessageEvent_sourceGetter(JSGlobalObject& lexicalGlobalObject, JSMessageEvent& thisObject) { - return jsNull(); - // auto throwScope = DECLARE_THROW_SCOPE(vm); - // auto& impl = thisObject.wrapped(); - // RELEASE_AND_RETURN(throwScope, (toJS, IDLInterface, IDLInterface>>>(lexicalGlobalObject, *thisObject.globalObject(), throwScope, impl.source()))); + auto& vm = JSC::getVM(&lexicalGlobalObject); + auto throwScope = DECLARE_THROW_SCOPE(vm); + auto& impl = thisObject.wrapped(); + RELEASE_AND_RETURN(throwScope, (toJS>>(lexicalGlobalObject, *thisObject.globalObject(), throwScope, impl.source()))); } JSC_DEFINE_CUSTOM_GETTER(jsMessageEvent_source, (JSGlobalObject * lexicalGlobalObject, JSC::EncodedJSValue thisValue, PropertyName attributeName)) @@ -412,8 +425,7 @@ static inline JSC::EncodedJSValue jsMessageEventPrototypeFunction_initMessageEve auto lastEventId = argument5.value().isUndefined() ? emptyString() : convert(*lexicalGlobalObject, argument5.value()); RETURN_IF_EXCEPTION(throwScope, {}); EnsureStillAliveScope argument6 = callFrame->argument(6); - auto source = WebCore::MessageEventSource(); - // auto source = argument6.value().isUndefined() ? std::nullopt : convert, IDLInterface, IDLInterface>>>(*lexicalGlobalObject, argument6.value()); + RefPtr source = argument6.value().isUndefined() ? nullptr : convert>>(*lexicalGlobalObject, argument6.value()); RETURN_IF_EXCEPTION(throwScope, {}); EnsureStillAliveScope argument7 = callFrame->argument(7); auto messagePorts = argument7.value().isUndefined() ? Converter>>::ReturnType {} : convert>>(*lexicalGlobalObject, argument7.value()); diff --git a/src/bun.js/bindings/webcore/MessageEvent.cpp b/src/bun.js/bindings/webcore/MessageEvent.cpp index 074dcc38a4..8fab43f3f2 100644 --- a/src/bun.js/bindings/webcore/MessageEvent.cpp +++ b/src/bun.js/bindings/webcore/MessageEvent.cpp @@ -54,7 +54,7 @@ inline MessageEvent::MessageEvent(const AtomString& type, Init&& initializer, Is { } -inline MessageEvent::MessageEvent(const AtomString& type, DataType&& data, const String& origin, const String& lastEventId, std::optional&& source, Vector>&& ports) +inline MessageEvent::MessageEvent(const AtomString& type, DataType&& data, const String& origin, const String& lastEventId, RefPtr&& source, Vector>&& ports) : Event(type, CanBubble::No, IsCancelable::No) , m_data(WTFMove(data)) , m_origin(origin) @@ -64,12 +64,12 @@ inline MessageEvent::MessageEvent(const AtomString& type, DataType&& data, const { } -Ref MessageEvent::create(const AtomString& type, DataType&& data, const String& origin, const String& lastEventId, std::optional&& source, Vector>&& ports) +Ref MessageEvent::create(const AtomString& type, DataType&& data, const String& origin, const String& lastEventId, RefPtr&& source, Vector>&& ports) { return adoptRef(*new MessageEvent(type, WTFMove(data), origin, lastEventId, WTFMove(source), WTFMove(ports))); } -Ref MessageEvent::create(DataType&& data, const String& origin, const String& lastEventId, std::optional&& source, Vector>&& ports) +Ref MessageEvent::create(DataType&& data, const String& origin, const String& lastEventId, RefPtr&& source, Vector>&& ports) { return create(eventNames().messageEvent, WTFMove(data), origin, lastEventId, WTFMove(source), WTFMove(ports)); } @@ -86,12 +86,12 @@ Ref MessageEvent::create(const AtomString& type, Init&& initialize MessageEvent::~MessageEvent() = default; -auto MessageEvent::create(JSC::JSGlobalObject& globalObject, Ref&& data, std::optional&& source, Vector>&& ports) -> MessageEventWithStrongData +auto MessageEvent::create(JSC::JSGlobalObject& globalObject, Ref&& data, RefPtr&& source, Vector>&& ports) -> MessageEventWithStrongData { return create(globalObject, WTFMove(data), {}, {}, WTFMove(source), WTFMove(ports)); } -auto MessageEvent::create(JSC::JSGlobalObject& globalObject, Ref&& data, const String& origin, const String& lastEventId, std::optional&& source, Vector>&& ports) -> MessageEventWithStrongData +auto MessageEvent::create(JSC::JSGlobalObject& globalObject, Ref&& data, const String& origin, const String& lastEventId, RefPtr&& source, Vector>&& ports) -> MessageEventWithStrongData { auto& vm = globalObject.vm(); // Locker locker(vm.apiLock()); @@ -115,7 +115,7 @@ auto MessageEvent::create(JSC::JSGlobalObject& globalObject, Ref&& source, Vector>&& ports) +void MessageEvent::initMessageEvent(const AtomString& type, bool canBubble, bool cancelable, JSValue data, const String& origin, const String& lastEventId, RefPtr&& source, Vector>&& ports) { if (isBeingDispatched()) return; diff --git a/src/bun.js/bindings/webcore/MessageEvent.h b/src/bun.js/bindings/webcore/MessageEvent.h index 3010f27eb3..3d65710df4 100644 --- a/src/bun.js/bindings/webcore/MessageEvent.h +++ b/src/bun.js/bindings/webcore/MessageEvent.h @@ -39,9 +39,6 @@ namespace WebCore { -class MessageEventSource { -}; - class MessageEvent final : public Event { WTF_MAKE_TZONE_ALLOCATED(MessageEvent); @@ -50,8 +47,8 @@ public: }; // using DataType = std::variant, String, Ref, Ref>; using DataType = std::variant, String, Ref>; - static Ref create(const AtomString& type, DataType&&, const String& origin = {}, const String& lastEventId = {}, std::optional&& = std::nullopt, Vector>&& = {}); - static Ref create(DataType&&, const String& origin = {}, const String& lastEventId = {}, std::optional&& = std::nullopt, Vector>&& = {}); + static Ref create(const AtomString& type, DataType&&, const String& origin = {}, const String& lastEventId = {}, RefPtr&& = nullptr, Vector>&& = {}); + static Ref create(DataType&&, const String& origin = {}, const String& lastEventId = {}, RefPtr&& = nullptr, Vector>&& = {}); static Ref createForBindings(); @@ -59,7 +56,7 @@ public: JSC::JSValue data; String origin; String lastEventId; - std::optional source; + RefPtr source; Vector> ports; }; static Ref create(const AtomString& type, Init&&, IsTrusted = IsTrusted::No); @@ -69,17 +66,17 @@ public: JSC::Strong strongWrapper; // Keep the wrapper alive until the event is fired, since it is what keeps `data` alive. }; - static MessageEventWithStrongData create(JSC::JSGlobalObject&, Ref&&, const String& origin = {}, const String& lastEventId = {}, std::optional&& = std::nullopt, Vector>&& = {}); + static MessageEventWithStrongData create(JSC::JSGlobalObject&, Ref&&, const String& origin = {}, const String& lastEventId = {}, RefPtr&& = nullptr, Vector>&& = {}); - static MessageEventWithStrongData create(JSC::JSGlobalObject&, Ref&&, std::optional&& = std::nullopt, Vector>&& = {}); + static MessageEventWithStrongData create(JSC::JSGlobalObject&, Ref&&, RefPtr&& = nullptr, Vector>&& = {}); virtual ~MessageEvent(); - void initMessageEvent(const AtomString& type, bool canBubble, bool cancelable, JSC::JSValue data, const String& origin, const String& lastEventId, std::optional&&, Vector>&&); + void initMessageEvent(const AtomString& type, bool canBubble, bool cancelable, JSC::JSValue data, const String& origin, const String& lastEventId, RefPtr&&, Vector>&&); const String& origin() const { return m_origin; } const String& lastEventId() const { return m_lastEventId; } - const std::optional& source() const { return m_source; } + const RefPtr& source() const { return m_source; } const Vector>& ports() const { return m_ports; } const DataType& data() const { return m_data; } @@ -93,14 +90,14 @@ public: private: MessageEvent(); MessageEvent(const AtomString& type, Init&&, IsTrusted); - MessageEvent(const AtomString& type, DataType&&, const String& origin, const String& lastEventId = {}, std::optional&& = std::nullopt, Vector>&& = {}); + MessageEvent(const AtomString& type, DataType&&, const String& origin, const String& lastEventId = {}, RefPtr&& = nullptr, Vector>&& = {}); EventInterface eventInterface() const final; DataType m_data; String m_origin; String m_lastEventId; - std::optional m_source; + RefPtr m_source; Vector> m_ports; JSValueInWrappedObject m_jsData; diff --git a/src/bun.js/bindings/webcore/Worker.cpp b/src/bun.js/bindings/webcore/Worker.cpp index 3c2cfb998e..5c0b9869fa 100644 --- a/src/bun.js/bindings/webcore/Worker.cpp +++ b/src/bun.js/bindings/webcore/Worker.cpp @@ -252,7 +252,7 @@ ExceptionOr Worker::postMessage(JSC::JSGlobalObject& state, JSC::JSValue m Zig::GlobalObject* globalObject = jsCast(context.jsGlobalObject()); auto ports = MessagePort::entanglePorts(context, WTFMove(message.transferredPorts)); - auto event = MessageEvent::create(*globalObject, message.message.releaseNonNull(), std::nullopt, WTFMove(ports)); + auto event = MessageEvent::create(*globalObject, message.message.releaseNonNull(), nullptr, WTFMove(ports)); globalObject->globalEventScope->dispatchEvent(event.event); }); @@ -649,7 +649,7 @@ JSC_DEFINE_HOST_FUNCTION(jsFunctionPostMessage, Zig::GlobalObject* globalObject = jsCast(context.jsGlobalObject()); auto ports = MessagePort::entanglePorts(context, WTFMove(message.transferredPorts)); - auto event = MessageEvent::create(*globalObject, message.message.releaseNonNull(), std::nullopt, WTFMove(ports)); + auto event = MessageEvent::create(*globalObject, message.message.releaseNonNull(), nullptr, WTFMove(ports)); protectedThis->dispatchEvent(event.event); }); diff --git a/test/js/node/test/parallel/test-worker-message-event.js b/test/js/node/test/parallel/test-worker-message-event.js new file mode 100644 index 0000000000..d6dc5cfa9a --- /dev/null +++ b/test/js/node/test/parallel/test-worker-message-event.js @@ -0,0 +1,93 @@ +'use strict'; +require('../common'); +const assert = require('assert'); + +const dummyPort = new MessageChannel().port1; +{ + for (const [args, expected] of [ + [ + ['message'], + { + type: 'message', data: null, origin: '', + lastEventId: '', source: null, ports: [] + }, + ], + [ + ['message', { data: undefined, origin: 'foo' }], + { + type: 'message', data: null, origin: 'foo', + lastEventId: '', source: null, ports: [] + }, + ], + [ + ['message', { data: 2, origin: 1, lastEventId: 0 }], + { + type: 'message', data: 2, origin: '1', + lastEventId: '0', source: null, ports: [] + }, + ], + [ + ['message', { lastEventId: 'foo' }], + { + type: 'message', data: null, origin: '', + lastEventId: 'foo', source: null, ports: [] + }, + ], + [ + ['messageerror', { lastEventId: 'foo', source: dummyPort }], + { + type: 'messageerror', data: null, origin: '', + lastEventId: 'foo', source: dummyPort, ports: [] + }, + ], + [ + ['message', { ports: [dummyPort], source: null }], + { + type: 'message', data: null, origin: '', + lastEventId: '', source: null, ports: [dummyPort] + }, + ], + ]) { + const ev = new MessageEvent(...args); + const { type, data, origin, lastEventId, source, ports } = ev; + assert.deepStrictEqual(expected, { + type, data, origin, lastEventId, source, ports + }); + } +} + +{ + assert.throws(() => { + new MessageEvent('message', { source: 1 }); + }, e => ( + e.name == 'TypeError' && + e.message.includes('eventInitDict.source') && e.message.match(/(an instance of|of type) MessagePort/) && e.message.includes('1') + )); + assert.throws(() => { + new MessageEvent('message', { source: {} }); + }, e => ( + e.name == 'TypeError' && + e.message.includes('eventInitDict.source') && e.message.match(/(an instance of|of type) MessagePort/) && (e.message.includes('{}') || e.message.includes('an instance of Object')) + )); + assert.throws(() => { + new MessageEvent('message', { ports: 0 }); + }, { + message: /MessageEvent constructor: eventInitDict\.ports( \(0\))? is not iterable\./, + }); + assert.throws(() => { + new MessageEvent('message', { ports: [ null ] }); + }, { + name: 'TypeError', + message: /MessageEvent constructor: Expected (every item of )?eventInitDict\.ports(\[0\])? (\("null"\) )?to be an instance of MessagePort\./, + }); + assert.throws(() => { + new MessageEvent('message', { ports: [ {} ] }); + }, { + name: 'TypeError', + message: /MessageEvent constructor: Expected (every item of )?eventInitDict\.ports(\[0\])? (\("\{\}"\) )?to be an instance of MessagePort\./, + }); +} + +{ + assert(new MessageEvent('message') instanceof Event); +} diff --git a/test/js/node/test/parallel/test-worker-message-port-terminate-transfer-list.js b/test/js/node/test/parallel/test-worker-message-port-terminate-transfer-list.js new file mode 100644 index 0000000000..a5c7a34c55 --- /dev/null +++ b/test/js/node/test/parallel/test-worker-message-port-terminate-transfer-list.js @@ -0,0 +1,27 @@ +'use strict'; +const common = require('../common'); + +const { parentPort, MessageChannel, Worker } = require('worker_threads'); + +// Do not use isMainThread so that this test itself can be run inside a Worker. +if (!process.env.HAS_STARTED_WORKER) { + process.env.HAS_STARTED_WORKER = 1; + const w = new Worker(__filename); + w.once('message', common.mustCall(() => { + w.once('message', common.mustNotCall()); + setTimeout(() => w.terminate(), 100); + })); +} else { + const { port1 } = new MessageChannel(); + + parentPort.postMessage('ready'); + + // Make sure we don’t end up running JS after the infinite loop is broken. + port1.postMessage({}, { + // eslint-disable-next-line require-yield + transfer: (function*() { while (true); })() + }); + + parentPort.postMessage('UNREACHABLE'); + process.kill(process.pid, 'SIGINT'); +} diff --git a/test/js/node/test/parallel/test-worker-uncaught-exception.js b/test/js/node/test/parallel/test-worker-uncaught-exception.js new file mode 100644 index 0000000000..2176779159 --- /dev/null +++ b/test/js/node/test/parallel/test-worker-uncaught-exception.js @@ -0,0 +1,34 @@ +'use strict'; +const common = require('../common'); +const assert = require('assert'); +const { Worker } = require('worker_threads'); + +// Do not use isMainThread so that this test itself can be run inside a Worker. +if (!process.env.HAS_STARTED_WORKER) { + process.env.HAS_STARTED_WORKER = 1; + const w = new Worker(__filename); + w.on('message', common.mustNotCall()); + w.on('error', common.mustCall((err) => { + console.log(err.message); + assert.match(String(err), /^Error: foo$/); + })); + w.on('exit', common.mustCall((code) => { + // uncaughtException is code 1 + assert.strictEqual(code, 1); + })); +} else { + // Cannot use common.mustCall as it cannot catch this + let called = false; + process.on('exit', (code) => { + if (!called) { + called = true; + } else { + assert.fail('Exit callback called twice in worker'); + } + }); + + setTimeout(() => assert.fail('Timeout executed after uncaughtException'), + 2000); + + throw new Error('foo'); +} diff --git a/test/js/web/workers/message-event.test.ts b/test/js/web/workers/message-event.test.ts new file mode 100644 index 0000000000..500b860769 --- /dev/null +++ b/test/js/web/workers/message-event.test.ts @@ -0,0 +1,104 @@ +import { describe, expect, test } from "bun:test"; + +describe("MessageEvent constructor", () => { + test("returns an Event instance", () => { + expect(new MessageEvent("message")).toBeInstanceOf(Event); + }); + + test("has the right defaults", () => { + const expected = { + type: "custom type", + data: null, + origin: "", + lastEventId: "", + source: null, + ports: [], + }; + expect(new MessageEvent("custom type")).toMatchObject(expected); + expect(new MessageEvent("custom type", undefined)).toMatchObject(expected); + expect(new MessageEvent("custom type", {})).toMatchObject(expected); + // @ts-expect-error + expect(new MessageEvent("custom type", null)).toMatchObject(expected); + }); + + test("includes all options in the returned object", () => { + const { port1 } = new MessageChannel(); + expect( + new MessageEvent("custom type", { + data: 123, + origin: "origin", + lastEventId: "id", + source: port1, + ports: [port1], + }), + ).toMatchObject({ + type: "custom type", + data: 123, + origin: "origin", + lastEventId: "id", + source: port1, + ports: [port1], + }); + }); + + test("coerces the type to a string", () => { + // @ts-expect-error + expect(new MessageEvent(5)).toMatchObject({ type: "5" }); + // @ts-expect-error + expect(new MessageEvent(undefined)).toMatchObject({ type: "undefined" }); + }); + + test("throws if you pass no arguments", () => { + // @ts-expect-error + expect(() => new MessageEvent()).toThrow({ + name: "TypeError", + message: "Not enough arguments", + }); + }); + + test("throws if options is not an object", () => { + // @ts-expect-error + expect(() => new MessageEvent("message", 5)).toThrow(TypeError); + }); + + test("coerces options.origin to a string", () => { + // @ts-expect-error + expect(new MessageEvent("message", { origin: 123 })).toMatchObject({ origin: "123" }); + }); + + test("coerces options.lastEventId to a string", () => { + // @ts-expect-error + expect(new MessageEvent("message", { lastEventId: 123 })).toMatchObject({ lastEventId: "123" }); + }); + + test("throws if options.source is the wrong type", () => { + // @ts-expect-error + expect(() => new MessageEvent("message", { source: 1 })).toThrow({ + name: "TypeError", + message: 'The "eventInitDict.source" property must be of type MessagePort. Received type number (1)', + }); + // @ts-expect-error + expect(() => new MessageEvent("message", { source: {} })).toThrow({ + name: "TypeError", + message: 'The "eventInitDict.source" property must be of type MessagePort. Received an instance of Object', + }); + }); + + test("throws if options.ports is the wrong type", () => { + // @ts-expect-error + expect(() => new MessageEvent("message", { ports: 1 })).toThrow({ + name: "TypeError", + message: "MessageEvent constructor: eventInitDict.ports is not iterable.", + }); + // @ts-expect-error + expect(() => new MessageEvent("message", { ports: [1] })).toThrow({ + name: "TypeError", + message: "MessageEvent constructor: Expected every item of eventInitDict.ports to be an instance of MessagePort.", + }); + // @ts-expect-error + expect(() => new MessageEvent("message", { ports: [{}] })).toThrow({ + name: "TypeError", + message: "MessageEvent constructor: Expected every item of eventInitDict.ports to be an instance of MessagePort.", + }); + }); +});