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