diff --git a/Userland/Libraries/LibWeb/Bindings/Transferable.h b/Userland/Libraries/LibWeb/Bindings/Transferable.h index 4873e9cb49..08ad1a8e65 100644 --- a/Userland/Libraries/LibWeb/Bindings/Transferable.h +++ b/Userland/Libraries/LibWeb/Bindings/Transferable.h @@ -22,7 +22,7 @@ public: virtual WebIDL::ExceptionOr transfer_steps(HTML::TransferDataHolder&) = 0; // NOTE: It is an error to call Base::transfer_receiving_steps in your impl - virtual WebIDL::ExceptionOr transfer_receiving_steps(HTML::TransferDataHolder const&) = 0; + virtual WebIDL::ExceptionOr transfer_receiving_steps(HTML::TransferDataHolder&) = 0; virtual HTML::TransferType primary_interface() const = 0; diff --git a/Userland/Libraries/LibWeb/HTML/MessagePort.cpp b/Userland/Libraries/LibWeb/HTML/MessagePort.cpp index ed9d6f4b11..2a0c9d7bfb 100644 --- a/Userland/Libraries/LibWeb/HTML/MessagePort.cpp +++ b/Userland/Libraries/LibWeb/HTML/MessagePort.cpp @@ -4,16 +4,24 @@ * SPDX-License-Identifier: BSD-2-Clause */ +#include +#include +#include +#include #include +#include #include #include #include #include #include #include +#include namespace Web::HTML { +constexpr u8 IPC_FILE_TAG = 0xA5; + JS_DEFINE_ALLOCATOR(MessagePort); JS::NonnullGCPtr MessagePort::create(JS::Realm& realm) @@ -41,7 +49,7 @@ void MessagePort::visit_edges(Cell::Visitor& visitor) } // https://html.spec.whatwg.org/multipage/web-messaging.html#message-ports:transfer-steps -WebIDL::ExceptionOr MessagePort::transfer_steps(HTML::TransferDataHolder&) +WebIDL::ExceptionOr MessagePort::transfer_steps(HTML::TransferDataHolder& data_holder) { // 1. Set value's has been shipped flag to true. m_has_been_shipped = true; @@ -55,18 +63,21 @@ WebIDL::ExceptionOr MessagePort::transfer_steps(HTML::TransferDataHolder&) m_remote_port->m_has_been_shipped = true; // 2. Set dataHolder.[[RemotePort]] to remotePort. - // FIXME: Append an IPC::File to the dataHolder + auto fd = MUST(m_socket->release_fd()); + m_socket = nullptr; + data_holder.fds.append(fd); + data_holder.data.append(IPC_FILE_TAG); } // 4. Otherwise, set dataHolder.[[RemotePort]] to null. else { - // FIXME: Note in the dataHolder that there are no fds + data_holder.data.append(0); } return {}; } -WebIDL::ExceptionOr MessagePort::transfer_receiving_steps(HTML::TransferDataHolder const&) +WebIDL::ExceptionOr MessagePort::transfer_receiving_steps(HTML::TransferDataHolder& data_holder) { // 1. Set value's has been shipped flag to true. m_has_been_shipped = true; @@ -75,8 +86,16 @@ WebIDL::ExceptionOr MessagePort::transfer_receiving_steps(HTML::TransferDa // if any, leaving value's port message queue in its initial disabled state, and, if value's relevant global object is a Window, // associating the moved tasks with value's relevant global object's associated Document. - // FIXME: 3. If dataHolder.[[RemotePort]] is not null, then entangle dataHolder.[[RemotePort]] and value. + // 3. If dataHolder.[[RemotePort]] is not null, then entangle dataHolder.[[RemotePort]] and value. // (This will disentangle dataHolder.[[RemotePort]] from the original port that was transferred.) + auto fd_tag = data_holder.data.take_first(); + if (fd_tag == IPC_FILE_TAG) { + auto fd = data_holder.fds.take_first(); + m_socket = MUST(Core::LocalSocket::adopt_fd(fd.take_fd())); + } else if (fd_tag != 0) { + dbgln("Unexpected byte {:x} in MessagePort transfer data", fd_tag); + VERIFY_NOT_REACHED(); + } return {}; } @@ -85,6 +104,8 @@ void MessagePort::disentangle() { m_remote_port->m_remote_port = nullptr; m_remote_port = nullptr; + + m_socket = nullptr; } // https://html.spec.whatwg.org/multipage/web-messaging.html#entangle @@ -103,50 +124,129 @@ void MessagePort::entangle_with(MessagePort& remote_port) // (There is no MessageChannel object that represents this channel.) remote_port.m_remote_port = this; m_remote_port = &remote_port; + + int fds[2] = {}; + MUST(Core::System::socketpair(AF_LOCAL, SOCK_STREAM, 0, fds)); + auto socket0 = MUST(Core::LocalSocket::adopt_fd(fds[0])); + MUST(socket0->set_blocking(false)); + MUST(socket0->set_close_on_exec(true)); + auto socket1 = MUST(Core::LocalSocket::adopt_fd(fds[1])); + MUST(socket1->set_blocking(false)); + MUST(socket1->set_close_on_exec(true)); + + m_socket = move(socket0); + m_remote_port->m_socket = move(socket1); +} + +// https://html.spec.whatwg.org/multipage/web-messaging.html#dom-messageport-postmessage-options +WebIDL::ExceptionOr MessagePort::post_message(JS::Value message, Vector> const& transfer) +{ + // 1. Let targetPort be the port with which this MessagePort is entangled, if any; otherwise let it be null. + JS::GCPtr target_port = m_remote_port; + + // 2. Let options be «[ "transfer" → transfer ]». + auto options = StructuredSerializeOptions { transfer }; + + // 3. Run the message port post message steps providing this, targetPort, message and options. + return message_port_post_message_steps(target_port, message, options); } // https://html.spec.whatwg.org/multipage/web-messaging.html#dom-messageport-postmessage -void MessagePort::post_message(JS::Value message) +WebIDL::ExceptionOr MessagePort::post_message(JS::Value message, StructuredSerializeOptions const& options) { // 1. Let targetPort be the port with which this MessagePort is entangled, if any; otherwise let it be null. - auto* target_port = m_remote_port.ptr(); + JS::GCPtr target_port = m_remote_port; - // FIXME: 2. Let options be «[ "transfer" → transfer ]». + // 2. Run the message port post message steps providing targetPort, message and options. + return message_port_post_message_steps(target_port, message, options); +} - // 3. Run the message port post message steps providing targetPort, message and options. +// https://html.spec.whatwg.org/multipage/web-messaging.html#message-port-post-message-steps +WebIDL::ExceptionOr MessagePort::message_port_post_message_steps(JS::GCPtr target_port, JS::Value message, StructuredSerializeOptions const& options) +{ + auto& realm = this->realm(); + auto& vm = this->vm(); - // https://html.spec.whatwg.org/multipage/web-messaging.html#message-port-post-message-steps + // 1. Let transfer be options["transfer"]. + auto const& transfer = options.transfer; - // FIXME: 1. Let transfer be options["transfer"]. - - // FIXME: 2. If transfer contains this MessagePort, then throw a "DataCloneError" DOMException. + // 2. If transfer contains this MessagePort, then throw a "DataCloneError" DOMException. + for (auto const& handle : transfer) { + if (handle == this) + return WebIDL::DataCloneError::create(realm, "Cannot transfer a MessagePort to itself"_fly_string); + } // 3. Let doomed be false. bool doomed = false; - // FIXME: 4. If targetPort is not null and transfer contains targetPort, then set doomed to true and optionally report to a developer console that the target port was posted to itself, causing the communication channel to be lost. + // 4. If targetPort is not null and transfer contains targetPort, then set doomed to true and optionally report to a developer console that the target port was posted to itself, causing the communication channel to be lost. + if (target_port) { + for (auto const& handle : transfer) { + if (handle == target_port.ptr()) { + doomed = true; + dbgln("FIXME: Report to a developer console that the target port was posted to itself, causing the communication channel to be lost"); + } + } + } - // FIXME: 5. Let serializeWithTransferResult be StructuredSerializeWithTransfer(message, transfer). Rethrow any exceptions. + // 5. Let serializeWithTransferResult be StructuredSerializeWithTransfer(message, transfer). Rethrow any exceptions. + auto serialize_with_transfer_result = TRY(structured_serialize_with_transfer(vm, message, transfer)); // 6. If targetPort is null, or if doomed is true, then return. if (!target_port || doomed) - return; + return {}; // FIXME: 7. Add a task that runs the following steps to the port message queue of targetPort: + // FIXME: Implement this using the port message queue/unshipped port message queue concept + main_thread_event_loop().task_queue().add(HTML::Task::create(HTML::Task::Source::PostedMessage, nullptr, [target_port, serialize_with_transfer_result = move(serialize_with_transfer_result)]() mutable { + // FIXME: 1. Let finalTargetPort be the MessagePort in whose port message queue the task now finds itself. + // FIXME: NOTE: This can be different from targetPort, if targetPort itself was transferred and thus all its tasks moved along with it. + auto final_target_port = target_port; - // FIXME: This is an ad-hoc hack implementation instead, since we don't currently - // have serialization and deserialization of messages. - main_thread_event_loop().task_queue().add(HTML::Task::create(HTML::Task::Source::PostedMessage, nullptr, [target_port, message] { + // 2. Let targetRealm be finalTargetPort's relevant realm. + auto& target_realm = relevant_realm(*final_target_port); + auto& target_vm = target_realm.vm(); + + // 3. Let deserializeRecord be StructuredDeserializeWithTransfer(serializeWithTransferResult, targetRealm). + TemporaryExecutionContext context { relevant_settings_object(*final_target_port) }; + auto deserialize_record_or_error = structured_deserialize_with_transfer(target_vm, serialize_with_transfer_result); + if (deserialize_record_or_error.is_error()) { + // If this throws an exception, catch it, fire an event named messageerror at finalTargetPort, using MessageEvent, and then return. + auto exception = deserialize_record_or_error.release_error(); + MessageEventInit event_init {}; + final_target_port->dispatch_event(MessageEvent::create(target_realm, HTML::EventNames::messageerror, event_init)); + return; + } + auto deserialize_record = deserialize_record_or_error.release_value(); + + // 4. Let messageClone be deserializeRecord.[[Deserialized]]. + auto message_clone = deserialize_record.deserialized; + + // 5. Let newPorts be a new frozen array consisting of all MessagePort objects in deserializeRecord.[[TransferredValues]], if any, maintaining their relative order. + // FIXME: Use a FrozenArray + Vector> new_ports; + for (auto const& object : deserialize_record.transferred_values) { + if (is(*object)) { + new_ports.append(object); + } + } + + // 6. Fire an event named message at finalTargetPort, using MessageEvent, with the data attribute initialized to messageClone and the ports attribute initialized to newPorts. MessageEventInit event_init {}; - event_init.data = message; - event_init.origin = ""_string; - target_port->dispatch_event(MessageEvent::create(target_port->realm(), HTML::EventNames::message, event_init)); + event_init.data = message_clone; + event_init.ports = move(new_ports); + final_target_port->dispatch_event(MessageEvent::create(target_realm, HTML::EventNames::message, event_init)); })); + + return {}; } +// https://html.spec.whatwg.org/multipage/web-messaging.html#dom-messageport-start void MessagePort::start() { - // FIXME: Message ports are supposed to be disabled by default. + VERIFY(m_socket); + + // TODO: The start() method steps are to enable this's port message queue, if it is not already enabled. } // https://html.spec.whatwg.org/multipage/web-messaging.html#dom-messageport-close diff --git a/Userland/Libraries/LibWeb/HTML/MessagePort.h b/Userland/Libraries/LibWeb/HTML/MessagePort.h index 6126e1b6c9..c7a69d7f6c 100644 --- a/Userland/Libraries/LibWeb/HTML/MessagePort.h +++ b/Userland/Libraries/LibWeb/HTML/MessagePort.h @@ -8,6 +8,7 @@ #include #include +#include #include #include #include @@ -38,7 +39,10 @@ public: void entangle_with(MessagePort&); // https://html.spec.whatwg.org/multipage/web-messaging.html#dom-messageport-postmessage - void post_message(JS::Value); + WebIDL::ExceptionOr post_message(JS::Value message, Vector> const& transfer); + + // https://html.spec.whatwg.org/multipage/web-messaging.html#dom-messageport-postmessage-options + WebIDL::ExceptionOr post_message(JS::Value message, StructuredSerializeOptions const& options); void start(); @@ -53,7 +57,7 @@ public: // ^Transferable virtual WebIDL::ExceptionOr transfer_steps(HTML::TransferDataHolder&) override; - virtual WebIDL::ExceptionOr transfer_receiving_steps(HTML::TransferDataHolder const&) override; + virtual WebIDL::ExceptionOr transfer_receiving_steps(HTML::TransferDataHolder&) override; virtual HTML::TransferType primary_interface() const override { return HTML::TransferType::MessagePort; } private: @@ -62,14 +66,18 @@ private: virtual void initialize(JS::Realm&) override; virtual void visit_edges(Cell::Visitor&) override; - bool is_entangled() const { return m_remote_port; } + bool is_entangled() const { return static_cast(m_socket); } void disentangle(); + WebIDL::ExceptionOr message_port_post_message_steps(JS::GCPtr target_port, JS::Value message, StructuredSerializeOptions const& options); + // The HTML spec implies(!) that this is MessagePort.[[RemotePort]] JS::GCPtr m_remote_port; // https://html.spec.whatwg.org/multipage/web-messaging.html#has-been-shipped bool m_has_been_shipped { false }; + + OwnPtr m_socket; }; } diff --git a/Userland/Libraries/LibWeb/HTML/MessagePort.idl b/Userland/Libraries/LibWeb/HTML/MessagePort.idl index c453bb46ac..e7f5d08506 100644 --- a/Userland/Libraries/LibWeb/HTML/MessagePort.idl +++ b/Userland/Libraries/LibWeb/HTML/MessagePort.idl @@ -4,7 +4,8 @@ // https://html.spec.whatwg.org/multipage/web-messaging.html#messageport [Exposed=(Window,Worker,AudioWorklet), Transferable] interface MessagePort : EventTarget { - undefined postMessage(any message); + undefined postMessage(any message, sequence transfer); + undefined postMessage(any message, optional StructuredSerializeOptions options = {}); undefined start(); undefined close(); diff --git a/Userland/Libraries/LibWeb/HTML/StructuredSerialize.cpp b/Userland/Libraries/LibWeb/HTML/StructuredSerialize.cpp index 33b29f87d9..3529732ab9 100644 --- a/Userland/Libraries/LibWeb/HTML/StructuredSerialize.cpp +++ b/Userland/Libraries/LibWeb/HTML/StructuredSerialize.cpp @@ -1019,7 +1019,7 @@ static bool is_interface_exposed_on_target_realm(u8 name, JS::Realm& realm) return false; } -static WebIDL::ExceptionOr> create_transferred_value(TransferType name, JS::Realm& target_realm, TransferDataHolder const& transfer_data_holder) +static WebIDL::ExceptionOr> create_transferred_value(TransferType name, JS::Realm& target_realm, TransferDataHolder& transfer_data_holder) { switch (name) { case TransferType::MessagePort: { @@ -1032,7 +1032,7 @@ static WebIDL::ExceptionOr> create_tr } // https://html.spec.whatwg.org/multipage/structured-data.html#structureddeserializewithtransfer -WebIDL::ExceptionOr structured_deserialize_with_transfer(JS::VM& vm, SerializedTransferRecord const& serialize_with_transfer_result) +WebIDL::ExceptionOr structured_deserialize_with_transfer(JS::VM& vm, SerializedTransferRecord& serialize_with_transfer_result) { auto& target_realm = *vm.current_realm(); @@ -1068,7 +1068,7 @@ WebIDL::ExceptionOr structured_deserialize_with_tran // 4. Otherwise: else { // 1. Let interfaceName be transferDataHolder.[[Type]]. - u8 const interface_name = transfer_data_holder.data[0]; + u8 const interface_name = transfer_data_holder.data.take_first(); // 2. If the interface identified by interfaceName is not exposed in targetRealm, then throw a "DataCloneError" DOMException. if (!is_interface_exposed_on_target_realm(interface_name, target_realm)) diff --git a/Userland/Libraries/LibWeb/HTML/StructuredSerialize.h b/Userland/Libraries/LibWeb/HTML/StructuredSerialize.h index 3afe51b313..97d987ffc8 100644 --- a/Userland/Libraries/LibWeb/HTML/StructuredSerialize.h +++ b/Userland/Libraries/LibWeb/HTML/StructuredSerialize.h @@ -51,6 +51,6 @@ WebIDL::ExceptionOr structured_serialize_internal(JS::VM& v WebIDL::ExceptionOr structured_deserialize(JS::VM& vm, SerializationRecord const& serialized, JS::Realm& target_realm, Optional); WebIDL::ExceptionOr structured_serialize_with_transfer(JS::VM& vm, JS::Value value, Vector> const& transfer_list); -WebIDL::ExceptionOr structured_deserialize_with_transfer(JS::VM& vm, SerializedTransferRecord const&); +WebIDL::ExceptionOr structured_deserialize_with_transfer(JS::VM& vm, SerializedTransferRecord&); } diff --git a/Userland/Libraries/LibWeb/HTML/Window.cpp b/Userland/Libraries/LibWeb/HTML/Window.cpp index a84dc1d752..896dd0842d 100644 --- a/Userland/Libraries/LibWeb/HTML/Window.cpp +++ b/Userland/Libraries/LibWeb/HTML/Window.cpp @@ -1053,7 +1053,7 @@ WebIDL::ExceptionOr Window::window_post_message_steps(JS::Value message, W auto serialize_with_transfer_result = TRY(structured_serialize_with_transfer(target_realm.vm(), message, transfer)); // 8. Queue a global task on the posted message task source given targetWindow to run the following steps: - queue_global_task(Task::Source::PostedMessage, *this, [this, serialize_with_transfer_result = move(serialize_with_transfer_result), target_origin = move(target_origin), &incumbent_settings, &target_realm]() { + queue_global_task(Task::Source::PostedMessage, *this, [this, serialize_with_transfer_result = move(serialize_with_transfer_result), target_origin = move(target_origin), &incumbent_settings, &target_realm]() mutable { // 1. If the targetOrigin argument is not a single literal U+002A ASTERISK character (*) and targetWindow's // associated Document's origin is not same origin with targetOrigin, then return. // NOTE: Due to step 4 and 5 above, the only time it's not '*' is if target_origin contains an Origin.