diff --git a/Libraries/LibWeb/HTML/StructuredSerialize.cpp b/Libraries/LibWeb/HTML/StructuredSerialize.cpp index 102c33b396b..10a7e732fc1 100644 --- a/Libraries/LibWeb/HTML/StructuredSerialize.cpp +++ b/Libraries/LibWeb/HTML/StructuredSerialize.cpp @@ -50,6 +50,7 @@ #include #include #include +#include #include #include #include @@ -1299,6 +1300,8 @@ static bool is_interface_exposed_on_target_realm(TransferType name, JS::Realm& r return intrinsics.is_exposed("ReadableStream"sv); case TransferType::WritableStream: return intrinsics.is_exposed("WritableStream"sv); + case TransferType::TransformStream: + return intrinsics.is_exposed("TransformStream"sv); case TransferType::Unknown: dbgln("Unknown interface type for transfer: {}", to_underlying(name)); break; @@ -1326,6 +1329,11 @@ static WebIDL::ExceptionOr> create_transferred TRY(writable_stream->transfer_receiving_steps(transfer_data_holder)); return writable_stream; } + case TransferType::TransformStream: { + auto transform_stream = target_realm.create(target_realm); + TRY(transform_stream->transfer_receiving_steps(transfer_data_holder)); + return transform_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 116d28d7f55..36371571b2e 100644 --- a/Libraries/LibWeb/HTML/StructuredSerialize.h +++ b/Libraries/LibWeb/HTML/StructuredSerialize.h @@ -51,6 +51,7 @@ enum class TransferType : u8 { ResizableArrayBuffer = 3, ReadableStream = 4, WritableStream = 5, + TransformStream = 6, }; WebIDL::ExceptionOr structured_serialize(JS::VM& vm, JS::Value); diff --git a/Libraries/LibWeb/Streams/TransformStream.cpp b/Libraries/LibWeb/Streams/TransformStream.cpp index 348a60d293f..00d272ad3a9 100644 --- a/Libraries/LibWeb/Streams/TransformStream.cpp +++ b/Libraries/LibWeb/Streams/TransformStream.cpp @@ -4,15 +4,18 @@ * SPDX-License-Identifier: BSD-2-Clause */ +#include #include #include #include #include +#include #include #include #include #include #include +#include #include #include @@ -76,6 +79,28 @@ WebIDL::ExceptionOr> TransformStream::construct_impl(JS return stream; } +TransformStream::TransformStream(JS::Realm& realm) + : Bindings::PlatformObject(realm) +{ +} + +TransformStream::~TransformStream() = default; + +void TransformStream::initialize(JS::Realm& realm) +{ + WEB_SET_PROTOTYPE_FOR_INTERFACE(TransformStream); + Base::initialize(realm); +} + +void TransformStream::visit_edges(Cell::Visitor& visitor) +{ + Base::visit_edges(visitor); + visitor.visit(m_backpressure_change_promise); + visitor.visit(m_controller); + visitor.visit(m_readable); + visitor.visit(m_writable); +} + // https://streams.spec.whatwg.org/#transformstream-enqueue void TransformStream::enqueue(JS::Value chunk) { @@ -161,26 +186,83 @@ void TransformStream::set_up(GC::Ref transform_algorithm, GC set_up_transform_stream_default_controller(*this, controller, transform_algorithm_wrapper, flush_algorithm_wrapper, cancel_algorithm_wrapper); } -TransformStream::TransformStream(JS::Realm& realm) - : Bindings::PlatformObject(realm) +// https://streams.spec.whatwg.org/#ref-for-transfer-steps② +WebIDL::ExceptionOr TransformStream::transfer_steps(HTML::TransferDataHolder& data_holder) { + auto& realm = this->realm(); + auto& vm = realm.vm(); + + auto serialize_stream = [&](auto stream) { + auto result = MUST(HTML::structured_serialize_with_transfer(vm, stream, { { GC::Root { stream } } })); + + data_holder.data.extend(move(result.transfer_data_holders.first().data)); + data_holder.fds.extend(move(result.transfer_data_holders.first().fds)); + }; + + // 1. Let readable be value.[[readable]]. + auto readable = this->readable(); + + // 2. Let writable be value.[[writable]]. + auto writable = this->writable(); + + // 3. If ! IsReadableStreamLocked(readable) is true, throw a "DataCloneError" DOMException. + if (is_readable_stream_locked(readable)) + return WebIDL::DataCloneError::create(realm, "Cannot transfer locked ReadableStream"_string); + + // 4. If ! IsWritableStreamLocked(writable) is true, throw a "DataCloneError" DOMException. + if (is_writable_stream_locked(writable)) + return WebIDL::DataCloneError::create(realm, "Cannot transfer locked WritableStream"_string); + + // 5. Set dataHolder.[[readable]] to ! StructuredSerializeWithTransfer(readable, « readable »). + serialize_stream(readable); + + // 6. Set dataHolder.[[writable]] to ! StructuredSerializeWithTransfer(writable, « writable »). + serialize_stream(writable); + + return {}; } -TransformStream::~TransformStream() = default; - -void TransformStream::initialize(JS::Realm& realm) +template +static WebIDL::ExceptionOr> deserialize_stream(JS::Realm& realm, HTML::TransferDataHolder& data_holder) { - WEB_SET_PROTOTYPE_FOR_INTERFACE(TransformStream); - Base::initialize(realm); + auto transfer_type = data_holder.data.take_first(); + + if constexpr (IsSame) + VERIFY(transfer_type == to_underlying(HTML::TransferType::ReadableStream)); + else if constexpr (IsSame) + VERIFY(transfer_type == to_underlying(HTML::TransferType::WritableStream)); + else + static_assert(DependentFalse); + + auto stream = realm.create(realm); + TRY(stream->transfer_receiving_steps(data_holder)); + + return stream; } -void TransformStream::visit_edges(Cell::Visitor& visitor) +// https://streams.spec.whatwg.org/#ref-for-transfer-receiving-steps② +WebIDL::ExceptionOr TransformStream::transfer_receiving_steps(HTML::TransferDataHolder& data_holder) { - Base::visit_edges(visitor); - visitor.visit(m_backpressure_change_promise); - visitor.visit(m_controller); - visitor.visit(m_readable); - visitor.visit(m_writable); + auto& realm = this->realm(); + + // 1. Let readableRecord be ! StructuredDeserializeWithTransfer(dataHolder.[[readable]], the current Realm). + auto readable = TRY(deserialize_stream(realm, data_holder)); + + // 2. Let writableRecord be ! StructuredDeserializeWithTransfer(dataHolder.[[writable]], the current Realm). + auto writable = TRY(deserialize_stream(realm, data_holder)); + + // 3. Set value.[[readable]] to readableRecord.[[Deserialized]]. + set_readable(readable); + + // 4. Set value.[[writable]] to writableRecord.[[Deserialized]]. + set_writable(writable); + + // 5. Set value.[[backpressure]], value.[[backpressureChangePromise]], and value.[[controller]] to undefined. + set_backpressure({}); + set_backpressure_change_promise({}); + set_controller({}); + + return {}; } } diff --git a/Libraries/LibWeb/Streams/TransformStream.h b/Libraries/LibWeb/Streams/TransformStream.h index 2d4a9d5b8ad..a9f5be1de5f 100644 --- a/Libraries/LibWeb/Streams/TransformStream.h +++ b/Libraries/LibWeb/Streams/TransformStream.h @@ -8,6 +8,7 @@ #include #include +#include #include #include #include @@ -15,7 +16,9 @@ namespace Web::Streams { -class TransformStream final : public Bindings::PlatformObject { +class TransformStream final + : public Bindings::PlatformObject + , public Bindings::Transferable { WEB_PLATFORM_OBJECT(TransformStream, Bindings::PlatformObject); GC_DECLARE_ALLOCATOR(TransformStream); @@ -44,6 +47,11 @@ public: void set_up(GC::Ref, GC::Ptr = {}, GC::Ptr = {}); void enqueue(JS::Value chunk); + // ^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::TransformStream; } + private: explicit TransformStream(JS::Realm& realm); @@ -63,10 +71,6 @@ private: // A TransformStreamDefaultController created with the ability to control [[readable]] and [[writable]] GC::Ptr m_controller; - // https://streams.spec.whatwg.org/#transformstream-detached - // A boolean flag set to true when the stream is transferred - bool m_detached { false }; - // https://streams.spec.whatwg.org/#transformstream-readable // The ReadableStream instance controlled by this object GC::Ptr m_readable; diff --git a/Tests/LibWeb/Text/expected/wpt-import/streams/transferable/transform-stream.txt b/Tests/LibWeb/Text/expected/wpt-import/streams/transferable/transform-stream.txt new file mode 100644 index 00000000000..ddb1862c807 --- /dev/null +++ b/Tests/LibWeb/Text/expected/wpt-import/streams/transferable/transform-stream.txt @@ -0,0 +1,10 @@ +Harness status: OK + +Found 5 tests + +5 Pass +Pass window.postMessage should be able to transfer a TransformStream +Pass a TransformStream with a locked writable should not be transferable +Pass a TransformStream with a locked readable should not be transferable +Pass a TransformStream with both sides locked should not be transferable +Pass piping through transferred transforms should work \ No newline at end of file diff --git a/Tests/LibWeb/Text/input/wpt-import/streams/transferable/transform-stream.html b/Tests/LibWeb/Text/input/wpt-import/streams/transferable/transform-stream.html new file mode 100644 index 00000000000..56022a19e80 --- /dev/null +++ b/Tests/LibWeb/Text/input/wpt-import/streams/transferable/transform-stream.html @@ -0,0 +1,108 @@ + + + + + +