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 @@
+
+
+
+
+
+