diff --git a/Tests/LibWeb/Text/expected/Streams/TransformStream-cancelled-by-readable-side.txt b/Tests/LibWeb/Text/expected/Streams/TransformStream-cancelled-by-readable-side.txt new file mode 100644 index 00000000000..0939d8fe628 --- /dev/null +++ b/Tests/LibWeb/Text/expected/Streams/TransformStream-cancelled-by-readable-side.txt @@ -0,0 +1 @@ +TransformStream cancelled due to: cancel called by the readable side. diff --git a/Tests/LibWeb/Text/expected/Streams/TransformStream-cancelled-by-writable-side.txt b/Tests/LibWeb/Text/expected/Streams/TransformStream-cancelled-by-writable-side.txt new file mode 100644 index 00000000000..8a8670d036d --- /dev/null +++ b/Tests/LibWeb/Text/expected/Streams/TransformStream-cancelled-by-writable-side.txt @@ -0,0 +1 @@ +TransformStream cancelled due to: abort called by the writable side. diff --git a/Tests/LibWeb/Text/input/Streams/TransformStream-cancelled-by-readable-side.html b/Tests/LibWeb/Text/input/Streams/TransformStream-cancelled-by-readable-side.html new file mode 100644 index 00000000000..c6ad7cd2927 --- /dev/null +++ b/Tests/LibWeb/Text/input/Streams/TransformStream-cancelled-by-readable-side.html @@ -0,0 +1,16 @@ + + diff --git a/Tests/LibWeb/Text/input/Streams/TransformStream-cancelled-by-writable-side.html b/Tests/LibWeb/Text/input/Streams/TransformStream-cancelled-by-writable-side.html new file mode 100644 index 00000000000..4207e8e5843 --- /dev/null +++ b/Tests/LibWeb/Text/input/Streams/TransformStream-cancelled-by-writable-side.html @@ -0,0 +1,16 @@ + + diff --git a/Userland/Libraries/LibWeb/Streams/AbstractOperations.cpp b/Userland/Libraries/LibWeb/Streams/AbstractOperations.cpp index 3470d452200..08f59111b25 100644 --- a/Userland/Libraries/LibWeb/Streams/AbstractOperations.cpp +++ b/Userland/Libraries/LibWeb/Streams/AbstractOperations.cpp @@ -4762,7 +4762,7 @@ void initialize_transform_stream(TransformStream& stream, JS::NonnullGCPtr transform_algorithm, JS::NonnullGCPtr flush_algorithm) +void set_up_transform_stream_default_controller(TransformStream& stream, TransformStreamDefaultController& controller, JS::NonnullGCPtr transform_algorithm, JS::NonnullGCPtr flush_algorithm, JS::NonnullGCPtr cancel_algorithm) { // 1. Assert: stream implements TransformStream. // 2. Assert: stream.[[controller]] is undefined. @@ -4779,6 +4779,9 @@ void set_up_transform_stream_default_controller(TransformStream& stream, Transfo // 6. Set controller.[[flushAlgorithm]] to flushAlgorithm. controller.set_flush_algorithm(flush_algorithm); + + // 7. Set controller.[[cancelAlgorithm]] to cancelAlgorithm. + controller.set_cancel_algorithm(cancel_algorithm); } // https://streams.spec.whatwg.org/#set-up-transform-stream-default-controller-from-transformer @@ -4810,7 +4813,12 @@ void set_up_transform_stream_default_controller_from_transformer(TransformStream return WebIDL::create_resolved_promise(realm, JS::js_undefined()); }); - // 4. If transformerDict["transform"] exists, set transformAlgorithm to an algorithm which takes an argument chunk + // 4. Let cancelAlgorithm be an algorithm which returns a promise resolved with undefined. + auto cancel_algorithm = JS::create_heap_function(realm.heap(), [&realm](JS::Value) { + return WebIDL::create_resolved_promise(realm, JS::js_undefined()); + }); + + // 5. If transformerDict["transform"] exists, set transformAlgorithm to an algorithm which takes an argument chunk // and returns the result of invoking transformerDict["transform"] with argument list « chunk, controller » and // callback this value transformer. if (transformer_dict.transform) { @@ -4821,7 +4829,7 @@ void set_up_transform_stream_default_controller_from_transformer(TransformStream }); } - // 5. If transformerDict["flush"] exists, set flushAlgorithm to an algorithm which returns the result of invoking + // 6. If transformerDict["flush"] exists, set flushAlgorithm to an algorithm which returns the result of invoking // transformerDict["flush"] with argument list « controller » and callback this value transformer. if (transformer_dict.flush) { flush_algorithm = JS::create_heap_function(realm.heap(), [&realm, transformer, callback = transformer_dict.flush, controller]() { @@ -4831,8 +4839,18 @@ void set_up_transform_stream_default_controller_from_transformer(TransformStream }); } - // 6. Perform ! SetUpTransformStreamDefaultController(stream, controller, transformAlgorithm, flushAlgorithm). - set_up_transform_stream_default_controller(stream, *controller, transform_algorithm, flush_algorithm); + // 7. If transformerDict["cancel"] exists, set cancelAlgorithm to an algorithm which takes an argument reason and returns + // the result of invoking transformerDict["cancel"] with argument list « reason » and callback this value transformer. + if (transformer_dict.cancel) { + cancel_algorithm = JS::create_heap_function(realm.heap(), [&realm, transformer, callback = transformer_dict.cancel](JS::Value reason) { + // Note: callback returns a promise, so invoke_callback will never return an abrupt completion + auto result = MUST(WebIDL::invoke_callback(*callback, transformer, reason)).release_value(); + return WebIDL::create_resolved_promise(realm, result); + }); + } + + // 8. Perform ! SetUpTransformStreamDefaultController(stream, controller, transformAlgorithm, flushAlgorithm). + set_up_transform_stream_default_controller(stream, *controller, transform_algorithm, flush_algorithm, cancel_algorithm); } // https://streams.spec.whatwg.org/#transform-stream-default-controller-clear-algorithms @@ -4844,6 +4862,9 @@ void transform_stream_default_controller_clear_algorithms(TransformStreamDefault // 2. Set controller.[[flushAlgorithm]] to undefined. controller.set_flush_algorithm({}); + + // 3. Set controller.[[cancelAlgorithm]] to undefined. + controller.set_cancel_algorithm({}); } // https://streams.spec.whatwg.org/#transform-stream-default-controller-enqueue @@ -5216,7 +5237,7 @@ void transform_stream_set_backpressure(TransformStream& stream, bool backpressur } // https://streams.spec.whatwg.org/#transformstream-set-up -void transform_stream_set_up(TransformStream& stream, JS::NonnullGCPtr transform_algorithm, JS::GCPtr flush_algorithm, JS::GCPtr) +void transform_stream_set_up(TransformStream& stream, JS::NonnullGCPtr transform_algorithm, JS::GCPtr flush_algorithm, JS::GCPtr cancel_algorithm) { auto& realm = stream.realm(); @@ -5265,7 +5286,20 @@ void transform_stream_set_up(TransformStream& stream, JS::NonnullGCPtr JS::NonnullGCPtr { + // 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. + JS::GCPtr result = nullptr; + if (cancel_algorithm) + result = cancel_algorithm->function()(reason); + + // 2. If result is a Promise, then return result. + if (result) + return JS::NonnullGCPtr { *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()); @@ -5277,7 +5311,7 @@ void transform_stream_set_up(TransformStream& stream, JS::NonnullGCPtr(realm, realm); // 11. Perform ! SetUpTransformStreamDefaultController(stream, controller, transformAlgorithmWrapper, flushAlgorithmWrapper, cancelAlgorithmWrapper). - set_up_transform_stream_default_controller(stream, controller, transform_algorithm_wrapper, flush_algorithm_wrapper); + set_up_transform_stream_default_controller(stream, controller, transform_algorithm_wrapper, flush_algorithm_wrapper, cancel_algorithm_wrapper); } // https://streams.spec.whatwg.org/#transform-stream-unblock-write diff --git a/Userland/Libraries/LibWeb/Streams/AbstractOperations.h b/Userland/Libraries/LibWeb/Streams/AbstractOperations.h index 63fc2a7be12..f71e1dac1bb 100644 --- a/Userland/Libraries/LibWeb/Streams/AbstractOperations.h +++ b/Userland/Libraries/LibWeb/Streams/AbstractOperations.h @@ -168,7 +168,7 @@ void writable_stream_default_controller_process_write(WritableStreamDefaultContr void writable_stream_default_controller_write(WritableStreamDefaultController&, JS::Value chunk, JS::Value chunk_size); void initialize_transform_stream(TransformStream&, JS::NonnullGCPtr start_promise, double writable_high_water_mark, JS::NonnullGCPtr writable_size_algorithm, double readable_high_water_mark, JS::NonnullGCPtr readable_size_algorithm); -void set_up_transform_stream_default_controller(TransformStream&, TransformStreamDefaultController&, JS::NonnullGCPtr, JS::NonnullGCPtr); +void set_up_transform_stream_default_controller(TransformStream&, TransformStreamDefaultController&, JS::NonnullGCPtr, JS::NonnullGCPtr, JS::NonnullGCPtr); void set_up_transform_stream_default_controller_from_transformer(TransformStream&, JS::Value transformer, Transformer&); void transform_stream_default_controller_clear_algorithms(TransformStreamDefaultController&); WebIDL::ExceptionOr transform_stream_default_controller_enqueue(TransformStreamDefaultController&, JS::Value chunk); diff --git a/Userland/Libraries/LibWeb/Streams/Transformer.cpp b/Userland/Libraries/LibWeb/Streams/Transformer.cpp index b3e2a483b0e..1d07ad4cbe6 100644 --- a/Userland/Libraries/LibWeb/Streams/Transformer.cpp +++ b/Userland/Libraries/LibWeb/Streams/Transformer.cpp @@ -22,6 +22,7 @@ JS::ThrowCompletionOr Transformer::from_value(JS::VM& vm, JS::Value .start = TRY(property_to_callback(vm, value, "start", WebIDL::OperationReturnsPromise::No)), .transform = TRY(property_to_callback(vm, value, "transform", WebIDL::OperationReturnsPromise::Yes)), .flush = TRY(property_to_callback(vm, value, "flush", WebIDL::OperationReturnsPromise::Yes)), + .cancel = TRY(property_to_callback(vm, value, "cancel", WebIDL::OperationReturnsPromise::Yes)), .readable_type = {}, .writable_type = {}, }; diff --git a/Userland/Libraries/LibWeb/Streams/Transformer.h b/Userland/Libraries/LibWeb/Streams/Transformer.h index 940a11a60c2..df33bd327e6 100644 --- a/Userland/Libraries/LibWeb/Streams/Transformer.h +++ b/Userland/Libraries/LibWeb/Streams/Transformer.h @@ -20,6 +20,8 @@ struct Transformer { JS::Handle transform; // https://streams.spec.whatwg.org/#dom-transformer-flush JS::Handle flush; + // https://streams.spec.whatwg.org/#dom-transformer-cancel + JS::Handle cancel; // https://streams.spec.whatwg.org/#dom-transformer-readabletype Optional readable_type;