diff options
author | 2023-07-28 16:44:28 -0700 | |
---|---|---|
committer | 2023-07-28 16:44:28 -0700 | |
commit | d614fdfaac13346d71ecf24712abaefe8224687d (patch) | |
tree | 2fce0943c663db984755df123d96d788984cad22 | |
parent | 0a4e476a7c08005e242ed48f3f27895e55deacc9 (diff) | |
download | bun-d614fdfaac13346d71ecf24712abaefe8224687d.tar.gz bun-d614fdfaac13346d71ecf24712abaefe8224687d.tar.zst bun-d614fdfaac13346d71ecf24712abaefe8224687d.zip |
`MessageChannel` and `MessagePort` (#3860)
* copy and format
* copy
* copy
* cleanup
* some tests
* spellcheck
* add types
* don't lock getting contextId
* array buffer test
57 files changed, 3752 insertions, 247 deletions
diff --git a/packages/bun-types/globals.d.ts b/packages/bun-types/globals.d.ts index ed22be080..bf374adfe 100644 --- a/packages/bun-types/globals.d.ts +++ b/packages/bun-types/globals.d.ts @@ -366,6 +366,152 @@ declare function structuredClone<T>( options?: StructuredSerializeOptions, ): T; +/** + * This Channel Messaging API interface allows us to create a new message channel and send data through it via its two MessagePort properties. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/MessageChannel) + */ +interface MessageChannel { + /** + * Returns the first MessagePort object. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/MessageChannel/port1) + */ + readonly port1: MessagePort; + /** + * Returns the second MessagePort object. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/MessageChannel/port2) + */ + readonly port2: MessagePort; +} + +declare var MessageChannel: { + prototype: MessageChannel; + new (): MessageChannel; +}; + +/** + * A message received by a target object. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/MessageEvent) + */ +interface MessageEvent<T = any> extends Event { + /** + * Returns the data of the message. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/MessageEvent/data) + */ + readonly data: T; + /** + * Returns the last event ID string, for server-sent events. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/MessageEvent/lastEventId) + */ + readonly lastEventId: string; + /** + * Returns the origin of the message, for server-sent events and cross-document messaging. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/MessageEvent/origin) + */ + readonly origin: string; + /** + * Returns the MessagePort array sent with the message, for cross-document messaging and channel messaging. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/MessageEvent/ports) + */ + readonly ports: ReadonlyArray<MessagePort>; + /** + * Returns the WindowProxy of the source window, for cross-document messaging, and the MessagePort being attached, in the connect event fired at SharedWorkerGlobalScope objects. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/MessageEvent/source) + */ + readonly source: MessageEventSource | null; + /** + * @deprecated + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/MessageEvent/initMessageEvent) + */ + initMessageEvent( + type: string, + bubbles?: boolean, + cancelable?: boolean, + data?: any, + origin?: string, + lastEventId?: string, + source?: MessageEventSource | null, + ports?: MessagePort[], + ): void; +} + +declare var MessageEvent: { + prototype: MessageEvent; + new <T>(type: string, eventInitDict?: MessageEventInit<T>): MessageEvent<T>; +}; + +interface MessagePortEventMap { + message: MessageEvent; + messageerror: MessageEvent; +} + +/** + * This Channel Messaging API interface represents one of the two ports of a MessageChannel, allowing messages to be sent from one port and listening out for them arriving at the other. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/MessagePort) + */ +interface MessagePort extends EventTarget { + /** [MDN Reference](https://developer.mozilla.org/docs/Web/API/MessagePort/message_event) */ + onmessage: ((this: MessagePort, ev: MessageEvent) => any) | null; + /** [MDN Reference](https://developer.mozilla.org/docs/Web/API/MessagePort/messageerror_event) */ + onmessageerror: ((this: MessagePort, ev: MessageEvent) => any) | null; + /** + * Disconnects the port, so that it is no longer active. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/MessagePort/close) + */ + close(): void; + /** + * Posts a message through the channel. Objects listed in transfer are transferred, not just cloned, meaning that they are no longer usable on the sending side. + * + * Throws a "DataCloneError" DOMException if transfer contains duplicate objects or port, or if message could not be cloned. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/MessagePort/postMessage) + */ + postMessage(message: any, transfer: Transferable[]): void; + postMessage(message: any, options?: StructuredSerializeOptions): void; + /** + * Begins dispatching messages received on the port. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/MessagePort/start) + */ + start(): void; + addEventListener<K extends keyof MessagePortEventMap>( + type: K, + listener: (this: MessagePort, ev: MessagePortEventMap[K]) => any, + options?: boolean | AddEventListenerOptions, + ): void; + addEventListener( + type: string, + listener: EventListenerOrEventListenerObject, + options?: boolean | AddEventListenerOptions, + ): void; + removeEventListener<K extends keyof MessagePortEventMap>( + type: K, + listener: (this: MessagePort, ev: MessagePortEventMap[K]) => any, + options?: boolean | EventListenerOptions, + ): void; + removeEventListener( + type: string, + listener: EventListenerOrEventListenerObject, + options?: boolean | EventListenerOptions, + ): void; +} + +declare var MessagePort: { + prototype: MessagePort; + new (): MessagePort; +}; + interface EncodeIntoResult { /** * The read Unicode code units of input. diff --git a/src/bun.js/bindings/BunJSCModule.cpp b/src/bun.js/bindings/BunJSCModule.cpp index a145f51c5..5e7a047ea 100644 --- a/src/bun.js/bindings/BunJSCModule.cpp +++ b/src/bun.js/bindings/BunJSCModule.cpp @@ -26,6 +26,7 @@ #include "JavaScriptCore/VMTrapsInlines.h" #include "SerializedScriptValue.h" #include "ExceptionOr.h" +#include "MessagePort.h" #if ENABLE(REMOTE_INSPECTOR) #include "JavaScriptCore/RemoteInspectorServer.h" @@ -552,8 +553,8 @@ JSC_DEFINE_HOST_FUNCTION(functionSerialize, (JSGlobalObject * lexicalGlobalObjec } Vector<JSC::Strong<JSC::JSObject>> transferList; - - ExceptionOr<Ref<SerializedScriptValue>> serialized = SerializedScriptValue::create(*globalObject, value, WTFMove(transferList)); + Vector<RefPtr<MessagePort>> dummyPorts; + ExceptionOr<Ref<SerializedScriptValue>> serialized = SerializedScriptValue::create(*globalObject, value, WTFMove(transferList), dummyPorts); if (serialized.hasException()) { WebCore::propagateException(*globalObject, throwScope, serialized.releaseException()); diff --git a/src/bun.js/bindings/BunWorkerGlobalScope.cpp b/src/bun.js/bindings/BunWorkerGlobalScope.cpp index 60b615e18..ef1f70fdf 100644 --- a/src/bun.js/bindings/BunWorkerGlobalScope.cpp +++ b/src/bun.js/bindings/BunWorkerGlobalScope.cpp @@ -1,10 +1,14 @@ #include "config.h" #include "BunWorkerGlobalScope.h" +#include "MessagePortChannelProviderImpl.h" -namespace Bun { -using namespace WebCore; +namespace WebCore { WTF_MAKE_ISO_ALLOCATED_IMPL(GlobalScope); +MessagePortChannelProvider& GlobalScope::messagePortChannelProvider() +{ + return *reinterpret_cast<MessagePortChannelProvider*>(&MessagePortChannelProviderImpl::singleton()); +} }
\ No newline at end of file diff --git a/src/bun.js/bindings/BunWorkerGlobalScope.h b/src/bun.js/bindings/BunWorkerGlobalScope.h index 767289716..fff50d6ec 100644 --- a/src/bun.js/bindings/BunWorkerGlobalScope.h +++ b/src/bun.js/bindings/BunWorkerGlobalScope.h @@ -1,3 +1,5 @@ +#pragma once + #include "root.h" #include "EventTarget.h" @@ -7,8 +9,12 @@ #include <wtf/HashSet.h> #include <wtf/Lock.h> -namespace Bun { -class GlobalScope final : public RefCounted<GlobalScope>, public EventTargetWithInlineData { +namespace WebCore { + +class MessagePortChannelProvider; +class MessagePortChannelProviderImpl; + +class GlobalScope : public RefCounted<GlobalScope>, public EventTargetWithInlineData { WTF_MAKE_ISO_ALLOCATED(GlobalScope); public: @@ -33,6 +39,11 @@ public: void derefEventTarget() final { deref(); } void eventListenersDidChange() final {} + MessagePortChannelProvider& messagePortChannelProvider(); + ScriptExecutionContext* m_context; + +private: + MessagePortChannelProviderImpl* m_messagePortChannelProvider; }; }
\ No newline at end of file diff --git a/src/bun.js/bindings/ScriptExecutionContext.cpp b/src/bun.js/bindings/ScriptExecutionContext.cpp index 47908b385..d9adbeb98 100644 --- a/src/bun.js/bindings/ScriptExecutionContext.cpp +++ b/src/bun.js/bindings/ScriptExecutionContext.cpp @@ -1,6 +1,7 @@ #include "root.h" #include "headers.h" #include "ScriptExecutionContext.h" +#include "MessagePort.h" #include "webcore/WebSocket.h" #include "libusockets.h" @@ -70,6 +71,23 @@ void ScriptExecutionContext::unrefEventLoop() Bun__eventLoop__incrementRefConcurrently(WebCore::clientData(vm())->bunVM, -1); } +ScriptExecutionContext::~ScriptExecutionContext() +{ + checkConsistency(); + + { + Locker locker { allScriptExecutionContextsMapLock }; + ASSERT_WITH_MESSAGE(!allScriptExecutionContextsMap().contains(m_identifier), "A ScriptExecutionContext subclass instance implementing postTask should have already removed itself from the map"); + } + + auto postMessageCompletionHandlers = WTFMove(m_processMessageWithMessagePortsSoonHandlers); + for (auto& completionHandler : postMessageCompletionHandlers) + completionHandler(); + + while (auto* destructionObserver = m_destructionObservers.takeAny()) + destructionObserver->contextDestroyed(); +} + bool ScriptExecutionContext::postTaskTo(ScriptExecutionContextIdentifier identifier, Function<void(ScriptExecutionContext&)>&& task) { Locker locker { allScriptExecutionContextsMapLock }; @@ -82,6 +100,125 @@ bool ScriptExecutionContext::postTaskTo(ScriptExecutionContextIdentifier identif return true; } +void ScriptExecutionContext::didCreateDestructionObserver(ContextDestructionObserver& observer) +{ + ASSERT(!m_inScriptExecutionContextDestructor); + m_destructionObservers.add(&observer); +} + +void ScriptExecutionContext::willDestroyDestructionObserver(ContextDestructionObserver& observer) +{ + m_destructionObservers.remove(&observer); +} + +extern "C" void* Bun__getVM(); + +bool ScriptExecutionContext::isContextThread() +{ + auto clientData = WebCore::clientData(vm()); + return clientData->bunVM == Bun__getVM(); +} + +bool ScriptExecutionContext::ensureOnContextThread(ScriptExecutionContextIdentifier identifier, Function<void(ScriptExecutionContext&)>&& task) +{ + ScriptExecutionContext* context = nullptr; + { + Locker locker { allScriptExecutionContextsMapLock }; + context = allScriptExecutionContextsMap().get(identifier); + + if (!context) + return false; + + if (!context->isContextThread()) { + context->postTaskConcurrently(WTFMove(task)); + return true; + } + } + + task(*context); + return true; +} + +bool ScriptExecutionContext::ensureOnMainThread(Function<void(ScriptExecutionContext&)>&& task) +{ + Locker locker { allScriptExecutionContextsMapLock }; + auto* context = allScriptExecutionContextsMap().get(1); + + if (!context) { + return false; + } + + context->postTaskConcurrently(WTFMove(task)); + return true; +} + +void ScriptExecutionContext::processMessageWithMessagePortsSoon(CompletionHandler<void()>&& completionHandler) +{ + ASSERT(isContextThread()); + m_processMessageWithMessagePortsSoonHandlers.append(WTFMove(completionHandler)); + + if (m_willProcessMessageWithMessagePortsSoon) { + return; + } + + m_willProcessMessageWithMessagePortsSoon = true; + + postTask([](ScriptExecutionContext& context) { + context.dispatchMessagePortEvents(); + }); +} + +void ScriptExecutionContext::dispatchMessagePortEvents() +{ + ASSERT(isContextThread()); + checkConsistency(); + + ASSERT(m_willprocessMessageWithMessagePortsSoon); + m_willProcessMessageWithMessagePortsSoon = false; + + auto completionHandlers = std::exchange(m_processMessageWithMessagePortsSoonHandlers, Vector<CompletionHandler<void()>> {}); + + // Make a frozen copy of the ports so we can iterate while new ones might be added or destroyed. + for (auto* messagePort : copyToVector(m_messagePorts)) { + // The port may be destroyed, and another one created at the same address, + // but this is harmless. The worst that can happen as a result is that + // dispatchMessages() will be called needlessly. + if (m_messagePorts.contains(messagePort) && messagePort->started()) + messagePort->dispatchMessages(); + } + + for (auto& completionHandler : completionHandlers) + completionHandler(); +} + +void ScriptExecutionContext::checkConsistency() const +{ + for (auto* messagePort : m_messagePorts) + ASSERT(messagePort->scriptExecutionContext() == this); + + for (auto* destructionObserver : m_destructionObservers) + ASSERT(destructionObserver->scriptExecutionContext() == this); + + // for (auto* activeDOMObject : m_activeDOMObjects) { + // ASSERT(activeDOMObject->scriptExecutionContext() == this); + // activeDOMObject->assertSuspendIfNeededWasCalled(); + // } +} + +void ScriptExecutionContext::createdMessagePort(MessagePort& messagePort) +{ + ASSERT(isContextThread()); + + m_messagePorts.add(&messagePort); +} + +void ScriptExecutionContext::destroyedMessagePort(MessagePort& messagePort) +{ + ASSERT(isContextThread()); + + m_messagePorts.remove(&messagePort); +} + us_socket_context_t* ScriptExecutionContext::webSocketContextNoSSL() { if (!m_client_websockets_ctx) { diff --git a/src/bun.js/bindings/ScriptExecutionContext.h b/src/bun.js/bindings/ScriptExecutionContext.h index b0e4e2096..23c44ab51 100644 --- a/src/bun.js/bindings/ScriptExecutionContext.h +++ b/src/bun.js/bindings/ScriptExecutionContext.h @@ -2,12 +2,14 @@ #include "root.h" #include "ActiveDOMObject.h" +#include "ContextDestructionObserver.h" #include <wtf/CrossThreadTask.h> #include <wtf/Function.h> #include <wtf/HashSet.h> #include <wtf/ObjectIdentifier.h> #include <wtf/WeakPtr.h> #include <wtf/text/WTFString.h> +#include <wtf/CompletionHandler.h> #include "CachedScript.h" #include "wtf/URL.h" @@ -27,6 +29,7 @@ struct us_loop_t; namespace WebCore { class WebSocket; +class MessagePort; class ScriptExecutionContext; @@ -89,6 +92,8 @@ public: addToContextsMap(); } + ~ScriptExecutionContext(); + static ScriptExecutionContextIdentifier generateIdentifier(); JSC::JSGlobalObject* jsGlobalObject() @@ -117,7 +122,7 @@ public: bool isMainThread() const { return static_cast<unsigned>(m_identifier) == 1; } bool activeDOMObjectsAreSuspended() { return false; } bool activeDOMObjectsAreStopped() { return false; } - bool isContextThread() { return true; } + bool isContextThread(); bool isDocument() { return false; } bool isWorkerGlobalScope() { return true; } bool isJSExecutionForbidden() { return false; } @@ -140,7 +145,21 @@ public: bool unwrapCryptoKey(const Vector<uint8_t>& wrappedKey, Vector<uint8_t>& key) { return false; } #endif - static bool postTaskTo(ScriptExecutionContextIdentifier identifier, Function<void(ScriptExecutionContext&)>&& task); + WEBCORE_EXPORT static bool postTaskTo(ScriptExecutionContextIdentifier identifier, Function<void(ScriptExecutionContext&)>&& task); + WEBCORE_EXPORT static bool ensureOnContextThread(ScriptExecutionContextIdentifier, Function<void(ScriptExecutionContext&)>&& task); + WEBCORE_EXPORT static bool ensureOnMainThread(Function<void(ScriptExecutionContext&)>&& task); + + WEBCORE_EXPORT JSC::JSGlobalObject* globalObject(); + + void didCreateDestructionObserver(ContextDestructionObserver&); + void willDestroyDestructionObserver(ContextDestructionObserver&); + + void processMessageWithMessagePortsSoon(CompletionHandler<void()>&&); + void createdMessagePort(MessagePort&); + void destroyedMessagePort(MessagePort&); + + void dispatchMessagePortEvents(); + void checkConsistency() const; void regenerateIdentifier(); void addToContextsMap(); @@ -196,6 +215,12 @@ private: WTF::URL m_url = WTF::URL(); ScriptExecutionContextIdentifier m_identifier; + HashSet<MessagePort*> m_messagePorts; + HashSet<ContextDestructionObserver*> m_destructionObservers; + Vector<CompletionHandler<void()>> m_processMessageWithMessagePortsSoonHandlers; + + bool m_willProcessMessageWithMessagePortsSoon { false }; + us_socket_context_t* webSocketContextSSL(); us_socket_context_t* webSocketContextNoSSL(); us_socket_context_t* connectedWebSocketKindClientSSL(); diff --git a/src/bun.js/bindings/ZigGlobalObject.cpp b/src/bun.js/bindings/ZigGlobalObject.cpp index b7171b21c..1aefe476c 100644 --- a/src/bun.js/bindings/ZigGlobalObject.cpp +++ b/src/bun.js/bindings/ZigGlobalObject.cpp @@ -119,6 +119,9 @@ #include "DOMIsoSubspaces.h" #include "BunWorkerGlobalScope.h" #include "JSWorker.h" +#include "JSMessageChannel.h" +#include "JSMessagePort.h" +// #include "JSBroadcastChannel.h" #if ENABLE(REMOTE_INSPECTOR) #include "JavaScriptCore/RemoteInspectorServer.h" @@ -718,6 +721,7 @@ GlobalObject::GlobalObject(JSC::VM& vm, JSC::Structure* structure) , m_scriptExecutionContext(new WebCore::ScriptExecutionContext(&vm, this)) , globalEventScope(*new Bun::GlobalScope(m_scriptExecutionContext)) { + // m_scriptExecutionContext = globalEventScope.m_context; mockModule = Bun::JSMockModule::create(this); globalEventScope.m_context = m_scriptExecutionContext; } @@ -732,6 +736,7 @@ GlobalObject::GlobalObject(JSC::VM& vm, JSC::Structure* structure, WebCore::Scri , m_scriptExecutionContext(new WebCore::ScriptExecutionContext(&vm, this, contextId)) , globalEventScope(*new Bun::GlobalScope(m_scriptExecutionContext)) { + // m_scriptExecutionContext = globalEventScope.m_context; mockModule = Bun::JSMockModule::create(this); globalEventScope.m_context = m_scriptExecutionContext; } @@ -931,6 +936,15 @@ WEBCORE_GENERATED_CONSTRUCTOR_SETTER(JSDOMFormData); WEBCORE_GENERATED_CONSTRUCTOR_GETTER(JSWorker); WEBCORE_GENERATED_CONSTRUCTOR_SETTER(JSWorker); +WEBCORE_GENERATED_CONSTRUCTOR_GETTER(JSMessageChannel); +WEBCORE_GENERATED_CONSTRUCTOR_SETTER(JSMessageChannel); + +WEBCORE_GENERATED_CONSTRUCTOR_GETTER(JSMessagePort); +WEBCORE_GENERATED_CONSTRUCTOR_SETTER(JSMessagePort); + +// WEBCORE_GENERATED_CONSTRUCTOR_GETTER(JSBroadcastChannel); +// WEBCORE_GENERATED_CONSTRUCTOR_SETTER(JSBroadcastChannel); + JSC_DECLARE_CUSTOM_GETTER(JSEvent_getter); JSC_DEFINE_CUSTOM_GETTER(JSEvent_getter, @@ -1285,13 +1299,14 @@ JSC_DEFINE_HOST_FUNCTION(functionStructuredClone, } } - ExceptionOr<Ref<SerializedScriptValue>> serialized = SerializedScriptValue::create(*globalObject, value, WTFMove(transferList)); + Vector<RefPtr<MessagePort>> ports; + ExceptionOr<Ref<SerializedScriptValue>> serialized = SerializedScriptValue::create(*globalObject, value, WTFMove(transferList), ports); if (serialized.hasException()) { WebCore::propagateException(*globalObject, throwScope, serialized.releaseException()); return JSValue::encode(jsUndefined()); } - JSValue deserialized = serialized.releaseReturnValue()->deserialize(*globalObject, globalObject); + JSValue deserialized = serialized.releaseReturnValue()->deserialize(*globalObject, globalObject, ports); return JSValue::encode(deserialized); } @@ -3665,26 +3680,28 @@ JSC_DEFINE_HOST_FUNCTION(jsFunctionPostMessage, } } - ExceptionOr<Ref<SerializedScriptValue>> serialized = SerializedScriptValue::create(*globalObject, value, WTFMove(transferList)); + Vector<RefPtr<MessagePort>> ports; + ExceptionOr<Ref<SerializedScriptValue>> serialized = SerializedScriptValue::create(*globalObject, value, WTFMove(transferList), ports); if (serialized.hasException()) { WebCore::propagateException(*globalObject, throwScope, serialized.releaseException()); return JSValue::encode(jsUndefined()); } - RefPtr<SerializedScriptValue> message = serialized.releaseReturnValue(); - ScriptExecutionContext::postTaskTo(context->identifier(), [message = WTFMove(message), protectedThis = Ref { *worker }](ScriptExecutionContext& context) { + ExceptionOr<Vector<TransferredMessagePort>> disentangledPorts = MessagePort::disentanglePorts(WTFMove(ports)); + if (disentangledPorts.hasException()) { + WebCore::propagateException(*globalObject, throwScope, serialized.releaseException()); + return JSValue::encode(jsUndefined()); + } + + MessageWithMessagePorts messageWithMessagePorts { serialized.releaseReturnValue(), disentangledPorts.releaseReturnValue() }; + + ScriptExecutionContext::postTaskTo(context->identifier(), [message = messageWithMessagePorts, protectedThis = Ref { *worker }, ports](ScriptExecutionContext& context) mutable { Zig::GlobalObject* globalObject = jsCast<Zig::GlobalObject*>(context.jsGlobalObject()); - bool didFail = false; - JSValue value = message->deserialize(*globalObject, globalObject, SerializationErrorMode::NonThrowing, &didFail); - if (didFail) { - protectedThis->dispatchEvent(MessageEvent::create(eventNames().messageerrorEvent, MessageEvent::Init {}, MessageEvent::IsTrusted::Yes)); - return; - } + auto ports = MessagePort::entanglePorts(context, WTFMove(message.transferredPorts)); + auto event = MessageEvent::create(*globalObject, message.message.releaseNonNull(), std::nullopt, WTFMove(ports)); - WebCore::MessageEvent::Init init; - init.data = value; - protectedThis->dispatchEvent(MessageEvent::create(eventNames().messageEvent, WTFMove(init), MessageEvent::IsTrusted::Yes)); + protectedThis->dispatchEvent(event.event); }); return JSValue::encode(jsUndefined()); @@ -4227,6 +4244,9 @@ void GlobalObject::addBuiltinGlobals(JSC::VM& vm) PUT_WEBCORE_GENERATED_CONSTRUCTOR("Headers"_s, JSFetchHeaders); PUT_WEBCORE_GENERATED_CONSTRUCTOR("URLSearchParams"_s, JSURLSearchParams); PUT_WEBCORE_GENERATED_CONSTRUCTOR("Worker"_s, JSWorker); + PUT_WEBCORE_GENERATED_CONSTRUCTOR("MessageChannel"_s, JSMessageChannel); + PUT_WEBCORE_GENERATED_CONSTRUCTOR("MessagePort"_s, JSMessagePort); + // PUT_WEBCORE_GENERATED_CONSTRUCTOR("BroadcastChannel"_s, JSBroadcastChannel); putDirectCustomAccessor(vm, builtinNames.TransformStreamPublicName(), CustomGetterSetter::create(vm, jsServiceWorkerGlobalScope_TransformStreamConstructor, nullptr), attributesForStructure(static_cast<unsigned>(JSC::PropertyAttribute::DontEnum))); putDirectCustomAccessor(vm, builtinNames.TransformStreamPrivateName(), CustomGetterSetter::create(vm, jsServiceWorkerGlobalScope_TransformStreamConstructor, nullptr), attributesForStructure(static_cast<unsigned>(JSC::PropertyAttribute::DontEnum))); @@ -4619,6 +4639,9 @@ void GlobalObject::visitChildrenImpl(JSCell* cell, Visitor& visitor) visitor.append(thisObject->m_JSURLSearchParamsSetterValue); visitor.append(thisObject->m_JSDOMFormDataSetterValue); visitor.append(thisObject->m_JSWorkerSetterValue); + visitor.append(thisObject->m_JSMessageChannelSetterValue); + visitor.append(thisObject->m_JSMessagePortSetterValue); + visitor.append(thisObject->m_JSBroadcastChannelSetterValue); thisObject->m_JSArrayBufferSinkClassStructure.visit(visitor); thisObject->m_JSBufferListClassStructure.visit(visitor); diff --git a/src/bun.js/bindings/ZigGlobalObject.h b/src/bun.js/bindings/ZigGlobalObject.h index e4364d248..9d04786d5 100644 --- a/src/bun.js/bindings/ZigGlobalObject.h +++ b/src/bun.js/bindings/ZigGlobalObject.h @@ -43,11 +43,8 @@ class DOMWrapperWorld; #include "BunPlugin.h" #include "JSMockFunction.h" -namespace Bun { -class GlobalScope; -} - namespace WebCore { +class GlobalScope; class SubtleCrypto; class EventTarget; } @@ -386,6 +383,9 @@ public: mutable WriteBarrier<Unknown> m_JSWebSocketSetterValue; mutable WriteBarrier<Unknown> m_JSDOMFormDataSetterValue; mutable WriteBarrier<Unknown> m_JSWorkerSetterValue; + mutable WriteBarrier<Unknown> m_JSMessageChannelSetterValue; + mutable WriteBarrier<Unknown> m_JSMessagePortSetterValue; + mutable WriteBarrier<Unknown> m_JSBroadcastChannelSetterValue; mutable WriteBarrier<Unknown> m_BunCommonJSModuleValue; mutable WriteBarrier<JSFunction> m_thenables[promiseFunctionsSize + 1]; diff --git a/src/bun.js/bindings/webcore/ContextDestructionObserver.cpp b/src/bun.js/bindings/webcore/ContextDestructionObserver.cpp new file mode 100644 index 000000000..72e6a85c4 --- /dev/null +++ b/src/bun.js/bindings/webcore/ContextDestructionObserver.cpp @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2008 Apple Inc. All Rights Reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include "config.h" +#include "ContextDestructionObserver.h" + +#include "ScriptExecutionContext.h" + +namespace WebCore { + +ContextDestructionObserver::ContextDestructionObserver(ScriptExecutionContext* scriptExecutionContext) + : m_context(nullptr) +{ + observeContext(scriptExecutionContext); +} + +ContextDestructionObserver::~ContextDestructionObserver() +{ + observeContext(nullptr); +} + +void ContextDestructionObserver::observeContext(ScriptExecutionContext* scriptExecutionContext) +{ + if (m_context) { + ASSERT(m_context->isContextThread()); + m_context->willDestroyDestructionObserver(*this); + } + + m_context = scriptExecutionContext; + + if (m_context) { + ASSERT(m_context->isContextThread()); + m_context->didCreateDestructionObserver(*this); + } +} + +void ContextDestructionObserver::contextDestroyed() +{ + m_context = nullptr; +} + +} // namespace WebCore diff --git a/src/bun.js/bindings/webcore/ContextDestructionObserver.h b/src/bun.js/bindings/webcore/ContextDestructionObserver.h index ccf20f301..7408b5f27 100644 --- a/src/bun.js/bindings/webcore/ContextDestructionObserver.h +++ b/src/bun.js/bindings/webcore/ContextDestructionObserver.h @@ -4,29 +4,23 @@ #include "root.h" -#include "ScriptExecutionContext.h" - namespace WebCore { -// TODO: +class ScriptExecutionContext; + class ContextDestructionObserver { public: - WEBCORE_EXPORT void contextDestroyed() {} + WEBCORE_EXPORT virtual void contextDestroyed(); ScriptExecutionContext* scriptExecutionContext() const { return m_context; } - ContextDestructionObserver(ScriptExecutionContext* context) - : m_context(context) - { - } - ContextDestructionObserver(ContextDestructionObserver& context) - : m_context(context.m_context) - { - } +protected: + WEBCORE_EXPORT ContextDestructionObserver(ScriptExecutionContext*); + WEBCORE_EXPORT virtual ~ContextDestructionObserver(); + void observeContext(ScriptExecutionContext*); private: - int m_junk = 0; ScriptExecutionContext* m_context; }; diff --git a/src/bun.js/bindings/webcore/DOMClientIsoSubspaces.h b/src/bun.js/bindings/webcore/DOMClientIsoSubspaces.h index 740c6d9a9..391060426 100644 --- a/src/bun.js/bindings/webcore/DOMClientIsoSubspaces.h +++ b/src/bun.js/bindings/webcore/DOMClientIsoSubspaces.h @@ -410,7 +410,7 @@ public: // std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForAnimationEvent; // std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForAttr; // std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForBeforeUnloadEvent; - // std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForBroadcastChannel; + std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForBroadcastChannel; // std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForCDATASection; // std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForCharacterData; // std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForClipboardEvent; @@ -443,9 +443,9 @@ public: // std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForIdleDeadline; // std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForInputEvent; // std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForKeyboardEvent; - // std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForMessageChannel; + std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForMessageChannel; std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForMessageEvent; - // std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForMessagePort; + std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForMessagePort; // std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForMouseEvent; // std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForMutationEvent; // std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForMutationObserver; diff --git a/src/bun.js/bindings/webcore/DOMIsoSubspaces.h b/src/bun.js/bindings/webcore/DOMIsoSubspaces.h index 52be5872a..01f202a81 100644 --- a/src/bun.js/bindings/webcore/DOMIsoSubspaces.h +++ b/src/bun.js/bindings/webcore/DOMIsoSubspaces.h @@ -401,7 +401,7 @@ public: // std::unique_ptr<IsoSubspace> m_subspaceForAnimationEvent; // std::unique_ptr<IsoSubspace> m_subspaceForAttr; // std::unique_ptr<IsoSubspace> m_subspaceForBeforeUnloadEvent; - // std::unique_ptr<IsoSubspace> m_subspaceForBroadcastChannel; + std::unique_ptr<IsoSubspace> m_subspaceForBroadcastChannel; // std::unique_ptr<IsoSubspace> m_subspaceForCDATASection; // std::unique_ptr<IsoSubspace> m_subspaceForCharacterData; // std::unique_ptr<IsoSubspace> m_subspaceForClipboardEvent; @@ -409,7 +409,6 @@ public: // std::unique_ptr<IsoSubspace> m_subspaceForCompositionEvent; // std::unique_ptr<IsoSubspace> m_subspaceForCustomElementRegistry; std::unique_ptr<IsoSubspace> m_subspaceForCustomEvent; - // std::unique_ptr<IsoSubspace> m_subspaceForDOMException; // std::unique_ptr<IsoSubspace> m_subspaceForDOMImplementation; // std::unique_ptr<IsoSubspace> m_subspaceForDOMPoint; // std::unique_ptr<IsoSubspace> m_subspaceForDOMPointReadOnly; @@ -436,9 +435,9 @@ public: // std::unique_ptr<IsoSubspace> m_subspaceForIdleDeadline; // std::unique_ptr<IsoSubspace> m_subspaceForInputEvent; // std::unique_ptr<IsoSubspace> m_subspaceForKeyboardEvent; - // std::unique_ptr<IsoSubspace> m_subspaceForMessageChannel; + std::unique_ptr<IsoSubspace> m_subspaceForMessageChannel; std::unique_ptr<IsoSubspace> m_subspaceForMessageEvent; - // std::unique_ptr<IsoSubspace> m_subspaceForMessagePort; + std::unique_ptr<IsoSubspace> m_subspaceForMessagePort; // std::unique_ptr<IsoSubspace> m_subspaceForMouseEvent; // std::unique_ptr<IsoSubspace> m_subspaceForMutationEvent; // std::unique_ptr<IsoSubspace> m_subspaceForMutationObserver; diff --git a/src/bun.js/bindings/webcore/EventListenerMap.cpp b/src/bun.js/bindings/webcore/EventListenerMap.cpp index 5014cfa00..540f498e0 100644 --- a/src/bun.js/bindings/webcore/EventListenerMap.cpp +++ b/src/bun.js/bindings/webcore/EventListenerMap.cpp @@ -41,7 +41,6 @@ #include <wtf/StdLibExtras.h> #include <wtf/Vector.h> - namespace WebCore { EventListenerMap::EventListenerMap() = default; @@ -168,7 +167,7 @@ EventListenerVector* EventListenerMap::find(const AtomString& eventType) static void removeFirstListenerCreatedFromMarkup(EventListenerVector& listenerVector) { - bool foundListener = listenerVector.removeFirstMatching([] (const auto& registeredListener) { + bool foundListener = listenerVector.removeFirstMatching([](const auto& registeredListener) { if (JSEventListener::wasCreatedFromMarkup(registeredListener->callback())) { registeredListener->markAsRemoved(); return true; diff --git a/src/bun.js/bindings/webcore/EventListenerMap.h b/src/bun.js/bindings/webcore/EventListenerMap.h index 572f999f9..690afd133 100644 --- a/src/bun.js/bindings/webcore/EventListenerMap.h +++ b/src/bun.js/bindings/webcore/EventListenerMap.h @@ -65,7 +65,7 @@ public: void removeFirstEventListenerCreatedFromMarkup(const AtomString& eventType); void copyEventListenersNotCreatedFromMarkupToTarget(EventTarget*); - + template<typename Visitor> void visitJSEventListeners(Visitor&); Lock& lock() { return m_lock; } diff --git a/src/bun.js/bindings/webcore/EventTargetFactory.cpp b/src/bun.js/bindings/webcore/EventTargetFactory.cpp index 97e66977b..867d007be 100644 --- a/src/bun.js/bindings/webcore/EventTargetFactory.cpp +++ b/src/bun.js/bindings/webcore/EventTargetFactory.cpp @@ -117,8 +117,8 @@ JSC::JSValue toJS(JSC::JSGlobalObject* state, JSDOMGlobalObject* globalObject, E // case MediaStreamTrackEventTargetInterfaceType: // return toJS(state, globalObject, static_cast<MediaStreamTrack&>(impl)); // #endif - // case MessagePortEventTargetInterfaceType: - // return toJS(state, globalObject, static_cast<MessagePort&>(impl)); + case MessagePortEventTargetInterfaceType: + return toJS(state, globalObject, static_cast<MessagePort&>(impl)); // case NodeEventTargetInterfaceType: // return toJS(state, globalObject, static_cast<Node&>(impl)); // #if ENABLE(NOTIFICATIONS) diff --git a/src/bun.js/bindings/webcore/EventTargetHeaders.h b/src/bun.js/bindings/webcore/EventTargetHeaders.h index b36e49a66..89c1f2393 100644 --- a/src/bun.js/bindings/webcore/EventTargetHeaders.h +++ b/src/bun.js/bindings/webcore/EventTargetHeaders.h @@ -106,8 +106,8 @@ // #include "JSMediaStreamTrack.h" // #include "MediaStreamTrack.h" // #endif -// #include "MessagePort.h" -// #include "JSMessagePort.h" +#include "MessagePort.h" +#include "JSMessagePort.h" // #include "JSNode.h" #include "Node.h" // #if ENABLE(NOTIFICATIONS) diff --git a/src/bun.js/bindings/webcore/JSMessageChannel.cpp b/src/bun.js/bindings/webcore/JSMessageChannel.cpp new file mode 100644 index 000000000..2082bd9a8 --- /dev/null +++ b/src/bun.js/bindings/webcore/JSMessageChannel.cpp @@ -0,0 +1,323 @@ +/* + This file is part of the WebKit open source project. + This file has been generated by generate-bindings.pl. DO NOT MODIFY! + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#include "config.h" + +#if ENABLE(CHANNEL_MESSAGING) + +#include "JSMessageChannel.h" + +#include "ActiveDOMObject.h" +#include "ExtendedDOMClientIsoSubspaces.h" +#include "ExtendedDOMIsoSubspaces.h" +#include "JSDOMAttribute.h" +#include "JSDOMBinding.h" +#include "JSDOMConstructor.h" +#include "JSDOMConvertInterface.h" +#include "JSDOMExceptionHandling.h" +#include "JSDOMGlobalObject.h" +#include "JSDOMGlobalObjectInlines.h" +#include "JSDOMWrapperCache.h" +#include "JSMessagePort.h" +#include "ScriptExecutionContext.h" +#include "WebCoreJSClientData.h" +#include <JavaScriptCore/FunctionPrototype.h> +#include <JavaScriptCore/HeapAnalyzer.h> +#include <JavaScriptCore/JSCInlines.h> +#include <JavaScriptCore/JSDestructibleObjectHeapCellType.h> +#include <JavaScriptCore/SlotVisitorMacros.h> +#include <JavaScriptCore/SubspaceInlines.h> +#include <wtf/GetPtr.h> +#include <wtf/PointerPreparations.h> +#include <wtf/URL.h> + +namespace WebCore { +using namespace JSC; + +// Attributes + +static JSC_DECLARE_CUSTOM_GETTER(jsMessageChannelConstructor); +static JSC_DECLARE_CUSTOM_GETTER(jsMessageChannel_port1); +static JSC_DECLARE_CUSTOM_GETTER(jsMessageChannel_port2); + +class JSMessageChannelPrototype final : public JSC::JSNonFinalObject { +public: + using Base = JSC::JSNonFinalObject; + static JSMessageChannelPrototype* create(JSC::VM& vm, JSDOMGlobalObject* globalObject, JSC::Structure* structure) + { + JSMessageChannelPrototype* ptr = new (NotNull, JSC::allocateCell<JSMessageChannelPrototype>(vm)) JSMessageChannelPrototype(vm, globalObject, structure); + ptr->finishCreation(vm); + return ptr; + } + + DECLARE_INFO; + template<typename CellType, JSC::SubspaceAccess> + static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm) + { + STATIC_ASSERT_ISO_SUBSPACE_SHARABLE(JSMessageChannelPrototype, Base); + 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: + JSMessageChannelPrototype(JSC::VM& vm, JSC::JSGlobalObject*, JSC::Structure* structure) + : JSC::JSNonFinalObject(vm, structure) + { + } + + void finishCreation(JSC::VM&); +}; +STATIC_ASSERT_ISO_SUBSPACE_SHARABLE(JSMessageChannelPrototype, JSMessageChannelPrototype::Base); + +using JSMessageChannelDOMConstructor = JSDOMConstructor<JSMessageChannel>; + +template<> EncodedJSValue JSC_HOST_CALL_ATTRIBUTES JSMessageChannelDOMConstructor::construct(JSGlobalObject* lexicalGlobalObject, CallFrame* callFrame) +{ + VM& vm = lexicalGlobalObject->vm(); + auto throwScope = DECLARE_THROW_SCOPE(vm); + auto* castedThis = jsCast<JSMessageChannelDOMConstructor*>(callFrame->jsCallee()); + ASSERT(castedThis); + auto* context = castedThis->scriptExecutionContext(); + if (UNLIKELY(!context)) + return throwConstructorScriptExecutionContextUnavailableError(*lexicalGlobalObject, throwScope, "MessageChannel"); + auto object = MessageChannel::create(*context); + if constexpr (IsExceptionOr<decltype(object)>) + RETURN_IF_EXCEPTION(throwScope, {}); + static_assert(TypeOrExceptionOrUnderlyingType<decltype(object)>::isRef); + auto jsValue = toJSNewlyCreated<IDLInterface<MessageChannel>>(*lexicalGlobalObject, *castedThis->globalObject(), throwScope, WTFMove(object)); + if constexpr (IsExceptionOr<decltype(object)>) + RETURN_IF_EXCEPTION(throwScope, {}); + setSubclassStructureIfNeeded<MessageChannel>(lexicalGlobalObject, callFrame, asObject(jsValue)); + RETURN_IF_EXCEPTION(throwScope, {}); + return JSValue::encode(jsValue); +} +JSC_ANNOTATE_HOST_FUNCTION(JSMessageChannelDOMConstructorConstruct, JSMessageChannelDOMConstructor::construct); + +template<> const ClassInfo JSMessageChannelDOMConstructor::s_info = { "MessageChannel"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSMessageChannelDOMConstructor) }; + +template<> JSValue JSMessageChannelDOMConstructor::prototypeForStructure(JSC::VM& vm, const JSDOMGlobalObject& globalObject) +{ + UNUSED_PARAM(vm); + return globalObject.functionPrototype(); +} + +template<> void JSMessageChannelDOMConstructor::initializeProperties(VM& vm, JSDOMGlobalObject& globalObject) +{ + putDirect(vm, vm.propertyNames->length, jsNumber(0), JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::DontEnum); + JSString* nameString = jsNontrivialString(vm, "MessageChannel"_s); + m_originalName.set(vm, this, nameString); + putDirect(vm, vm.propertyNames->name, nameString, JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::DontEnum); + putDirect(vm, vm.propertyNames->prototype, JSMessageChannel::prototype(vm, globalObject), JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::DontEnum | JSC::PropertyAttribute::DontDelete); +} + +/* Hash table for prototype */ + +static const HashTableValue JSMessageChannelPrototypeTableValues[] = { + { "constructor"_s, static_cast<unsigned>(PropertyAttribute::DontEnum), NoIntrinsic, { HashTableValue::GetterSetterType, jsMessageChannelConstructor, 0 } }, + { "port1"_s, JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::CustomAccessor | JSC::PropertyAttribute::DOMAttribute, NoIntrinsic, { HashTableValue::GetterSetterType, jsMessageChannel_port1, 0 } }, + { "port2"_s, JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::CustomAccessor | JSC::PropertyAttribute::DOMAttribute, NoIntrinsic, { HashTableValue::GetterSetterType, jsMessageChannel_port2, 0 } }, +}; + +const ClassInfo JSMessageChannelPrototype::s_info = { "MessageChannel"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSMessageChannelPrototype) }; + +void JSMessageChannelPrototype::finishCreation(VM& vm) +{ + Base::finishCreation(vm); + reifyStaticProperties(vm, JSMessageChannel::info(), JSMessageChannelPrototypeTableValues, *this); + JSC_TO_STRING_TAG_WITHOUT_TRANSITION(); +} + +const ClassInfo JSMessageChannel::s_info = { "MessageChannel"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSMessageChannel) }; + +JSMessageChannel::JSMessageChannel(Structure* structure, JSDOMGlobalObject& globalObject, Ref<MessageChannel>&& impl) + : JSDOMWrapper<MessageChannel>(structure, globalObject, WTFMove(impl)) +{ +} + +// static_assert(!std::is_base_of<ActiveDOMObject, MessageChannel>::value, "Interface is not marked as [ActiveDOMObject] even though implementation class subclasses ActiveDOMObject."); + +JSObject* JSMessageChannel::createPrototype(VM& vm, JSDOMGlobalObject& globalObject) +{ + auto* structure = JSMessageChannelPrototype::createStructure(vm, &globalObject, globalObject.objectPrototype()); + structure->setMayBePrototype(true); + return JSMessageChannelPrototype::create(vm, &globalObject, structure); +} + +JSObject* JSMessageChannel::prototype(VM& vm, JSDOMGlobalObject& globalObject) +{ + return getDOMPrototype<JSMessageChannel>(vm, globalObject); +} + +JSValue JSMessageChannel::getConstructor(VM& vm, const JSGlobalObject* globalObject) +{ + return getDOMConstructor<JSMessageChannelDOMConstructor, DOMConstructorID::MessageChannel>(vm, *jsCast<const JSDOMGlobalObject*>(globalObject)); +} + +void JSMessageChannel::destroy(JSC::JSCell* cell) +{ + JSMessageChannel* thisObject = static_cast<JSMessageChannel*>(cell); + thisObject->JSMessageChannel::~JSMessageChannel(); +} + +JSC_DEFINE_CUSTOM_GETTER(jsMessageChannelConstructor, (JSGlobalObject * lexicalGlobalObject, EncodedJSValue thisValue, PropertyName)) +{ + VM& vm = JSC::getVM(lexicalGlobalObject); + auto throwScope = DECLARE_THROW_SCOPE(vm); + auto* prototype = jsDynamicCast<JSMessageChannelPrototype*>(JSValue::decode(thisValue)); + if (UNLIKELY(!prototype)) + return throwVMTypeError(lexicalGlobalObject, throwScope); + return JSValue::encode(JSMessageChannel::getConstructor(JSC::getVM(lexicalGlobalObject), prototype->globalObject())); +} + +static inline JSValue jsMessageChannel_port1Getter(JSGlobalObject& lexicalGlobalObject, JSMessageChannel& thisObject) +{ + auto& vm = JSC::getVM(&lexicalGlobalObject); + auto throwScope = DECLARE_THROW_SCOPE(vm); + auto& impl = thisObject.wrapped(); + RELEASE_AND_RETURN(throwScope, (toJS<IDLInterface<MessagePort>>(lexicalGlobalObject, *thisObject.globalObject(), throwScope, impl.port1()))); +} + +JSC_DEFINE_CUSTOM_GETTER(jsMessageChannel_port1, (JSGlobalObject * lexicalGlobalObject, EncodedJSValue thisValue, PropertyName attributeName)) +{ + return IDLAttribute<JSMessageChannel>::get<jsMessageChannel_port1Getter, CastedThisErrorBehavior::Assert>(*lexicalGlobalObject, thisValue, attributeName); +} + +static inline JSValue jsMessageChannel_port2Getter(JSGlobalObject& lexicalGlobalObject, JSMessageChannel& thisObject) +{ + auto& vm = JSC::getVM(&lexicalGlobalObject); + auto throwScope = DECLARE_THROW_SCOPE(vm); + auto& impl = thisObject.wrapped(); + RELEASE_AND_RETURN(throwScope, (toJS<IDLInterface<MessagePort>>(lexicalGlobalObject, *thisObject.globalObject(), throwScope, impl.port2()))); +} + +JSC_DEFINE_CUSTOM_GETTER(jsMessageChannel_port2, (JSGlobalObject * lexicalGlobalObject, EncodedJSValue thisValue, PropertyName attributeName)) +{ + return IDLAttribute<JSMessageChannel>::get<jsMessageChannel_port2Getter, CastedThisErrorBehavior::Assert>(*lexicalGlobalObject, thisValue, attributeName); +} + +JSC::GCClient::IsoSubspace* JSMessageChannel::subspaceForImpl(JSC::VM& vm) +{ + return WebCore::subspaceForImpl<JSMessageChannel, UseCustomHeapCellType::No>( + vm, + [](auto& spaces) { return spaces.m_clientSubspaceForMessageChannel.get(); }, + [](auto& spaces, auto&& space) { spaces.m_clientSubspaceForMessageChannel = std::forward<decltype(space)>(space); }, + [](auto& spaces) { return spaces.m_subspaceForMessageChannel.get(); }, + [](auto& spaces, auto&& space) { spaces.m_subspaceForMessageChannel = std::forward<decltype(space)>(space); }); +} + +template<typename Visitor> +void JSMessageChannel::visitChildrenImpl(JSCell* cell, Visitor& visitor) +{ + auto* thisObject = jsCast<JSMessageChannel*>(cell); + ASSERT_GC_OBJECT_INHERITS(thisObject, info()); + Base::visitChildren(thisObject, visitor); + thisObject->visitAdditionalChildren(visitor); +} + +DEFINE_VISIT_CHILDREN(JSMessageChannel); + +template<typename Visitor> +void JSMessageChannel::visitOutputConstraints(JSCell* cell, Visitor& visitor) +{ + auto* thisObject = jsCast<JSMessageChannel*>(cell); + ASSERT_GC_OBJECT_INHERITS(thisObject, info()); + Base::visitOutputConstraints(thisObject, visitor); + thisObject->visitAdditionalChildren(visitor); +} + +template void JSMessageChannel::visitOutputConstraints(JSCell*, AbstractSlotVisitor&); +template void JSMessageChannel::visitOutputConstraints(JSCell*, SlotVisitor&); +void JSMessageChannel::analyzeHeap(JSCell* cell, HeapAnalyzer& analyzer) +{ + auto* thisObject = jsCast<JSMessageChannel*>(cell); + analyzer.setWrappedObjectForCell(cell, &thisObject->wrapped()); + if (thisObject->scriptExecutionContext()) + analyzer.setLabelForCell(cell, "url "_s + thisObject->scriptExecutionContext()->url().string()); + Base::analyzeHeap(cell, analyzer); +} + +bool JSMessageChannelOwner::isReachableFromOpaqueRoots(JSC::Handle<JSC::Unknown> handle, void*, AbstractSlotVisitor& visitor, const char** reason) +{ + UNUSED_PARAM(handle); + UNUSED_PARAM(visitor); + UNUSED_PARAM(reason); + return false; +} + +void JSMessageChannelOwner::finalize(JSC::Handle<JSC::Unknown> handle, void* context) +{ + auto* jsMessageChannel = static_cast<JSMessageChannel*>(handle.slot()->asCell()); + auto& world = *static_cast<DOMWrapperWorld*>(context); + uncacheWrapper(world, &jsMessageChannel->wrapped(), jsMessageChannel); +} + +#if ENABLE(BINDING_INTEGRITY) +#if PLATFORM(WIN) +#pragma warning(disable : 4483) +extern "C" { +extern void (*const __identifier("??_7MessageChannel@WebCore@@6B@")[])(); +} +#else +extern "C" { +extern void* _ZTVN7WebCore14MessageChannelE[]; +} +#endif +#endif + +JSC::JSValue toJSNewlyCreated(JSC::JSGlobalObject*, JSDOMGlobalObject* globalObject, Ref<MessageChannel>&& impl) +{ + + if constexpr (std::is_polymorphic_v<MessageChannel>) { +#if ENABLE(BINDING_INTEGRITY) + const void* actualVTablePointer = getVTablePointer(impl.ptr()); +#if PLATFORM(WIN) + void* expectedVTablePointer = __identifier("??_7MessageChannel@WebCore@@6B@"); +#else + void* expectedVTablePointer = &_ZTVN7WebCore14MessageChannelE[2]; +#endif + + // If you hit this assertion you either have a use after free bug, or + // MessageChannel has subclasses. If MessageChannel has subclasses that get passed + // to toJS() we currently require MessageChannel you to opt out of binding hardening + // by adding the SkipVTableValidation attribute to the interface IDL definition + RELEASE_ASSERT(actualVTablePointer == expectedVTablePointer); +#endif + } + return createWrapper<MessageChannel>(globalObject, WTFMove(impl)); +} + +JSC::JSValue toJS(JSC::JSGlobalObject* lexicalGlobalObject, JSDOMGlobalObject* globalObject, MessageChannel& impl) +{ + return wrap(lexicalGlobalObject, globalObject, impl); +} + +MessageChannel* JSMessageChannel::toWrapped(JSC::VM&, JSC::JSValue value) +{ + if (auto* wrapper = jsDynamicCast<JSMessageChannel*>(value)) + return &wrapper->wrapped(); + return nullptr; +} + +} + +#endif // ENABLE(CHANNEL_MESSAGING) diff --git a/src/bun.js/bindings/webcore/JSMessageChannel.h b/src/bun.js/bindings/webcore/JSMessageChannel.h new file mode 100644 index 000000000..551be7459 --- /dev/null +++ b/src/bun.js/bindings/webcore/JSMessageChannel.h @@ -0,0 +1,102 @@ +/* + This file is part of the WebKit open source project. + This file has been generated by generate-bindings.pl. DO NOT MODIFY! + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#pragma once + +#if ENABLE(CHANNEL_MESSAGING) + +#include "JSDOMWrapper.h" +#include "MessageChannel.h" +#include <wtf/NeverDestroyed.h> + +namespace WebCore { + +class JSMessageChannel : public JSDOMWrapper<MessageChannel> { +public: + using Base = JSDOMWrapper<MessageChannel>; + static JSMessageChannel* create(JSC::Structure* structure, JSDOMGlobalObject* globalObject, Ref<MessageChannel>&& impl) + { + JSMessageChannel* ptr = new (NotNull, JSC::allocateCell<JSMessageChannel>(globalObject->vm())) JSMessageChannel(structure, *globalObject, WTFMove(impl)); + ptr->finishCreation(globalObject->vm()); + return ptr; + } + + static JSC::JSObject* createPrototype(JSC::VM&, JSDOMGlobalObject&); + static JSC::JSObject* prototype(JSC::VM&, JSDOMGlobalObject&); + static MessageChannel* toWrapped(JSC::VM&, JSC::JSValue); + static void destroy(JSC::JSCell*); + + DECLARE_INFO; + + 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(), JSC::NonArray); + } + + static JSC::JSValue getConstructor(JSC::VM&, const JSC::JSGlobalObject*); + template<typename, JSC::SubspaceAccess mode> static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm) + { + if constexpr (mode == JSC::SubspaceAccess::Concurrently) + return nullptr; + return subspaceForImpl(vm); + } + static JSC::GCClient::IsoSubspace* subspaceForImpl(JSC::VM& vm); + DECLARE_VISIT_CHILDREN; + template<typename Visitor> void visitAdditionalChildren(Visitor&); + + template<typename Visitor> static void visitOutputConstraints(JSCell*, Visitor&); + static void analyzeHeap(JSCell*, JSC::HeapAnalyzer&); + +protected: + JSMessageChannel(JSC::Structure*, JSDOMGlobalObject&, Ref<MessageChannel>&&); + + DECLARE_DEFAULT_FINISH_CREATION; +}; + +class JSMessageChannelOwner final : public JSC::WeakHandleOwner { +public: + bool isReachableFromOpaqueRoots(JSC::Handle<JSC::Unknown>, void* context, JSC::AbstractSlotVisitor&, const char**) final; + void finalize(JSC::Handle<JSC::Unknown>, void* context) final; +}; + +inline JSC::WeakHandleOwner* wrapperOwner(DOMWrapperWorld&, MessageChannel*) +{ + static NeverDestroyed<JSMessageChannelOwner> owner; + return &owner.get(); +} + +inline void* wrapperKey(MessageChannel* wrappableObject) +{ + return wrappableObject; +} + +JSC::JSValue toJS(JSC::JSGlobalObject*, JSDOMGlobalObject*, MessageChannel&); +inline JSC::JSValue toJS(JSC::JSGlobalObject* lexicalGlobalObject, JSDOMGlobalObject* globalObject, MessageChannel* impl) { return impl ? toJS(lexicalGlobalObject, globalObject, *impl) : JSC::jsNull(); } +JSC::JSValue toJSNewlyCreated(JSC::JSGlobalObject*, JSDOMGlobalObject*, Ref<MessageChannel>&&); +inline JSC::JSValue toJSNewlyCreated(JSC::JSGlobalObject* lexicalGlobalObject, JSDOMGlobalObject* globalObject, RefPtr<MessageChannel>&& impl) { return impl ? toJSNewlyCreated(lexicalGlobalObject, globalObject, impl.releaseNonNull()) : JSC::jsNull(); } + +template<> struct JSDOMWrapperConverterTraits<MessageChannel> { + using WrapperClass = JSMessageChannel; + using ToWrappedReturnType = MessageChannel*; +}; + +} // namespace WebCore + +#endif // ENABLE(CHANNEL_MESSAGING) diff --git a/src/bun.js/bindings/webcore/JSMessageChannelCustom.cpp b/src/bun.js/bindings/webcore/JSMessageChannelCustom.cpp new file mode 100644 index 000000000..75b074048 --- /dev/null +++ b/src/bun.js/bindings/webcore/JSMessageChannelCustom.cpp @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2008-2021 Apple Inc. All Rights Reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" + +#if ENABLE(CHANNEL_MESSAGING) + +#include "DOMWrapperWorld.h" +#include "JSMessageChannel.h" +// #include "JSNodeCustom.h" +#include "MessagePort.h" +// #include "WebCoreOpaqueRootInlines.h" +#include <JavaScriptCore/SlotVisitorInlines.h> + +namespace WebCore { + +template<typename Visitor> +void JSMessageChannel::visitAdditionalChildren(Visitor& visitor) +{ + visitor.addOpaqueRoot(WTF::getPtr(wrapped().port1())); + visitor.addOpaqueRoot(WTF::getPtr(wrapped().port2())); + // addWebCoreOpaqueRoot(visitor, wrapped().port1()); + // addWebCoreOpaqueRoot(visitor, wrapped().port2()); +} + +DEFINE_VISIT_ADDITIONAL_CHILDREN(JSMessageChannel); + +} // namespace WebCore + +#endif // ENABLE(CHANNEL_MESSAGING) diff --git a/src/bun.js/bindings/webcore/JSMessageEvent.cpp b/src/bun.js/bindings/webcore/JSMessageEvent.cpp index e797e3af2..f6090bd91 100644 --- a/src/bun.js/bindings/webcore/JSMessageEvent.cpp +++ b/src/bun.js/bindings/webcore/JSMessageEvent.cpp @@ -41,7 +41,7 @@ #include "JSDOMGlobalObjectInlines.h" #include "JSDOMOperation.h" #include "JSDOMWrapperCache.h" -// #include "JSMessagePort.h" +#include "JSMessagePort.h" #include "JSServiceWorker.h" #include "JSWindowProxy.h" #include "ScriptExecutionContext.h" @@ -143,18 +143,18 @@ template<> MessageEvent::Init convertDictionary<MessageEvent::Init>(JSGlobalObje RETURN_IF_EXCEPTION(throwScope, {}); } else result.origin = emptyString(); - // JSValue portsValue; - // if (isNullOrUndefined) - // portsValue = jsUndefined(); - // else { - // portsValue = object->get(&lexicalGlobalObject, Identifier::fromString(vm, "ports"_s)); - // RETURN_IF_EXCEPTION(throwScope, { }); - // } - // if (!portsValue.isUndefined()) { - // result.ports = convert<IDLSequence<IDLInterface<MessagePort>>>(lexicalGlobalObject, portsValue); - // RETURN_IF_EXCEPTION(throwScope, { }); - // } else - // result.ports = Converter<IDLSequence<IDLInterface<MessagePort>>>::ReturnType{ }; + JSValue portsValue; + if (isNullOrUndefined) + portsValue = jsUndefined(); + else { + portsValue = object->get(&lexicalGlobalObject, Identifier::fromString(vm, "ports"_s)); + RETURN_IF_EXCEPTION(throwScope, {}); + } + if (!portsValue.isUndefined()) { + result.ports = convert<IDLSequence<IDLInterface<MessagePort>>>(lexicalGlobalObject, portsValue); + RETURN_IF_EXCEPTION(throwScope, {}); + } else + result.ports = Converter<IDLSequence<IDLInterface<MessagePort>>>::ReturnType {}; JSValue sourceValue; if (isNullOrUndefined) sourceValue = jsUndefined(); @@ -377,9 +377,7 @@ JSC_DEFINE_CUSTOM_GETTER(jsMessageEvent_data, (JSGlobalObject * lexicalGlobalObj static inline JSValue jsMessageEvent_portsGetter(JSGlobalObject& lexicalGlobalObject, JSMessageEvent& thisObject) { UNUSED_PARAM(lexicalGlobalObject); - // TODO: - return JSArray::create(lexicalGlobalObject.vm(), lexicalGlobalObject.arrayStructureForIndexingTypeDuringAllocation(ArrayWithContiguous), 0); - // return thisObject.ports(lexicalGlobalObject); + return thisObject.ports(lexicalGlobalObject); } JSC_DEFINE_CUSTOM_GETTER(jsMessageEvent_ports, (JSGlobalObject * lexicalGlobalObject, EncodedJSValue thisValue, PropertyName attributeName)) @@ -416,13 +414,12 @@ static inline JSC::EncodedJSValue jsMessageEventPrototypeFunction_initMessageEve RETURN_IF_EXCEPTION(throwScope, encodedJSValue()); EnsureStillAliveScope argument6 = callFrame->argument(6); auto source = WebCore::MessageEventSource(); - // RETURN_IF_EXCEPTION(throwScope, encodedJSValue()); - // EnsureStillAliveScope argument7 = callFrame->argument(7); - // auto messagePorts = JSArray::create(lexicalGlobalObject.vm(), lexicalGlobalObject->arrayStructureForIndexingTypeDuringAllocation(ArrayWithContiguous), 0); - // auto messagePorts = Converter<IDLSequence<IDLInterface<MessagePort>>>::ReturnType {}; + // auto source = argument6.value().isUndefined() ? std::nullopt : convert<IDLNullable<IDLUnion<IDLInterface<WindowProxy>, IDLInterface<MessagePort>, IDLInterface<ServiceWorker>>>>(*lexicalGlobalObject, argument6.value()); + RETURN_IF_EXCEPTION(throwScope, encodedJSValue()); + EnsureStillAliveScope argument7 = callFrame->argument(7); + auto messagePorts = argument7.value().isUndefined() ? Converter<IDLSequence<IDLInterface<MessagePort>>>::ReturnType {} : convert<IDLSequence<IDLInterface<MessagePort>>>(*lexicalGlobalObject, argument7.value()); RETURN_IF_EXCEPTION(throwScope, encodedJSValue()); - // RELEASE_AND_RETURN(throwScope, JSValue::encode(toJS<IDLUndefined>(*lexicalGlobalObject, throwScope, [&]() -> decltype(auto) { return impl.initMessageEvent(WTFMove(type), WTFMove(bubbles), WTFMove(cancelable), WTFMove(data), WTFMove(originArg), WTFMove(lastEventId), WTFMove(source), WTFMove(messagePorts)); }))); - RELEASE_AND_RETURN(throwScope, JSValue::encode(toJS<IDLUndefined>(*lexicalGlobalObject, throwScope, [&]() -> decltype(auto) { return impl.initMessageEvent(WTFMove(type), WTFMove(bubbles), WTFMove(cancelable), WTFMove(data), WTFMove(originArg), WTFMove(lastEventId), WTFMove(source)); }))); + RELEASE_AND_RETURN(throwScope, JSValue::encode(toJS<IDLUndefined>(*lexicalGlobalObject, throwScope, [&]() -> decltype(auto) { return impl.initMessageEvent(WTFMove(type), WTFMove(bubbles), WTFMove(cancelable), WTFMove(data), WTFMove(originArg), WTFMove(lastEventId), WTFMove(source), WTFMove(messagePorts)); }))); } JSC_DEFINE_HOST_FUNCTION(jsMessageEventPrototypeFunction_initMessageEvent, (JSGlobalObject * lexicalGlobalObject, CallFrame* callFrame)) @@ -474,7 +471,7 @@ void JSMessageEvent::analyzeHeap(JSCell* cell, HeapAnalyzer& analyzer) auto* thisObject = jsCast<JSMessageEvent*>(cell); analyzer.setWrappedObjectForCell(cell, &thisObject->wrapped()); if (thisObject->scriptExecutionContext()) - analyzer.setLabelForCell(cell, "url " + thisObject->scriptExecutionContext()->url().string()); + analyzer.setLabelForCell(cell, "url "_s + thisObject->scriptExecutionContext()->url().string()); Base::analyzeHeap(cell, analyzer); } diff --git a/src/bun.js/bindings/webcore/JSMessageEvent.h b/src/bun.js/bindings/webcore/JSMessageEvent.h index 85424d9ec..8f48568f7 100644 --- a/src/bun.js/bindings/webcore/JSMessageEvent.h +++ b/src/bun.js/bindings/webcore/JSMessageEvent.h @@ -70,6 +70,7 @@ public: { return static_cast<MessageEvent&>(Base::wrapped()); } + protected: JSMessageEvent(JSC::Structure*, JSDOMGlobalObject&, Ref<MessageEvent>&&); @@ -87,5 +88,4 @@ template<> struct JSDOMWrapperConverterTraits<MessageEvent> { }; template<> MessageEvent::Init convertDictionary<MessageEvent::Init>(JSC::JSGlobalObject&, JSC::JSValue); - } // namespace WebCore diff --git a/src/bun.js/bindings/webcore/JSMessageEventCustom.cpp b/src/bun.js/bindings/webcore/JSMessageEventCustom.cpp index 66390f86b..772a1b3c4 100644 --- a/src/bun.js/bindings/webcore/JSMessageEventCustom.cpp +++ b/src/bun.js/bindings/webcore/JSMessageEventCustom.cpp @@ -45,26 +45,21 @@ namespace WebCore { JSC::JSValue JSMessageEvent::ports(JSC::JSGlobalObject& lexicalGlobalObject) const { - return JSC::jsUndefined(); + auto throwScope = DECLARE_THROW_SCOPE(lexicalGlobalObject.vm()); + return cachedPropertyValue(lexicalGlobalObject, *this, wrapped().cachedPorts(), [&] { + JSC::JSValue ports = toJS<IDLFrozenArray<IDLInterface<MessagePort>>>(lexicalGlobalObject, *globalObject(), throwScope, wrapped().ports()); + return ports; + }); } -// auto throwScope = DECLARE_THROW_SCOPE(lexicalGlobalObject.vm()); -// return cachedPropertyValue(lexicalGlobalObject, *this, wrapped().cachedPorts(), [&] { -// JSC::JSValue ports = toJS<IDLFrozenArray<IDLInterface<MessagePort>>>(lexicalGlobalObject, *globalObject(), throwScope, wrapped().ports()); -// return ports; -// }); -// } JSC::JSValue JSMessageEvent::data(JSC::JSGlobalObject& lexicalGlobalObject) const { - return cachedPropertyValue(lexicalGlobalObject, *this, wrapped().cachedData(), [this, &lexicalGlobalObject] { + auto throwScope = DECLARE_THROW_SCOPE(lexicalGlobalObject.vm()); + return cachedPropertyValue(throwScope, lexicalGlobalObject, *this, wrapped().cachedData(), [this, &lexicalGlobalObject](JSC::ThrowScope&) { return WTF::switchOn( - wrapped().data(), [this](MessageEvent::JSValueTag) -> JSC::JSValue { return wrapped().jsData().getValue(JSC::jsNull()); }, - // [this, &lexicalGlobalObject](const Ref<SerializedScriptValue>& data) { - // // FIXME: Is it best to handle errors by returning null rather than throwing an exception? - // return data->deserialize(lexicalGlobalObject, globalObject(), wrapped().ports(), SerializationErrorMode::NonThrowing); }, - [&lexicalGlobalObject](const String& data) { return toJS<IDLDOMString>(lexicalGlobalObject, data); }, - // [this, &lexicalGlobalObject](const Ref<Blob>& data) { return toJS<IDLInterface<Blob>>(lexicalGlobalObject, *globalObject(), data); }, - [this, &lexicalGlobalObject](const Ref<ArrayBuffer>& data) { return toJS<IDLInterface<ArrayBuffer>>(lexicalGlobalObject, *globalObject(), data); }); + wrapped().data(), [this](MessageEvent::JSValueTag) -> JSC::JSValue { return wrapped().jsData().getValue(JSC::jsNull()); }, [this, &lexicalGlobalObject](const Ref<SerializedScriptValue>& data) { + // FIXME: Is it best to handle errors by returning null rather than throwing an exception? + return data->deserialize(lexicalGlobalObject, globalObject(), wrapped().ports(), SerializationErrorMode::NonThrowing); }, [&lexicalGlobalObject](const String& data) { return toJS<IDLDOMString>(lexicalGlobalObject, data); }, [this, &lexicalGlobalObject](const Ref<ArrayBuffer>& data) { return toJS<IDLInterface<ArrayBuffer>>(lexicalGlobalObject, *globalObject(), data); }); }); } diff --git a/src/bun.js/bindings/webcore/JSMessagePort.cpp b/src/bun.js/bindings/webcore/JSMessagePort.cpp new file mode 100644 index 000000000..d957e32ce --- /dev/null +++ b/src/bun.js/bindings/webcore/JSMessagePort.cpp @@ -0,0 +1,440 @@ +/* + This file is part of the WebKit open source project. + This file has been generated by generate-bindings.pl. DO NOT MODIFY! + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#include "config.h" +#include "JSMessagePort.h" + +#include "ActiveDOMObject.h" +#include "EventNames.h" +#include "ExtendedDOMClientIsoSubspaces.h" +#include "ExtendedDOMIsoSubspaces.h" +#include "IDLTypes.h" +#include "JSDOMAttribute.h" +#include "JSDOMBinding.h" +#include "JSDOMConstructorNotConstructable.h" +#include "JSDOMConvertAny.h" +#include "JSDOMConvertBase.h" +#include "JSDOMConvertDictionary.h" +#include "JSDOMConvertObject.h" +#include "JSDOMConvertSequences.h" +#include "JSDOMExceptionHandling.h" +#include "JSDOMGlobalObjectInlines.h" +#include "JSDOMOperation.h" +#include "JSDOMWrapperCache.h" +#include "JSEventListener.h" +#include "JSStructuredSerializeOptions.h" +#include "ScriptExecutionContext.h" +#include "WebCoreJSClientData.h" +// #include "WebCoreOpaqueRootInlines.h" +#include <JavaScriptCore/HeapAnalyzer.h> +#include <JavaScriptCore/IteratorOperations.h> +#include <JavaScriptCore/JSArray.h> +#include <JavaScriptCore/JSCInlines.h> +#include <JavaScriptCore/JSDestructibleObjectHeapCellType.h> +#include <JavaScriptCore/SlotVisitorMacros.h> +#include <JavaScriptCore/SubspaceInlines.h> +#include <wtf/GetPtr.h> +#include <wtf/PointerPreparations.h> +#include <wtf/URL.h> + +namespace WebCore { +using namespace JSC; + +// Functions + +static JSC_DECLARE_HOST_FUNCTION(jsMessagePortPrototypeFunction_postMessage); +static JSC_DECLARE_HOST_FUNCTION(jsMessagePortPrototypeFunction_start); +static JSC_DECLARE_HOST_FUNCTION(jsMessagePortPrototypeFunction_close); + +// Attributes + +static JSC_DECLARE_CUSTOM_GETTER(jsMessagePortConstructor); +static JSC_DECLARE_CUSTOM_GETTER(jsMessagePort_onmessage); +static JSC_DECLARE_CUSTOM_SETTER(setJSMessagePort_onmessage); +static JSC_DECLARE_CUSTOM_GETTER(jsMessagePort_onmessageerror); +static JSC_DECLARE_CUSTOM_SETTER(setJSMessagePort_onmessageerror); + +class JSMessagePortPrototype final : public JSC::JSNonFinalObject { +public: + using Base = JSC::JSNonFinalObject; + static JSMessagePortPrototype* create(JSC::VM& vm, JSDOMGlobalObject* globalObject, JSC::Structure* structure) + { + JSMessagePortPrototype* ptr = new (NotNull, JSC::allocateCell<JSMessagePortPrototype>(vm)) JSMessagePortPrototype(vm, globalObject, structure); + ptr->finishCreation(vm); + return ptr; + } + + DECLARE_INFO; + template<typename CellType, JSC::SubspaceAccess> + static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm) + { + STATIC_ASSERT_ISO_SUBSPACE_SHARABLE(JSMessagePortPrototype, Base); + 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: + JSMessagePortPrototype(JSC::VM& vm, JSC::JSGlobalObject*, JSC::Structure* structure) + : JSC::JSNonFinalObject(vm, structure) + { + } + + void finishCreation(JSC::VM&); +}; +STATIC_ASSERT_ISO_SUBSPACE_SHARABLE(JSMessagePortPrototype, JSMessagePortPrototype::Base); + +using JSMessagePortDOMConstructor = JSDOMConstructorNotConstructable<JSMessagePort>; + +template<> const ClassInfo JSMessagePortDOMConstructor::s_info = { "MessagePort"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSMessagePortDOMConstructor) }; + +template<> JSValue JSMessagePortDOMConstructor::prototypeForStructure(JSC::VM& vm, const JSDOMGlobalObject& globalObject) +{ + return JSEventTarget::getConstructor(vm, &globalObject); +} + +template<> void JSMessagePortDOMConstructor::initializeProperties(VM& vm, JSDOMGlobalObject& globalObject) +{ + putDirect(vm, vm.propertyNames->length, jsNumber(0), JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::DontEnum); + JSString* nameString = jsNontrivialString(vm, "MessagePort"_s); + m_originalName.set(vm, this, nameString); + putDirect(vm, vm.propertyNames->name, nameString, JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::DontEnum); + putDirect(vm, vm.propertyNames->prototype, JSMessagePort::prototype(vm, globalObject), JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::DontEnum | JSC::PropertyAttribute::DontDelete); +} + +/* Hash table for prototype */ + +static const HashTableValue JSMessagePortPrototypeTableValues[] = { + { "constructor"_s, static_cast<unsigned>(PropertyAttribute::DontEnum), NoIntrinsic, { HashTableValue::GetterSetterType, jsMessagePortConstructor, 0 } }, + { "onmessage"_s, JSC::PropertyAttribute::CustomAccessor | JSC::PropertyAttribute::DOMAttribute, NoIntrinsic, { HashTableValue::GetterSetterType, jsMessagePort_onmessage, setJSMessagePort_onmessage } }, + { "onmessageerror"_s, JSC::PropertyAttribute::CustomAccessor | JSC::PropertyAttribute::DOMAttribute, NoIntrinsic, { HashTableValue::GetterSetterType, jsMessagePort_onmessageerror, setJSMessagePort_onmessageerror } }, + { "postMessage"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function), NoIntrinsic, { HashTableValue::NativeFunctionType, jsMessagePortPrototypeFunction_postMessage, 1 } }, + { "start"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function), NoIntrinsic, { HashTableValue::NativeFunctionType, jsMessagePortPrototypeFunction_start, 0 } }, + { "close"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function), NoIntrinsic, { HashTableValue::NativeFunctionType, jsMessagePortPrototypeFunction_close, 0 } }, +}; + +const ClassInfo JSMessagePortPrototype::s_info = { "MessagePort"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSMessagePortPrototype) }; + +void JSMessagePortPrototype::finishCreation(VM& vm) +{ + Base::finishCreation(vm); + reifyStaticProperties(vm, JSMessagePort::info(), JSMessagePortPrototypeTableValues, *this); + JSC_TO_STRING_TAG_WITHOUT_TRANSITION(); +} + +const ClassInfo JSMessagePort::s_info = { "MessagePort"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSMessagePort) }; + +JSMessagePort::JSMessagePort(Structure* structure, JSDOMGlobalObject& globalObject, Ref<MessagePort>&& impl) + : JSEventTarget(structure, globalObject, WTFMove(impl)) +{ +} + +// static_assert(std::is_base_of<ActiveDOMObject, MessagePort>::value, "Interface is marked as [ActiveDOMObject] but implementation class does not subclass ActiveDOMObject."); + +JSObject* JSMessagePort::createPrototype(VM& vm, JSDOMGlobalObject& globalObject) +{ + auto* structure = JSMessagePortPrototype::createStructure(vm, &globalObject, JSEventTarget::prototype(vm, globalObject)); + structure->setMayBePrototype(true); + return JSMessagePortPrototype::create(vm, &globalObject, structure); +} + +JSObject* JSMessagePort::prototype(VM& vm, JSDOMGlobalObject& globalObject) +{ + return getDOMPrototype<JSMessagePort>(vm, globalObject); +} + +JSValue JSMessagePort::getConstructor(VM& vm, const JSGlobalObject* globalObject) +{ + return getDOMConstructor<JSMessagePortDOMConstructor, DOMConstructorID::MessagePort>(vm, *jsCast<const JSDOMGlobalObject*>(globalObject)); +} + +JSC_DEFINE_CUSTOM_GETTER(jsMessagePortConstructor, (JSGlobalObject * lexicalGlobalObject, EncodedJSValue thisValue, PropertyName)) +{ + VM& vm = JSC::getVM(lexicalGlobalObject); + auto throwScope = DECLARE_THROW_SCOPE(vm); + auto* prototype = jsDynamicCast<JSMessagePortPrototype*>(JSValue::decode(thisValue)); + if (UNLIKELY(!prototype)) + return throwVMTypeError(lexicalGlobalObject, throwScope); + return JSValue::encode(JSMessagePort::getConstructor(JSC::getVM(lexicalGlobalObject), prototype->globalObject())); +} + +static inline JSValue jsMessagePort_onmessageGetter(JSGlobalObject& lexicalGlobalObject, JSMessagePort& thisObject) +{ + UNUSED_PARAM(lexicalGlobalObject); + return eventHandlerAttribute(thisObject.wrapped(), eventNames().messageEvent, worldForDOMObject(thisObject)); +} + +JSC_DEFINE_CUSTOM_GETTER(jsMessagePort_onmessage, (JSGlobalObject * lexicalGlobalObject, EncodedJSValue thisValue, PropertyName attributeName)) +{ + return IDLAttribute<JSMessagePort>::get<jsMessagePort_onmessageGetter, CastedThisErrorBehavior::Assert>(*lexicalGlobalObject, thisValue, attributeName); +} + +static inline bool setJSMessagePort_onmessageSetter(JSGlobalObject& lexicalGlobalObject, JSMessagePort& thisObject, JSValue value) +{ + auto& vm = JSC::getVM(&lexicalGlobalObject); + UNUSED_PARAM(vm); + setEventHandlerAttribute<JSEventListener>(thisObject.wrapped(), eventNames().messageEvent, value, thisObject); + vm.writeBarrier(&thisObject, value); + ensureStillAliveHere(value); + + return true; +} + +JSC_DEFINE_CUSTOM_SETTER(setJSMessagePort_onmessage, (JSGlobalObject * lexicalGlobalObject, EncodedJSValue thisValue, EncodedJSValue encodedValue, PropertyName attributeName)) +{ + return IDLAttribute<JSMessagePort>::set<setJSMessagePort_onmessageSetter>(*lexicalGlobalObject, thisValue, encodedValue, attributeName); +} + +static inline JSValue jsMessagePort_onmessageerrorGetter(JSGlobalObject& lexicalGlobalObject, JSMessagePort& thisObject) +{ + UNUSED_PARAM(lexicalGlobalObject); + return eventHandlerAttribute(thisObject.wrapped(), eventNames().messageerrorEvent, worldForDOMObject(thisObject)); +} + +JSC_DEFINE_CUSTOM_GETTER(jsMessagePort_onmessageerror, (JSGlobalObject * lexicalGlobalObject, EncodedJSValue thisValue, PropertyName attributeName)) +{ + return IDLAttribute<JSMessagePort>::get<jsMessagePort_onmessageerrorGetter, CastedThisErrorBehavior::Assert>(*lexicalGlobalObject, thisValue, attributeName); +} + +static inline bool setJSMessagePort_onmessageerrorSetter(JSGlobalObject& lexicalGlobalObject, JSMessagePort& thisObject, JSValue value) +{ + auto& vm = JSC::getVM(&lexicalGlobalObject); + UNUSED_PARAM(vm); + setEventHandlerAttribute<JSEventListener>(thisObject.wrapped(), eventNames().messageerrorEvent, value, thisObject); + vm.writeBarrier(&thisObject, value); + ensureStillAliveHere(value); + + return true; +} + +JSC_DEFINE_CUSTOM_SETTER(setJSMessagePort_onmessageerror, (JSGlobalObject * lexicalGlobalObject, EncodedJSValue thisValue, EncodedJSValue encodedValue, PropertyName attributeName)) +{ + return IDLAttribute<JSMessagePort>::set<setJSMessagePort_onmessageerrorSetter>(*lexicalGlobalObject, thisValue, encodedValue, attributeName); +} + +static inline JSC::EncodedJSValue jsMessagePortPrototypeFunction_postMessage1Body(JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame, typename IDLOperation<JSMessagePort>::ClassParameter castedThis) +{ + auto& vm = JSC::getVM(lexicalGlobalObject); + auto throwScope = DECLARE_THROW_SCOPE(vm); + UNUSED_PARAM(throwScope); + UNUSED_PARAM(callFrame); + auto& impl = castedThis->wrapped(); + EnsureStillAliveScope argument0 = callFrame->uncheckedArgument(0); + auto message = convert<IDLAny>(*lexicalGlobalObject, argument0.value()); + RETURN_IF_EXCEPTION(throwScope, encodedJSValue()); + EnsureStillAliveScope argument1 = callFrame->uncheckedArgument(1); + auto transfer = convert<IDLSequence<IDLObject>>(*lexicalGlobalObject, argument1.value()); + RETURN_IF_EXCEPTION(throwScope, encodedJSValue()); + RELEASE_AND_RETURN(throwScope, JSValue::encode(toJS<IDLUndefined>(*lexicalGlobalObject, throwScope, [&]() -> decltype(auto) { return impl.postMessage(*jsCast<JSDOMGlobalObject*>(lexicalGlobalObject), WTFMove(message), WTFMove(transfer)); }))); +} + +static inline JSC::EncodedJSValue jsMessagePortPrototypeFunction_postMessage2Body(JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame, typename IDLOperation<JSMessagePort>::ClassParameter castedThis) +{ + auto& vm = JSC::getVM(lexicalGlobalObject); + auto throwScope = DECLARE_THROW_SCOPE(vm); + UNUSED_PARAM(throwScope); + UNUSED_PARAM(callFrame); + auto& impl = castedThis->wrapped(); + EnsureStillAliveScope argument0 = callFrame->uncheckedArgument(0); + auto message = convert<IDLAny>(*lexicalGlobalObject, argument0.value()); + RETURN_IF_EXCEPTION(throwScope, encodedJSValue()); + EnsureStillAliveScope argument1 = callFrame->argument(1); + auto options = convert<IDLDictionary<StructuredSerializeOptions>>(*lexicalGlobalObject, argument1.value()); + RETURN_IF_EXCEPTION(throwScope, encodedJSValue()); + RELEASE_AND_RETURN(throwScope, JSValue::encode(toJS<IDLUndefined>(*lexicalGlobalObject, throwScope, [&]() -> decltype(auto) { return impl.postMessage(*jsCast<JSDOMGlobalObject*>(lexicalGlobalObject), WTFMove(message), WTFMove(options)); }))); +} + +static inline JSC::EncodedJSValue jsMessagePortPrototypeFunction_postMessageOverloadDispatcher(JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame, typename IDLOperation<JSMessagePort>::ClassParameter castedThis) +{ + auto& vm = JSC::getVM(lexicalGlobalObject); + auto throwScope = DECLARE_THROW_SCOPE(vm); + UNUSED_PARAM(throwScope); + UNUSED_PARAM(callFrame); + size_t argsCount = std::min<size_t>(2, callFrame->argumentCount()); + if (argsCount == 1) { + RELEASE_AND_RETURN(throwScope, (jsMessagePortPrototypeFunction_postMessage2Body(lexicalGlobalObject, callFrame, castedThis))); + } + if (argsCount == 2) { + JSValue distinguishingArg = callFrame->uncheckedArgument(1); + if (distinguishingArg.isUndefined()) + RELEASE_AND_RETURN(throwScope, (jsMessagePortPrototypeFunction_postMessage2Body(lexicalGlobalObject, callFrame, castedThis))); + if (distinguishingArg.isUndefinedOrNull()) + RELEASE_AND_RETURN(throwScope, (jsMessagePortPrototypeFunction_postMessage2Body(lexicalGlobalObject, callFrame, castedThis))); + { + bool success = hasIteratorMethod(lexicalGlobalObject, distinguishingArg); + RETURN_IF_EXCEPTION(throwScope, {}); + if (success) + RELEASE_AND_RETURN(throwScope, (jsMessagePortPrototypeFunction_postMessage1Body(lexicalGlobalObject, callFrame, castedThis))); + } + if (distinguishingArg.isObject()) + RELEASE_AND_RETURN(throwScope, (jsMessagePortPrototypeFunction_postMessage2Body(lexicalGlobalObject, callFrame, castedThis))); + } + return argsCount < 1 ? throwVMError(lexicalGlobalObject, throwScope, createNotEnoughArgumentsError(lexicalGlobalObject)) : throwVMTypeError(lexicalGlobalObject, throwScope); +} + +JSC_DEFINE_HOST_FUNCTION(jsMessagePortPrototypeFunction_postMessage, (JSGlobalObject * lexicalGlobalObject, CallFrame* callFrame)) +{ + return IDLOperation<JSMessagePort>::call<jsMessagePortPrototypeFunction_postMessageOverloadDispatcher>(*lexicalGlobalObject, *callFrame, "postMessage"); +} + +static inline JSC::EncodedJSValue jsMessagePortPrototypeFunction_startBody(JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame, typename IDLOperation<JSMessagePort>::ClassParameter castedThis) +{ + auto& vm = JSC::getVM(lexicalGlobalObject); + auto throwScope = DECLARE_THROW_SCOPE(vm); + UNUSED_PARAM(throwScope); + UNUSED_PARAM(callFrame); + auto& impl = castedThis->wrapped(); + RELEASE_AND_RETURN(throwScope, JSValue::encode(toJS<IDLUndefined>(*lexicalGlobalObject, throwScope, [&]() -> decltype(auto) { return impl.start(); }))); +} + +JSC_DEFINE_HOST_FUNCTION(jsMessagePortPrototypeFunction_start, (JSGlobalObject * lexicalGlobalObject, CallFrame* callFrame)) +{ + return IDLOperation<JSMessagePort>::call<jsMessagePortPrototypeFunction_startBody>(*lexicalGlobalObject, *callFrame, "start"); +} + +static inline JSC::EncodedJSValue jsMessagePortPrototypeFunction_closeBody(JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame, typename IDLOperation<JSMessagePort>::ClassParameter castedThis) +{ + auto& vm = JSC::getVM(lexicalGlobalObject); + auto throwScope = DECLARE_THROW_SCOPE(vm); + UNUSED_PARAM(throwScope); + UNUSED_PARAM(callFrame); + auto& impl = castedThis->wrapped(); + RELEASE_AND_RETURN(throwScope, JSValue::encode(toJS<IDLUndefined>(*lexicalGlobalObject, throwScope, [&]() -> decltype(auto) { return impl.close(); }))); +} + +JSC_DEFINE_HOST_FUNCTION(jsMessagePortPrototypeFunction_close, (JSGlobalObject * lexicalGlobalObject, CallFrame* callFrame)) +{ + return IDLOperation<JSMessagePort>::call<jsMessagePortPrototypeFunction_closeBody>(*lexicalGlobalObject, *callFrame, "close"); +} + +JSC::GCClient::IsoSubspace* JSMessagePort::subspaceForImpl(JSC::VM& vm) +{ + return WebCore::subspaceForImpl<JSMessagePort, UseCustomHeapCellType::No>( + vm, + [](auto& spaces) { return spaces.m_clientSubspaceForMessagePort.get(); }, + [](auto& spaces, auto&& space) { spaces.m_clientSubspaceForMessagePort = std::forward<decltype(space)>(space); }, + [](auto& spaces) { return spaces.m_subspaceForMessagePort.get(); }, + [](auto& spaces, auto&& space) { spaces.m_subspaceForMessagePort = std::forward<decltype(space)>(space); }); +} + +template<typename Visitor> +void JSMessagePort::visitChildrenImpl(JSCell* cell, Visitor& visitor) +{ + auto* thisObject = jsCast<JSMessagePort*>(cell); + ASSERT_GC_OBJECT_INHERITS(thisObject, info()); + Base::visitChildren(thisObject, visitor); + thisObject->visitAdditionalChildren(visitor); +} + +DEFINE_VISIT_CHILDREN(JSMessagePort); + +template<typename Visitor> +void JSMessagePort::visitOutputConstraints(JSCell* cell, Visitor& visitor) +{ + auto* thisObject = jsCast<JSMessagePort*>(cell); + ASSERT_GC_OBJECT_INHERITS(thisObject, info()); + Base::visitOutputConstraints(thisObject, visitor); + thisObject->visitAdditionalChildren(visitor); +} + +template void JSMessagePort::visitOutputConstraints(JSCell*, AbstractSlotVisitor&); +template void JSMessagePort::visitOutputConstraints(JSCell*, SlotVisitor&); +void JSMessagePort::analyzeHeap(JSCell* cell, HeapAnalyzer& analyzer) +{ + auto* thisObject = jsCast<JSMessagePort*>(cell); + analyzer.setWrappedObjectForCell(cell, &thisObject->wrapped()); + if (thisObject->scriptExecutionContext()) + analyzer.setLabelForCell(cell, "url "_s + thisObject->scriptExecutionContext()->url().string()); + Base::analyzeHeap(cell, analyzer); +} + +bool JSMessagePortOwner::isReachableFromOpaqueRoots(JSC::Handle<JSC::Unknown> handle, void*, AbstractSlotVisitor& visitor, const char** reason) +{ + auto* jsMessagePort = jsCast<JSMessagePort*>(handle.slot()->asCell()); + auto& wrapped = jsMessagePort->wrapped(); + if (wrapped.hasPendingActivity()) { + if (UNLIKELY(reason)) + *reason = "ActiveDOMObject with pending activity"; + return true; + } + MessagePort* owner = &jsMessagePort->wrapped(); + if (UNLIKELY(reason)) + *reason = "Reachable from MessagePort"; + + return visitor.containsOpaqueRoot(owner); +} + +void JSMessagePortOwner::finalize(JSC::Handle<JSC::Unknown> handle, void* context) +{ + auto* jsMessagePort = static_cast<JSMessagePort*>(handle.slot()->asCell()); + auto& world = *static_cast<DOMWrapperWorld*>(context); + uncacheWrapper(world, &jsMessagePort->wrapped(), jsMessagePort); +} + +#if ENABLE(BINDING_INTEGRITY) +#if PLATFORM(WIN) +#pragma warning(disable : 4483) +extern "C" { +extern void (*const __identifier("??_7MessagePort@WebCore@@6B@")[])(); +} +#else +extern "C" { +extern void* _ZTVN7WebCore11MessagePortE[]; +} +#endif +#endif + +JSC::JSValue toJSNewlyCreated(JSC::JSGlobalObject*, JSDOMGlobalObject* globalObject, Ref<MessagePort>&& impl) +{ + + if constexpr (std::is_polymorphic_v<MessagePort>) { +#if ENABLE(BINDING_INTEGRITY) + const void* actualVTablePointer = getVTablePointer(impl.ptr()); +#if PLATFORM(WIN) + void* expectedVTablePointer = __identifier("??_7MessagePort@WebCore@@6B@"); +#else + void* expectedVTablePointer = &_ZTVN7WebCore11MessagePortE[2]; +#endif + + // If you hit this assertion you either have a use after free bug, or + // MessagePort has subclasses. If MessagePort has subclasses that get passed + // to toJS() we currently require MessagePort you to opt out of binding hardening + // by adding the SkipVTableValidation attribute to the interface IDL definition + RELEASE_ASSERT(actualVTablePointer == expectedVTablePointer); +#endif + } + return createWrapper<MessagePort>(globalObject, WTFMove(impl)); +} + +JSC::JSValue toJS(JSC::JSGlobalObject* lexicalGlobalObject, JSDOMGlobalObject* globalObject, MessagePort& impl) +{ + return wrap(lexicalGlobalObject, globalObject, impl); +} + +MessagePort* JSMessagePort::toWrapped(JSC::VM&, JSC::JSValue value) +{ + if (auto* wrapper = jsDynamicCast<JSMessagePort*>(value)) + return &wrapper->wrapped(); + return nullptr; +} + +} diff --git a/src/bun.js/bindings/webcore/JSMessagePort.h b/src/bun.js/bindings/webcore/JSMessagePort.h new file mode 100644 index 000000000..44c928050 --- /dev/null +++ b/src/bun.js/bindings/webcore/JSMessagePort.h @@ -0,0 +1,103 @@ +/* + This file is part of the WebKit open source project. + This file has been generated by generate-bindings.pl. DO NOT MODIFY! + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#pragma once + +#include "JSDOMWrapper.h" +#include "JSEventTarget.h" +#include "MessagePort.h" +#include <wtf/NeverDestroyed.h> + +namespace WebCore { + +class WEBCORE_EXPORT JSMessagePort : public JSEventTarget { +public: + using Base = JSEventTarget; + using DOMWrapped = MessagePort; + static JSMessagePort* create(JSC::Structure* structure, JSDOMGlobalObject* globalObject, Ref<MessagePort>&& impl) + { + JSMessagePort* ptr = new (NotNull, JSC::allocateCell<JSMessagePort>(globalObject->vm())) JSMessagePort(structure, *globalObject, WTFMove(impl)); + ptr->finishCreation(globalObject->vm()); + return ptr; + } + + static JSC::JSObject* createPrototype(JSC::VM&, JSDOMGlobalObject&); + static JSC::JSObject* prototype(JSC::VM&, JSDOMGlobalObject&); + static MessagePort* toWrapped(JSC::VM&, JSC::JSValue); + + DECLARE_INFO; + + 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(), JSC::NonArray); + } + + static JSC::JSValue getConstructor(JSC::VM&, const JSC::JSGlobalObject*); + template<typename, JSC::SubspaceAccess mode> static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm) + { + if constexpr (mode == JSC::SubspaceAccess::Concurrently) + return nullptr; + return subspaceForImpl(vm); + } + static JSC::GCClient::IsoSubspace* subspaceForImpl(JSC::VM& vm); + DECLARE_VISIT_CHILDREN; + template<typename Visitor> void visitAdditionalChildren(Visitor&); + + template<typename Visitor> static void visitOutputConstraints(JSCell*, Visitor&); + static void analyzeHeap(JSCell*, JSC::HeapAnalyzer&); + MessagePort& wrapped() const + { + return static_cast<MessagePort&>(Base::wrapped()); + } + +protected: + JSMessagePort(JSC::Structure*, JSDOMGlobalObject&, Ref<MessagePort>&&); + + DECLARE_DEFAULT_FINISH_CREATION; +}; + +class WEBCORE_EXPORT JSMessagePortOwner final : public JSC::WeakHandleOwner { +public: + bool isReachableFromOpaqueRoots(JSC::Handle<JSC::Unknown>, void* context, JSC::AbstractSlotVisitor&, const char**) final; + void finalize(JSC::Handle<JSC::Unknown>, void* context) final; +}; + +inline JSC::WeakHandleOwner* wrapperOwner(DOMWrapperWorld&, MessagePort*) +{ + static NeverDestroyed<JSMessagePortOwner> owner; + return &owner.get(); +} + +inline void* wrapperKey(MessagePort* wrappableObject) +{ + return wrappableObject; +} + +WEBCORE_EXPORT JSC::JSValue toJS(JSC::JSGlobalObject*, JSDOMGlobalObject*, MessagePort&); +inline JSC::JSValue toJS(JSC::JSGlobalObject* lexicalGlobalObject, JSDOMGlobalObject* globalObject, MessagePort* impl) { return impl ? toJS(lexicalGlobalObject, globalObject, *impl) : JSC::jsNull(); } +JSC::JSValue toJSNewlyCreated(JSC::JSGlobalObject*, JSDOMGlobalObject*, Ref<MessagePort>&&); +inline JSC::JSValue toJSNewlyCreated(JSC::JSGlobalObject* lexicalGlobalObject, JSDOMGlobalObject* globalObject, RefPtr<MessagePort>&& impl) { return impl ? toJSNewlyCreated(lexicalGlobalObject, globalObject, impl.releaseNonNull()) : JSC::jsNull(); } + +template<> struct JSDOMWrapperConverterTraits<MessagePort> { + using WrapperClass = JSMessagePort; + using ToWrappedReturnType = MessagePort*; +}; + +} // namespace WebCore diff --git a/src/bun.js/bindings/webcore/JSMessagePortCustom.cpp b/src/bun.js/bindings/webcore/JSMessagePortCustom.cpp new file mode 100644 index 000000000..56031dd60 --- /dev/null +++ b/src/bun.js/bindings/webcore/JSMessagePortCustom.cpp @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2008-2021 Apple Inc. All Rights Reserved. + * Copyright (C) 2011 Google Inc. All Rights Reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#include "JSMessagePort.h" +// #include "WebCoreOpaqueRootInlines.h" + +namespace WebCore { +using namespace JSC; + +template<typename Visitor> +void JSMessagePort::visitAdditionalChildren(Visitor& visitor) +{ + // If we have a locally entangled port, we can directly mark it as reachable. Ports that are remotely entangled are marked in-use by markActiveObjectsForContext(). + if (auto* port = wrapped().locallyEntangledPort()) { + visitor.addOpaqueRoot(port); + // addWebCoreOpaqueRoot(visitor, *port); + } +} + +DEFINE_VISIT_ADDITIONAL_CHILDREN(JSMessagePort); + +} // namespace WebCore diff --git a/src/bun.js/bindings/webcore/JSStructuredSerializeOptions.cpp b/src/bun.js/bindings/webcore/JSStructuredSerializeOptions.cpp new file mode 100644 index 000000000..4311426e6 --- /dev/null +++ b/src/bun.js/bindings/webcore/JSStructuredSerializeOptions.cpp @@ -0,0 +1,58 @@ +/* + This file is part of the WebKit open source project. + This file has been generated by generate-bindings.pl. DO NOT MODIFY! + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#include "config.h" +#include "JSStructuredSerializeOptions.h" + +#include "JSDOMConvertObject.h" +#include "JSDOMConvertSequences.h" +#include <JavaScriptCore/JSArray.h> +#include <JavaScriptCore/JSCInlines.h> + +namespace WebCore { +using namespace JSC; + +template<> StructuredSerializeOptions convertDictionary<StructuredSerializeOptions>(JSGlobalObject& lexicalGlobalObject, JSValue value) +{ + VM& vm = JSC::getVM(&lexicalGlobalObject); + auto throwScope = DECLARE_THROW_SCOPE(vm); + bool isNullOrUndefined = value.isUndefinedOrNull(); + auto* object = isNullOrUndefined ? nullptr : value.getObject(); + if (UNLIKELY(!isNullOrUndefined && !object)) { + throwTypeError(&lexicalGlobalObject, throwScope); + return {}; + } + StructuredSerializeOptions result; + JSValue transferValue; + if (isNullOrUndefined) + transferValue = jsUndefined(); + else { + transferValue = object->get(&lexicalGlobalObject, Identifier::fromString(vm, "transfer"_s)); + RETURN_IF_EXCEPTION(throwScope, {}); + } + if (!transferValue.isUndefined()) { + result.transfer = convert<IDLSequence<IDLObject>>(lexicalGlobalObject, transferValue); + RETURN_IF_EXCEPTION(throwScope, {}); + } else + result.transfer = Converter<IDLSequence<IDLObject>>::ReturnType {}; + return result; +} + +} // namespace WebCore diff --git a/src/bun.js/bindings/webcore/JSStructuredSerializeOptions.h b/src/bun.js/bindings/webcore/JSStructuredSerializeOptions.h new file mode 100644 index 000000000..8fc8fddd0 --- /dev/null +++ b/src/bun.js/bindings/webcore/JSStructuredSerializeOptions.h @@ -0,0 +1,30 @@ +/* + This file is part of the WebKit open source project. + This file has been generated by generate-bindings.pl. DO NOT MODIFY! + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#pragma once + +#include "JSDOMConvertDictionary.h" +#include "StructuredSerializeOptions.h" + +namespace WebCore { + +template<> StructuredSerializeOptions convertDictionary<StructuredSerializeOptions>(JSC::JSGlobalObject&, JSC::JSValue); + +} // namespace WebCore diff --git a/src/bun.js/bindings/webcore/JSValueInWrappedObject.h b/src/bun.js/bindings/webcore/JSValueInWrappedObject.h index d05fdb8fd..d11796912 100644 --- a/src/bun.js/bindings/webcore/JSValueInWrappedObject.h +++ b/src/bun.js/bindings/webcore/JSValueInWrappedObject.h @@ -61,6 +61,7 @@ private: JSC::Weak<JSC::JSCell> m_cell {}; }; +JSC::JSValue cachedPropertyValue(JSC::ThrowScope&, JSC::JSGlobalObject&, const JSDOMObject& owner, JSValueInWrappedObject& cacheSlot, const Function<JSC::JSValue(JSC::ThrowScope&)>&); JSC::JSValue cachedPropertyValue(JSC::JSGlobalObject&, const JSDOMObject& owner, JSValueInWrappedObject& cacheSlot, const Function<JSC::JSValue()>&); inline JSValueInWrappedObject::JSValueInWrappedObject(JSC::JSValue value) @@ -132,4 +133,17 @@ inline JSC::JSValue cachedPropertyValue(JSC::JSGlobalObject& lexicalGlobalObject return cachedValue.getValue(); } +inline JSC::JSValue cachedPropertyValue(JSC::ThrowScope& throwScope, JSC::JSGlobalObject& lexicalGlobalObject, const JSDOMObject& owner, JSValueInWrappedObject& cachedValue, const Function<JSC::JSValue(JSC::ThrowScope&)>& function) +{ + if (cachedValue && isWorldCompatible(lexicalGlobalObject, cachedValue.getValue())) + return cachedValue.getValue(); + + auto value = function(throwScope); + RETURN_IF_EXCEPTION(throwScope, {}); + + cachedValue.set(lexicalGlobalObject.vm(), &owner, cloneAcrossWorlds(lexicalGlobalObject, owner, value)); + ASSERT(isWorldCompatible(lexicalGlobalObject, cachedValue.getValue())); + return cachedValue.getValue(); +} + } // namespace WebCore diff --git a/src/bun.js/bindings/webcore/MessageChannel.cpp b/src/bun.js/bindings/webcore/MessageChannel.cpp new file mode 100644 index 000000000..67407b9c0 --- /dev/null +++ b/src/bun.js/bindings/webcore/MessageChannel.cpp @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2008 Apple Inc. All Rights Reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include "config.h" +#include "MessageChannel.h" + +#include "MessagePort.h" +#include "MessagePortChannelProvider.h" +#include "ScriptExecutionContext.h" + +namespace WebCore { + +static std::pair<Ref<MessagePort>, Ref<MessagePort>> generateMessagePorts(ScriptExecutionContext& context) +{ + MessagePortIdentifier id1 = { ProcessIdent::identifier(), PortIdentifier::generate() }; + MessagePortIdentifier id2 = { ProcessIdent::identifier(), PortIdentifier::generate() }; + + return { MessagePort::create(context, id1, id2), MessagePort::create(context, id2, id1) }; +} + +Ref<MessageChannel> MessageChannel::create(ScriptExecutionContext& context) +{ + return adoptRef(*new MessageChannel(context)); +} + +MessageChannel::MessageChannel(ScriptExecutionContext& context) + : m_ports(generateMessagePorts(context)) +{ + if (!context.activeDOMObjectsAreStopped()) { + ASSERT(!port1().isDetached()); + ASSERT(!port2().isDetached()); + MessagePortChannelProvider::fromContext(context).createNewMessagePortChannel(port1().identifier(), port2().identifier()); + } else { + ASSERT(port1().isDetached()); + ASSERT(port2().isDetached()); + } +} + +MessageChannel::~MessageChannel() = default; + +} // namespace WebCore diff --git a/src/bun.js/bindings/webcore/MessageChannel.h b/src/bun.js/bindings/webcore/MessageChannel.h new file mode 100644 index 000000000..1cc936094 --- /dev/null +++ b/src/bun.js/bindings/webcore/MessageChannel.h @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2008 Apple Inc. All Rights Reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#pragma once + +#include <wtf/RefCounted.h> +#include <wtf/RefPtr.h> + +namespace WebCore { + +class MessagePort; +class ScriptExecutionContext; + +class MessageChannel : public RefCounted<MessageChannel> { +public: + static Ref<MessageChannel> create(ScriptExecutionContext&); + ~MessageChannel(); + + MessagePort& port1() const { return m_ports.first; } + MessagePort& port2() const { return m_ports.second; } + +private: + explicit MessageChannel(ScriptExecutionContext&); + + std::pair<Ref<MessagePort>, Ref<MessagePort>> m_ports; +}; + +} // namespace WebCore diff --git a/src/bun.js/bindings/webcore/MessageEvent.cpp b/src/bun.js/bindings/webcore/MessageEvent.cpp index b210f4cec..b5a96a4a2 100644 --- a/src/bun.js/bindings/webcore/MessageEvent.cpp +++ b/src/bun.js/bindings/webcore/MessageEvent.cpp @@ -30,7 +30,10 @@ // #include "Blob.h" #include "EventNames.h" +#include "JSDOMConvert.h" +#include "JSMessageEvent.h" #include <JavaScriptCore/JSCInlines.h> + // #include <wtf/IsoMallocInlines.h> namespace WebCore { @@ -47,29 +50,29 @@ inline MessageEvent::MessageEvent(const AtomString& type, Init&& initializer, Is , m_origin(initializer.origin) , m_lastEventId(initializer.lastEventId) , m_source(WTFMove(initializer.source)) - // , m_ports(WTFMove(initializer.ports)) + , m_ports(WTFMove(initializer.ports)) , m_jsData(initializer.data) { } -inline MessageEvent::MessageEvent(const AtomString& type, DataType&& data, const String& origin, const String& lastEventId, std::optional<MessageEventSource>&& source /*, Vector<RefPtr<MessagePort>>&& ports*/) +inline MessageEvent::MessageEvent(const AtomString& type, DataType&& data, const String& origin, const String& lastEventId, std::optional<MessageEventSource>&& source, Vector<RefPtr<MessagePort>>&& ports) : Event(type, CanBubble::No, IsCancelable::No) , m_data(WTFMove(data)) , m_origin(origin) , m_lastEventId(lastEventId) , m_source(WTFMove(source)) -// , m_ports(WTFMove(ports)) + , m_ports(WTFMove(ports)) { } -Ref<MessageEvent> MessageEvent::create(const AtomString& type, DataType&& data, const String& origin, const String& lastEventId, std::optional<MessageEventSource>&& source /*, Vector<RefPtr<MessagePort>>&& ports*/) +Ref<MessageEvent> MessageEvent::create(const AtomString& type, DataType&& data, const String& origin, const String& lastEventId, std::optional<MessageEventSource>&& source, Vector<RefPtr<MessagePort>>&& ports) { - return adoptRef(*new MessageEvent(type, WTFMove(data), origin, lastEventId, WTFMove(source) /*, /*WTFMove(ports)*/)); + return adoptRef(*new MessageEvent(type, WTFMove(data), origin, lastEventId, WTFMove(source), WTFMove(ports))); } -Ref<MessageEvent> MessageEvent::create(DataType&& data, const String& origin, const String& lastEventId, std::optional<MessageEventSource>&& source /*, Vector<RefPtr<MessagePort>>&& ports*/) +Ref<MessageEvent> MessageEvent::create(DataType&& data, const String& origin, const String& lastEventId, std::optional<MessageEventSource>&& source, Vector<RefPtr<MessagePort>>&& ports) { - return create(eventNames().messageEvent, WTFMove(data), origin, lastEventId, WTFMove(source) /*, /*WTFMove(ports)*/); + return create(eventNames().messageEvent, WTFMove(data), origin, lastEventId, WTFMove(source), WTFMove(ports)); } Ref<MessageEvent> MessageEvent::createForBindings() @@ -84,7 +87,32 @@ Ref<MessageEvent> MessageEvent::create(const AtomString& type, Init&& initialize MessageEvent::~MessageEvent() = default; -void MessageEvent::initMessageEvent(const AtomString& type, bool canBubble, bool cancelable, JSValue data, const String& origin, const String& lastEventId, std::optional<MessageEventSource>&& source /*, Vector<RefPtr<MessagePort>>&& ports*/) +auto MessageEvent::create(JSC::JSGlobalObject& globalObject, Ref<SerializedScriptValue>&& data, std::optional<MessageEventSource>&& source, Vector<RefPtr<MessagePort>>&& ports) -> MessageEventWithStrongData +{ + return create(globalObject, WTFMove(data), {}, {}, WTFMove(source), WTFMove(ports)); +} + +auto MessageEvent::create(JSC::JSGlobalObject& globalObject, Ref<SerializedScriptValue>&& data, const String& origin, const String& lastEventId, std::optional<MessageEventSource>&& source, Vector<RefPtr<MessagePort>>&& ports) -> MessageEventWithStrongData +{ + auto& vm = globalObject.vm(); + // Locker<JSC::JSLock> locker(vm.apiLock()); + + bool didFail = false; + + auto deserialized = data->deserialize(globalObject, &globalObject, ports, SerializationErrorMode::NonThrowing, &didFail); + JSC::Strong<JSC::Unknown> strongData(vm, deserialized); + + auto& eventType = didFail ? eventNames().messageerrorEvent : eventNames().messageEvent; + auto event = adoptRef(*new MessageEvent(eventType, WTFMove(data), origin, lastEventId, WTFMove(source), WTFMove(ports))); + JSC::Strong<JSC::JSObject> strongWrapper(vm, JSC::jsCast<JSC::JSObject*>(toJS(&globalObject, JSC::jsCast<JSDOMGlobalObject*>(&globalObject), event.get()))); + // Since we've already deserialized the SerializedScriptValue, cache the result so we don't have to deserialize + // again the next time JSMessageEvent::data() gets called by the main world. + event->cachedData().set(vm, strongWrapper.get(), deserialized); + + return MessageEventWithStrongData { event, WTFMove(strongWrapper) }; +} + +void MessageEvent::initMessageEvent(const AtomString& type, bool canBubble, bool cancelable, JSValue data, const String& origin, const String& lastEventId, std::optional<MessageEventSource>&& source, Vector<RefPtr<MessagePort>>&& ports) { if (isBeingDispatched()) return; @@ -102,7 +130,7 @@ void MessageEvent::initMessageEvent(const AtomString& type, bool canBubble, bool m_origin = origin; m_lastEventId = lastEventId; m_source = WTFMove(source); - // m_ports = WTFMove(ports); + m_ports = WTFMove(ports); m_cachedPorts.clear(); } @@ -116,7 +144,7 @@ size_t MessageEvent::memoryCost() const Locker { m_concurrentDataAccessLock }; return WTF::switchOn( m_data, [](JSValueTag) -> size_t { return 0; }, - // [](const Ref<SerializedScriptValue>& data) -> size_t { return data->memoryCost(); }, + [](const Ref<SerializedScriptValue>& data) -> size_t { return data->memoryCost(); }, [](const String& string) -> size_t { return string.sizeInBytes(); }, // [](const Ref<Blob>& blob) -> size_t { return blob->size(); }, [](const Ref<ArrayBuffer>& buffer) -> size_t { return buffer->byteLength(); }); diff --git a/src/bun.js/bindings/webcore/MessageEvent.h b/src/bun.js/bindings/webcore/MessageEvent.h index 4089e2ca1..184aa63a1 100644 --- a/src/bun.js/bindings/webcore/MessageEvent.h +++ b/src/bun.js/bindings/webcore/MessageEvent.h @@ -29,17 +29,16 @@ #include "Event.h" #include "JSValueInWrappedObject.h" -// #include "MessagePort.h" -// #include "SerializedScriptValue.h" +#include "MessagePort.h" +#include "SerializedScriptValue.h" +#include "MessagePort.h" +// #include "JSMessageEvent.h" // #include "ServiceWorker.h" // #include "WindowProxy.h" #include <variant> namespace WebCore { -class MessagePort : public RefCounted<MessagePort> { -}; - class MessageEventSource { }; @@ -50,9 +49,10 @@ public: struct JSValueTag { }; // using DataType = std::variant<JSValueTag, Ref<SerializedScriptValue>, String, Ref<Blob>, Ref<ArrayBuffer>>; - using DataType = std::variant<JSValueTag, String, Ref<ArrayBuffer>>; - static Ref<MessageEvent> create(const AtomString& type, DataType&&, const String& origin = {}, const String& lastEventId = {}, std::optional<MessageEventSource>&& = std::nullopt /*, Vector<RefPtr<MessagePort>>&& = {}*/); - static Ref<MessageEvent> create(DataType&&, const String& origin = {}, const String& lastEventId = {}, std::optional<MessageEventSource>&& = std::nullopt /*, Vector<RefPtr<MessagePort>>&& = {}*/); + using DataType = std::variant<JSValueTag, Ref<SerializedScriptValue>, String, Ref<ArrayBuffer>>; + static Ref<MessageEvent> create(const AtomString& type, DataType&&, const String& origin = {}, const String& lastEventId = {}, std::optional<MessageEventSource>&& = std::nullopt, Vector<RefPtr<MessagePort>>&& = {}); + static Ref<MessageEvent> create(DataType&&, const String& origin = {}, const String& lastEventId = {}, std::optional<MessageEventSource>&& = std::nullopt, Vector<RefPtr<MessagePort>>&& = {}); + static Ref<MessageEvent> createForBindings(); struct Init : EventInit { @@ -60,18 +60,27 @@ public: String origin; String lastEventId; std::optional<MessageEventSource> source; - // Vector<RefPtr<MessagePort>> ports; + Vector<RefPtr<MessagePort>> ports; }; static Ref<MessageEvent> create(const AtomString& type, Init&&, IsTrusted = IsTrusted::No); + struct MessageEventWithStrongData { + Ref<MessageEvent> event; + JSC::Strong<JSC::JSObject> strongWrapper; // Keep the wrapper alive until the event is fired, since it is what keeps `data` alive. + }; + + static MessageEventWithStrongData create(JSC::JSGlobalObject&, Ref<SerializedScriptValue>&&, const String& origin = {}, const String& lastEventId = {}, std::optional<MessageEventSource>&& = std::nullopt, Vector<RefPtr<MessagePort>>&& = {}); + + static MessageEventWithStrongData create(JSC::JSGlobalObject&, Ref<SerializedScriptValue>&&, std::optional<MessageEventSource>&& = std::nullopt, Vector<RefPtr<MessagePort>>&& = {}); + virtual ~MessageEvent(); - void initMessageEvent(const AtomString& type, bool canBubble, bool cancelable, JSC::JSValue data, const String& origin, const String& lastEventId, std::optional<MessageEventSource>&& /*, Vector<RefPtr<MessagePort>>&&*/); + void initMessageEvent(const AtomString& type, bool canBubble, bool cancelable, JSC::JSValue data, const String& origin, const String& lastEventId, std::optional<MessageEventSource>&&, Vector<RefPtr<MessagePort>>&&); const String& origin() const { return m_origin; } const String& lastEventId() const { return m_lastEventId; } const std::optional<MessageEventSource>& source() const { return m_source; } - // const Vector<RefPtr<MessagePort>>& ports() const { return m_ports; } + const Vector<RefPtr<MessagePort>>& ports() const { return m_ports; } const DataType& data() const { return m_data; } @@ -84,7 +93,7 @@ public: private: MessageEvent(); MessageEvent(const AtomString& type, Init&&, IsTrusted); - MessageEvent(const AtomString& type, DataType&&, const String& origin, const String& lastEventId = {}, std::optional<MessageEventSource>&& = std::nullopt /*, Vector<RefPtr<MessagePort>>&& = {}*/); + MessageEvent(const AtomString& type, DataType&&, const String& origin, const String& lastEventId = {}, std::optional<MessageEventSource>&& = std::nullopt, Vector<RefPtr<MessagePort>>&& = {}); EventInterface eventInterface() const final; @@ -92,7 +101,7 @@ private: String m_origin; String m_lastEventId; std::optional<MessageEventSource> m_source; - // Vector<RefPtr<MessagePort>> m_ports; + Vector<RefPtr<MessagePort>> m_ports; JSValueInWrappedObject m_jsData; JSValueInWrappedObject m_cachedData; diff --git a/src/bun.js/bindings/webcore/MessagePort.cpp b/src/bun.js/bindings/webcore/MessagePort.cpp new file mode 100644 index 000000000..956143c80 --- /dev/null +++ b/src/bun.js/bindings/webcore/MessagePort.cpp @@ -0,0 +1,419 @@ +/* + * Copyright (C) 2008 Apple Inc. All Rights Reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include "config.h" +#include "MessagePort.h" + +// #include "Document.h" +#include "EventNames.h" +// #include "Logging.h" +#include "MessageEvent.h" +#include "BunWorkerGlobalScope.h" +#include "MessagePortChannelProvider.h" +#include "MessageWithMessagePorts.h" +#include "StructuredSerializeOptions.h" +#include "WebCoreOpaqueRoot.h" +// #include "WorkerGlobalScope.h" +// #include "WorkerThread.h" +#include "TaskSource.h" +#include <wtf/CompletionHandler.h> +#include <wtf/IsoMallocInlines.h> +#include <wtf/Lock.h> +#include <wtf/Scope.h> + +namespace WebCore { + +WTF_MAKE_ISO_ALLOCATED_IMPL(MessagePort); + +static Lock allMessagePortsLock; +static HashMap<MessagePortIdentifier, MessagePort*>& allMessagePorts() WTF_REQUIRES_LOCK(allMessagePortsLock) +{ + static NeverDestroyed<HashMap<MessagePortIdentifier, MessagePort*>> map; + return map; +} + +static HashMap<MessagePortIdentifier, ScriptExecutionContextIdentifier>& portToContextIdentifier() WTF_REQUIRES_LOCK(allMessagePortsLock) +{ + static NeverDestroyed<HashMap<MessagePortIdentifier, ScriptExecutionContextIdentifier>> map; + return map; +} + +void MessagePort::ref() const +{ + ++m_refCount; +} + +void MessagePort::deref() const +{ + // This custom deref() function ensures that as long as the lock to allMessagePortsLock is taken, no MessagePort will be destroyed. + // This allows notifyMessageAvailable to easily query the map and manipulate MessagePort instances. + + if (!--m_refCount) { + Locker locker { allMessagePortsLock }; + + if (m_refCount) + return; + + auto iterator = allMessagePorts().find(m_identifier); + if (iterator != allMessagePorts().end() && iterator->value == this) { + allMessagePorts().remove(iterator); + portToContextIdentifier().remove(m_identifier); + } + + delete this; + } +} + +bool MessagePort::hasPendingActivity() const +{ + return m_refCount > 0; +} + +bool MessagePort::isMessagePortAliveForTesting(const MessagePortIdentifier& identifier) +{ + Locker locker { allMessagePortsLock }; + return allMessagePorts().contains(identifier); +} + +ScriptExecutionContextIdentifier MessagePort::contextIdForMessagePortId(MessagePortIdentifier messagePortId) +{ + Locker locker { allMessagePortsLock }; + return portToContextIdentifier().get(messagePortId); +} + +void MessagePort::notifyMessageAvailable(const MessagePortIdentifier& identifier) +{ + ASSERT(isMainThread()); + ScriptExecutionContextIdentifier scriptExecutionContextIdentifier; + { + Locker locker { allMessagePortsLock }; + scriptExecutionContextIdentifier = portToContextIdentifier().get(identifier); + } + if (!scriptExecutionContextIdentifier) + return; + + ScriptExecutionContext::ensureOnContextThread(scriptExecutionContextIdentifier, [identifier](auto&) { + RefPtr<MessagePort> port; + { + Locker locker { allMessagePortsLock }; + port = allMessagePorts().get(identifier); + } + if (port) + port->messageAvailable(); + }); +} + +Ref<MessagePort> MessagePort::create(ScriptExecutionContext& scriptExecutionContext, const MessagePortIdentifier& local, const MessagePortIdentifier& remote) +{ + auto messagePort = adoptRef(*new MessagePort(scriptExecutionContext, local, remote)); + // messagePort->suspendIfNeeded(); + return messagePort; +} + +MessagePort::MessagePort(ScriptExecutionContext& scriptExecutionContext, const MessagePortIdentifier& local, const MessagePortIdentifier& remote) + // : ActiveDOMObject(&scriptExecutionContext) + : ContextDestructionObserver(&scriptExecutionContext) + , m_identifier(local) + , m_remoteIdentifier(remote) +{ + LOG(MessagePorts, "Created MessagePort %s (%p) in process %" PRIu64, m_identifier.logString().utf8().data(), this, ProcessIdent::identifier().toUInt64()); + + Locker locker { allMessagePortsLock }; + allMessagePorts().set(m_identifier, this); + portToContextIdentifier().set(m_identifier, scriptExecutionContext.identifier()); + + // Make sure the WeakPtrFactory gets initialized eagerly on the thread the MessagePort gets constructed on for thread-safety reasons. + initializeWeakPtrFactory(); + + scriptExecutionContext.createdMessagePort(*this); + + // Don't need to call processMessageWithMessagePortsSoon() here, because the port will not be opened until start() is invoked. +} + +MessagePort::~MessagePort() +{ + LOG(MessagePorts, "Destroyed MessagePort %s (%p) in process %" PRIu64, m_identifier.logString().utf8().data(), this, ProcessIdent::identifier().toUInt64()); + + ASSERT(allMessagePortsLock.isLocked()); + + if (m_entangled) + close(); + + if (auto contextId = portToContextIdentifier().get(m_identifier)) + ScriptExecutionContext::getScriptExecutionContext(contextId)->destroyedMessagePort(*this); +} + +void MessagePort::entangle() +{ + ScriptExecutionContext::ensureOnMainThread([identifier = m_identifier, remoteIdentifier = m_remoteIdentifier](ScriptExecutionContext& context) { + MessagePortChannelProvider::fromContext(context).entangleLocalPortInThisProcessToRemote(identifier, remoteIdentifier); + }); +} + +ExceptionOr<void> MessagePort::postMessage(JSC::JSGlobalObject& state, JSC::JSValue messageValue, StructuredSerializeOptions&& options) +{ + LOG(MessagePorts, "Attempting to post message to port %s (to be received by port %s)", m_identifier.logString().utf8().data(), m_remoteIdentifier.logString().utf8().data()); + + Vector<RefPtr<MessagePort>> ports; + auto messageData = SerializedScriptValue::create(state, messageValue, WTFMove(options.transfer), ports, SerializationForStorage::No, SerializationContext::WorkerPostMessage); + if (messageData.hasException()) + return messageData.releaseException(); + + if (!isEntangled()) + return {}; + ASSERT(scriptExecutionContext()); + + Vector<TransferredMessagePort> transferredPorts; + // Make sure we aren't connected to any of the passed-in ports. + if (!ports.isEmpty()) { + for (auto& port : ports) { + if (port->identifier() == m_identifier || port->identifier() == m_remoteIdentifier) + return Exception { DataCloneError }; + } + + auto disentangleResult = MessagePort::disentanglePorts(WTFMove(ports)); + if (disentangleResult.hasException()) + return disentangleResult.releaseException(); + transferredPorts = disentangleResult.releaseReturnValue(); + } + + MessageWithMessagePorts message { messageData.releaseReturnValue(), WTFMove(transferredPorts) }; + + LOG(MessagePorts, "Actually posting message to port %s (to be received by port %s)", m_identifier.logString().utf8().data(), m_remoteIdentifier.logString().utf8().data()); + + ScriptExecutionContextIdentifier contextId = contextIdForMessagePortId(m_remoteIdentifier); + + MessagePortChannelProvider::fromContext(*ScriptExecutionContext::getScriptExecutionContext(contextId)).postMessageToRemote(WTFMove(message), m_remoteIdentifier); + + return {}; +} + +TransferredMessagePort MessagePort::disentangle() +{ + ASSERT(m_entangled); + m_entangled = false; + + auto& context = *scriptExecutionContext(); + MessagePortChannelProvider::fromContext(context).messagePortDisentangled(m_identifier); + + // We can't receive any messages or generate any events after this, so remove ourselves from the list of active ports. + context.destroyedMessagePort(*this); + // context.willDestroyActiveDOMObject(*this); + context.willDestroyDestructionObserver(*this); + + observeContext(nullptr); + + return { identifier(), remoteIdentifier() }; +} + +// Invoked to notify us that there are messages available for this port. +// This code may be called from another thread, and so should not call any non-threadsafe APIs (i.e. should not call into the entangled channel or access mutable variables). +void MessagePort::messageAvailable() +{ + // This MessagePort object might be disentangled because the port is being transferred, + // in which case we'll notify it that messages are available once a new end point is created. + auto* context = scriptExecutionContext(); + if (!context || context->activeDOMObjectsAreSuspended()) + return; + + context->processMessageWithMessagePortsSoon([pendingActivity = Ref { *this }] {}); +} + +void MessagePort::start() +{ + // Do nothing if we've been cloned or closed. + if (!isEntangled()) + return; + + ASSERT(scriptExecutionContext()); + if (m_started) + return; + + m_started = true; + scriptExecutionContext()->processMessageWithMessagePortsSoon([pendingActivity = Ref { *this }] {}); +} + +void MessagePort::close() +{ + if (m_isDetached) + return; + m_isDetached = true; + + ScriptExecutionContext::ensureOnMainThread([identifier = m_identifier, protectedThis = Ref { *this }](ScriptExecutionContext& context) { + MessagePortChannelProvider::singleton().messagePortClosed(identifier); + }); + + removeAllEventListeners(); +} + +void MessagePort::dispatchMessages() +{ + // Messages for contexts that are not fully active get dispatched too, but JSAbstractEventListener::handleEvent() doesn't call handlers for these. + // The HTML5 spec specifies that any messages sent to a document that is not fully active should be dropped, so this behavior is OK. + ASSERT(started()); + + auto* context = scriptExecutionContext(); + if (!context || context->activeDOMObjectsAreSuspended() || !isEntangled()) + return; + + auto messagesTakenHandler = [this, protectedThis = Ref { *this }](Vector<MessageWithMessagePorts>&& messages, CompletionHandler<void()>&& completionCallback) mutable { + auto scopeExit = makeScopeExit(WTFMove(completionCallback)); + + LOG(MessagePorts, "MessagePort %s (%p) dispatching %zu messages", m_identifier.logString().utf8().data(), this, messages.size()); + + auto* context = scriptExecutionContext(); + if (!context || !context->jsGlobalObject()) + return; + + ASSERT(context->isContextThread()); + + // bool contextIsWorker = is<WorkerGlobalScope>(*context); + for (auto& message : messages) { + // close() in Worker onmessage handler should prevent next message from dispatching. + // if (contextIsWorker && downcast<WorkerGlobalScope>(*context).isClosing()) + // return; + + auto ports = MessagePort::entanglePorts(*context, WTFMove(message.transferredPorts)); + + // Per specification, each MessagePort object has a task source called the port message queue. + // queueTaskKeepingObjectAlive(context, *this, TaskSource::PostedMessageQueue, [this, event = WTFMove(event)] { + // dispatchEvent(event.event); + // }); + + ScriptExecutionContext::postTaskTo(contextIdForMessagePortId(m_identifier), [protectedThis = Ref { *this }, ports = WTFMove(ports), message = WTFMove(message)](ScriptExecutionContext& context) mutable { + auto event = MessageEvent::create(*context.jsGlobalObject(), message.message.releaseNonNull(), {}, {}, {}, WTFMove(ports)); + protectedThis->dispatchEvent(event.event); + }); + } + }; + + MessagePortChannelProvider::fromContext(*context).takeAllMessagesForPort(m_identifier, WTFMove(messagesTakenHandler)); +} + +void MessagePort::dispatchEvent(Event& event) +{ + if (m_isDetached) { + return; + } + + // auto* context = scriptExecutionContext(); + // if (is<WebCore::GlobalScope>(*context) && downcast<WebCore::GlobalScope>(*context).isClosing()) + // return; + + EventTarget::dispatchEvent(event); +} + +// https://html.spec.whatwg.org/multipage/web-messaging.html#ports-and-garbage-collection +// bool MessagePort::virtualHasPendingActivity() const +// { +// // If the ScriptExecutionContext has been shut down on this object close()'ed, we can GC. +// auto* context = scriptExecutionContext(); +// if (!context || m_isDetached) +// return false; + +// // If this MessagePort has no message event handler then there is no point in keeping it alive. +// if (!m_hasMessageEventListener) +// return false; + +// return m_entangled; +// } + +MessagePort* MessagePort::locallyEntangledPort() +{ + // FIXME: As the header describes, this is an optional optimization. + // Even in the new async model we should be able to get it right. + return nullptr; +} + +ExceptionOr<Vector<TransferredMessagePort>> MessagePort::disentanglePorts(Vector<RefPtr<MessagePort>>&& ports) +{ + if (ports.isEmpty()) + return Vector<TransferredMessagePort> {}; + + // Walk the incoming array - if there are any duplicate ports, or null ports or cloned ports, throw an error (per section 8.3.3 of the HTML5 spec). + HashSet<MessagePort*> portSet; + for (auto& port : ports) { + if (!port || !port->m_entangled || !portSet.add(port.get()).isNewEntry) + return Exception { DataCloneError }; + } + + // Passed-in ports passed validity checks, so we can disentangle them. + return WTF::map(ports, [](auto& port) { + return port->disentangle(); + }); +} + +Vector<RefPtr<MessagePort>> MessagePort::entanglePorts(ScriptExecutionContext& context, Vector<TransferredMessagePort>&& transferredPorts) +{ + LOG(MessagePorts, "Entangling %zu transferred ports to ScriptExecutionContext %s (%p)", transferredPorts.size(), context.url().string().utf8().data(), &context); + + if (transferredPorts.isEmpty()) + return {}; + + return WTF::map(transferredPorts, [&](auto& port) -> RefPtr<MessagePort> { + return MessagePort::entangle(context, WTFMove(port)); + }); +} + +Ref<MessagePort> MessagePort::entangle(ScriptExecutionContext& context, TransferredMessagePort&& transferredPort) +{ + auto port = MessagePort::create(context, transferredPort.first, transferredPort.second); + port->entangle(); + return port; +} + +bool MessagePort::addEventListener(const AtomString& eventType, Ref<EventListener>&& listener, const AddEventListenerOptions& options) +{ + if (eventType == eventNames().messageEvent) { + if (listener->isAttribute()) + start(); + m_hasMessageEventListener = true; + } + + return EventTarget::addEventListener(eventType, WTFMove(listener), options); +} + +bool MessagePort::removeEventListener(const AtomString& eventType, EventListener& listener, const EventListenerOptions& options) +{ + auto result = EventTarget::removeEventListener(eventType, listener, options); + + if (!hasEventListeners(eventNames().messageEvent)) + m_hasMessageEventListener = false; + + return result; +} + +// const char* MessagePort::activeDOMObjectName() const +// { +// return "MessagePort"; +// } + +WebCoreOpaqueRoot root(MessagePort* port) +{ + return WebCoreOpaqueRoot { port }; +} + +} // namespace WebCore diff --git a/src/bun.js/bindings/webcore/MessagePort.h b/src/bun.js/bindings/webcore/MessagePort.h new file mode 100644 index 000000000..18f0ba212 --- /dev/null +++ b/src/bun.js/bindings/webcore/MessagePort.h @@ -0,0 +1,140 @@ +/* + * Copyright (C) 2008 Apple Inc. All Rights Reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#pragma once + +#include "ActiveDOMObject.h" +#include "EventTarget.h" +#include "ExceptionOr.h" +#include "MessagePortChannel.h" +#include "MessagePortIdentifier.h" +#include "MessageWithMessagePorts.h" +#include <wtf/WeakPtr.h> + +namespace JSC { +class CallFrame; +class JSObject; +class JSValue; +} + +namespace WebCore { + +class LocalFrame; +class WebCoreOpaqueRoot; + +struct StructuredSerializeOptions; + +class MessagePort final : /* public ActiveDOMObject, */ public ContextDestructionObserver, public EventTarget { + WTF_MAKE_NONCOPYABLE(MessagePort); + WTF_MAKE_ISO_ALLOCATED(MessagePort); + +public: + static Ref<MessagePort> create(ScriptExecutionContext&, const MessagePortIdentifier& local, const MessagePortIdentifier& remote); + virtual ~MessagePort(); + + ExceptionOr<void> postMessage(JSC::JSGlobalObject&, JSC::JSValue message, StructuredSerializeOptions&&); + + void start(); + void close(); + void entangle(); + + // Returns nullptr if the passed-in vector is empty. + static ExceptionOr<Vector<TransferredMessagePort>> disentanglePorts(Vector<RefPtr<MessagePort>>&&); + static Vector<RefPtr<MessagePort>> entanglePorts(ScriptExecutionContext&, Vector<TransferredMessagePort>&&); + + WEBCORE_EXPORT static bool isMessagePortAliveForTesting(const MessagePortIdentifier&); + WEBCORE_EXPORT static void notifyMessageAvailable(const MessagePortIdentifier&); + + WEBCORE_EXPORT void messageAvailable(); + bool started() const { return m_started; } + bool isDetached() const { return m_isDetached; } + + void dispatchMessages(); + + // Returns null if there is no entangled port, or if the entangled port is run by a different thread. + // This is used solely to enable a GC optimization. Some platforms may not be able to determine ownership + // of the remote port (since it may live cross-process) - those platforms may always return null. + MessagePort* locallyEntangledPort(); + + const MessagePortIdentifier& identifier() const { return m_identifier; } + const MessagePortIdentifier& remoteIdentifier() const { return m_remoteIdentifier; } + + WEBCORE_EXPORT void ref() const; + WEBCORE_EXPORT void deref() const; + + // EventTarget. + EventTargetInterface eventTargetInterface() const final + { + return MessagePortEventTargetInterfaceType; + } + ScriptExecutionContext* scriptExecutionContext() const final { return ScriptExecutionContext::getScriptExecutionContext(contextIdForMessagePortId(m_identifier)); } + void refEventTarget() final { ref(); } + void derefEventTarget() final { deref(); } + + void dispatchEvent(Event&) final; + + TransferredMessagePort disentangle(); + static Ref<MessagePort> entangle(ScriptExecutionContext&, TransferredMessagePort&&); + + bool hasPendingActivity() const; + + static ScriptExecutionContextIdentifier contextIdForMessagePortId(MessagePortIdentifier); + +private: + explicit MessagePort(ScriptExecutionContext&, const MessagePortIdentifier& local, const MessagePortIdentifier& remote); + + bool addEventListener(const AtomString& eventType, Ref<EventListener>&&, const AddEventListenerOptions&) final; + bool removeEventListener(const AtomString& eventType, EventListener&, const EventListenerOptions&) final; + + // ActiveDOMObject + // const char* activeDOMObjectName() const final; + // void contextDestroyed() final; + // void stop() final { close(); } + // bool virtualHasPendingActivity() const final; + + EventTargetData* eventTargetData() final { return &m_eventTargetData; } + EventTargetData* eventTargetDataConcurrently() final { return &m_eventTargetData; } + EventTargetData& ensureEventTargetData() final { return m_eventTargetData; } + + EventTargetData m_eventTargetData; + + // A port starts out its life entangled, and remains entangled until it is detached or is cloned. + bool isEntangled() const { return !m_isDetached && m_entangled; } + + bool m_started { false }; + bool m_isDetached { false }; + bool m_entangled { true }; + bool m_hasMessageEventListener { false }; + + MessagePortIdentifier m_identifier; + MessagePortIdentifier m_remoteIdentifier; + + mutable std::atomic<unsigned> m_refCount { 1 }; +}; + +WebCoreOpaqueRoot root(MessagePort*); + +} // namespace WebCore diff --git a/src/bun.js/bindings/webcore/MessagePortChannel.cpp b/src/bun.js/bindings/webcore/MessagePortChannel.cpp new file mode 100644 index 000000000..4e0f44524 --- /dev/null +++ b/src/bun.js/bindings/webcore/MessagePortChannel.cpp @@ -0,0 +1,190 @@ +/* + * Copyright (C) 2018 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#include "MessagePortChannel.h" + +// #include "Logging.h" +#include "MessagePortChannelRegistry.h" +#include <wtf/CompletionHandler.h> +#include <wtf/MainThread.h> + +namespace WebCore { + +Ref<MessagePortChannel> MessagePortChannel::create(MessagePortChannelRegistry& registry, const MessagePortIdentifier& port1, const MessagePortIdentifier& port2) +{ + return adoptRef(*new MessagePortChannel(registry, port1, port2)); +} + +MessagePortChannel::MessagePortChannel(MessagePortChannelRegistry& registry, const MessagePortIdentifier& port1, const MessagePortIdentifier& port2) + : m_registry(registry) +{ + ASSERT(isMainThread()); + + relaxAdoptionRequirement(); + + m_ports[0] = port1; + m_processes[0] = port1.processIdentifier; + m_entangledToProcessProtectors[0] = this; + m_ports[1] = port2; + m_processes[1] = port2.processIdentifier; + m_entangledToProcessProtectors[1] = this; + + m_registry.messagePortChannelCreated(*this); +} + +MessagePortChannel::~MessagePortChannel() +{ + m_registry.messagePortChannelDestroyed(*this); +} + +std::optional<ProcessIdentifier> MessagePortChannel::processForPort(const MessagePortIdentifier& port) +{ + ASSERT(isMainThread()); + ASSERT(port == m_ports[0] || port == m_ports[1]); + size_t i = port == m_ports[0] ? 0 : 1; + return m_processes[i]; +} + +bool MessagePortChannel::includesPort(const MessagePortIdentifier& port) +{ + ASSERT(isMainThread()); + + return m_ports[0] == port || m_ports[1] == port; +} + +void MessagePortChannel::entanglePortWithProcess(const MessagePortIdentifier& port, ProcessIdentifier process) +{ + ASSERT(isMainThread()); + + ASSERT(port == m_ports[0] || port == m_ports[1]); + size_t i = port == m_ports[0] ? 0 : 1; + + LOG(MessagePorts, "MessagePortChannel %s (%p) entangling port %s (that port has %zu messages available)", logString().utf8().data(), this, port.logString().utf8().data(), m_pendingMessages[i].size()); + + ASSERT(!m_processes[i] || *m_processes[i] == process); + m_processes[i] = process; + m_entangledToProcessProtectors[i] = this; + m_pendingMessagePortTransfers[i].remove(this); +} + +void MessagePortChannel::disentanglePort(const MessagePortIdentifier& port) +{ + ASSERT(isMainThread()); + + LOG(MessagePorts, "MessagePortChannel %s (%p) disentangling port %s", logString().utf8().data(), this, port.logString().utf8().data()); + + ASSERT(port == m_ports[0] || port == m_ports[1]); + size_t i = port == m_ports[0] ? 0 : 1; + + ASSERT(m_processes[i] || m_isClosed[i]); + m_processes[i] = std::nullopt; + m_pendingMessagePortTransfers[i].add(this); + + // This set of steps is to guarantee that the lock is unlocked before the + // last ref to this object is released. + auto protectedThis = WTFMove(m_entangledToProcessProtectors[i]); +} + +void MessagePortChannel::closePort(const MessagePortIdentifier& port) +{ + ASSERT(isMainThread()); + + ASSERT(port == m_ports[0] || port == m_ports[1]); + size_t i = port == m_ports[0] ? 0 : 1; + + m_processes[i] = std::nullopt; + m_isClosed[i] = true; + + // This set of steps is to guarantee that the lock is unlocked before the + // last ref to this object is released. + Ref protectedThis { *this }; + + m_pendingMessages[i].clear(); + m_pendingMessagePortTransfers[i].clear(); + m_pendingMessageProtectors[i] = nullptr; + m_entangledToProcessProtectors[i] = nullptr; +} + +bool MessagePortChannel::postMessageToRemote(MessageWithMessagePorts&& message, const MessagePortIdentifier& remoteTarget) +{ + ASSERT(isMainThread()); + + ASSERT(remoteTarget == m_ports[0] || remoteTarget == m_ports[1]); + size_t i = remoteTarget == m_ports[0] ? 0 : 1; + + m_pendingMessages[i].append(WTFMove(message)); + LOG(MessagePorts, "MessagePortChannel %s (%p) now has %zu messages pending on port %s", logString().utf8().data(), this, m_pendingMessages[i].size(), remoteTarget.logString().utf8().data()); + + if (m_pendingMessages[i].size() == 1) { + m_pendingMessageProtectors[i] = this; + return true; + } + + ASSERT(m_pendingMessageProtectors[i] == this); + return false; +} + +void MessagePortChannel::takeAllMessagesForPort(const MessagePortIdentifier& port, CompletionHandler<void(Vector<MessageWithMessagePorts>&&, CompletionHandler<void()>&&)>&& callback) +{ + ASSERT(isMainThread()); + + LOG(MessagePorts, "MessagePortChannel %p taking all messages for port %s", this, port.logString().utf8().data()); + + ASSERT(port == m_ports[0] || port == m_ports[1]); + size_t i = port == m_ports[0] ? 0 : 1; + + if (m_pendingMessages[i].isEmpty()) { + callback({}, [] {}); + return; + } + + ASSERT(m_pendingMessageProtectors[i]); + + Vector<MessageWithMessagePorts> result; + result.swap(m_pendingMessages[i]); + + ++m_messageBatchesInFlight; + + LOG(MessagePorts, "There are %zu messages to take for port %s. Taking them now, messages in flight is now %" PRIu64, result.size(), port.logString().utf8().data(), m_messageBatchesInFlight); + + auto size = result.size(); + callback(WTFMove(result), [size, this, port, protectedThis = WTFMove(m_pendingMessageProtectors[i])] { + UNUSED_PARAM(port); +#if LOG_DISABLED + UNUSED_PARAM(size); +#endif + --m_messageBatchesInFlight; + LOG(MessagePorts, "Message port channel %s was notified that a batch of %zu message port messages targeted for port %s just completed dispatch, in flight is now %" PRIu64, logString().utf8().data(), size, port.logString().utf8().data(), m_messageBatchesInFlight); + }); +} + +bool MessagePortChannel::hasAnyMessagesPendingOrInFlight() const +{ + ASSERT(isMainThread()); + return m_messageBatchesInFlight || !m_pendingMessages[0].isEmpty() || !m_pendingMessages[1].isEmpty(); +} + +} // namespace WebCore diff --git a/src/bun.js/bindings/webcore/MessagePortChannel.h b/src/bun.js/bindings/webcore/MessagePortChannel.h new file mode 100644 index 000000000..787cc8f7a --- /dev/null +++ b/src/bun.js/bindings/webcore/MessagePortChannel.h @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2018 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#pragma once + +#include "MessagePortChannelProvider.h" +#include "MessagePortIdentifier.h" +#include "MessageWithMessagePorts.h" +#include "ProcessIdentifier.h" +#include <wtf/HashSet.h> +#include <wtf/RefCounted.h> +#include <wtf/text/WTFString.h> + +namespace WebCore { + +class MessagePortChannelRegistry; + +class MessagePortChannel : public RefCounted<MessagePortChannel> { +public: + static Ref<MessagePortChannel> create(MessagePortChannelRegistry&, const MessagePortIdentifier& port1, const MessagePortIdentifier& port2); + + ~MessagePortChannel(); + + const MessagePortIdentifier& port1() const { return m_ports[0]; } + const MessagePortIdentifier& port2() const { return m_ports[1]; } + + WEBCORE_EXPORT std::optional<ProcessIdentifier> processForPort(const MessagePortIdentifier&); + bool includesPort(const MessagePortIdentifier&); + void entanglePortWithProcess(const MessagePortIdentifier&, ProcessIdentifier); + void disentanglePort(const MessagePortIdentifier&); + void closePort(const MessagePortIdentifier&); + bool postMessageToRemote(MessageWithMessagePorts&&, const MessagePortIdentifier& remoteTarget); + + void takeAllMessagesForPort(const MessagePortIdentifier&, CompletionHandler<void(Vector<MessageWithMessagePorts>&&, CompletionHandler<void()>&&)>&&); + + WEBCORE_EXPORT bool hasAnyMessagesPendingOrInFlight() const; + + uint64_t beingTransferredCount(); + +#if !LOG_DISABLED + String logString() const + { + return makeString(m_ports[0].logString(), ":", m_ports[1].logString()); + } +#endif + +private: + MessagePortChannel(MessagePortChannelRegistry&, const MessagePortIdentifier& port1, const MessagePortIdentifier& port2); + + MessagePortIdentifier m_ports[2]; + bool m_isClosed[2] { false, false }; + std::optional<ProcessIdentifier> m_processes[2]; + RefPtr<MessagePortChannel> m_entangledToProcessProtectors[2]; + Vector<MessageWithMessagePorts> m_pendingMessages[2]; + HashSet<RefPtr<MessagePortChannel>> m_pendingMessagePortTransfers[2]; + RefPtr<MessagePortChannel> m_pendingMessageProtectors[2]; + uint64_t m_messageBatchesInFlight { 0 }; + + MessagePortChannelRegistry& m_registry; +}; + +} // namespace WebCore diff --git a/src/bun.js/bindings/webcore/MessagePortChannelProvider.cpp b/src/bun.js/bindings/webcore/MessagePortChannelProvider.cpp new file mode 100644 index 000000000..7675a2e75 --- /dev/null +++ b/src/bun.js/bindings/webcore/MessagePortChannelProvider.cpp @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2018 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +// #include "MessagePortChannelProvider.h" + +// #include "Document.h" +#include "MessagePortChannelProviderImpl.h" +// #include "WorkerGlobalScope.h" +// #include "WorkletGlobalScope.h" +#include <wtf/MainThread.h> + +namespace WebCore { + +static MessagePortChannelProviderImpl* globalProvider; + +MessagePortChannelProvider& MessagePortChannelProvider::singleton() +{ + ASSERT(isMainThread()); + static std::once_flag onceFlag; + std::call_once(onceFlag, [] { + if (!globalProvider) + globalProvider = new MessagePortChannelProviderImpl; + }); + + return *globalProvider; +} + +// void MessagePortChannelProvider::setSharedProvider(MessagePortChannelProvider& provider) +// { +// RELEASE_ASSERT(isMainThread()); +// RELEASE_ASSERT(!globalProvider); +// globalProvider = &provider; +// } + +MessagePortChannelProvider& MessagePortChannelProvider::fromContext(ScriptExecutionContext& context) +{ + // if (auto document = dynamicDowncast<Document>(context)) + // return document->messagePortChannelProvider(); + + // if (auto workletScope = dynamicDowncast<WorkletGlobalScope>(context)) + // return workletScope->messagePortChannelProvider(); + + return jsCast<Zig::GlobalObject*>(context.jsGlobalObject())->globalEventScope.messagePortChannelProvider(); +} + +} // namespace WebCore diff --git a/src/bun.js/bindings/webcore/MessagePortChannelProvider.h b/src/bun.js/bindings/webcore/MessagePortChannelProvider.h new file mode 100644 index 000000000..2f9ab1787 --- /dev/null +++ b/src/bun.js/bindings/webcore/MessagePortChannelProvider.h @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2018 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#pragma once + +#include "ProcessIdentifier.h" +#include "BunWorkerGlobalScope.h" +#include <wtf/CompletionHandler.h> +#include <wtf/Vector.h> + +namespace WebCore { + +class ScriptExecutionContext; +struct MessagePortIdentifier; +struct MessageWithMessagePorts; + +class MessagePortChannelProvider { +public: + static MessagePortChannelProvider& fromContext(ScriptExecutionContext&); + static MessagePortChannelProvider& singleton(); + // WEBCORE_EXPORT static void setSharedProvider(MessagePortChannelProvider&); + + virtual ~MessagePortChannelProvider() {} + + // Operations that WebProcesses perform + virtual void createNewMessagePortChannel(const MessagePortIdentifier& local, const MessagePortIdentifier& remote) = 0; + virtual void entangleLocalPortInThisProcessToRemote(const MessagePortIdentifier& local, const MessagePortIdentifier& remote) = 0; + virtual void messagePortDisentangled(const MessagePortIdentifier& local) = 0; + virtual void messagePortClosed(const MessagePortIdentifier& local) = 0; + + virtual void takeAllMessagesForPort(const MessagePortIdentifier&, CompletionHandler<void(Vector<MessageWithMessagePorts>&&, CompletionHandler<void()>&&)>&&) = 0; + + virtual void postMessageToRemote(MessageWithMessagePorts&&, const MessagePortIdentifier& remoteTarget) = 0; +}; + +} // namespace WebCore diff --git a/src/bun.js/bindings/webcore/MessagePortChannelProviderImpl.cpp b/src/bun.js/bindings/webcore/MessagePortChannelProviderImpl.cpp new file mode 100644 index 000000000..c33770dab --- /dev/null +++ b/src/bun.js/bindings/webcore/MessagePortChannelProviderImpl.cpp @@ -0,0 +1,93 @@ +/* + * Copyright (C) 2018 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#include "MessagePortChannelProviderImpl.h" + +#include "MessagePort.h" +#include <wtf/MainThread.h> +#include <wtf/RunLoop.h> + +namespace WebCore { + +MessagePortChannelProviderImpl::MessagePortChannelProviderImpl() +{ +} + +MessagePortChannelProviderImpl::~MessagePortChannelProviderImpl() +{ + ASSERT_NOT_REACHED(); +} + +void MessagePortChannelProviderImpl::createNewMessagePortChannel(const MessagePortIdentifier& local, const MessagePortIdentifier& remote) +{ + ScriptExecutionContext::ensureOnMainThread([registry = &m_registry, local, remote](ScriptExecutionContext& context) mutable { + registry->didCreateMessagePortChannel(local, remote); + }); +} + +void MessagePortChannelProviderImpl::entangleLocalPortInThisProcessToRemote(const MessagePortIdentifier& local, const MessagePortIdentifier& remote) +{ + ScriptExecutionContext::ensureOnMainThread([registry = &m_registry, local, remote](ScriptExecutionContext& context) mutable { + registry->didEntangleLocalToRemote(local, remote, ProcessIdent::identifier()); + }); +} + +void MessagePortChannelProviderImpl::messagePortDisentangled(const MessagePortIdentifier& local) +{ + ScriptExecutionContext::ensureOnMainThread([registry = &m_registry, local](ScriptExecutionContext& context) mutable { + registry->didDisentangleMessagePort(local); + }); +} + +void MessagePortChannelProviderImpl::messagePortClosed(const MessagePortIdentifier& local) +{ + ScriptExecutionContext::ensureOnMainThread([registry = &m_registry, local](ScriptExecutionContext& context) mutable { + registry->didCloseMessagePort(local); + }); +} + +void MessagePortChannelProviderImpl::postMessageToRemote(MessageWithMessagePorts&& message, const MessagePortIdentifier& remoteTarget) +{ + ScriptExecutionContext::ensureOnMainThread([message = WTFMove(message), registry = &m_registry, remoteTarget](ScriptExecutionContext& context) mutable { + if (registry->didPostMessageToRemote(WTFMove(message), remoteTarget)) + MessagePort::notifyMessageAvailable(remoteTarget); + }); +} + +void MessagePortChannelProviderImpl::takeAllMessagesForPort(const MessagePortIdentifier& port, CompletionHandler<void(Vector<MessageWithMessagePorts>&&, CompletionHandler<void()>&&)>&& outerCallback) +{ + // It is the responsibility of outerCallback to get itself to the appropriate thread (e.g. WebWorker thread) + auto callback = [outerCallback = WTFMove(outerCallback)](Vector<MessageWithMessagePorts>&& messages, CompletionHandler<void()>&& messageDeliveryCallback) mutable { + ASSERT(isMainThread()); + outerCallback(WTFMove(messages), WTFMove(messageDeliveryCallback)); + }; + + ScriptExecutionContext::ensureOnMainThread([registry = &m_registry, port, callback = WTFMove(callback)](ScriptExecutionContext& context) mutable { + registry->takeAllMessagesForPort(port, WTFMove(callback)); + }); +} + +} // namespace WebCore diff --git a/src/bun.js/bindings/webcore/MessagePortChannelProviderImpl.h b/src/bun.js/bindings/webcore/MessagePortChannelProviderImpl.h new file mode 100644 index 000000000..a6bcf79db --- /dev/null +++ b/src/bun.js/bindings/webcore/MessagePortChannelProviderImpl.h @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2018 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#pragma once + +#include "MessagePortChannelProvider.h" +#include "MessagePortChannelRegistry.h" + +namespace WebCore { + +class MessagePortChannelProviderImpl final : public MessagePortChannelProvider { +public: + MessagePortChannelProviderImpl(); + ~MessagePortChannelProviderImpl() final; + +private: + void createNewMessagePortChannel(const MessagePortIdentifier& local, const MessagePortIdentifier& remote) final; + void entangleLocalPortInThisProcessToRemote(const MessagePortIdentifier& local, const MessagePortIdentifier& remote) final; + void messagePortDisentangled(const MessagePortIdentifier& local) final; + void messagePortClosed(const MessagePortIdentifier& local) final; + void postMessageToRemote(MessageWithMessagePorts&&, const MessagePortIdentifier& remoteTarget) final; + void takeAllMessagesForPort(const MessagePortIdentifier&, CompletionHandler<void(Vector<MessageWithMessagePorts>&&, CompletionHandler<void()>&&)>&&) final; + + MessagePortChannelRegistry m_registry; +}; + +} // namespace WebCore diff --git a/src/bun.js/bindings/webcore/MessagePortChannelRegistry.cpp b/src/bun.js/bindings/webcore/MessagePortChannelRegistry.cpp new file mode 100644 index 000000000..7675e0eab --- /dev/null +++ b/src/bun.js/bindings/webcore/MessagePortChannelRegistry.cpp @@ -0,0 +1,164 @@ +/* + * Copyright (C) 2018 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#include "MessagePortChannelRegistry.h" + +// #include "Logging.h" +#include <wtf/CompletionHandler.h> +#include <wtf/MainThread.h> + +namespace WebCore { + +MessagePortChannelRegistry::MessagePortChannelRegistry() = default; + +MessagePortChannelRegistry::~MessagePortChannelRegistry() +{ + ASSERT(m_openChannels.isEmpty()); +} + +void MessagePortChannelRegistry::didCreateMessagePortChannel(const MessagePortIdentifier& port1, const MessagePortIdentifier& port2) +{ + LOG(MessagePorts, "Registry: Creating MessagePortChannel %p linking %s and %s", this, port1.logString().utf8().data(), port2.logString().utf8().data()); + ASSERT(isMainThread()); + + MessagePortChannel::create(*this, port1, port2); +} + +void MessagePortChannelRegistry::messagePortChannelCreated(MessagePortChannel& channel) +{ + ASSERT(isMainThread()); + + auto result = m_openChannels.ensure(channel.port1(), [channel = &channel] { + return channel; + }); + ASSERT(result.isNewEntry); + + result = m_openChannels.ensure(channel.port2(), [channel = &channel] { + return channel; + }); + ASSERT(result.isNewEntry); +} + +void MessagePortChannelRegistry::messagePortChannelDestroyed(MessagePortChannel& channel) +{ + ASSERT(isMainThread()); + + ASSERT(m_openChannels.get(channel.port1()) == &channel); + ASSERT(m_openChannels.get(channel.port2()) == &channel); + + m_openChannels.remove(channel.port1()); + m_openChannels.remove(channel.port2()); + + LOG(MessagePorts, "Registry: After removing channel %s there are %u channels left in the registry:", channel.logString().utf8().data(), m_openChannels.size()); +} + +void MessagePortChannelRegistry::didEntangleLocalToRemote(const MessagePortIdentifier& local, const MessagePortIdentifier& remote, ProcessIdentifier process) +{ + ASSERT(isMainThread()); + + // The channel might be gone if the remote side was closed. + auto* channel = m_openChannels.get(local); + if (!channel) + return; + + ASSERT_UNUSED(remote, channel->includesPort(remote)); + + channel->entanglePortWithProcess(local, process); +} + +void MessagePortChannelRegistry::didDisentangleMessagePort(const MessagePortIdentifier& port) +{ + ASSERT(isMainThread()); + + // The channel might be gone if the remote side was closed. + auto* channel = m_openChannels.get(port); + if (!channel) + return; + + channel->disentanglePort(port); +} + +void MessagePortChannelRegistry::didCloseMessagePort(const MessagePortIdentifier& port) +{ + ASSERT(isMainThread()); + + LOG(MessagePorts, "Registry: MessagePort %s closed in registry", port.logString().utf8().data()); + + auto* channel = m_openChannels.get(port); + if (!channel) + return; + +#ifndef NDEBUG + if (channel && channel->hasAnyMessagesPendingOrInFlight()) + LOG(MessagePorts, "Registry: (Note) The channel closed for port %s had messages pending or in flight", port.logString().utf8().data()); +#endif + + channel->closePort(port); + + // FIXME: When making message ports be multi-process, this should probably push a notification + // to the remaining port to tell it this port closed. +} + +bool MessagePortChannelRegistry::didPostMessageToRemote(MessageWithMessagePorts&& message, const MessagePortIdentifier& remoteTarget) +{ + ASSERT(isMainThread()); + + LOG(MessagePorts, "Registry: Posting message to MessagePort %s in registry", remoteTarget.logString().utf8().data()); + + // The channel might be gone if the remote side was closed. + auto* channel = m_openChannels.get(remoteTarget); + if (!channel) { + LOG(MessagePorts, "Registry: Could not find MessagePortChannel for port %s; It was probably closed. Message will be dropped.", remoteTarget.logString().utf8().data()); + return false; + } + + return channel->postMessageToRemote(WTFMove(message), remoteTarget); +} + +void MessagePortChannelRegistry::takeAllMessagesForPort(const MessagePortIdentifier& port, CompletionHandler<void(Vector<MessageWithMessagePorts>&&, CompletionHandler<void()>&&)>&& callback) +{ + ASSERT(isMainThread()); + + LOG(MessagePorts, "Registry: Taking all messages for MessagePort %s", port.logString().utf8().data()); + + // The channel might be gone if the remote side was closed. + auto* channel = m_openChannels.get(port); + if (!channel) { + callback({}, [] {}); + return; + } + + channel->takeAllMessagesForPort(port, WTFMove(callback)); +} + +MessagePortChannel* MessagePortChannelRegistry::existingChannelContainingPort(const MessagePortIdentifier& port) +{ + ASSERT(isMainThread()); + + return m_openChannels.get(port); +} + +} // namespace WebCore diff --git a/src/bun.js/bindings/webcore/MessagePortChannelRegistry.h b/src/bun.js/bindings/webcore/MessagePortChannelRegistry.h new file mode 100644 index 000000000..334fb5f9c --- /dev/null +++ b/src/bun.js/bindings/webcore/MessagePortChannelRegistry.h @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2018 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#pragma once + +#include "MessagePortChannel.h" +#include "MessagePortChannelProvider.h" +#include "MessagePortIdentifier.h" +#include "ProcessIdentifier.h" +#include <wtf/HashMap.h> + +namespace WebCore { + +class MessagePortChannelRegistry { +public: + WEBCORE_EXPORT MessagePortChannelRegistry(); + + WEBCORE_EXPORT ~MessagePortChannelRegistry(); + + WEBCORE_EXPORT void didCreateMessagePortChannel(const MessagePortIdentifier& port1, const MessagePortIdentifier& port2); + WEBCORE_EXPORT void didEntangleLocalToRemote(const MessagePortIdentifier& local, const MessagePortIdentifier& remote, ProcessIdentifier); + WEBCORE_EXPORT void didDisentangleMessagePort(const MessagePortIdentifier& local); + WEBCORE_EXPORT void didCloseMessagePort(const MessagePortIdentifier& local); + WEBCORE_EXPORT bool didPostMessageToRemote(MessageWithMessagePorts&&, const MessagePortIdentifier& remoteTarget); + WEBCORE_EXPORT void takeAllMessagesForPort(const MessagePortIdentifier&, CompletionHandler<void(Vector<MessageWithMessagePorts>&&, CompletionHandler<void()>&&)>&&); + + WEBCORE_EXPORT MessagePortChannel* existingChannelContainingPort(const MessagePortIdentifier&); + + WEBCORE_EXPORT void messagePortChannelCreated(MessagePortChannel&); + WEBCORE_EXPORT void messagePortChannelDestroyed(MessagePortChannel&); + +private: + HashMap<MessagePortIdentifier, MessagePortChannel*> m_openChannels; +}; + +} // namespace WebCore diff --git a/src/bun.js/bindings/webcore/MessagePortIdentifier.h b/src/bun.js/bindings/webcore/MessagePortIdentifier.h new file mode 100644 index 000000000..eac8546de --- /dev/null +++ b/src/bun.js/bindings/webcore/MessagePortIdentifier.h @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2018 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#pragma once + +#include "PortIdentifier.h" +#include "ProcessIdentifier.h" +#include <wtf/Hasher.h> +#include <wtf/text/StringConcatenateNumbers.h> + +namespace WebCore { + +struct MessagePortIdentifier { + ProcessIdentifier processIdentifier; + PortIdentifier portIdentifier; + +#if !LOG_DISABLED + String logString() const; +#endif +}; + +inline void add(Hasher& hasher, const MessagePortIdentifier& identifier) +{ + add(hasher, identifier.processIdentifier, identifier.portIdentifier); +} + +inline bool operator==(const MessagePortIdentifier& a, const MessagePortIdentifier& b) +{ + return a.processIdentifier == b.processIdentifier && a.portIdentifier == b.portIdentifier; +} + +#if !LOG_DISABLED + +inline String MessagePortIdentifier::logString() const +{ + return makeString(processIdentifier.toUInt64(), '-', portIdentifier.toUInt64()); +} + +#endif + +} // namespace WebCore + +namespace WTF { + +struct MessagePortIdentifierHash { + static unsigned hash(const WebCore::MessagePortIdentifier& key) { return computeHash(key); } + static bool equal(const WebCore::MessagePortIdentifier& a, const WebCore::MessagePortIdentifier& b) { return a == b; } + static const bool safeToCompareToEmptyOrDeleted = true; +}; + +template<> struct HashTraits<WebCore::MessagePortIdentifier> : GenericHashTraits<WebCore::MessagePortIdentifier> { + static WebCore::MessagePortIdentifier emptyValue() { return {}; } + + static void constructDeletedValue(WebCore::MessagePortIdentifier& slot) { new (NotNull, &slot.processIdentifier) WebCore::ProcessIdentifier(WTF::HashTableDeletedValue); } + + static bool isDeletedValue(const WebCore::MessagePortIdentifier& slot) { return slot.processIdentifier.isHashTableDeletedValue(); } +}; + +template<> struct DefaultHash<WebCore::MessagePortIdentifier> : MessagePortIdentifierHash {}; + +} // namespace WTF diff --git a/src/bun.js/bindings/webcore/MessageWithMessagePorts.h b/src/bun.js/bindings/webcore/MessageWithMessagePorts.h new file mode 100644 index 000000000..a986bfb4c --- /dev/null +++ b/src/bun.js/bindings/webcore/MessageWithMessagePorts.h @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2018 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#pragma once + +#include "SerializedScriptValue.h" +#include "TransferredMessagePort.h" +#include <wtf/RefPtr.h> + +namespace WebCore { + +struct MessageWithMessagePorts { + RefPtr<SerializedScriptValue> message; + Vector<TransferredMessagePort> transferredPorts; +}; + +} // namespace WebCore diff --git a/src/bun.js/bindings/webcore/PortIdentifier.h b/src/bun.js/bindings/webcore/PortIdentifier.h new file mode 100644 index 000000000..8b77e792e --- /dev/null +++ b/src/bun.js/bindings/webcore/PortIdentifier.h @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2023 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#pragma once + +#include "ProcessIdentifier.h" + +namespace WebCore { + +enum PortIdentifierType {}; +using PortIdentifier = AtomicObjectIdentifier<PortIdentifierType>; + +} diff --git a/src/bun.js/bindings/webcore/SerializedScriptValue.cpp b/src/bun.js/bindings/webcore/SerializedScriptValue.cpp index e80bc9493..9eeee155a 100644 --- a/src/bun.js/bindings/webcore/SerializedScriptValue.cpp +++ b/src/bun.js/bindings/webcore/SerializedScriptValue.cpp @@ -54,7 +54,7 @@ // #include "JSIDBSerializationGlobalObject.h" // #include "JSImageBitmap.h" // #include "JSImageData.h" -// #include "JSMessagePort.h" +#include "JSMessagePort.h" // #include "JSNavigator.h" // #include "JSRTCCertificate.h" // #include "JSRTCDataChannel.h" @@ -715,7 +715,7 @@ public: // return serializer.serialize(value); // } - static SerializationReturnCode serialize(JSGlobalObject* lexicalGlobalObject, JSValue value, Vector<RefPtr<JSC::ArrayBuffer>>& arrayBuffers, + static SerializationReturnCode serialize(JSGlobalObject* lexicalGlobalObject, JSValue value, Vector<RefPtr<MessagePort>>& messagePorts, Vector<RefPtr<JSC::ArrayBuffer>>& arrayBuffers, #if ENABLE(OFFSCREEN_CANVAS_IN_WORKERS) const Vector<RefPtr<OffscreenCanvas>>& offscreenCanvases, #endif @@ -733,7 +733,7 @@ public: Vector<uint8_t>& out, SerializationContext context, ArrayBufferContentsArray& sharedBuffers, SerializationForStorage forStorage) { - CloneSerializer serializer(lexicalGlobalObject, arrayBuffers, + CloneSerializer serializer(lexicalGlobalObject, messagePorts, arrayBuffers, #if ENABLE(OFFSCREEN_CANVAS_IN_WORKERS) offscreenCanvases, #endif @@ -815,7 +815,7 @@ private: // #endif // } - CloneSerializer(JSGlobalObject* lexicalGlobalObject, Vector<RefPtr<JSC::ArrayBuffer>>& arrayBuffers, + CloneSerializer(JSGlobalObject* lexicalGlobalObject, Vector<RefPtr<MessagePort>>& messagePorts, Vector<RefPtr<JSC::ArrayBuffer>>& arrayBuffers, #if ENABLE(OFFSCREEN_CANVAS_IN_WORKERS) const Vector<RefPtr<OffscreenCanvas>>& offscreenCanvases, #endif @@ -847,6 +847,7 @@ private: , m_forStorage(forStorage) { write(CurrentVersion); + fillTransferMap(messagePorts, m_transferredMessagePorts); fillTransferMap(arrayBuffers, m_transferredArrayBuffers); #if ENABLE(OFFSCREEN_CANVAS_IN_WORKERS) fillTransferMap(offscreenCanvases, m_transferredOffscreenCanvases); @@ -1501,17 +1502,17 @@ private: write(String::fromLatin1(JSC::Yarr::flagsString(regExp->regExp()->flags()).data())); return true; } - // if (obj->inherits<JSMessagePort>()) { - // auto index = m_transferredMessagePorts.find(obj); - // if (index != m_transferredMessagePorts.end()) { - // write(MessagePortReferenceTag); - // write(index->value); - // return true; - // } - // // MessagePort object could not be found in transferred message ports - // code = SerializationReturnCode::ValidationError; - // return true; - // } + if (obj->inherits<JSMessagePort>()) { + auto index = m_transferredMessagePorts.find(obj); + if (index != m_transferredMessagePorts.end()) { + write(MessagePortReferenceTag); + write(index->value); + return true; + } + // MessagePort object could not be found in transferred message ports + code = SerializationReturnCode::ValidationError; + return true; + } if (auto* arrayBuffer = toPossiblySharedArrayBuffer(vm, obj)) { if (arrayBuffer->isDetached()) { code = SerializationReturnCode::ValidationError; @@ -1570,7 +1571,7 @@ private: write(CryptoKeyTag); Vector<uint8_t> serializedKey; // Vector<URLKeepingBlobAlive> dummyBlobHandles; - // Vector<RefPtr<MessagePort>> dummyMessagePorts; + Vector<RefPtr<MessagePort>> dummyMessagePorts; Vector<RefPtr<JSC::ArrayBuffer>> dummyArrayBuffers; #if ENABLE(WEB_CODECS) Vector<RefPtr<WebCodecsEncodedVideoChunkStorage>> dummyVideoChunks; @@ -1597,7 +1598,7 @@ private: // dummyMemoryHandles, // #endif // dummyBlobHandles, serializedKey, SerializationContext::Default, dummySharedBuffers, m_forStorage); - CloneSerializer rawKeySerializer(m_lexicalGlobalObject, dummyArrayBuffers, + CloneSerializer rawKeySerializer(m_lexicalGlobalObject, dummyMessagePorts, dummyArrayBuffers, #if ENABLE(OFFSCREEN_CANVAS_IN_WORKERS) {}, #endif @@ -2518,7 +2519,7 @@ public: // return deserializer.deserialize(); // } - static DeserializationResult deserialize(JSGlobalObject* lexicalGlobalObject, JSGlobalObject* globalObject + static DeserializationResult deserialize(JSGlobalObject* lexicalGlobalObject, JSGlobalObject* globalObject, const Vector<RefPtr<MessagePort>>& messagePorts #if ENABLE(OFFSCREEN_CANVAS_IN_WORKERS) , Vector<std::unique_ptr<DetachedOffscreenCanvas>>&& detachedOffscreenCanvases @@ -2541,7 +2542,7 @@ public: { if (!buffer.size()) return std::make_pair(jsNull(), SerializationReturnCode::UnspecifiedError); - CloneDeserializer deserializer(lexicalGlobalObject, globalObject, arrayBufferContentsArray, std::span<uint8_t> { buffer.begin(), buffer.end() }, blobURLs, blobFilePaths, sharedBuffers + CloneDeserializer deserializer(lexicalGlobalObject, globalObject, messagePorts, arrayBufferContentsArray, std::span<uint8_t> { buffer.begin(), buffer.end() }, blobURLs, blobFilePaths, sharedBuffers #if ENABLE(OFFSCREEN_CANVAS_IN_WORKERS) , WTFMove(detachedOffscreenCanvases) @@ -2675,7 +2676,7 @@ private: // m_version = 0xFFFFFFFF; // } - CloneDeserializer(JSGlobalObject* lexicalGlobalObject, JSGlobalObject* globalObject, ArrayBufferContentsArray* arrayBufferContents, const std::span<uint8_t>& buffer + CloneDeserializer(JSGlobalObject* lexicalGlobalObject, JSGlobalObject* globalObject, const Vector<RefPtr<MessagePort>>& messagePorts, ArrayBufferContentsArray* arrayBufferContents, const std::span<uint8_t>& buffer #if ENABLE(OFFSCREEN_CANVAS_IN_WORKERS) , Vector<std::unique_ptr<DetachedOffscreenCanvas>>&& detachedOffscreenCanvases = {} @@ -2700,6 +2701,7 @@ private: , m_ptr(buffer.data()) , m_end(buffer.data() + buffer.size()) , m_version(0xFFFFFFFF) + , m_messagePorts(messagePorts) , m_arrayBufferContents(arrayBufferContents) , m_arrayBuffers(arrayBufferContents ? arrayBufferContents->size() : 0) #if ENABLE(OFFSCREEN_CANVAS_IN_WORKERS) @@ -2781,7 +2783,7 @@ private: // m_version = 0xFFFFFFFF; // } - CloneDeserializer(JSGlobalObject* lexicalGlobalObject, JSGlobalObject* globalObject, ArrayBufferContentsArray* arrayBufferContents, const std::span<uint8_t>& buffer, const Vector<String>& blobURLs, const Vector<String> blobFilePaths, ArrayBufferContentsArray* sharedBuffers + CloneDeserializer(JSGlobalObject* lexicalGlobalObject, JSGlobalObject* globalObject, const Vector<RefPtr<MessagePort>>& messagePorts, ArrayBufferContentsArray* arrayBufferContents, const std::span<uint8_t>& buffer, const Vector<String>& blobURLs, const Vector<String> blobFilePaths, ArrayBufferContentsArray* sharedBuffers #if ENABLE(OFFSCREEN_CANVAS_IN_WORKERS) , Vector<std::unique_ptr<DetachedOffscreenCanvas>>&& detachedOffscreenCanvases @@ -2806,6 +2808,7 @@ private: , m_ptr(buffer.data()) , m_end(buffer.data() + buffer.size()) , m_version(0xFFFFFFFF) + , m_messagePorts(messagePorts) , m_arrayBufferContents(arrayBufferContents) , m_arrayBuffers(arrayBufferContents ? arrayBufferContents->size() : 0) , m_blobURLs(blobURLs) @@ -4391,15 +4394,15 @@ private: } return m_gcBuffer.at(index); } - // case MessagePortReferenceTag: { - // uint32_t index; - // bool indexSuccessfullyRead = read(index); - // if (!indexSuccessfullyRead || index >= m_messagePorts.size()) { - // fail(); - // return JSValue(); - // } - // return getJSValue(m_messagePorts[index].get()); - // } + case MessagePortReferenceTag: { + uint32_t index; + bool indexSuccessfullyRead = read(index); + if (!indexSuccessfullyRead || index >= m_messagePorts.size()) { + fail(); + return JSValue(); + } + return getJSValue(m_messagePorts[index].get()); + } #if ENABLE(WEBASSEMBLY) case WasmModuleTag: { if (m_version >= 12) { @@ -4554,7 +4557,7 @@ private: JSValue cryptoKey; // Vector<RefPtr<MessagePort>> dummyMessagePorts; // CloneDeserializer rawKeyDeserializer(m_lexicalGlobalObject, m_globalObject, dummyMessagePorts, nullptr, {}, serializedKey); - CloneDeserializer rawKeyDeserializer(m_lexicalGlobalObject, m_globalObject, nullptr, serializedKey); + CloneDeserializer rawKeyDeserializer(m_lexicalGlobalObject, m_globalObject, {}, nullptr, serializedKey); if (!rawKeyDeserializer.readCryptoKey(cryptoKey)) { fail(); return JSValue(); @@ -4625,7 +4628,7 @@ private: const uint8_t* const m_end; unsigned m_version; Vector<CachedString> m_constantPool; - // const Vector<RefPtr<MessagePort>>& m_messagePorts; + const Vector<RefPtr<MessagePort>>& m_messagePorts; ArrayBufferContentsArray* m_arrayBufferContents; Vector<RefPtr<JSC::ArrayBuffer>> m_arrayBuffers; Vector<String> m_blobURLs; @@ -5117,9 +5120,9 @@ static bool canDetachRTCDataChannels(const Vector<Ref<RTCDataChannel>>& channels RefPtr<SerializedScriptValue> SerializedScriptValue::create(JSC::JSGlobalObject& globalObject, JSC::JSValue value, SerializationForStorage forStorage, SerializationErrorMode throwExceptions, SerializationContext serializationContext) { - // Vector<RefPtr<MessagePort>> dummyPorts; - // auto result = create(globalObject, value, {}, dummyPorts, forStorage, throwExceptions, serializationContext); - auto result = create(globalObject, value, {}, forStorage, throwExceptions, serializationContext); + Vector<RefPtr<MessagePort>> dummyPorts; + auto result = create(globalObject, value, {}, dummyPorts, forStorage, throwExceptions, serializationContext); + // auto result = create(globalObject, value, {}, forStorage, throwExceptions, serializationContext); if (result.hasException()) return nullptr; return result.releaseReturnValue(); @@ -5130,13 +5133,13 @@ RefPtr<SerializedScriptValue> SerializedScriptValue::create(JSC::JSGlobalObject& // return create(globalObject, value, WTFMove(transferList), messagePorts, forStorage, SerializationErrorMode::NonThrowing, serializationContext); // } -ExceptionOr<Ref<SerializedScriptValue>> SerializedScriptValue::create(JSGlobalObject& globalObject, JSValue value, Vector<JSC::Strong<JSC::JSObject>>&& transferList, SerializationForStorage forStorage, SerializationContext serializationContext) +ExceptionOr<Ref<SerializedScriptValue>> SerializedScriptValue::create(JSGlobalObject& globalObject, JSValue value, Vector<JSC::Strong<JSC::JSObject>>&& transferList, Vector<RefPtr<MessagePort>>& messagePorts, SerializationForStorage forStorage, SerializationContext serializationContext) { - return create(globalObject, value, WTFMove(transferList), forStorage, SerializationErrorMode::Throwing, serializationContext); + return create(globalObject, value, WTFMove(transferList), messagePorts, forStorage, SerializationErrorMode::Throwing, serializationContext); } -// ExceptionOr<Ref<SerializedScriptValue>> SerializedScriptValue::create(JSGlobalObject& lexicalGlobalObject, JSValue value, Vector<JSC::Strong<JSC::JSObject>>&& transferList, Vector<RefPtr<MessagePort>>& messagePorts, SerializationForStorage forStorage, SerializationErrorMode throwExceptions, SerializationContext context) -ExceptionOr<Ref<SerializedScriptValue>> SerializedScriptValue::create(JSGlobalObject& lexicalGlobalObject, JSValue value, Vector<JSC::Strong<JSC::JSObject>>&& transferList, SerializationForStorage forStorage, SerializationErrorMode throwExceptions, SerializationContext context) +// ExceptionOr<Ref<SerializedScriptValue>> SerializedScriptValue::create(JSGlobalObject& lexicalGlobalObject, JSValue value, Vector<JSC::Strong<JSC::JSObject>>&& transferList, SerializationForStorage forStorage, SerializationErrorMode throwExceptions, SerializationContext context) +ExceptionOr<Ref<SerializedScriptValue>> SerializedScriptValue::create(JSGlobalObject& lexicalGlobalObject, JSValue value, Vector<JSC::Strong<JSC::JSObject>>&& transferList, Vector<RefPtr<MessagePort>>& messagePorts, SerializationForStorage forStorage, SerializationErrorMode throwExceptions, SerializationContext context) { VM& vm = lexicalGlobalObject.vm(); Vector<RefPtr<JSC::ArrayBuffer>> arrayBuffers; @@ -5166,12 +5169,12 @@ ExceptionOr<Ref<SerializedScriptValue>> SerializedScriptValue::create(JSGlobalOb arrayBuffers.append(WTFMove(arrayBuffer)); continue; } - // if (auto port = JSMessagePort::toWrapped(vm, transferable.get())) { - // if (port->isDetached()) - // return Exception { DataCloneError, "MessagePort is detached"_s }; - // messagePorts.append(WTFMove(port)); - // continue; - // } + if (auto port = JSMessagePort::toWrapped(vm, transferable.get())) { + if (port->isDetached()) + return Exception { DataCloneError, "MessagePort is detached"_s }; + messagePorts.append(WTFMove(port)); + continue; + } // if (auto imageBitmap = JSImageBitmap::toWrapped(vm, transferable.get())) { // if (imageBitmap->isDetached()) @@ -5247,7 +5250,7 @@ ExceptionOr<Ref<SerializedScriptValue>> SerializedScriptValue::create(JSGlobalOb // #endif // blobHandles, buffer, context, *sharedBuffers, forStorage); - auto code = CloneSerializer::serialize(&lexicalGlobalObject, value, arrayBuffers, + auto code = CloneSerializer::serialize(&lexicalGlobalObject, value, messagePorts, arrayBuffers, #if ENABLE(OFFSCREEN_CANVAS_IN_WORKERS) offscreenCanvases, #endif @@ -5408,7 +5411,7 @@ JSC::JSValue SerializedScriptValue::fromArrayBuffer(JSC::JSGlobalObject& domGlob auto size = std::min(arrayBuffer->byteLength(), maxByteLength); auto span = std::span<uint8_t> { data, size }; - auto result = CloneDeserializer::deserialize(&domGlobal, globalObject, nullptr, span, blobURLs, blobFiles, nullptr + auto result = CloneDeserializer::deserialize(&domGlobal, globalObject, {}, nullptr, span, blobURLs, blobFiles, nullptr #if ENABLE(WEBASSEMBLY) , nullptr, nullptr @@ -5437,71 +5440,24 @@ JSC::JSValue SerializedScriptValue::fromArrayBuffer(JSC::JSGlobalObject& domGlob // return deserialize(lexicalGlobalObject, globalObject, {}, throwExceptions, didFail); // } -// JSValue SerializedScriptValue::deserialize(JSGlobalObject& lexicalGlobalObject, JSGlobalObject* globalObject, const Vector<RefPtr<MessagePort>>& messagePorts, SerializationErrorMode throwExceptions, bool* didFail) -// { -// Vector<String> dummyBlobs; -// Vector<String> dummyPaths; -// return deserialize(lexicalGlobalObject, globalObject, messagePorts, dummyBlobs, dummyPaths, throwExceptions, didFail); -// } +JSValue SerializedScriptValue::deserialize(JSGlobalObject& lexicalGlobalObject, JSGlobalObject* globalObject, const Vector<RefPtr<MessagePort>>& messagePorts, SerializationErrorMode throwExceptions, bool* didFail) +{ + Vector<String> dummyBlobs; + Vector<String> dummyPaths; + return deserialize(lexicalGlobalObject, globalObject, messagePorts, dummyBlobs, dummyPaths, throwExceptions, didFail); +} JSValue SerializedScriptValue::deserialize(JSGlobalObject& lexicalGlobalObject, JSGlobalObject* globalObject, SerializationErrorMode throwExceptions, bool* didFail) { Vector<String> dummyBlobs; Vector<String> dummyPaths; - return deserialize(lexicalGlobalObject, globalObject, dummyBlobs, dummyPaths, throwExceptions, didFail); + Vector<RefPtr<MessagePort>> dummyPorts; + return deserialize(lexicalGlobalObject, globalObject, dummyPorts, dummyBlobs, dummyPaths, throwExceptions, didFail); } -// JSValue SerializedScriptValue::deserialize(JSGlobalObject& lexicalGlobalObject, JSGlobalObject* globalObject, const Vector<RefPtr<MessagePort>>& messagePorts, const Vector<String>& blobURLs, const Vector<String>& blobFilePaths, SerializationErrorMode throwExceptions, bool* didFail) -// { -// DeserializationResult result = CloneDeserializer::deserialize(&lexicalGlobalObject, globalObject, messagePorts, WTFMove(m_backingStores) -// #if ENABLE(OFFSCREEN_CANVAS_IN_WORKERS) -// , -// WTFMove(m_detachedOffscreenCanvases) -// #endif -// #if ENABLE(WEB_RTC) -// , -// WTFMove(m_detachedRTCDataChannels) -// #endif -// , -// m_arrayBufferContentsArray.get(), m_data, blobURLs, blobFilePaths, m_sharedBufferContentsArray.get() -// #if ENABLE(WEBASSEMBLY) -// , -// m_wasmModulesArray.get(), m_wasmMemoryHandlesArray.get() -// #endif -// #if ENABLE(WEB_CODECS) -// , -// WTFMove(m_serializedVideoChunks), WTFMove(m_serializedVideoFrames) -// #endif -// ); -// if (didFail) -// *didFail = result.second != SerializationReturnCode::SuccessfullyCompleted; -// if (throwExceptions == SerializationErrorMode::Throwing) -// maybeThrowExceptionIfSerializationFailed(lexicalGlobalObject, result.second); -// return result.first ? result.first : jsNull(); -// } -JSValue SerializedScriptValue::deserialize(JSGlobalObject& lexicalGlobalObject, JSGlobalObject* globalObject, const Vector<String>& blobURLs, const Vector<String>& blobFilePaths, SerializationErrorMode throwExceptions, bool* didFail) +JSValue SerializedScriptValue::deserialize(JSGlobalObject& lexicalGlobalObject, JSGlobalObject* globalObject, const Vector<RefPtr<MessagePort>>& messagePorts, const Vector<String>& blobURLs, const Vector<String>& blobFilePaths, SerializationErrorMode throwExceptions, bool* didFail) { - // DeserializationResult result = CloneDeserializer::deserialize(&lexicalGlobalObject, globalObject, messagePorts, WTFMove(m_backingStores) - // #if ENABLE(OFFSCREEN_CANVAS_IN_WORKERS) - // , - // WTFMove(m_detachedOffscreenCanvases) - // #endif - // #if ENABLE(WEB_RTC) - // , - // WTFMove(m_detachedRTCDataChannels) - // #endif - // , - // m_arrayBufferContentsArray.get(), m_data, blobURLs, blobFilePaths, m_sharedBufferContentsArray.get() - // #if ENABLE(WEBASSEMBLY) - // , - // m_wasmModulesArray.get(), m_wasmMemoryHandlesArray.get() - // #endif - // #if ENABLE(WEB_CODECS) - // , - // WTFMove(m_serializedVideoChunks), WTFMove(m_serializedVideoFrames) - // #endif - // ); - DeserializationResult result = CloneDeserializer::deserialize(&lexicalGlobalObject, globalObject + DeserializationResult result = CloneDeserializer::deserialize(&lexicalGlobalObject, globalObject, messagePorts #if ENABLE(OFFSCREEN_CANVAS_IN_WORKERS) , WTFMove(m_detachedOffscreenCanvases) @@ -5527,6 +5483,54 @@ JSValue SerializedScriptValue::deserialize(JSGlobalObject& lexicalGlobalObject, maybeThrowExceptionIfSerializationFailed(lexicalGlobalObject, result.second); return result.first ? result.first : jsNull(); } +// JSValue SerializedScriptValue::deserialize(JSGlobalObject& lexicalGlobalObject, JSGlobalObject* globalObject, const Vector<String>& blobURLs, const Vector<String>& blobFilePaths, SerializationErrorMode throwExceptions, bool* didFail) +// { +// // DeserializationResult result = CloneDeserializer::deserialize(&lexicalGlobalObject, globalObject, messagePorts, WTFMove(m_backingStores) +// // #if ENABLE(OFFSCREEN_CANVAS_IN_WORKERS) +// // , +// // WTFMove(m_detachedOffscreenCanvases) +// // #endif +// // #if ENABLE(WEB_RTC) +// // , +// // WTFMove(m_detachedRTCDataChannels) +// // #endif +// // , +// // m_arrayBufferContentsArray.get(), m_data, blobURLs, blobFilePaths, m_sharedBufferContentsArray.get() +// // #if ENABLE(WEBASSEMBLY) +// // , +// // m_wasmModulesArray.get(), m_wasmMemoryHandlesArray.get() +// // #endif +// // #if ENABLE(WEB_CODECS) +// // , +// // WTFMove(m_serializedVideoChunks), WTFMove(m_serializedVideoFrames) +// // #endif +// // ); +// DeserializationResult result = CloneDeserializer::deserialize(&lexicalGlobalObject, globalObject +// #if ENABLE(OFFSCREEN_CANVAS_IN_WORKERS) +// , +// WTFMove(m_detachedOffscreenCanvases) +// #endif +// #if ENABLE(WEB_RTC) +// , +// WTFMove(m_detachedRTCDataChannels) +// #endif +// , +// m_arrayBufferContentsArray.get(), m_data, blobURLs, blobFilePaths, m_sharedBufferContentsArray.get() +// #if ENABLE(WEBASSEMBLY) +// , +// m_wasmModulesArray.get(), m_wasmMemoryHandlesArray.get() +// #endif +// #if ENABLE(WEB_CODECS) +// , +// WTFMove(m_serializedVideoChunks), WTFMove(m_serializedVideoFrames) +// #endif +// ); +// if (didFail) +// *didFail = result.second != SerializationReturnCode::SuccessfullyCompleted; +// if (throwExceptions == SerializationErrorMode::Throwing) +// maybeThrowExceptionIfSerializationFailed(lexicalGlobalObject, result.second); +// return result.first ? result.first : jsNull(); +// } JSValueRef SerializedScriptValue::deserialize(JSContextRef destinationContext, JSValueRef* exception) { diff --git a/src/bun.js/bindings/webcore/SerializedScriptValue.h b/src/bun.js/bindings/webcore/SerializedScriptValue.h index 189945d52..5d5e93854 100644 --- a/src/bun.js/bindings/webcore/SerializedScriptValue.h +++ b/src/bun.js/bindings/webcore/SerializedScriptValue.h @@ -60,7 +60,7 @@ namespace WebCore { class DetachedOffscreenCanvas; #endif // class IDBValue; -// class MessagePort; +class MessagePort; // class ImageBitmapBacking; class CloneSerializer; class FragmentedSharedBuffer; @@ -87,8 +87,8 @@ class SerializedScriptValue : public ThreadSafeRefCounted<SerializedScriptValue> public: static void writeBytesForBun(CloneSerializer*, const uint8_t*, uint32_t); - // WEBCORE_EXPORT static ExceptionOr<Ref<SerializedScriptValue>> create(JSC::JSGlobalObject&, JSC::JSValue, Vector<JSC::Strong<JSC::JSObject>>&& transfer, Vector<RefPtr<MessagePort>>&, SerializationForStorage = SerializationForStorage::No, SerializationContext = SerializationContext::Default); - WEBCORE_EXPORT static ExceptionOr<Ref<SerializedScriptValue>> create(JSC::JSGlobalObject&, JSC::JSValue, Vector<JSC::Strong<JSC::JSObject>>&& transfer, SerializationForStorage = SerializationForStorage::No, SerializationContext = SerializationContext::Default); + WEBCORE_EXPORT static ExceptionOr<Ref<SerializedScriptValue>> create(JSC::JSGlobalObject&, JSC::JSValue, Vector<JSC::Strong<JSC::JSObject>>&& transfer, Vector<RefPtr<MessagePort>>&, SerializationForStorage = SerializationForStorage::No, SerializationContext = SerializationContext::Default); + // WEBCORE_EXPORT static ExceptionOr<Ref<SerializedScriptValue>> create(JSC::JSGlobalObject&, JSC::JSValue, Vector<JSC::Strong<JSC::JSObject>>&& transfer, SerializationForStorage = SerializationForStorage::No, SerializationContext = SerializationContext::Default); WEBCORE_EXPORT static RefPtr<SerializedScriptValue> create(JSC::JSGlobalObject&, JSC::JSValue, SerializationForStorage = SerializationForStorage::No, SerializationErrorMode = SerializationErrorMode::Throwing, SerializationContext = SerializationContext::Default); @@ -99,10 +99,10 @@ public: static Ref<SerializedScriptValue> nullValue(); WEBCORE_EXPORT JSC::JSValue deserialize(JSC::JSGlobalObject&, JSC::JSGlobalObject*, SerializationErrorMode = SerializationErrorMode::Throwing, bool* didFail = nullptr); - // WEBCORE_EXPORT JSC::JSValue deserialize(JSC::JSGlobalObject&, JSC::JSGlobalObject*, const Vector<RefPtr<MessagePort>>&, SerializationErrorMode = SerializationErrorMode::Throwing, bool* didFail = nullptr); + WEBCORE_EXPORT JSC::JSValue deserialize(JSC::JSGlobalObject&, JSC::JSGlobalObject*, const Vector<RefPtr<MessagePort>>&, SerializationErrorMode = SerializationErrorMode::Throwing, bool* didFail = nullptr); - // JSC::JSValue deserialize(JSC::JSGlobalObject&, JSC::JSGlobalObject*, const Vector<RefPtr<MessagePort>>&, const Vector<String>& blobURLs, const Vector<String>& blobFilePaths, SerializationErrorMode = SerializationErrorMode::Throwing, bool* didFail = nullptr); - JSC::JSValue deserialize(JSC::JSGlobalObject&, JSC::JSGlobalObject*, const Vector<String>& blobURLs, const Vector<String>& blobFilePaths, SerializationErrorMode = SerializationErrorMode::Throwing, bool* didFail = nullptr); + JSC::JSValue deserialize(JSC::JSGlobalObject&, JSC::JSGlobalObject*, const Vector<RefPtr<MessagePort>>&, const Vector<String>& blobURLs, const Vector<String>& blobFilePaths, SerializationErrorMode = SerializationErrorMode::Throwing, bool* didFail = nullptr); + // JSC::JSValue deserialize(JSC::JSGlobalObject&, JSC::JSGlobalObject*, const Vector<String>& blobURLs, const Vector<String>& blobFilePaths, SerializationErrorMode = SerializationErrorMode::Throwing, bool* didFail = nullptr); static uint32_t wireFormatVersion(); @@ -146,7 +146,7 @@ private: // Vector<RefPtr<WebCodecsEncodedVideoChunkStorage>>&& = {}, Vector<WebCodecsVideoFrameData>&& = {} // #endif // ); - static ExceptionOr<Ref<SerializedScriptValue>> create(JSC::JSGlobalObject&, JSC::JSValue, Vector<JSC::Strong<JSC::JSObject>>&& transfer, SerializationForStorage, SerializationErrorMode, SerializationContext); + static ExceptionOr<Ref<SerializedScriptValue>> create(JSC::JSGlobalObject&, JSC::JSValue, Vector<JSC::Strong<JSC::JSObject>>&& transfer, Vector<RefPtr<MessagePort>>&, SerializationForStorage, SerializationErrorMode, SerializationContext); WEBCORE_EXPORT SerializedScriptValue(Vector<unsigned char>&&, std::unique_ptr<ArrayBufferContentsArray>&& = nullptr #if ENABLE(WEB_RTC) , diff --git a/src/bun.js/bindings/webcore/TransferredMessagePort.h b/src/bun.js/bindings/webcore/TransferredMessagePort.h new file mode 100644 index 000000000..19aa63746 --- /dev/null +++ b/src/bun.js/bindings/webcore/TransferredMessagePort.h @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2018-2022 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#pragma once + +#include "MessagePortIdentifier.h" + +namespace WebCore { + +// When a message port is transferred, it is represented by a pair of identifiers. +// The first identifier is the port being transferred and the second is its remote port. +using TransferredMessagePort = std::pair<MessagePortIdentifier, MessagePortIdentifier>; + +} // namespace WebCore diff --git a/src/bun.js/bindings/webcore/Worker.cpp b/src/bun.js/bindings/webcore/Worker.cpp index dfbc0e007..d9bd6f9b4 100644 --- a/src/bun.js/bindings/webcore/Worker.cpp +++ b/src/bun.js/bindings/webcore/Worker.cpp @@ -185,25 +185,25 @@ ExceptionOr<void> Worker::postMessage(JSC::JSGlobalObject& state, JSC::JSValue m if (m_wasTerminated) return Exception { InvalidStateError, "Worker has been terminated"_s }; - auto message = SerializedScriptValue::create(state, messageValue, WTFMove(options.transfer), SerializationForStorage::No, SerializationContext::WorkerPostMessage); - if (message.hasException()) - return message.releaseException(); + Vector<RefPtr<MessagePort>> ports; + auto serialized = SerializedScriptValue::create(state, messageValue, WTFMove(options.transfer), ports); + if (serialized.hasException()) + return serialized.releaseException(); + + ExceptionOr<Vector<TransferredMessagePort>> disentangledPorts = MessagePort::disentanglePorts(WTFMove(ports)); + if (disentangledPorts.hasException()) { + return disentangledPorts.releaseException(); + } - RefPtr<SerializedScriptValue> result = message.releaseReturnValue(); + MessageWithMessagePorts messageWithMessagePorts { serialized.releaseReturnValue(), disentangledPorts.releaseReturnValue() }; - this->postTaskToWorkerGlobalScope([message = WTFMove(result)](auto& context) { + this->postTaskToWorkerGlobalScope([message = messageWithMessagePorts](auto& context) mutable { Zig::GlobalObject* globalObject = jsCast<Zig::GlobalObject*>(context.jsGlobalObject()); - bool didFail = false; - JSValue value = message->deserialize(*globalObject, globalObject, SerializationErrorMode::NonThrowing, &didFail); - if (didFail) { - globalObject->globalEventScope.dispatchEvent(MessageEvent::create(eventNames().messageerrorEvent, MessageEvent::Init {}, MessageEvent::IsTrusted::Yes)); - return; - } + auto ports = MessagePort::entanglePorts(context, WTFMove(message.transferredPorts)); + auto event = MessageEvent::create(*globalObject, message.message.releaseNonNull(), std::nullopt, WTFMove(ports)); - WebCore::MessageEvent::Init init; - init.data = value; - globalObject->globalEventScope.dispatchEvent(MessageEvent::create(eventNames().messageEvent, WTFMove(init), MessageEvent::IsTrusted::Yes)); + globalObject->globalEventScope.dispatchEvent(event.event); }); return {}; } diff --git a/src/bun.js/bindings/webcore/Worker.h b/src/bun.js/bindings/webcore/Worker.h index b0b8e0a2b..76607d8da 100644 --- a/src/bun.js/bindings/webcore/Worker.h +++ b/src/bun.js/bindings/webcore/Worker.h @@ -135,7 +135,6 @@ private: bool m_isClosing { false }; const ScriptExecutionContextIdentifier m_clientIdentifier; void* impl_ { nullptr }; - size_t m_pendingActivityCount { 0 }; }; } // namespace WebCore diff --git a/src/bun.js/scripts/class-definitions.ts b/src/bun.js/scripts/class-definitions.ts index 281cd62c5..d9c327f6b 100644 --- a/src/bun.js/scripts/class-definitions.ts +++ b/src/bun.js/scripts/class-definitions.ts @@ -41,7 +41,7 @@ export interface ClassDefinition { configurable?: boolean; enumerable?: boolean; - structuredClone?: boolean | { transferrable: boolean; tag: number }; + structuredClone?: boolean | { transferable: boolean; tag: number }; } export interface CustomField { diff --git a/src/bun.js/scripts/generate-classes.ts b/src/bun.js/scripts/generate-classes.ts index fb4419642..ec0021637 100644 --- a/src/bun.js/scripts/generate-classes.ts +++ b/src/bun.js/scripts/generate-classes.ts @@ -1249,7 +1249,7 @@ function generateZig( if (structuredClone) { exports.set("onStructuredCloneSerialize", symbolName(typeName, "onStructuredCloneSerialize")); - if (structuredClone === "transferrable") { + if (structuredClone === "transferable") { exports.set("onStructuredCloneTransfer", symbolName(typeName, "onStructuredCloneTransfer")); } @@ -1306,8 +1306,8 @@ function generateZig( } `; - if (structuredClone === "transferrable") { - exports.set("structuredClone", symbolName(typeName, "onTransferrableStructuredClone")); + if (structuredClone === "transferable") { + exports.set("structuredClone", symbolName(typeName, "onTransferableStructuredClone")); output += ` if (@TypeOf(${typeName}.onStructuredCloneTransfer) != (fn(*${typeName}, globalThis: *JSC.JSGlobalObject, ctx: *anyopaque, write: *const fn(*anyopaque, ptr: [*]const u8, len: usize) callconv(.C) void) callconv(.C) void)) { @compileLog("${typeName}.onStructuredCloneTransfer is not a structured clone transfer function"); diff --git a/src/bun.js/webcore/response.classes.ts b/src/bun.js/webcore/response.classes.ts index f0d6e1451..abd29c654 100644 --- a/src/bun.js/webcore/response.classes.ts +++ b/src/bun.js/webcore/response.classes.ts @@ -125,7 +125,7 @@ export default [ JSType: "0b11101110", klass: {}, configurable: false, - structuredClone: { transferrable: false, tag: 254 }, + structuredClone: { transferable: false, tag: 254 }, proto: { text: { fn: "getText" }, json: { fn: "getJSON" }, diff --git a/test/js/web/workers/create-port-worker.js b/test/js/web/workers/create-port-worker.js new file mode 100644 index 000000000..6e8709de0 --- /dev/null +++ b/test/js/web/workers/create-port-worker.js @@ -0,0 +1,6 @@ +var channel = new MessageChannel(); +channel.port1.onmessage = e => { + channel.port1.postMessage("done!"); +}; + +postMessage(channel.port2, { transfer: [channel.port2] }); diff --git a/test/js/web/workers/message-channel.test.ts b/test/js/web/workers/message-channel.test.ts new file mode 100644 index 000000000..7d3e3cc25 --- /dev/null +++ b/test/js/web/workers/message-channel.test.ts @@ -0,0 +1,254 @@ +test("simple usage", done => { + var channel = new MessageChannel(); + var port1 = channel.port1; + var port2 = channel.port2; + + port2.onmessage = function (e) { + expect(e.data).toEqual("hello"); + done(); + }; + + port1.postMessage("hello"); +}); + +test("transfer message port", done => { + var channel = new MessageChannel(); + var anotherChannel = new MessageChannel(); + var port1 = channel.port1; + var port2 = channel.port2; + + port2.onmessage = function (e) { + expect(e.data).toEqual("hello"); + expect(e.ports).toHaveLength(1); + expect(e.ports[0]).toBeInstanceOf(MessagePort); + done(); + }; + + port1.postMessage("hello", [anotherChannel.port2]); +}); + +test("tranfer array buffer", done => { + var channel = new MessageChannel(); + var port1 = channel.port1; + var port2 = channel.port2; + + port2.onmessage = function (e) { + expect(e.data).toBeInstanceOf(ArrayBuffer); + expect(e.data.byteLength).toEqual(8); + done(); + }; + + const buffer = new ArrayBuffer(8); + + port1.postMessage(buffer, [buffer]); +}); + +test("non-transferable", () => { + var channel = new MessageChannel(); + channel.port2.onmessage = function (e) { + // not reached + expect(1).toBe(2); + }; + expect(() => { + channel.port1.postMessage("hello", [channel.port1]); + }).toThrow(); + expect(() => { + channel.port1.postMessage("hello", [channel.port2]); + }).toThrow(); +}); + +test("transfer message ports and post messages", done => { + var c1 = new MessageChannel(); + var c2 = new MessageChannel(); + + c1.port1.onmessage = e => { + var port = e.ports[0]; + expect(port).toBeInstanceOf(MessagePort); + expect(e.data).toEqual("hello from channel 1 port 2"); + port.onmessage = e => { + expect(e.data).toEqual("hello from channel 1 port 2"); + done(); + }; + port.postMessage("hello from channel 1 port 1", [c1.port1]); + }; + + c1.port2.onmessage = e => { + var port = e.ports[0]; + expect(port).toBeInstanceOf(MessagePort); + expect(e.data).toEqual("hello from channel 2 port 1"); + port.postMessage("hello from channel 1 port 2"); + }; + + c2.port1.onmessage = e => { + var port = e.ports[0]; + expect(port).toBeInstanceOf(MessagePort); + expect(e.data).toEqual("hello from channel 1 port 1"); + port.postMessage("hello from channel 2 port 1", [c2.port1]); + }; + + c2.port2.onmessage = e => { + // should not be reached. onmessage defined in c1.port1 should be called instead + expect(1).toBe(2); + }; + + c1.port2.postMessage("hello from channel 1 port 2", [c2.port2]); +}); + +test("message channel created on main thread", done => { + var worker = new Worker(new URL("receive-port-worker.js", import.meta.url).href); + worker.onerror = e => { + expect(1).toBe(2); + done(); + }; + var channel = new MessageChannel(); + channel.port1.onmessage = e => { + if (e.data === "done!") return done(); + expect(e.data).toEqual("received port!"); + channel.port1.postMessage("more message!"); + }; + worker.postMessage(channel.port2, { transfer: [channel.port2] }); +}); + +test("message channel created on other thread", done => { + var worker = new Worker(new URL("create-port-worker.js", import.meta.url).href); + worker.onerror = e => { + expect(1).toBe(2); + done(); + }; + worker.onmessage = e => { + expect(e.data).toBeInstanceOf(MessagePort); + var port = e.data; + port.onmessage = e => { + expect(e.data).toEqual("done!"); + done(); + }; + port.postMessage("hello from main thread"); + }; +}); + +test("many message channels", done => { + var channel = new MessageChannel(); + var channel2 = new MessageChannel(); + var channel3 = new MessageChannel(); + var channel4 = new MessageChannel(); + + channel.port1.postMessage("noport"); + channel.port1.postMessage("zero ports", []); + channel.port1.postMessage("two ports", [channel2.port1, channel2.port2]); + + // Now test various failure cases + expect(() => { + channel.port1.postMessage("same port", [channel.port1]); + }).toThrow(); + expect(() => { + channel.port1.postMessage("entangled port", [channel.port2]); + }).toThrow(); + expect(() => { + channel.port1.postMessage("null port", [channel3.port1, null, channel3.port2]); + }).toThrow(); + expect(() => { + channel.port1.postMessage("notAPort", [channel3.port1, {}, channel3.port2]); + }).toThrow(); + expect(() => { + channel.port1.postMessage("duplicate port", [channel3.port1, channel3.port1]); + }).toThrow(); + + // Should be OK to send channel3.port1 (should not have been disentangled by the previous failed calls). + expect(() => { + channel.port1.postMessage("entangled ports", [channel3.port1, channel3.port2]); + }).not.toThrow(); + + expect(() => { + channel.port1.postMessage("notAnArray", "foo"); + }).toThrow(); + expect(() => { + channel.port1.postMessage("notASequence", [{ length: 3 }]); + }).toThrow(); + + // Should not crash (we should figure out that the array contains undefined + // entries). + var largePortArray = []; + largePortArray[1234567890] = channel4.port1; + expect(() => { + channel.port1.postMessage("largeSequence", largePortArray); + }).toThrow(); + + channel.port1.postMessage("done"); + + function testTransfers(done) { + var channel0 = new MessageChannel(); + + var c1 = new MessageChannel(); + channel0.port1.postMessage({ id: "send-port", port: c1.port1 }, [c1.port1]); + var c2 = new MessageChannel(); + channel0.port1.postMessage({ id: "send-port-twice", port0: c2.port1, port1: c2.port1 }, [c2.port1]); + var c3 = new MessageChannel(); + channel0.port1.postMessage({ id: "send-two-ports", port0: c3.port1, port1: c3.port2 }, [c3.port1, c3.port2]); + var c4 = new MessageChannel(); + + // Sending host objects should throw + expect(() => { + channel0.port1.postMessage({ id: "host-object", hostObject: c3, port: c4.port1 }, [c4.port1]); + }).toThrow(); + + // Sending Function object should throw + expect(() => { + var f1 = function () {}; + channel0.port1.postMessage({ id: "function-object", function: f1, port: c4.port1 }, [c4.port1]); + }).toThrow(); + + // Sending Error object should not throw + // expect(() => { + // var err = new Error(); + // channel0.port1.postMessage({ id: "error-object", error: err, port: c4.port1 }, [c4.port1]); + // }).not.toThrow(); + + c4.port1.postMessage("Should succeed"); + channel0.port1.postMessage({ id: "done" }); + + channel0.port2.onmessage = function (event) { + if (event.data.id == "send-port") { + expect(event.ports.length).toBeGreaterThan(0); + expect(event.ports[0]).toBe(event.data.port); + } else if (event.data.id == "error-object") { + expect(event.data.error).toBeInstanceOf(Error); + } else if (event.data.id == "send-port-twice") { + expect(event.ports).toBeDefined(); + expect(event.ports.length).toBe(1); + expect(event.ports[0]).toBe(event.data.port0); + expect(event.ports[0]).toBe(event.data.port1); + } else if (event.data.id == "send-two-ports") { + expect(event.ports).toBeDefined(); + expect(event.ports.length).toBe(2); + expect(event.ports[0]).toBe(event.data.port0); + expect(event.ports[1]).toBe(event.data.port1); + } else if (event.data.id == "done") { + done(); + } else { + // should not be reached + expect(1).toBe(2); + } + }; + } + + channel.port2.onmessage = function (event) { + if (event.data == "noport") { + expect(event.ports).toBeDefined(); + expect(event.ports.length).toBe(0); + } else if (event.data == "zero ports") { + expect(event.ports).toBeDefined(); + expect(event.ports.length).toBe(0); + } else if (event.data == "two ports") { + expect(event.ports).toBeDefined(); + expect(event.ports.length).toBe(2); + } else if (event.data == "entangled ports") { + expect(event.ports).toBeDefined(); + expect(event.ports.length).toBe(2); + } else if (event.data == "done") { + testTransfers(done); + } else { + // should not be reached + expect(1).toBe(2); + } + }; +}); diff --git a/test/js/web/workers/receive-port-worker.js b/test/js/web/workers/receive-port-worker.js new file mode 100644 index 000000000..871fc4405 --- /dev/null +++ b/test/js/web/workers/receive-port-worker.js @@ -0,0 +1,9 @@ +onmessage = e => { + if (e.data instanceof MessagePort) { + var port = e.data; + port.onmessage = e => { + port.postMessage("done!"); + }; + port.postMessage("received port!"); + } +}; diff --git a/test/js/web/workers/structured-clone.test.ts b/test/js/web/workers/structured-clone.test.ts index 61a0c1320..bdc7fcfea 100644 --- a/test/js/web/workers/structured-clone.test.ts +++ b/test/js/web/workers/structured-clone.test.ts @@ -183,7 +183,7 @@ describe("structured clone", () => { }); }); - describe("transferrables", () => { + describe("transferables", () => { test("ArrayBuffer", () => { const buffer = Uint8Array.from([1]).buffer; const cloned = structuredClone(buffer, { transfer: [buffer] }); |