diff --git a/Libraries/LibWeb/HTML/StructuredSerialize.cpp b/Libraries/LibWeb/HTML/StructuredSerialize.cpp index 0cd43016aab..102c33b396b 100644 --- a/Libraries/LibWeb/HTML/StructuredSerialize.cpp +++ b/Libraries/LibWeb/HTML/StructuredSerialize.cpp @@ -50,6 +50,7 @@ #include #include #include +#include #include #include @@ -1296,6 +1297,8 @@ static bool is_interface_exposed_on_target_realm(TransferType name, JS::Realm& r return intrinsics.is_exposed("MessagePort"sv); case TransferType::ReadableStream: return intrinsics.is_exposed("ReadableStream"sv); + case TransferType::WritableStream: + return intrinsics.is_exposed("WritableStream"sv); case TransferType::Unknown: dbgln("Unknown interface type for transfer: {}", to_underlying(name)); break; @@ -1318,6 +1321,11 @@ static WebIDL::ExceptionOr> create_transferred TRY(readable_stream->transfer_receiving_steps(transfer_data_holder)); return readable_stream; } + case TransferType::WritableStream: { + auto writable_stream = target_realm.create(target_realm); + TRY(writable_stream->transfer_receiving_steps(transfer_data_holder)); + return writable_stream; + } case TransferType::ArrayBuffer: case TransferType::ResizableArrayBuffer: dbgln("ArrayBuffer ({}) is not a platform object.", to_underlying(name)); diff --git a/Libraries/LibWeb/HTML/StructuredSerialize.h b/Libraries/LibWeb/HTML/StructuredSerialize.h index 7d3ca04a946..116d28d7f55 100644 --- a/Libraries/LibWeb/HTML/StructuredSerialize.h +++ b/Libraries/LibWeb/HTML/StructuredSerialize.h @@ -50,6 +50,7 @@ enum class TransferType : u8 { ArrayBuffer = 2, ResizableArrayBuffer = 3, ReadableStream = 4, + WritableStream = 5, }; WebIDL::ExceptionOr structured_serialize(JS::VM& vm, JS::Value); diff --git a/Libraries/LibWeb/Streams/WritableStream.cpp b/Libraries/LibWeb/Streams/WritableStream.cpp index 8d42da7d7ab..6cb0bb3480e 100644 --- a/Libraries/LibWeb/Streams/WritableStream.cpp +++ b/Libraries/LibWeb/Streams/WritableStream.cpp @@ -7,7 +7,11 @@ #include #include #include +#include +#include #include +#include +#include #include #include #include @@ -51,6 +55,34 @@ WebIDL::ExceptionOr> WritableStream::construct_impl(JS:: return writable_stream; } +WritableStream::WritableStream(JS::Realm& realm) + : Bindings::PlatformObject(realm) +{ +} + +void WritableStream::initialize(JS::Realm& realm) +{ + WEB_SET_PROTOTYPE_FOR_INTERFACE(WritableStream); + Base::initialize(realm); +} + +void WritableStream::visit_edges(Cell::Visitor& visitor) +{ + Base::visit_edges(visitor); + visitor.visit(m_close_request); + visitor.visit(m_controller); + visitor.visit(m_in_flight_write_request); + visitor.visit(m_in_flight_close_request); + if (m_pending_abort_request.has_value()) { + visitor.visit(m_pending_abort_request->promise); + visitor.visit(m_pending_abort_request->reason); + } + visitor.visit(m_stored_error); + visitor.visit(m_writer); + for (auto& write_request : m_write_requests) + visitor.visit(write_request); +} + // https://streams.spec.whatwg.org/#ws-locked bool WritableStream::locked() const { @@ -101,32 +133,62 @@ WebIDL::ExceptionOr> WritableStream::get_wr return acquire_writable_stream_default_writer(*this); } -WritableStream::WritableStream(JS::Realm& realm) - : Bindings::PlatformObject(realm) +// https://streams.spec.whatwg.org/#ref-for-transfer-steps① +WebIDL::ExceptionOr WritableStream::transfer_steps(HTML::TransferDataHolder& data_holder) { + auto& realm = this->realm(); + auto& vm = realm.vm(); + + HTML::TemporaryExecutionContext execution_context { realm, HTML::TemporaryExecutionContext::CallbacksEnabled::Yes }; + + // 1. If ! IsWritableStreamLocked(value) is true, throw a "DataCloneError" DOMException. + if (is_writable_stream_locked(*this)) + return WebIDL::DataCloneError::create(realm, "Cannot transfer locked WritableStream"_string); + + // 2. Let port1 be a new MessagePort in the current Realm. + auto port1 = HTML::MessagePort::create(realm); + + // 3. Let port2 be a new MessagePort in the current Realm. + auto port2 = HTML::MessagePort::create(realm, HTML::TransferType::WritableStream); + + // 4. Entangle port1 and port2. + port1->entangle_with(port2); + + // 5. Let readable be a new ReadableStream in the current Realm. + auto readable = realm.create(realm); + + // 6. Perform ! SetUpCrossRealmTransformReadable(readable, port1). + set_up_cross_realm_transform_readable(realm, readable, port1); + + // 7. Let promise be ! ReadableStreamPipeTo(readable, value, false, false, false). + auto promise = readable_stream_pipe_to(readable, *this, false, false, false); + + // 8. Set promise.[[PromiseIsHandled]] to true. + WebIDL::mark_promise_as_handled(promise); + + // 9. Set dataHolder.[[port]] to ! StructuredSerializeWithTransfer(port2, « port2 »). + auto result = MUST(HTML::structured_serialize_with_transfer(vm, port2, { { GC::Root { port2 } } })); + data_holder = move(result.transfer_data_holders.first()); + + return {}; } -void WritableStream::initialize(JS::Realm& realm) +// https://streams.spec.whatwg.org/#ref-for-transfer-receiving-steps① +WebIDL::ExceptionOr WritableStream::transfer_receiving_steps(HTML::TransferDataHolder& data_holder) { - WEB_SET_PROTOTYPE_FOR_INTERFACE(WritableStream); - Base::initialize(realm); -} + auto& realm = this->realm(); -void WritableStream::visit_edges(Cell::Visitor& visitor) -{ - Base::visit_edges(visitor); - visitor.visit(m_close_request); - visitor.visit(m_controller); - visitor.visit(m_in_flight_write_request); - visitor.visit(m_in_flight_close_request); - if (m_pending_abort_request.has_value()) { - visitor.visit(m_pending_abort_request->promise); - visitor.visit(m_pending_abort_request->reason); - } - visitor.visit(m_stored_error); - visitor.visit(m_writer); - for (auto& write_request : m_write_requests) - visitor.visit(write_request); + HTML::TemporaryExecutionContext execution_context { realm, HTML::TemporaryExecutionContext::CallbacksEnabled::Yes }; + + // 1. Let deserializedRecord be ! StructuredDeserializeWithTransfer(dataHolder.[[port]], the current Realm). + // 2. Let port be deserializedRecord.[[Deserialized]]. + auto port = HTML::MessagePort::create(realm); + TRY(port->transfer_receiving_steps(data_holder)); + + // 3. Perform ! SetUpCrossRealmTransformWritable(value, port). + set_up_cross_realm_transform_writable(realm, *this, port); + + return {}; } } diff --git a/Libraries/LibWeb/Streams/WritableStream.h b/Libraries/LibWeb/Streams/WritableStream.h index 508d3fde72e..b7355e2a18e 100644 --- a/Libraries/LibWeb/Streams/WritableStream.h +++ b/Libraries/LibWeb/Streams/WritableStream.h @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -32,7 +33,9 @@ struct PendingAbortRequest { }; // https://streams.spec.whatwg.org/#writablestream -class WritableStream final : public Bindings::PlatformObject { +class WritableStream final + : public Bindings::PlatformObject + , public Bindings::Transferable { WEB_PLATFORM_OBJECT(WritableStream, Bindings::PlatformObject); GC_DECLARE_ALLOCATOR(WritableStream); @@ -85,6 +88,11 @@ public: SinglyLinkedList>& write_requests() { return m_write_requests; } + // ^Transferable + virtual WebIDL::ExceptionOr transfer_steps(HTML::TransferDataHolder&) override; + virtual WebIDL::ExceptionOr transfer_receiving_steps(HTML::TransferDataHolder&) override; + virtual HTML::TransferType primary_interface() const override { return HTML::TransferType::WritableStream; } + private: explicit WritableStream(JS::Realm&); @@ -104,10 +112,6 @@ private: // A WritableStreamDefaultController created with the ability to control the state and queue of this stream GC::Ptr m_controller; - // https://streams.spec.whatwg.org/#writablestream-detached - // A boolean flag set to true when the stream is transferred - bool m_detached { false }; - // https://streams.spec.whatwg.org/#writablestream-inflightwriterequest // A slot set to the promise for the current in-flight write operation while the underlying sink's write algorithm is executing and has not yet fulfilled, used to prevent reentrant calls GC::Ptr m_in_flight_write_request; diff --git a/Tests/LibWeb/Text/expected/wpt-import/streams/transferable/writable-stream.txt b/Tests/LibWeb/Text/expected/wpt-import/streams/transferable/writable-stream.txt new file mode 100644 index 00000000000..bfaa26ce20b --- /dev/null +++ b/Tests/LibWeb/Text/expected/wpt-import/streams/transferable/writable-stream.txt @@ -0,0 +1,13 @@ +Harness status: OK + +Found 8 tests + +8 Pass +Pass window.postMessage should be able to transfer a WritableStream +Pass a locked WritableStream should not be transferable +Pass window.postMessage should be able to transfer a {readable, writable} pair +Pass desiredSize for a newly-transferred stream should be 1 +Pass effective queue size of a transferred writable should be 2 +Pass second write should wait for first underlying write to complete +Pass abort() should work +Pass writing a unclonable object should error the stream \ No newline at end of file diff --git a/Tests/LibWeb/Text/input/wpt-import/streams/transferable/writable-stream.html b/Tests/LibWeb/Text/input/wpt-import/streams/transferable/writable-stream.html new file mode 100644 index 00000000000..ee02a3609c2 --- /dev/null +++ b/Tests/LibWeb/Text/input/wpt-import/streams/transferable/writable-stream.html @@ -0,0 +1,146 @@ + + + + + + + +