2023-07-06 19:37:12 +02:00
/*
* Copyright ( c ) 2023 , Kenneth Myhra < kennethmyhra @ serenityos . org >
*
* SPDX - License - Identifier : BSD - 2 - Clause
*/
2025-05-20 18:05:52 -04:00
# include <LibIPC/File.h>
2023-07-06 19:37:12 +02:00
# include <LibWeb/Bindings/Intrinsics.h>
2024-04-27 12:09:58 +12:00
# include <LibWeb/Bindings/TransformStreamPrototype.h>
2025-07-17 13:40:50 -04:00
# include <LibWeb/HTML/StructuredSerialize.h>
2023-07-06 19:37:12 +02:00
# include <LibWeb/Streams/AbstractOperations.h>
2025-04-17 15:47:53 -04:00
# include <LibWeb/Streams/ReadableStream.h>
2025-05-20 18:05:52 -04:00
# include <LibWeb/Streams/ReadableStreamOperations.h>
2023-07-06 19:37:12 +02:00
# include <LibWeb/Streams/TransformStream.h>
2023-07-08 20:10:32 +02:00
# include <LibWeb/Streams/TransformStreamDefaultController.h>
2025-04-17 20:02:23 -04:00
# include <LibWeb/Streams/TransformStreamOperations.h>
2023-07-06 19:37:12 +02:00
# include <LibWeb/Streams/Transformer.h>
# include <LibWeb/Streams/WritableStream.h>
2025-05-20 18:05:52 -04:00
# include <LibWeb/Streams/WritableStreamOperations.h>
2023-07-08 20:10:32 +02:00
# include <LibWeb/WebIDL/AbstractOperations.h>
2023-07-06 19:37:12 +02:00
# include <LibWeb/WebIDL/ExceptionOr.h>
namespace Web : : Streams {
2024-11-15 04:01:23 +13:00
GC_DEFINE_ALLOCATOR ( TransformStream ) ;
2023-11-19 19:47:52 +01:00
2024-04-29 18:38:03 -04:00
// https://streams.spec.whatwg.org/#ts-constructor
2024-11-15 04:01:23 +13:00
WebIDL : : ExceptionOr < GC : : Ref < TransformStream > > TransformStream : : construct_impl ( JS : : Realm & realm , Optional < GC : : Root < JS : : Object > > transformer_object , QueuingStrategy const & writable_strategy , QueuingStrategy const & readable_strategy )
2023-07-06 19:37:12 +02:00
{
2023-07-08 20:10:32 +02:00
auto & vm = realm . vm ( ) ;
2023-07-06 19:37:12 +02:00
2024-11-14 05:50:17 +13:00
auto stream = realm . create < TransformStream > ( realm ) ;
2023-07-08 20:10:32 +02:00
// 1. If transformer is missing, set it to null.
2023-12-18 14:01:39 -07:00
auto transformer = transformer_object . has_value ( ) ? JS : : Value { transformer_object . value ( ) } : JS : : js_null ( ) ;
2023-07-08 20:10:32 +02:00
// 2. Let transformerDict be transformer, converted to an IDL value of type Transformer.
auto transformer_dict = TRY ( Transformer : : from_value ( vm , transformer ) ) ;
// 3. If transformerDict["readableType"] exists, throw a RangeError exception.
if ( transformer_dict . readable_type . has_value ( ) )
return WebIDL : : SimpleException { WebIDL : : SimpleExceptionType : : RangeError , " Invalid use of reserved key 'readableType' " sv } ;
// 4. If transformerDict["writableType"] exists, throw a RangeError exception.
if ( transformer_dict . writable_type . has_value ( ) )
return WebIDL : : SimpleException { WebIDL : : SimpleExceptionType : : RangeError , " Invalid use of reserved key 'writableType' " sv } ;
// 5. Let readableHighWaterMark be ? ExtractHighWaterMark(readableStrategy, 0).
auto readable_high_water_mark = TRY ( extract_high_water_mark ( readable_strategy , 0 ) ) ;
// 6. Let readableSizeAlgorithm be ! ExtractSizeAlgorithm(readableStrategy).
2024-01-26 18:54:24 +13:00
auto readable_size_algorithm = extract_size_algorithm ( vm , readable_strategy ) ;
2023-07-08 20:10:32 +02:00
// 7. Let writableHighWaterMark be ? ExtractHighWaterMark(writableStrategy, 1).
auto writable_high_water_mark = TRY ( extract_high_water_mark ( writable_strategy , 1 ) ) ;
// 8. Let writableSizeAlgorithm be ! ExtractSizeAlgorithm(writableStrategy).
2024-01-26 18:54:24 +13:00
auto writable_size_algorithm = extract_size_algorithm ( vm , writable_strategy ) ;
2023-07-08 20:10:32 +02:00
// 9. Let startPromise be a new promise.
auto start_promise = WebIDL : : create_promise ( realm ) ;
2024-04-29 18:01:44 -04:00
// 10. Perform ! InitializeTransformStream(this, startPromise, writableHighWaterMark, writableSizeAlgorithm, readableHighWaterMark, readableSizeAlgorithm).
2025-04-17 20:02:23 -04:00
initialize_transform_stream ( * stream , start_promise , writable_high_water_mark , writable_size_algorithm , readable_high_water_mark , readable_size_algorithm ) ;
2023-07-08 20:10:32 +02:00
// 11. Perform ? SetUpTransformStreamDefaultControllerFromTransformer(this, transformer, transformerDict).
2024-04-29 18:15:07 -04:00
set_up_transform_stream_default_controller_from_transformer ( * stream , transformer , transformer_dict ) ;
2023-07-08 20:10:32 +02:00
// 12. If transformerDict["start"] exists, then resolve startPromise with the result of invoking
2024-04-29 16:17:32 -04:00
// transformerDict["start"] with argument list « this.[[controller]] » and callback this value transformer.
2023-07-08 20:10:32 +02:00
if ( transformer_dict . start ) {
2025-04-15 20:56:03 -04:00
auto result = TRY ( WebIDL : : invoke_callback ( * transformer_dict . start , transformer , { { stream - > controller ( ) } } ) ) ;
2023-07-08 20:10:32 +02:00
WebIDL : : resolve_promise ( realm , start_promise , result ) ;
}
// 13. Otherwise, resolve startPromise with undefined.
else {
WebIDL : : resolve_promise ( realm , start_promise , JS : : js_undefined ( ) ) ;
}
return stream ;
2023-07-06 19:37:12 +02:00
}
2025-05-20 18:05:52 -04:00
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 ) ;
}
2024-12-24 12:56:59 +13:00
// https://streams.spec.whatwg.org/#transformstream-enqueue
void TransformStream : : enqueue ( JS : : Value chunk )
{
// To enqueue the JavaScript value chunk into a TransformStream stream, perform ! TransformStreamDefaultControllerEnqueue(stream.[[controller]], chunk).
MUST ( Streams : : transform_stream_default_controller_enqueue ( * controller ( ) , chunk ) ) ;
}
2024-12-08 17:35:07 +13:00
// https://streams.spec.whatwg.org/#transformstream-set-up
void TransformStream : : set_up ( GC : : Ref < TransformAlgorithm > transform_algorithm , GC : : Ptr < FlushAlgorithm > flush_algorithm , GC : : Ptr < CancelAlgorithm > cancel_algorithm )
{
auto & realm = this - > realm ( ) ;
// 1. Let writableHighWaterMark be 1.
auto writable_high_water_mark = 1.0 ;
// 2. Let writableSizeAlgorithm be an algorithm that returns 1.
auto writable_size_algorithm = GC : : create_function ( realm . heap ( ) , [ ] ( JS : : Value ) {
return JS : : normal_completion ( JS : : Value { 1 } ) ;
} ) ;
// 3. Let readableHighWaterMark be 0.
auto readable_high_water_mark = 0.0 ;
// 4. Let readableSizeAlgorithm be an algorithm that returns 1.
auto readable_size_algorithm = GC : : create_function ( realm . heap ( ) , [ ] ( JS : : Value ) {
return JS : : normal_completion ( JS : : Value { 1 } ) ;
} ) ;
// 5. Let transformAlgorithmWrapper be an algorithm that runs these steps given a value chunk:
auto transform_algorithm_wrapper = GC : : create_function ( realm . heap ( ) , [ & realm , transform_algorithm ] ( JS : : Value chunk ) - > GC : : Ref < WebIDL : : Promise > {
// 1. Let result be the result of running transformAlgorithm given chunk. If this throws an exception e, return a promise rejected with e.
GC : : Ptr < JS : : PromiseCapability > result = nullptr ;
result = transform_algorithm - > function ( ) ( chunk ) ;
// 2. If result is a Promise, then return result.
if ( result )
return GC : : Ref { * result } ;
// 3. Return a promise resolved with undefined.
return WebIDL : : create_resolved_promise ( realm , JS : : js_undefined ( ) ) ;
} ) ;
// 6. Let flushAlgorithmWrapper be an algorithm that runs these steps:
auto flush_algorithm_wrapper = GC : : create_function ( realm . heap ( ) , [ & realm , flush_algorithm ] ( ) - > GC : : Ref < WebIDL : : Promise > {
// 1. Let result be the result of running flushAlgorithm, if flushAlgorithm was given, or null otherwise. If this throws an exception e, return a promise rejected with e.
GC : : Ptr < JS : : PromiseCapability > result = nullptr ;
if ( flush_algorithm )
result = flush_algorithm - > function ( ) ( ) ;
// 2. If result is a Promise, then return result.
if ( result )
return GC : : Ref { * result } ;
// 3. Return a promise resolved with undefined.
return WebIDL : : create_resolved_promise ( realm , JS : : js_undefined ( ) ) ;
} ) ;
// 7. Let cancelAlgorithmWrapper be an algorithm that runs these steps given a value reason:
auto cancel_algorithm_wrapper = GC : : create_function ( realm . heap ( ) , [ & realm , cancel_algorithm ] ( JS : : Value reason ) - > GC : : Ref < WebIDL : : Promise > {
// 1. Let result be the result of running cancelAlgorithm given reason, if cancelAlgorithm was given, or null otherwise. If this throws an exception e, return a promise rejected with e.
GC : : Ptr < JS : : PromiseCapability > result = nullptr ;
if ( cancel_algorithm )
result = cancel_algorithm - > function ( ) ( reason ) ;
// 2. If result is a Promise, then return result.
if ( result )
return GC : : Ref { * result } ;
// 3. Return a promise resolved with undefined.
return WebIDL : : create_resolved_promise ( realm , JS : : js_undefined ( ) ) ;
} ) ;
// 8. Let startPromise be a promise resolved with undefined.
auto start_promise = WebIDL : : create_resolved_promise ( realm , JS : : js_undefined ( ) ) ;
// 9. Perform ! InitializeTransformStream(stream, startPromise, writableHighWaterMark, writableSizeAlgorithm, readableHighWaterMark, readableSizeAlgorithm).
initialize_transform_stream ( * this , start_promise , writable_high_water_mark , writable_size_algorithm , readable_high_water_mark , readable_size_algorithm ) ;
// 10. Let controller be a new TransformStreamDefaultController.
auto controller = realm . create < TransformStreamDefaultController > ( realm ) ;
// 11. Perform ! SetUpTransformStreamDefaultController(stream, controller, transformAlgorithmWrapper, flushAlgorithmWrapper, cancelAlgorithmWrapper).
set_up_transform_stream_default_controller ( * this , controller , transform_algorithm_wrapper , flush_algorithm_wrapper , cancel_algorithm_wrapper ) ;
}
2025-05-20 18:05:52 -04:00
// https://streams.spec.whatwg.org/#ref-for-transfer-steps②
WebIDL : : ExceptionOr < void > TransformStream : : transfer_steps ( HTML : : TransferDataHolder & data_holder )
2023-07-06 19:37:12 +02:00
{
2025-05-20 18:05:52 -04:00
auto & realm = this - > realm ( ) ;
auto & vm = realm . vm ( ) ;
2023-07-06 19:37:12 +02:00
2025-05-20 18:05:52 -04:00
auto serialize_stream = [ & ] ( auto stream ) {
auto result = MUST ( HTML : : structured_serialize_with_transfer ( vm , stream , { { GC : : Root { stream } } } ) ) ;
2023-07-06 19:37:12 +02:00
2025-05-20 18:05:52 -04:00
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 { } ;
}
template < typename StreamType >
static WebIDL : : ExceptionOr < GC : : Ref < StreamType > > deserialize_stream ( JS : : Realm & realm , HTML : : TransferDataHolder & data_holder )
2023-07-06 19:37:12 +02:00
{
2025-05-20 18:05:52 -04:00
auto transfer_type = data_holder . data . take_first ( ) ;
if constexpr ( IsSame < StreamType , ReadableStream > )
VERIFY ( transfer_type = = to_underlying ( HTML : : TransferType : : ReadableStream ) ) ;
else if constexpr ( IsSame < StreamType , WritableStream > )
VERIFY ( transfer_type = = to_underlying ( HTML : : TransferType : : WritableStream ) ) ;
else
static_assert ( DependentFalse < StreamType > ) ;
auto stream = realm . create < StreamType > ( realm ) ;
TRY ( stream - > transfer_receiving_steps ( data_holder ) ) ;
return stream ;
2023-07-06 19:37:12 +02:00
}
2025-05-20 18:05:52 -04:00
// https://streams.spec.whatwg.org/#ref-for-transfer-receiving-steps②
WebIDL : : ExceptionOr < void > TransformStream : : transfer_receiving_steps ( HTML : : TransferDataHolder & data_holder )
2023-07-06 19:37:12 +02:00
{
2025-05-20 18:05:52 -04:00
auto & realm = this - > realm ( ) ;
// 1. Let readableRecord be ! StructuredDeserializeWithTransfer(dataHolder.[[readable]], the current Realm).
auto readable = TRY ( deserialize_stream < ReadableStream > ( realm , data_holder ) ) ;
// 2. Let writableRecord be ! StructuredDeserializeWithTransfer(dataHolder.[[writable]], the current Realm).
auto writable = TRY ( deserialize_stream < WritableStream > ( 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 { } ;
2023-07-06 19:37:12 +02:00
}
}