Everywhere: Hoist the Libraries folder to the top-level

This commit is contained in:
Timothy Flynn 2024-11-09 12:25:08 -05:00 committed by Andreas Kling
commit 93712b24bf
Notes: github-actions[bot] 2024-11-10 11:51:52 +00:00
4547 changed files with 104 additions and 113 deletions

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,281 @@
/*
* Copyright (c) 2022, Linus Groh <linusg@serenityos.org>
* Copyright (c) 2023, Matthew Olsson <mattco@serenityos.org>
* Copyright (c) 2023-2024, Shannon Booth <shannon@serenityos.org>
* Copyright (c) 2023-2024, Kenneth Myhra <kennethmyhra@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <LibJS/Heap/GCPtr.h>
#include <LibWeb/Forward.h>
#include <LibWeb/Streams/ReadableStream.h>
#include <LibWeb/WebIDL/CallbackType.h>
#include <LibWeb/WebIDL/ExceptionOr.h>
#include <LibWeb/WebIDL/Promise.h>
#include <LibWeb/WebIDL/Types.h>
namespace Web::Streams {
using SizeAlgorithm = JS::HeapFunction<JS::Completion(JS::Value)>;
using PullAlgorithm = JS::HeapFunction<JS::NonnullGCPtr<WebIDL::Promise>()>;
using CancelAlgorithm = JS::HeapFunction<JS::NonnullGCPtr<WebIDL::Promise>(JS::Value)>;
using StartAlgorithm = JS::HeapFunction<WebIDL::ExceptionOr<JS::Value>()>;
using AbortAlgorithm = JS::HeapFunction<JS::NonnullGCPtr<WebIDL::Promise>(JS::Value)>;
using CloseAlgorithm = JS::HeapFunction<JS::NonnullGCPtr<WebIDL::Promise>()>;
using WriteAlgorithm = JS::HeapFunction<JS::NonnullGCPtr<WebIDL::Promise>(JS::Value)>;
using FlushAlgorithm = JS::HeapFunction<JS::NonnullGCPtr<WebIDL::Promise>()>;
using TransformAlgorithm = JS::HeapFunction<JS::NonnullGCPtr<WebIDL::Promise>(JS::Value)>;
WebIDL::ExceptionOr<JS::NonnullGCPtr<ReadableStreamDefaultReader>> acquire_readable_stream_default_reader(ReadableStream&);
WebIDL::ExceptionOr<JS::NonnullGCPtr<ReadableStreamBYOBReader>> acquire_readable_stream_byob_reader(ReadableStream&);
bool is_readable_stream_locked(ReadableStream const&);
JS::NonnullGCPtr<SizeAlgorithm> extract_size_algorithm(JS::VM&, QueuingStrategy const&);
WebIDL::ExceptionOr<double> extract_high_water_mark(QueuingStrategy const&, double default_hwm);
void readable_stream_close(ReadableStream&);
void readable_stream_error(ReadableStream&, JS::Value error);
WebIDL::ExceptionOr<JS::NonnullGCPtr<ReadableStream>> readable_stream_from_iterable(JS::VM& vm, JS::Value async_iterable);
void readable_stream_add_read_request(ReadableStream&, JS::NonnullGCPtr<ReadRequest>);
void readable_stream_add_read_into_request(ReadableStream&, JS::NonnullGCPtr<ReadIntoRequest>);
JS::NonnullGCPtr<WebIDL::Promise> readable_stream_cancel(ReadableStream&, JS::Value reason);
void readable_stream_fulfill_read_into_request(ReadableStream&, JS::Value chunk, bool done);
void readable_stream_fulfill_read_request(ReadableStream&, JS::Value chunk, bool done);
size_t readable_stream_get_num_read_into_requests(ReadableStream const&);
size_t readable_stream_get_num_read_requests(ReadableStream const&);
bool readable_stream_has_byob_reader(ReadableStream const&);
bool readable_stream_has_default_reader(ReadableStream const&);
JS::NonnullGCPtr<WebIDL::Promise> readable_stream_pipe_to(ReadableStream& source, WritableStream& dest, bool prevent_close, bool prevent_abort, bool prevent_cancel, Optional<JS::Value> signal);
WebIDL::ExceptionOr<ReadableStreamPair> readable_stream_tee(JS::Realm&, ReadableStream&, bool clone_for_branch2);
WebIDL::ExceptionOr<ReadableStreamPair> readable_stream_default_tee(JS::Realm& realm, ReadableStream& stream, bool clone_for_branch2);
WebIDL::ExceptionOr<ReadableStreamPair> readable_byte_stream_tee(JS::Realm& realm, ReadableStream& stream);
JS::NonnullGCPtr<WebIDL::Promise> readable_stream_reader_generic_cancel(ReadableStreamGenericReaderMixin&, JS::Value reason);
void readable_stream_reader_generic_initialize(ReadableStreamReader, ReadableStream&);
void readable_stream_reader_generic_release(ReadableStreamGenericReaderMixin&);
void readable_stream_default_reader_error_read_requests(ReadableStreamDefaultReader&, JS::Value error);
void readable_stream_byob_reader_error_read_into_requests(ReadableStreamBYOBReader&, JS::Value error);
JS::Value readable_byte_stream_controller_convert_pull_into_descriptor(JS::Realm&, PullIntoDescriptor const&);
void readable_byte_stream_controller_pull_into(ReadableByteStreamController&, WebIDL::ArrayBufferView&, u64 min, ReadIntoRequest&);
void readable_stream_byob_reader_read(ReadableStreamBYOBReader&, WebIDL::ArrayBufferView&, u64 min, ReadIntoRequest&);
void readable_byte_stream_controller_fill_head_pull_into_descriptor(ReadableByteStreamController const&, u64 size, PullIntoDescriptor&);
void readable_stream_default_reader_read(ReadableStreamDefaultReader&, ReadRequest&);
void readable_stream_default_reader_release(ReadableStreamDefaultReader&);
void readable_stream_byob_reader_release(ReadableStreamBYOBReader&);
WebIDL::ExceptionOr<void> set_up_readable_stream_default_reader(ReadableStreamDefaultReader&, ReadableStream&);
WebIDL::ExceptionOr<void> set_up_readable_stream_byob_reader(ReadableStreamBYOBReader&, ReadableStream&);
void readable_stream_default_controller_close(ReadableStreamDefaultController&);
bool readable_stream_default_controller_has_backpressure(ReadableStreamDefaultController&);
WebIDL::ExceptionOr<void> readable_stream_default_controller_enqueue(ReadableStreamDefaultController&, JS::Value chunk);
void readable_stream_default_controller_can_pull_if_needed(ReadableStreamDefaultController&);
bool readable_stream_default_controller_should_call_pull(ReadableStreamDefaultController&);
void readable_stream_default_controller_clear_algorithms(ReadableStreamDefaultController&);
void readable_stream_default_controller_error(ReadableStreamDefaultController&, JS::Value error);
Optional<double> readable_stream_default_controller_get_desired_size(ReadableStreamDefaultController&);
bool readable_stream_default_controller_can_close_or_enqueue(ReadableStreamDefaultController&);
WebIDL::ExceptionOr<void> set_up_readable_stream_default_controller(ReadableStream&, ReadableStreamDefaultController&, JS::NonnullGCPtr<StartAlgorithm>, JS::NonnullGCPtr<PullAlgorithm>, JS::NonnullGCPtr<CancelAlgorithm>, double high_water_mark, JS::NonnullGCPtr<SizeAlgorithm>);
WebIDL::ExceptionOr<void> set_up_readable_stream_default_controller_from_underlying_source(ReadableStream&, JS::Value underlying_source_value, UnderlyingSource, double high_water_mark, JS::NonnullGCPtr<SizeAlgorithm>);
void set_up_readable_stream_controller_with_byte_reading_support(ReadableStream&, JS::GCPtr<PullAlgorithm> = {}, JS::GCPtr<CancelAlgorithm> = {}, double high_water_mark = 0);
WebIDL::ExceptionOr<void> set_up_readable_byte_stream_controller(ReadableStream&, ReadableByteStreamController&, JS::NonnullGCPtr<StartAlgorithm>, JS::NonnullGCPtr<PullAlgorithm>, JS::NonnullGCPtr<CancelAlgorithm>, double high_water_mark, JS::Value auto_allocate_chunk_size);
WebIDL::ExceptionOr<void> set_up_readable_byte_stream_controller_from_underlying_source(ReadableStream&, JS::Value underlying_source, UnderlyingSource const& underlying_source_dict, double high_water_mark);
JS::GCPtr<ReadableStreamBYOBRequest> readable_byte_stream_controller_get_byob_request(JS::NonnullGCPtr<ReadableByteStreamController>);
WebIDL::ExceptionOr<void> readable_byte_stream_controller_respond_in_readable_state(ReadableByteStreamController&, u64 bytes_written, PullIntoDescriptor&);
void readable_byte_stream_controller_respond_in_closed_state(ReadableByteStreamController&, PullIntoDescriptor&);
WebIDL::ExceptionOr<void> readable_byte_stream_controller_respond_internal(ReadableByteStreamController&, u64 bytes_written);
WebIDL::ExceptionOr<void> readable_byte_stream_controller_respond(ReadableByteStreamController&, u64 bytes_written);
WebIDL::ExceptionOr<void> readable_byte_stream_controller_respond_with_new_view(JS::Realm&, ReadableByteStreamController&, WebIDL::ArrayBufferView&);
WebIDL::ExceptionOr<void> readable_stream_enqueue(ReadableStreamController& controller, JS::Value chunk);
WebIDL::ExceptionOr<void> readable_byte_stream_controller_enqueue(ReadableByteStreamController& controller, JS::Value chunk);
WebIDL::ExceptionOr<void> readable_stream_pull_from_bytes(ReadableStream&, ByteBuffer bytes);
WebIDL::ExceptionOr<JS::NonnullGCPtr<JS::ArrayBuffer>> transfer_array_buffer(JS::Realm& realm, JS::ArrayBuffer& buffer);
WebIDL::ExceptionOr<void> readable_byte_stream_controller_enqueue_detached_pull_into_queue(ReadableByteStreamController& controller, PullIntoDescriptor& pull_into_descriptor);
void readable_byte_stream_controller_commit_pull_into_descriptor(ReadableStream&, PullIntoDescriptor const&);
void readable_byte_stream_controller_process_read_requests_using_queue(ReadableByteStreamController& controller);
void readable_byte_stream_controller_process_pull_into_descriptors_using_queue(ReadableByteStreamController&);
void readable_byte_stream_controller_enqueue_chunk_to_queue(ReadableByteStreamController& controller, JS::NonnullGCPtr<JS::ArrayBuffer> buffer, u32 byte_offset, u32 byte_length);
WebIDL::ExceptionOr<void> readable_byte_stream_controller_enqueue_cloned_chunk_to_queue(ReadableByteStreamController& controller, JS::ArrayBuffer& buffer, u64 byte_offset, u64 byte_length);
PullIntoDescriptor readable_byte_stream_controller_shift_pending_pull_into(ReadableByteStreamController& controller);
void readable_byte_stream_controller_call_pull_if_needed(ReadableByteStreamController&);
void readable_byte_stream_controller_clear_algorithms(ReadableByteStreamController&);
void readable_byte_stream_controller_clear_pending_pull_intos(ReadableByteStreamController&);
WebIDL::ExceptionOr<void> readable_byte_stream_controller_close(ReadableByteStreamController&);
void readable_byte_stream_controller_error(ReadableByteStreamController&, JS::Value error);
void readable_byte_stream_controller_fill_read_request_from_queue(ReadableByteStreamController&, JS::NonnullGCPtr<ReadRequest>);
bool readable_byte_stream_controller_fill_pull_into_descriptor_from_queue(ReadableByteStreamController&, PullIntoDescriptor&);
Optional<double> readable_byte_stream_controller_get_desired_size(ReadableByteStreamController const&);
void readable_byte_stream_controller_handle_queue_drain(ReadableByteStreamController&);
void readable_byte_stream_controller_invalidate_byob_request(ReadableByteStreamController&);
bool readable_byte_stream_controller_should_call_pull(ReadableByteStreamController const&);
WebIDL::ExceptionOr<void> set_up_readable_stream(JS::Realm& realm, ReadableStream& stream, JS::NonnullGCPtr<StartAlgorithm> start_algorithm, JS::NonnullGCPtr<PullAlgorithm> pull_algorithm, JS::NonnullGCPtr<CancelAlgorithm> cancel_algorithm, Optional<double> high_water_mark = {}, JS::GCPtr<SizeAlgorithm> size_algorithm = {});
WebIDL::ExceptionOr<JS::NonnullGCPtr<ReadableStream>> create_readable_stream(JS::Realm& realm, JS::NonnullGCPtr<StartAlgorithm> start_algorithm, JS::NonnullGCPtr<PullAlgorithm> pull_algorithm, JS::NonnullGCPtr<CancelAlgorithm> cancel_algorithm, Optional<double> high_water_mark = {}, JS::GCPtr<SizeAlgorithm> size_algorithm = {});
WebIDL::ExceptionOr<JS::NonnullGCPtr<ReadableStream>> create_readable_byte_stream(JS::Realm& realm, JS::NonnullGCPtr<StartAlgorithm> start_algorithm, JS::NonnullGCPtr<PullAlgorithm> pull_algorithm, JS::NonnullGCPtr<CancelAlgorithm> cancel_algorithm);
WebIDL::ExceptionOr<JS::NonnullGCPtr<WritableStream>> create_writable_stream(JS::Realm& realm, JS::NonnullGCPtr<StartAlgorithm> start_algorithm, JS::NonnullGCPtr<WriteAlgorithm> write_algorithm, JS::NonnullGCPtr<CloseAlgorithm> close_algorithm, JS::NonnullGCPtr<AbortAlgorithm> abort_algorithm, double high_water_mark, JS::NonnullGCPtr<SizeAlgorithm> size_algorithm);
void initialize_readable_stream(ReadableStream&);
void initialize_writable_stream(WritableStream&);
WebIDL::ExceptionOr<JS::NonnullGCPtr<WritableStreamDefaultWriter>> acquire_writable_stream_default_writer(WritableStream&);
bool is_writable_stream_locked(WritableStream const&);
WebIDL::ExceptionOr<void> set_up_writable_stream_default_writer(WritableStreamDefaultWriter&, WritableStream&);
JS::NonnullGCPtr<WebIDL::Promise> writable_stream_abort(WritableStream&, JS::Value reason);
JS::NonnullGCPtr<WebIDL::Promise> writable_stream_close(WritableStream&);
JS::NonnullGCPtr<WebIDL::Promise> writable_stream_add_write_request(WritableStream&);
bool writable_stream_close_queued_or_in_flight(WritableStream const&);
void writable_stream_deal_with_rejection(WritableStream&, JS::Value error);
void writable_stream_finish_erroring(WritableStream&);
void writable_stream_finish_in_flight_close(WritableStream&);
void writable_stream_finish_in_flight_close_with_error(WritableStream&, JS::Value error);
void writable_stream_finish_in_flight_write(WritableStream&);
void writable_stream_finish_in_flight_write_with_error(WritableStream&, JS::Value error);
bool writable_stream_has_operation_marked_in_flight(WritableStream const&);
void writable_stream_mark_close_request_in_flight(WritableStream&);
void writable_stream_mark_first_write_request_in_flight(WritableStream&);
void writable_stream_reject_close_and_closed_promise_if_needed(WritableStream&);
void writable_stream_start_erroring(WritableStream&, JS::Value reason);
void writable_stream_update_backpressure(WritableStream&, bool backpressure);
JS::NonnullGCPtr<WebIDL::Promise> writable_stream_default_writer_abort(WritableStreamDefaultWriter&, JS::Value reason);
JS::NonnullGCPtr<WebIDL::Promise> writable_stream_default_writer_close(WritableStreamDefaultWriter&);
void writable_stream_default_writer_ensure_closed_promise_rejected(WritableStreamDefaultWriter&, JS::Value error);
void writable_stream_default_writer_ensure_ready_promise_rejected(WritableStreamDefaultWriter&, JS::Value error);
Optional<double> writable_stream_default_writer_get_desired_size(WritableStreamDefaultWriter const&);
void writable_stream_default_writer_release(WritableStreamDefaultWriter&);
JS::NonnullGCPtr<WebIDL::Promise> writable_stream_default_writer_write(WritableStreamDefaultWriter&, JS::Value chunk);
WebIDL::ExceptionOr<void> set_up_writable_stream_default_controller(WritableStream&, WritableStreamDefaultController&, JS::NonnullGCPtr<StartAlgorithm>, JS::NonnullGCPtr<WriteAlgorithm>, JS::NonnullGCPtr<CloseAlgorithm>, JS::NonnullGCPtr<AbortAlgorithm>, double high_water_mark, JS::NonnullGCPtr<SizeAlgorithm>);
WebIDL::ExceptionOr<void> set_up_writable_stream_default_controller_from_underlying_sink(WritableStream&, JS::Value underlying_sink_value, UnderlyingSink&, double high_water_mark, JS::NonnullGCPtr<SizeAlgorithm> size_algorithm);
void writable_stream_default_controller_advance_queue_if_needed(WritableStreamDefaultController&);
void writable_stream_default_controller_clear_algorithms(WritableStreamDefaultController&);
void writable_stream_default_controller_close(WritableStreamDefaultController&);
void writable_stream_default_controller_error(WritableStreamDefaultController&, JS::Value error);
void writable_stream_default_controller_error_if_needed(WritableStreamDefaultController&, JS::Value error);
bool writable_stream_default_controller_get_backpressure(WritableStreamDefaultController const&);
JS::Value writable_stream_default_controller_get_chunk_size(WritableStreamDefaultController&, JS::Value chunk);
double writable_stream_default_controller_get_desired_size(WritableStreamDefaultController const&);
void writable_stream_default_controller_process_close(WritableStreamDefaultController&);
void writable_stream_default_controller_process_write(WritableStreamDefaultController&, JS::Value chunk);
void writable_stream_default_controller_write(WritableStreamDefaultController&, JS::Value chunk, JS::Value chunk_size);
void initialize_transform_stream(TransformStream&, JS::NonnullGCPtr<WebIDL::Promise> start_promise, double writable_high_water_mark, JS::NonnullGCPtr<SizeAlgorithm> writable_size_algorithm, double readable_high_water_mark, JS::NonnullGCPtr<SizeAlgorithm> readable_size_algorithm);
void set_up_transform_stream_default_controller(TransformStream&, TransformStreamDefaultController&, JS::NonnullGCPtr<TransformAlgorithm>, JS::NonnullGCPtr<FlushAlgorithm>, JS::NonnullGCPtr<CancelAlgorithm>);
void set_up_transform_stream_default_controller_from_transformer(TransformStream&, JS::Value transformer, Transformer&);
void transform_stream_default_controller_clear_algorithms(TransformStreamDefaultController&);
WebIDL::ExceptionOr<void> transform_stream_default_controller_enqueue(TransformStreamDefaultController&, JS::Value chunk);
void transform_stream_default_controller_error(TransformStreamDefaultController&, JS::Value error);
void transform_stream_default_controller_terminate(TransformStreamDefaultController&);
JS::NonnullGCPtr<WebIDL::Promise> transform_stream_default_controller_perform_transform(TransformStreamDefaultController&, JS::Value chunk);
JS::NonnullGCPtr<WebIDL::Promise> transform_stream_default_sink_abort_algorithm(TransformStream&, JS::Value reason);
JS::NonnullGCPtr<WebIDL::Promise> transform_stream_default_sink_close_algorithm(TransformStream&);
JS::NonnullGCPtr<WebIDL::Promise> transform_stream_default_sink_write_algorithm(TransformStream&, JS::Value chunk);
JS::NonnullGCPtr<WebIDL::Promise> transform_stream_default_source_pull_algorithm(TransformStream&);
JS::NonnullGCPtr<WebIDL::Promise> transform_stream_default_source_cancel_algorithm(TransformStream&, JS::Value reason);
void transform_stream_error(TransformStream&, JS::Value error);
void transform_stream_error_writable_and_unblock_write(TransformStream&, JS::Value error);
void transform_stream_set_backpressure(TransformStream&, bool backpressure);
void transform_stream_set_up(TransformStream&, JS::NonnullGCPtr<TransformAlgorithm>, JS::GCPtr<FlushAlgorithm> = {}, JS::GCPtr<CancelAlgorithm> = {});
void transform_stream_unblock_write(TransformStream&);
bool is_non_negative_number(JS::Value);
bool can_transfer_array_buffer(JS::ArrayBuffer const& array_buffer);
WebIDL::ExceptionOr<JS::Value> clone_as_uint8_array(JS::Realm&, WebIDL::ArrayBufferView&);
WebIDL::ExceptionOr<JS::Value> structured_clone(JS::Realm&, JS::Value value);
JS::Value create_close_sentinel();
bool is_close_sentinel(JS::Value);
JS::ThrowCompletionOr<JS::Handle<WebIDL::CallbackType>> property_to_callback(JS::VM& vm, JS::Value value, JS::PropertyKey const& property_key, WebIDL::OperationReturnsPromise);
// https://streams.spec.whatwg.org/#value-with-size
struct ValueWithSize {
JS::Value value;
double size;
};
// https://streams.spec.whatwg.org/#dequeue-value
template<typename T>
JS::Value dequeue_value(T& container)
{
// 1. Assert: container has [[queue]] and [[queueTotalSize]] internal slots.
// 2. Assert: container.[[queue]] is not empty.
VERIFY(!container.queue().is_empty());
// 3. Let valueWithSize be container.[[queue]][0].
// 4. Remove valueWithSize from container.[[queue]].
auto value_with_size = container.queue().take_first();
// 5. Set container.[[queueTotalSize]] to container.[[queueTotalSize]] valueWithSizes size.
container.set_queue_total_size(container.queue_total_size() - value_with_size.size);
// 6. If container.[[queueTotalSize]] < 0, set container.[[queueTotalSize]] to 0. (This can occur due to rounding errors.)
if (container.queue_total_size() < 0.0)
container.set_queue_total_size(0.0);
// 7. Return valueWithSizes value.
return value_with_size.value;
}
// https://streams.spec.whatwg.org/#enqueue-value-with-size
template<typename T>
WebIDL::ExceptionOr<void> enqueue_value_with_size(T& container, JS::Value value, JS::Value size)
{
// 1. Assert: container has [[queue]] and [[queueTotalSize]] internal slots.
// 2. If ! IsNonNegativeNumber(size) is false, throw a RangeError exception.
if (!is_non_negative_number(size))
return WebIDL::SimpleException { WebIDL::SimpleExceptionType::RangeError, "Chunk has non-positive size"sv };
// 3. If size is +∞, throw a RangeError exception.
if (size.is_positive_infinity())
return WebIDL::SimpleException { WebIDL::SimpleExceptionType::RangeError, "Chunk has infinite size"sv };
// 4. Append a new value-with-size with value value and size size to container.[[queue]].
container.queue().append({ value, size.as_double() });
// 5. Set container.[[queueTotalSize]] to container.[[queueTotalSize]] + size.
container.set_queue_total_size(container.queue_total_size() + size.as_double());
return {};
}
// https://streams.spec.whatwg.org/#peek-queue-value
template<typename T>
JS::Value peek_queue_value(T& container)
{
// 1. Assert: container has [[queue]] and [[queueTotalSize]] internal slots.
// 2. Assert: container.[[queue]] is not empty.
VERIFY(!container.queue().is_empty());
// 3. Let valueWithSize be container.[[queue]][0].
auto& value_with_size = container.queue().first();
// 4. Return valueWithSizes value.
return value_with_size.value;
}
// https://streams.spec.whatwg.org/#reset-queue
template<typename T>
void reset_queue(T& container)
{
// 1. Assert: container has [[queue]] and [[queueTotalSize]] internal slots.
// 2. Set container.[[queue]] to a new empty list.
container.queue().clear();
// 3. Set container.[[queueTotalSize]] to 0.
container.set_queue_total_size(0);
}
}

View file

@ -0,0 +1,46 @@
/*
* Copyright (c) 2023, Shannon Booth <shannon@serenityos.org>
* Copyright (c) 2023, Matthew Olsson <mattco@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibWeb/Bindings/ByteLengthQueuingStrategyPrototype.h>
#include <LibWeb/Bindings/Intrinsics.h>
#include <LibWeb/HTML/Window.h>
#include <LibWeb/Streams/ByteLengthQueuingStrategy.h>
namespace Web::Streams {
JS_DEFINE_ALLOCATOR(ByteLengthQueuingStrategy);
// https://streams.spec.whatwg.org/#blqs-constructor
JS::NonnullGCPtr<ByteLengthQueuingStrategy> ByteLengthQueuingStrategy::construct_impl(JS::Realm& realm, QueuingStrategyInit const& init)
{
// The new ByteLengthQueuingStrategy(init) constructor steps are:
// 1. Set this.[[highWaterMark]] to init["highWaterMark"].
return realm.heap().allocate<ByteLengthQueuingStrategy>(realm, realm, init.high_water_mark);
}
ByteLengthQueuingStrategy::ByteLengthQueuingStrategy(JS::Realm& realm, double high_water_mark)
: PlatformObject(realm)
, m_high_water_mark(high_water_mark)
{
}
ByteLengthQueuingStrategy::~ByteLengthQueuingStrategy() = default;
// https://streams.spec.whatwg.org/#blqs-size
JS::NonnullGCPtr<WebIDL::CallbackType> ByteLengthQueuingStrategy::size()
{
// 1. Return this's relevant global object's byte length queuing strategy size function.
return verify_cast<HTML::Window>(HTML::relevant_global_object(*this)).byte_length_queuing_strategy_size_function();
}
void ByteLengthQueuingStrategy::initialize(JS::Realm& realm)
{
Base::initialize(realm);
WEB_SET_PROTOTYPE_FOR_INTERFACE(ByteLengthQueuingStrategy);
}
}

View file

@ -0,0 +1,46 @@
/*
* Copyright (c) 2023, Shannon Booth <shannon@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/Forward.h>
#include <LibJS/Forward.h>
#include <LibWeb/Bindings/PlatformObject.h>
#include <LibWeb/Forward.h>
#include <LibWeb/Streams/QueuingStrategyInit.h>
namespace Web::Streams {
// https://streams.spec.whatwg.org/#bytelengthqueuingstrategy
class ByteLengthQueuingStrategy final : public Bindings::PlatformObject {
WEB_PLATFORM_OBJECT(ByteLengthQueuingStrategy, Bindings::PlatformObject);
JS_DECLARE_ALLOCATOR(ByteLengthQueuingStrategy);
public:
static JS::NonnullGCPtr<ByteLengthQueuingStrategy> construct_impl(JS::Realm&, QueuingStrategyInit const&);
virtual ~ByteLengthQueuingStrategy() override;
// https://streams.spec.whatwg.org/#blqs-high-water-mark
double high_water_mark() const
{
// The highWaterMark getter steps are:
// 1. Return this.[[highWaterMark]].
return m_high_water_mark;
}
JS::NonnullGCPtr<WebIDL::CallbackType> size();
private:
explicit ByteLengthQueuingStrategy(JS::Realm&, double high_water_mark);
virtual void initialize(JS::Realm&) override;
// https://streams.spec.whatwg.org/#bytelengthqueuingstrategy-highwatermark
double m_high_water_mark { 0 };
};
}

View file

@ -0,0 +1,11 @@
#import <WebIDL/Function.idl>
#import <Streams/QueuingStrategyInit.idl>
// https://streams.spec.whatwg.org/#blqs-class-definition
[Exposed=*]
interface ByteLengthQueuingStrategy {
constructor(QueuingStrategyInit init);
readonly attribute unrestricted double highWaterMark;
readonly attribute Function size;
};

View file

@ -0,0 +1,46 @@
/*
* Copyright (c) 2023, Shannon Booth <shannon@serenityos.org>
* Copyright (c) 2023, Matthew Olsson <mattco@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibWeb/Bindings/CountQueuingStrategyPrototype.h>
#include <LibWeb/Bindings/Intrinsics.h>
#include <LibWeb/HTML/Window.h>
#include <LibWeb/Streams/CountQueuingStrategy.h>
namespace Web::Streams {
JS_DEFINE_ALLOCATOR(CountQueuingStrategy);
// https://streams.spec.whatwg.org/#blqs-constructor
JS::NonnullGCPtr<CountQueuingStrategy> CountQueuingStrategy::construct_impl(JS::Realm& realm, QueuingStrategyInit const& init)
{
// The new CountQueuingStrategy(init) constructor steps are:
// 1. Set this.[[highWaterMark]] to init["highWaterMark"].
return realm.heap().allocate<CountQueuingStrategy>(realm, realm, init.high_water_mark);
}
CountQueuingStrategy::CountQueuingStrategy(JS::Realm& realm, double high_water_mark)
: PlatformObject(realm)
, m_high_water_mark(high_water_mark)
{
}
CountQueuingStrategy::~CountQueuingStrategy() = default;
// https://streams.spec.whatwg.org/#cqs-size
JS::NonnullGCPtr<WebIDL::CallbackType> CountQueuingStrategy::size()
{
// 1. Return this's relevant global object's count queuing strategy size function.
return verify_cast<HTML::Window>(HTML::relevant_global_object(*this)).count_queuing_strategy_size_function();
}
void CountQueuingStrategy::initialize(JS::Realm& realm)
{
Base::initialize(realm);
WEB_SET_PROTOTYPE_FOR_INTERFACE(CountQueuingStrategy);
}
}

View file

@ -0,0 +1,46 @@
/*
* Copyright (c) 2023, Shannon Booth <shannon@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/Forward.h>
#include <LibJS/Forward.h>
#include <LibWeb/Bindings/PlatformObject.h>
#include <LibWeb/Forward.h>
#include <LibWeb/Streams/QueuingStrategyInit.h>
namespace Web::Streams {
// https://streams.spec.whatwg.org/#countqueuingstrategy
class CountQueuingStrategy final : public Bindings::PlatformObject {
WEB_PLATFORM_OBJECT(CountQueuingStrategy, Bindings::PlatformObject);
JS_DECLARE_ALLOCATOR(CountQueuingStrategy);
public:
static JS::NonnullGCPtr<CountQueuingStrategy> construct_impl(JS::Realm&, QueuingStrategyInit const&);
virtual ~CountQueuingStrategy() override;
// https://streams.spec.whatwg.org/#cqs-high-water-mark
double high_water_mark() const
{
// The highWaterMark getter steps are:
// 1. Return this.[[highWaterMark]].
return m_high_water_mark;
}
JS::NonnullGCPtr<WebIDL::CallbackType> size();
private:
explicit CountQueuingStrategy(JS::Realm&, double high_water_mark);
virtual void initialize(JS::Realm&) override;
// https://streams.spec.whatwg.org/#countqueuingstrategy-highwatermark
double m_high_water_mark { 0 };
};
}

View file

@ -0,0 +1,11 @@
#import <WebIDL/Function.idl>
#import <Streams/QueuingStrategyInit.idl>
// https://streams.spec.whatwg.org/#cqs-class-definition
[Exposed=*]
interface CountQueuingStrategy {
constructor(QueuingStrategyInit init);
readonly attribute unrestricted double highWaterMark;
readonly attribute Function size;
};

View file

@ -0,0 +1,21 @@
/*
* Copyright (c) 2023, Shannon Booth <shannon@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/Optional.h>
#include <LibJS/Heap/GCPtr.h>
#include <LibWeb/WebIDL/CallbackType.h>
namespace Web::Streams {
// https://streams.spec.whatwg.org/#dictdef-queuingstrategy
struct QueuingStrategy {
Optional<double> high_water_mark;
JS::GCPtr<WebIDL::CallbackType> size;
};
}

View file

@ -0,0 +1,7 @@
callback QueuingStrategySize = unrestricted double (any chunk);
// https://streams.spec.whatwg.org/#dictdef-queuingstrategy
dictionary QueuingStrategy {
unrestricted double highWaterMark;
QueuingStrategySize size;
};

View file

@ -0,0 +1,16 @@
/*
* Copyright (c) 2023, Shannon Booth <shannon@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
namespace Web::Streams {
// https://streams.spec.whatwg.org/#dictdef-queuingstrategyinit
struct QueuingStrategyInit {
double high_water_mark;
};
}

View file

@ -0,0 +1,4 @@
// https://streams.spec.whatwg.org/#dictdef-queuingstrategyinit
dictionary QueuingStrategyInit {
required unrestricted double highWaterMark;
};

View file

@ -0,0 +1,207 @@
/*
* Copyright (c) 2023, Matthew Olsson <mattco@serenityos.org>
* Copyright (c) 2023-2024, Shannon Booth <shannon@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibJS/Runtime/TypedArray.h>
#include <LibWeb/Bindings/Intrinsics.h>
#include <LibWeb/Bindings/ReadableByteStreamControllerPrototype.h>
#include <LibWeb/Streams/AbstractOperations.h>
#include <LibWeb/Streams/ReadableByteStreamController.h>
#include <LibWeb/Streams/ReadableStream.h>
#include <LibWeb/Streams/ReadableStreamBYOBRequest.h>
#include <LibWeb/Streams/ReadableStreamDefaultReader.h>
#include <LibWeb/WebIDL/Buffers.h>
namespace Web::Streams {
JS_DEFINE_ALLOCATOR(ReadableByteStreamController);
// https://streams.spec.whatwg.org/#rbs-controller-desired-size
Optional<double> ReadableByteStreamController::desired_size() const
{
// 1. Return ! ReadableByteStreamControllerGetDesiredSize(this).
return readable_byte_stream_controller_get_desired_size(*this);
}
// https://streams.spec.whatwg.org/#rbs-controller-byob-request
JS::GCPtr<ReadableStreamBYOBRequest> ReadableByteStreamController::byob_request()
{
// 1. Return ! ReadableByteStreamControllerGetBYOBRequest(this).
return readable_byte_stream_controller_get_byob_request(*this);
}
// https://streams.spec.whatwg.org/#rbs-controller-close
WebIDL::ExceptionOr<void> ReadableByteStreamController::close()
{
// 1. If this.[[closeRequested]] is true, throw a TypeError exception.
if (m_close_requested)
return WebIDL::SimpleException { WebIDL::SimpleExceptionType::TypeError, "Controller is already closed"sv };
// 2. If this.[[stream]].[[state]] is not "readable", throw a TypeError exception.
if (m_stream->state() != ReadableStream::State::Readable) {
auto message = m_stream->state() == ReadableStream::State::Closed ? "Cannot close a closed stream"sv : "Cannot close an errored stream"sv;
return WebIDL::SimpleException { WebIDL::SimpleExceptionType::TypeError, message };
}
// 3. Perform ? ReadableByteStreamControllerClose(this).
TRY(readable_byte_stream_controller_close(*this));
return {};
}
// https://streams.spec.whatwg.org/#rbs-controller-error
void ReadableByteStreamController::error(JS::Value error)
{
// 1. Perform ! ReadableByteStreamControllerError(this, e).
readable_byte_stream_controller_error(*this, error);
}
ReadableByteStreamController::ReadableByteStreamController(JS::Realm& realm)
: Bindings::PlatformObject(realm)
{
}
void ReadableByteStreamController::initialize(JS::Realm& realm)
{
Base::initialize(realm);
WEB_SET_PROTOTYPE_FOR_INTERFACE(ReadableByteStreamController);
}
// https://streams.spec.whatwg.org/#rbs-controller-enqueue
WebIDL::ExceptionOr<void> ReadableByteStreamController::enqueue(JS::Handle<WebIDL::ArrayBufferView>& chunk)
{
// 1. If chunk.[[ByteLength]] is 0, throw a TypeError exception.
// 2. If chunk.[[ViewedArrayBuffer]].[[ArrayBufferByteLength]] is 0, throw a TypeError exception.
if (chunk->byte_length() == 0 || chunk->viewed_array_buffer()->byte_length() == 0)
return WebIDL::SimpleException { WebIDL::SimpleExceptionType::TypeError, "Cannot enqueue chunk with byte length of zero"sv };
// 3. If this.[[closeRequested]] is true, throw a TypeError exception.
if (m_close_requested)
return WebIDL::SimpleException { WebIDL::SimpleExceptionType::TypeError, "Close is requested for controller"sv };
// 4. If this.[[stream]].[[state]] is not "readable", throw a TypeError exception.
if (!m_stream->is_readable())
return WebIDL::SimpleException { WebIDL::SimpleExceptionType::TypeError, "Stream is not readable"sv };
// 5. Return ? ReadableByteStreamControllerEnqueue(this, chunk).
return readable_byte_stream_controller_enqueue(*this, chunk->raw_object());
}
// https://streams.spec.whatwg.org/#rbs-controller-private-cancel
JS::NonnullGCPtr<WebIDL::Promise> ReadableByteStreamController::cancel_steps(JS::Value reason)
{
// 1. Perform ! ReadableByteStreamControllerClearPendingPullIntos(this).
readable_byte_stream_controller_clear_pending_pull_intos(*this);
// 2. Perform ! ResetQueue(this).
reset_queue(*this);
// 3. Let result be the result of performing this.[[cancelAlgorithm]], passing in reason.
auto result = m_cancel_algorithm->function()(reason);
// 4. Perform ! ReadableByteStreamControllerClearAlgorithms(this).
readable_byte_stream_controller_clear_algorithms(*this);
// 5. Return result.
return result;
}
// https://streams.spec.whatwg.org/#rbs-controller-private-pull
void ReadableByteStreamController::pull_steps(JS::NonnullGCPtr<ReadRequest> read_request)
{
auto& realm = this->realm();
// 1. Let stream be this.[[stream]].
// 2. Assert: ! ReadableStreamHasDefaultReader(stream) is true.
VERIFY(readable_stream_has_default_reader(*m_stream));
// 3. If this.[[queueTotalSize]] > 0,
if (m_queue_total_size > 0) {
// 1. Assert: ! ReadableStreamGetNumReadRequests(stream) is 0.
VERIFY(readable_stream_get_num_read_requests(*m_stream) == 0);
// 2. Perform ! ReadableByteStreamControllerFillReadRequestFromQueue(this, readRequest).
readable_byte_stream_controller_fill_read_request_from_queue(*this, read_request);
// 3. Return.
return;
}
// 4. Let autoAllocateChunkSize be this.[[autoAllocateChunkSize]].
// 5. If autoAllocateChunkSize is not undefined,
if (m_auto_allocate_chunk_size.has_value()) {
// 1. Let buffer be Construct(%ArrayBuffer%, « autoAllocateChunkSize »).
auto buffer = JS::ArrayBuffer::create(realm, *m_auto_allocate_chunk_size);
// 2. If buffer is an abrupt completion,
if (buffer.is_throw_completion()) {
// 1. Perform readRequests error steps, given buffer.[[Value]].
read_request->on_error(*buffer.throw_completion().value());
// 2. Return.
return;
}
// 3. Let pullIntoDescriptor be a new pull-into descriptor with buffer buffer.[[Value]], buffer byte length autoAllocateChunkSize, byte offset 0,
// byte length autoAllocateChunkSize, bytes filled 0, element size 1, view constructor %Uint8Array%, and reader type "default".
PullIntoDescriptor pull_into_descriptor {
.buffer = buffer.release_value(),
.buffer_byte_length = *m_auto_allocate_chunk_size,
.byte_offset = 0,
.byte_length = *m_auto_allocate_chunk_size,
.bytes_filled = 0,
.minimum_fill = 1,
.element_size = 1,
.view_constructor = *realm.intrinsics().uint8_array_constructor(),
.reader_type = ReaderType::Default,
};
// 4. Append pullIntoDescriptor to this.[[pendingPullIntos]].
m_pending_pull_intos.append(move(pull_into_descriptor));
}
// 6. Perform ! ReadableStreamAddReadRequest(stream, readRequest).
readable_stream_add_read_request(*m_stream, read_request);
// 7. Perform ! ReadableByteStreamControllerCallPullIfNeeded(this).
readable_byte_stream_controller_call_pull_if_needed(*this);
}
// https://streams.spec.whatwg.org/#rbs-controller-private-pull
void ReadableByteStreamController::release_steps()
{
// 1. If this.[[pendingPullIntos]] is not empty,
if (!m_pending_pull_intos.is_empty()) {
// 1. Let firstPendingPullInto be this.[[pendingPullIntos]][0].
auto first_pending_pull_into = m_pending_pull_intos.first();
// 2. Set firstPendingPullIntos reader type to "none".
first_pending_pull_into.reader_type = ReaderType::None;
// 3. Set this.[[pendingPullIntos]] to the list « firstPendingPullInto ».
m_pending_pull_intos.clear();
m_pending_pull_intos.append(first_pending_pull_into);
}
}
void ReadableByteStreamController::visit_edges(Cell::Visitor& visitor)
{
Base::visit_edges(visitor);
visitor.visit(m_byob_request);
for (auto const& pending_pull_into : m_pending_pull_intos) {
visitor.visit(pending_pull_into.buffer);
visitor.visit(pending_pull_into.view_constructor);
}
for (auto const& item : m_queue)
visitor.visit(item.buffer);
visitor.visit(m_stream);
visitor.visit(m_cancel_algorithm);
visitor.visit(m_pull_algorithm);
}
}

View file

@ -0,0 +1,198 @@
/*
* Copyright (c) 2023, Matthew Olsson <mattco@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/Optional.h>
#include <AK/SinglyLinkedList.h>
#include <LibWeb/Bindings/PlatformObject.h>
#include <LibWeb/Streams/AbstractOperations.h>
namespace Web::Streams {
enum class ReaderType {
Default,
Byob,
None,
};
// https://streams.spec.whatwg.org/#pull-into-descriptor
struct PullIntoDescriptor {
// https://streams.spec.whatwg.org/#pull-into-descriptor-buffer
// An ArrayBuffer
JS::NonnullGCPtr<JS::ArrayBuffer> buffer;
// https://streams.spec.whatwg.org/#pull-into-descriptor-buffer-byte-length
// A positive integer representing the initial byte length of buffer
u64 buffer_byte_length;
// https://streams.spec.whatwg.org/#pull-into-descriptor-byte-offset
// A nonnegative integer byte offset into the buffer where the underlying byte source will start writing
u64 byte_offset;
// https://streams.spec.whatwg.org/#pull-into-descriptor-byte-length
// A positive integer number of bytes which can be written into the buffer
u64 byte_length;
// https://streams.spec.whatwg.org/#pull-into-descriptor-bytes-filled
// A nonnegative integer number of bytes that have been written into the buffer so far
u64 bytes_filled;
// https://streams.spec.whatwg.org/#pull-into-descriptor-minimum-fill
// A positive integer representing the minimum number of bytes that must be written into the buffer before the associated read() request may be fulfilled. By default, this equals the element size.
u64 minimum_fill;
// https://streams.spec.whatwg.org/#pull-into-descriptor-element-size
// A positive integer representing the number of bytes that can be written into the buffer at a time, using views of the type described by the view constructor
u64 element_size;
// https://streams.spec.whatwg.org/#pull-into-descriptor-view-constructor
// A typed array constructor or %DataView%, which will be used for constructing a view with which to write into the buffer
JS::NonnullGCPtr<JS::NativeFunction> view_constructor;
// https://streams.spec.whatwg.org/#pull-into-descriptor-reader-type
// Either "default" or "byob", indicating what type of readable stream reader initiated this request, or "none" if the initiating reader was released
ReaderType reader_type;
};
// https://streams.spec.whatwg.org/#readable-byte-stream-queue-entry
struct ReadableByteStreamQueueEntry {
// https://streams.spec.whatwg.org/#readable-byte-stream-queue-entry-buffer
// An ArrayBuffer, which will be a transferred version of the one originally supplied by the underlying byte source
JS::NonnullGCPtr<JS::ArrayBuffer> buffer;
// https://streams.spec.whatwg.org/#readable-byte-stream-queue-entry-byte-offset
// A nonnegative integer number giving the byte offset derived from the view originally supplied by the underlying byte source
u64 byte_offset;
// https://streams.spec.whatwg.org/#readable-byte-stream-queue-entry-byte-length
// A nonnegative integer number giving the byte length derived from the view originally supplied by the underlying byte source
u64 byte_length;
};
// https://streams.spec.whatwg.org/#readablebytestreamcontroller
class ReadableByteStreamController : public Bindings::PlatformObject {
WEB_PLATFORM_OBJECT(ReadableByteStreamController, Bindings::PlatformObject);
JS_DECLARE_ALLOCATOR(ReadableByteStreamController);
public:
virtual ~ReadableByteStreamController() override = default;
// IDL getter, returns current [[byobRequest]] (if any), and otherwise the [[byobRequest]] for the next pending pull into request
JS::GCPtr<ReadableStreamBYOBRequest> byob_request();
void set_byob_request(JS::GCPtr<ReadableStreamBYOBRequest> request) { m_byob_request = request; }
// Raw [[byobRequest]] slot
JS::GCPtr<ReadableStreamBYOBRequest const> raw_byob_request() const { return m_byob_request; }
JS::GCPtr<ReadableStreamBYOBRequest> raw_byob_request() { return m_byob_request; }
Optional<double> desired_size() const;
WebIDL::ExceptionOr<void> close();
void error(JS::Value error);
WebIDL::ExceptionOr<void> enqueue(JS::Handle<WebIDL::ArrayBufferView>&);
Optional<u64> const& auto_allocate_chunk_size() { return m_auto_allocate_chunk_size; }
void set_auto_allocate_chunk_size(Optional<u64> value) { m_auto_allocate_chunk_size = value; }
JS::GCPtr<CancelAlgorithm> cancel_algorithm() { return m_cancel_algorithm; }
void set_cancel_algorithm(JS::GCPtr<CancelAlgorithm> value) { m_cancel_algorithm = value; }
bool close_requested() const { return m_close_requested; }
void set_close_requested(bool value) { m_close_requested = value; }
bool pull_again() const { return m_pull_again; }
void set_pull_again(bool value) { m_pull_again = value; }
JS::GCPtr<PullAlgorithm> pull_algorithm() { return m_pull_algorithm; }
void set_pull_algorithm(JS::GCPtr<PullAlgorithm> value) { m_pull_algorithm = value; }
bool pulling() const { return m_pulling; }
void set_pulling(bool value) { m_pulling = value; }
SinglyLinkedList<PullIntoDescriptor>& pending_pull_intos() { return m_pending_pull_intos; }
SinglyLinkedList<PullIntoDescriptor> const& pending_pull_intos() const { return m_pending_pull_intos; }
SinglyLinkedList<ReadableByteStreamQueueEntry>& queue() { return m_queue; }
double queue_total_size() const { return m_queue_total_size; }
void set_queue_total_size(double size) { m_queue_total_size = size; }
bool started() const { return m_started; }
void set_started(bool value) { m_started = value; }
double strategy_hwm() const { return m_strategy_hwm; }
void set_strategy_hwm(double value) { m_strategy_hwm = value; }
JS::GCPtr<ReadableStream const> stream() const { return m_stream; }
JS::GCPtr<ReadableStream> stream() { return m_stream; }
void set_stream(JS::GCPtr<ReadableStream> stream) { m_stream = stream; }
JS::NonnullGCPtr<WebIDL::Promise> cancel_steps(JS::Value reason);
void pull_steps(JS::NonnullGCPtr<ReadRequest>);
void release_steps();
private:
explicit ReadableByteStreamController(JS::Realm&);
virtual void visit_edges(Cell::Visitor&) override;
virtual void initialize(JS::Realm&) override;
// https://streams.spec.whatwg.org/#readablebytestreamcontroller-autoallocatechunksize
// A positive integer, when the automatic buffer allocation feature is enabled. In that case, this value specifies the size of buffer to allocate. It is undefined otherwise.
Optional<u64> m_auto_allocate_chunk_size;
// https://streams.spec.whatwg.org/#readablebytestreamcontroller-byobrequest
// A ReadableStreamBYOBRequest instance representing the current BYOB pull request, or null if there are no pending requests
JS::GCPtr<ReadableStreamBYOBRequest> m_byob_request;
// https://streams.spec.whatwg.org/#readablestreamdefaultcontroller-cancelalgorithm
// A promise-returning algorithm, taking one argument (the cancel reason), which communicates a requested cancelation to the underlying source
JS::GCPtr<CancelAlgorithm> m_cancel_algorithm;
// https://streams.spec.whatwg.org/#readablestreamdefaultcontroller-closerequested
// A boolean flag indicating whether the stream has been closed by its underlying source, but still has chunks in its internal queue that have not yet been read
bool m_close_requested { false };
// https://streams.spec.whatwg.org/#readablestreamdefaultcontroller-pullagain
// A boolean flag set to true if the streams mechanisms requested a call to the underlying source's pull algorithm to pull more data, but the pull could not yet be done since a previous call is still executing
bool m_pull_again { false };
// https://streams.spec.whatwg.org/#readablestreamdefaultcontroller-pullalgorithm
// A promise-returning algorithm that pulls data from the underlying source
JS::GCPtr<PullAlgorithm> m_pull_algorithm;
// https://streams.spec.whatwg.org/#readablestreamdefaultcontroller-pulling
// A boolean flag set to true while the underlying source's pull algorithm is executing and the returned promise has not yet fulfilled, used to prevent reentrant calls
bool m_pulling { false };
// https://streams.spec.whatwg.org/#readablebytestreamcontroller-pendingpullintos
// A list of pull-into descriptors
SinglyLinkedList<PullIntoDescriptor> m_pending_pull_intos;
// https://streams.spec.whatwg.org/#readablestreamdefaultcontroller-queue
// A list representing the streams internal queue of chunks
SinglyLinkedList<ReadableByteStreamQueueEntry> m_queue;
// https://streams.spec.whatwg.org/#readablestreamdefaultcontroller-queuetotalsize
// The total size of all the chunks stored in [[queue]]
double m_queue_total_size { 0 };
// https://streams.spec.whatwg.org/#readablestreamdefaultcontroller-started
// A boolean flag indicating whether the underlying source has finished starting
bool m_started { false };
// https://streams.spec.whatwg.org/#readablestreamdefaultcontroller-strategyhwm
// A number supplied to the constructor as part of the streams queuing strategy, indicating the point at which the stream will apply backpressure to its underlying source
double m_strategy_hwm { 0 };
// https://streams.spec.whatwg.org/#readablestreamdefaultcontroller-stream
// The ReadableStream instance controlled
JS::GCPtr<ReadableStream> m_stream;
};
}

View file

@ -0,0 +1,12 @@
#import <Streams/ReadableStreamBYOBRequest.idl>
// https://streams.spec.whatwg.org/#rbs-controller-class-definition
[Exposed=*]
interface ReadableByteStreamController {
readonly attribute ReadableStreamBYOBRequest? byobRequest;
readonly attribute unrestricted double? desiredSize;
undefined close();
undefined error(optional any e);
undefined enqueue(ArrayBufferView chunk);
};

View file

@ -0,0 +1,259 @@
/*
* Copyright (c) 2022, Linus Groh <linusg@serenityos.org>
* Copyright (c) 2023-2024, Shannon Booth <shannon@serenityos.org>
* Copyright (c) 2024, Kenneth Myhra <kennethmyhra@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibJS/Runtime/PromiseCapability.h>
#include <LibWeb/Bindings/Intrinsics.h>
#include <LibWeb/Bindings/ReadableStreamPrototype.h>
#include <LibWeb/DOM/AbortSignal.h>
#include <LibWeb/Streams/AbstractOperations.h>
#include <LibWeb/Streams/ReadableByteStreamController.h>
#include <LibWeb/Streams/ReadableStream.h>
#include <LibWeb/Streams/ReadableStreamBYOBReader.h>
#include <LibWeb/Streams/ReadableStreamDefaultController.h>
#include <LibWeb/Streams/ReadableStreamDefaultReader.h>
#include <LibWeb/Streams/UnderlyingSource.h>
#include <LibWeb/WebIDL/ExceptionOr.h>
namespace Web::Streams {
JS_DEFINE_ALLOCATOR(ReadableStream);
// https://streams.spec.whatwg.org/#rs-constructor
WebIDL::ExceptionOr<JS::NonnullGCPtr<ReadableStream>> ReadableStream::construct_impl(JS::Realm& realm, Optional<JS::Handle<JS::Object>> const& underlying_source_object, QueuingStrategy const& strategy)
{
auto& vm = realm.vm();
auto readable_stream = realm.heap().allocate<ReadableStream>(realm, realm);
// 1. If underlyingSource is missing, set it to null.
auto underlying_source = underlying_source_object.has_value() ? JS::Value(underlying_source_object.value()) : JS::js_null();
// 2. Let underlyingSourceDict be underlyingSource, converted to an IDL value of type UnderlyingSource.
auto underlying_source_dict = TRY(UnderlyingSource::from_value(vm, underlying_source));
// 3. Perform ! InitializeReadableStream(this).
// 4. If underlyingSourceDict["type"] is "bytes":
if (underlying_source_dict.type.has_value() && underlying_source_dict.type.value() == ReadableStreamType::Bytes) {
// 1. If strategy["size"] exists, throw a RangeError exception.
if (strategy.size)
return WebIDL::SimpleException { WebIDL::SimpleExceptionType::RangeError, "Size strategy not allowed for byte stream"sv };
// 2. Let highWaterMark be ? ExtractHighWaterMark(strategy, 0).
auto high_water_mark = TRY(extract_high_water_mark(strategy, 0));
// 3. Perform ? SetUpReadableByteStreamControllerFromUnderlyingSource(this, underlyingSource, underlyingSourceDict, highWaterMark).
TRY(set_up_readable_byte_stream_controller_from_underlying_source(*readable_stream, underlying_source, underlying_source_dict, high_water_mark));
}
// 5. Otherwise,
else {
// 1. Assert: underlyingSourceDict["type"] does not exist.
VERIFY(!underlying_source_dict.type.has_value());
// 2. Let sizeAlgorithm be ! ExtractSizeAlgorithm(strategy).
auto size_algorithm = extract_size_algorithm(vm, strategy);
// 3. Let highWaterMark be ? ExtractHighWaterMark(strategy, 1).
auto high_water_mark = TRY(extract_high_water_mark(strategy, 1));
// 4. Perform ? SetUpReadableStreamDefaultControllerFromUnderlyingSource(this, underlyingSource, underlyingSourceDict, highWaterMark, sizeAlgorithm).
TRY(set_up_readable_stream_default_controller_from_underlying_source(*readable_stream, underlying_source, underlying_source_dict, high_water_mark, size_algorithm));
}
return readable_stream;
}
// https://streams.spec.whatwg.org/#rs-from
WebIDL::ExceptionOr<JS::NonnullGCPtr<ReadableStream>> ReadableStream::from(JS::VM& vm, JS::Value async_iterable)
{
// 1. Return ? ReadableStreamFromIterable(asyncIterable).
return TRY(readable_stream_from_iterable(vm, async_iterable));
}
ReadableStream::ReadableStream(JS::Realm& realm)
: PlatformObject(realm)
{
}
ReadableStream::~ReadableStream() = default;
// https://streams.spec.whatwg.org/#rs-locked
bool ReadableStream::locked() const
{
// 1. Return ! IsReadableStreamLocked(this).
return is_readable_stream_locked(*this);
}
// https://streams.spec.whatwg.org/#rs-cancel
JS::NonnullGCPtr<WebIDL::Promise> ReadableStream::cancel(JS::Value reason)
{
auto& realm = this->realm();
// 1. If ! IsReadableStreamLocked(this) is true, return a promise rejected with a TypeError exception.
if (is_readable_stream_locked(*this)) {
auto exception = JS::TypeError::create(realm, "Cannot cancel a locked stream"sv);
return WebIDL::create_rejected_promise(realm, exception);
}
// 2. Return ! ReadableStreamCancel(this, reason).
return readable_stream_cancel(*this, reason);
}
// https://streams.spec.whatwg.org/#rs-get-reader
WebIDL::ExceptionOr<ReadableStreamReader> ReadableStream::get_reader(ReadableStreamGetReaderOptions const& options)
{
// 1. If options["mode"] does not exist, return ? AcquireReadableStreamDefaultReader(this).
if (!options.mode.has_value())
return ReadableStreamReader { TRY(acquire_readable_stream_default_reader(*this)) };
// 2. Assert: options["mode"] is "byob".
VERIFY(*options.mode == Bindings::ReadableStreamReaderMode::Byob);
// 3. Return ? AcquireReadableStreamBYOBReader(this).
return ReadableStreamReader { TRY(acquire_readable_stream_byob_reader(*this)) };
}
WebIDL::ExceptionOr<JS::NonnullGCPtr<ReadableStream>> ReadableStream::pipe_through(ReadableWritablePair transform, StreamPipeOptions const& options)
{
// 1. If ! IsReadableStreamLocked(this) is true, throw a TypeError exception.
if (is_readable_stream_locked(*this))
return WebIDL::SimpleException { WebIDL::SimpleExceptionType::TypeError, "Failed to execute 'pipeThrough' on 'ReadableStream': Cannot pipe a locked stream"sv };
// 2. If ! IsWritableStreamLocked(transform["writable"]) is true, throw a TypeError exception.
if (is_writable_stream_locked(*transform.writable))
return WebIDL::SimpleException { WebIDL::SimpleExceptionType::TypeError, "Failed to execute 'pipeThrough' on 'ReadableStream': parameter 1's 'writable' is locked"sv };
// 3. Let signal be options["signal"] if it exists, or undefined otherwise.
auto signal = options.signal ? JS::Value(options.signal) : JS::js_undefined();
// 4. Let promise be ! ReadableStreamPipeTo(this, transform["writable"], options["preventClose"], options["preventAbort"], options["preventCancel"], signal).
auto promise = readable_stream_pipe_to(*this, *transform.writable, options.prevent_close, options.prevent_abort, options.prevent_cancel, signal);
// 5. Set promise.[[PromiseIsHandled]] to true.
WebIDL::mark_promise_as_handled(*promise);
// 6. Return transform["readable"].
return JS::NonnullGCPtr { *transform.readable };
}
JS::NonnullGCPtr<WebIDL::Promise> ReadableStream::pipe_to(WritableStream& destination, StreamPipeOptions const& options)
{
auto& realm = this->realm();
auto& vm = realm.vm();
// 1. If ! IsReadableStreamLocked(this) is true, return a promise rejected with a TypeError exception.
if (is_readable_stream_locked(*this)) {
return WebIDL::create_rejected_promise_from_exception(realm, vm.throw_completion<JS::TypeError>("Failed to execute 'pipeTo' on 'ReadableStream': Cannot pipe a locked stream"sv));
}
// 2. If ! IsWritableStreamLocked(destination) is true, return a promise rejected with a TypeError exception.
if (is_writable_stream_locked(destination)) {
return WebIDL::create_rejected_promise_from_exception(realm, vm.throw_completion<JS::TypeError>("Failed to execute 'pipeTo' on 'ReadableStream': Cannot pipe to a locked stream"sv));
}
// 3. Let signal be options["signal"] if it exists, or undefined otherwise.
auto signal = options.signal ? JS::Value(options.signal) : JS::js_undefined();
// 4. Return ! ReadableStreamPipeTo(this, destination, options["preventClose"], options["preventAbort"], options["preventCancel"], signal).
return readable_stream_pipe_to(*this, destination, options.prevent_close, options.prevent_abort, options.prevent_cancel, signal);
}
// https://streams.spec.whatwg.org/#readablestream-tee
WebIDL::ExceptionOr<ReadableStreamPair> ReadableStream::tee()
{
// To tee a ReadableStream stream, return ? ReadableStreamTee(stream, true).
return TRY(readable_stream_tee(realm(), *this, true));
}
// https://streams.spec.whatwg.org/#readablestream-close
void ReadableStream::close()
{
controller()->visit(
// 1. If stream.[[controller]] implements ReadableByteStreamController
[&](JS::NonnullGCPtr<ReadableByteStreamController> controller) {
// 1. Perform ! ReadableByteStreamControllerClose(stream.[[controller]]).
MUST(readable_byte_stream_controller_close(controller));
// 2. If stream.[[controller]].[[pendingPullIntos]] is not empty, perform ! ReadableByteStreamControllerRespond(stream.[[controller]], 0).
if (!controller->pending_pull_intos().is_empty())
MUST(readable_byte_stream_controller_respond(controller, 0));
},
// 2. Otherwise, perform ! ReadableStreamDefaultControllerClose(stream.[[controller]]).
[&](JS::NonnullGCPtr<ReadableStreamDefaultController> controller) {
readable_stream_default_controller_close(*controller);
});
}
// https://streams.spec.whatwg.org/#readablestream-error
void ReadableStream::error(JS::Value error)
{
controller()->visit(
// 1. If stream.[[controller]] implements ReadableByteStreamController, then perform
// ! ReadableByteStreamControllerError(stream.[[controller]], e).
[&](JS::NonnullGCPtr<ReadableByteStreamController> controller) {
readable_byte_stream_controller_error(controller, error);
},
// 2. Otherwise, perform ! ReadableStreamDefaultControllerError(stream.[[controller]], e).
[&](JS::NonnullGCPtr<ReadableStreamDefaultController> controller) {
readable_stream_default_controller_error(controller, error);
});
}
void ReadableStream::initialize(JS::Realm& realm)
{
Base::initialize(realm);
WEB_SET_PROTOTYPE_FOR_INTERFACE(ReadableStream);
}
void ReadableStream::visit_edges(Cell::Visitor& visitor)
{
Base::visit_edges(visitor);
if (m_controller.has_value())
m_controller->visit([&](auto& controller) { visitor.visit(controller); });
visitor.visit(m_stored_error);
if (m_reader.has_value())
m_reader->visit([&](auto& reader) { visitor.visit(reader); });
}
// https://streams.spec.whatwg.org/#readablestream-locked
bool ReadableStream::is_readable() const
{
// A ReadableStream stream is readable if stream.[[state]] is "readable".
return m_state == State::Readable;
}
// https://streams.spec.whatwg.org/#readablestream-closed
bool ReadableStream::is_closed() const
{
// A ReadableStream stream is closed if stream.[[state]] is "closed".
return m_state == State::Closed;
}
// https://streams.spec.whatwg.org/#readablestream-errored
bool ReadableStream::is_errored() const
{
// A ReadableStream stream is errored if stream.[[state]] is "errored".
return m_state == State::Errored;
}
// https://streams.spec.whatwg.org/#readablestream-locked
bool ReadableStream::is_locked() const
{
// A ReadableStream stream is locked if ! IsReadableStreamLocked(stream) returns true.
return is_readable_stream_locked(*this);
}
// https://streams.spec.whatwg.org/#is-readable-stream-disturbed
bool ReadableStream::is_disturbed() const
{
// A ReadableStream stream is disturbed if stream.[[disturbed]] is true.
return m_disturbed;
}
}

View file

@ -0,0 +1,138 @@
/*
* Copyright (c) 2022, Linus Groh <linusg@serenityos.org>
* Copyright (c) 2024, Kenneth Myhra <kennethmyhra@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/Forward.h>
#include <LibJS/Forward.h>
#include <LibWeb/Bindings/PlatformObject.h>
#include <LibWeb/Bindings/ReadableStreamPrototype.h>
#include <LibWeb/Forward.h>
#include <LibWeb/Streams/QueuingStrategy.h>
namespace Web::Streams {
// https://streams.spec.whatwg.org/#typedefdef-readablestreamreader
using ReadableStreamReader = Variant<JS::NonnullGCPtr<ReadableStreamDefaultReader>, JS::NonnullGCPtr<ReadableStreamBYOBReader>>;
// https://streams.spec.whatwg.org/#typedefdef-readablestreamcontroller
using ReadableStreamController = Variant<JS::NonnullGCPtr<ReadableStreamDefaultController>, JS::NonnullGCPtr<ReadableByteStreamController>>;
// https://streams.spec.whatwg.org/#dictdef-readablestreamgetreaderoptions
struct ReadableStreamGetReaderOptions {
Optional<Bindings::ReadableStreamReaderMode> mode;
};
struct ReadableWritablePair {
JS::GCPtr<ReadableStream> readable;
JS::GCPtr<WritableStream> writable;
};
struct StreamPipeOptions {
bool prevent_close { false };
bool prevent_abort { false };
bool prevent_cancel { false };
JS::GCPtr<DOM::AbortSignal> signal;
};
struct ReadableStreamPair {
// Define a couple container-like methods so this type may be used as the return type of the IDL `tee` implementation.
size_t size() const { return 2; }
JS::NonnullGCPtr<ReadableStream>& at(size_t index)
{
if (index == 0)
return first;
if (index == 1)
return second;
VERIFY_NOT_REACHED();
}
JS::NonnullGCPtr<ReadableStream> first;
JS::NonnullGCPtr<ReadableStream> second;
};
// https://streams.spec.whatwg.org/#readablestream
class ReadableStream final : public Bindings::PlatformObject {
WEB_PLATFORM_OBJECT(ReadableStream, Bindings::PlatformObject);
JS_DECLARE_ALLOCATOR(ReadableStream);
public:
enum class State {
Readable,
Closed,
Errored,
};
static WebIDL::ExceptionOr<JS::NonnullGCPtr<ReadableStream>> construct_impl(JS::Realm&, Optional<JS::Handle<JS::Object>> const& underlying_source, QueuingStrategy const& = {});
static WebIDL::ExceptionOr<JS::NonnullGCPtr<ReadableStream>> from(JS::VM& vm, JS::Value async_iterable);
virtual ~ReadableStream() override;
bool locked() const;
JS::NonnullGCPtr<WebIDL::Promise> cancel(JS::Value reason);
WebIDL::ExceptionOr<ReadableStreamReader> get_reader(ReadableStreamGetReaderOptions const& = {});
WebIDL::ExceptionOr<JS::NonnullGCPtr<ReadableStream>> pipe_through(ReadableWritablePair transform, StreamPipeOptions const& = {});
JS::NonnullGCPtr<WebIDL::Promise> pipe_to(WritableStream& destination, StreamPipeOptions const& = {});
WebIDL::ExceptionOr<ReadableStreamPair> tee();
void close();
void error(JS::Value);
Optional<ReadableStreamController>& controller() { return m_controller; }
void set_controller(Optional<ReadableStreamController> value) { m_controller = move(value); }
JS::Value stored_error() const { return m_stored_error; }
void set_stored_error(JS::Value value) { m_stored_error = value; }
Optional<ReadableStreamReader> const& reader() const { return m_reader; }
void set_reader(Optional<ReadableStreamReader> value) { m_reader = move(value); }
bool is_disturbed() const;
void set_disturbed(bool value) { m_disturbed = value; }
bool is_readable() const;
bool is_closed() const;
bool is_errored() const;
bool is_locked() const;
State state() const { return m_state; }
void set_state(State value) { m_state = value; }
private:
explicit ReadableStream(JS::Realm&);
virtual void initialize(JS::Realm&) override;
virtual void visit_edges(Cell::Visitor&) override;
// https://streams.spec.whatwg.org/#readablestream-controller
// A ReadableStreamDefaultController or ReadableByteStreamController created with the ability to control the state and queue of this stream
Optional<ReadableStreamController> m_controller;
// https://streams.spec.whatwg.org/#readablestream-detached
// A boolean flag set to true when the stream is transferred
bool m_detached { false };
// https://streams.spec.whatwg.org/#readablestream-disturbed
// A boolean flag set to true when the stream has been read from or canceled
bool m_disturbed { false };
// https://streams.spec.whatwg.org/#readablestream-reader
// A ReadableStreamDefaultReader or ReadableStreamBYOBReader instance, if the stream is locked to a reader, or undefined if it is not
Optional<ReadableStreamReader> m_reader;
// https://streams.spec.whatwg.org/#readablestream-state
// A string containing the streams current state, used internally; one of "readable", "closed", or "errored"
State m_state { State::Readable };
// https://streams.spec.whatwg.org/#readablestream-storederror
// A value indicating how the stream failed, to be given as a failure reason or exception when trying to operate on an errored stream
JS::Value m_stored_error { JS::js_undefined() };
};
}

View file

@ -0,0 +1,45 @@
#import <DOM/AbortSignal.idl>
#import <Streams/QueuingStrategy.idl>
#import <Streams/ReadableStreamBYOBReader.idl>
#import <Streams/ReadableStreamDefaultReader.idl>
#import <Streams/WritableStream.idl>
dictionary ReadableWritablePair {
required ReadableStream readable;
required WritableStream writable;
};
dictionary StreamPipeOptions {
boolean preventClose = false;
boolean preventAbort = false;
boolean preventCancel = false;
AbortSignal signal;
};
// https://streams.spec.whatwg.org/#enumdef-readablestreamreadermode
enum ReadableStreamReaderMode { "byob" };
// https://streams.spec.whatwg.org/#dictdef-readablestreamgetreaderoptions
dictionary ReadableStreamGetReaderOptions {
ReadableStreamReaderMode mode;
};
// https://streams.spec.whatwg.org/#readablestream
[Exposed=*, Transferable]
interface ReadableStream {
constructor(optional object underlyingSource, optional QueuingStrategy strategy = {});
static ReadableStream from(any asyncIterable);
readonly attribute boolean locked;
Promise<undefined> cancel(optional any reason);
ReadableStreamReader getReader(optional ReadableStreamGetReaderOptions options = {});
ReadableStream pipeThrough(ReadableWritablePair transform, optional StreamPipeOptions options = {});
Promise<undefined> pipeTo(WritableStream destination, optional StreamPipeOptions options = {});
sequence<ReadableStream> tee();
// FIXME: async iterable<any>(optional ReadableStreamIteratorOptions options = {});
};
typedef (ReadableStreamDefaultReader or ReadableStreamBYOBReader) ReadableStreamReader;

View file

@ -0,0 +1,182 @@
/*
* Copyright (c) 2023, Matthew Olsson <mattco@serenityos.org>
* Copyright (c) 2023, Shannon Booth <shannon@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibJS/Runtime/PromiseCapability.h>
#include <LibJS/Runtime/TypedArray.h>
#include <LibWeb/Bindings/Intrinsics.h>
#include <LibWeb/Bindings/ReadableStreamBYOBReaderPrototype.h>
#include <LibWeb/Streams/AbstractOperations.h>
#include <LibWeb/Streams/ReadableStream.h>
#include <LibWeb/Streams/ReadableStreamBYOBReader.h>
#include <LibWeb/WebIDL/Buffers.h>
#include <LibWeb/WebIDL/ExceptionOr.h>
namespace Web::Streams {
JS_DEFINE_ALLOCATOR(ReadableStreamBYOBReader);
ReadableStreamBYOBReader::ReadableStreamBYOBReader(JS::Realm& realm)
: Bindings::PlatformObject(realm)
, ReadableStreamGenericReaderMixin(realm)
{
}
void ReadableStreamBYOBReader::initialize(JS::Realm& realm)
{
Base::initialize(realm);
WEB_SET_PROTOTYPE_FOR_INTERFACE(ReadableStreamBYOBReader);
}
// https://streams.spec.whatwg.org/#byob-reader-constructor
WebIDL::ExceptionOr<JS::NonnullGCPtr<ReadableStreamBYOBReader>> ReadableStreamBYOBReader::construct_impl(JS::Realm& realm, JS::NonnullGCPtr<ReadableStream> stream)
{
auto reader = realm.heap().allocate<ReadableStreamBYOBReader>(realm, realm);
// 1. Perform ? SetUpReadableStreamBYOBReader(this, stream).
TRY(set_up_readable_stream_byob_reader(reader, *stream));
return reader;
}
// https://streams.spec.whatwg.org/#byob-reader-release-lock
void ReadableStreamBYOBReader::release_lock()
{
// 1. If this.[[stream]] is undefined, return.
if (!m_stream)
return;
// 2. Perform ! ReadableStreamBYOBReaderRelease(this).
readable_stream_byob_reader_release(*this);
}
void ReadableStreamBYOBReader::visit_edges(Cell::Visitor& visitor)
{
Base::visit_edges(visitor);
ReadableStreamGenericReaderMixin::visit_edges(visitor);
visitor.visit(m_read_into_requests);
}
class BYOBReaderReadIntoRequest : public ReadIntoRequest {
JS_CELL(BYOBReaderReadIntoRequest, ReadIntoRequest);
JS_DECLARE_ALLOCATOR(BYOBReaderReadIntoRequest);
public:
BYOBReaderReadIntoRequest(JS::Realm& realm, WebIDL::Promise& promise)
: m_realm(realm)
, m_promise(promise)
{
}
// chunk steps, given chunk
virtual void on_chunk(JS::Value chunk) override
{
// 1. Resolve promise with «[ "value" → chunk, "done" → false ]».
WebIDL::resolve_promise(m_realm, m_promise, JS::create_iterator_result_object(m_realm->vm(), chunk, false));
}
// close steps, given chunk
virtual void on_close(JS::Value chunk) override
{
// 1. Resolve promise with «[ "value" → chunk, "done" → true ]».
WebIDL::resolve_promise(m_realm, m_promise, JS::create_iterator_result_object(m_realm->vm(), chunk, true));
}
// error steps, given e
virtual void on_error(JS::Value error) override
{
// 1. Reject promise with e.
WebIDL::reject_promise(m_realm, m_promise, error);
}
private:
virtual void visit_edges(Visitor& visitor) override
{
Base::visit_edges(visitor);
visitor.visit(m_realm);
visitor.visit(m_promise);
}
JS::NonnullGCPtr<JS::Realm> m_realm;
JS::NonnullGCPtr<WebIDL::Promise> m_promise;
};
JS_DEFINE_ALLOCATOR(BYOBReaderReadIntoRequest);
// https://streams.spec.whatwg.org/#byob-reader-read
JS::NonnullGCPtr<WebIDL::Promise> ReadableStreamBYOBReader::read(JS::Handle<WebIDL::ArrayBufferView>& view, ReadableStreamBYOBReaderReadOptions options)
{
auto& realm = this->realm();
// 1. If view.[[ByteLength]] is 0, return a promise rejected with a TypeError exception.
if (view->byte_length() == 0) {
WebIDL::SimpleException exception { WebIDL::SimpleExceptionType::TypeError, "Cannot read in an empty buffer"sv };
return WebIDL::create_rejected_promise_from_exception(realm, move(exception));
}
// 2. If view.[[ViewedArrayBuffer]].[[ArrayBufferByteLength]] is 0, return a promise rejected with a TypeError exception.
if (view->viewed_array_buffer()->byte_length() == 0) {
WebIDL::SimpleException exception { WebIDL::SimpleExceptionType::TypeError, "Cannot read in an empty buffer"sv };
return WebIDL::create_rejected_promise_from_exception(realm, move(exception));
}
// 3. If ! IsDetachedBuffer(view.[[ViewedArrayBuffer]]) is true, return a promise rejected with a TypeError exception.
if (view->viewed_array_buffer()->is_detached()) {
WebIDL::SimpleException exception { WebIDL::SimpleExceptionType::TypeError, "Cannot read in a detached buffer"sv };
return WebIDL::create_rejected_promise_from_exception(realm, move(exception));
}
// 4. If options["min"] is 0, return a promise rejected with a TypeError exception.
if (options.min == 0) {
WebIDL::SimpleException exception { WebIDL::SimpleExceptionType::TypeError, "options[\"min\'] cannot have a value of 0."sv };
return WebIDL::create_rejected_promise_from_exception(realm, move(exception));
}
// 5. If view has a [[TypedArrayName]] internal slot,
if (view->is_typed_array_base()) {
auto const& typed_array = *view->bufferable_object().get<JS::NonnullGCPtr<JS::TypedArrayBase>>();
// 1. If options["min"] > view.[[ArrayLength]], return a promise rejected with a RangeError exception.
if (options.min > typed_array.array_length().length()) {
WebIDL::SimpleException exception { WebIDL::SimpleExceptionType::RangeError, "options[\"min\"] cannot be larger than the length of the view."sv };
return WebIDL::create_rejected_promise_from_exception(realm, move(exception));
}
}
// 6. Otherwise (i.e., it is a DataView),
if (view->is_data_view()) {
// 1. If options["min"] > view.[[ByteLength]], return a promise rejected with a RangeError exception.
if (options.min > view->byte_length()) {
WebIDL::SimpleException exception { WebIDL::SimpleExceptionType::RangeError, "options[\"min\"] cannot be larger than the length of the view."sv };
return WebIDL::create_rejected_promise_from_exception(realm, move(exception));
}
}
// 7. If this.[[stream]] is undefined, return a promise rejected with a TypeError exception.
if (!m_stream) {
WebIDL::SimpleException exception { WebIDL::SimpleExceptionType::TypeError, "Cannot read from an empty stream"sv };
return WebIDL::create_rejected_promise_from_exception(realm, move(exception));
}
// 8. Let promise be a new promise.
auto promise_capability = WebIDL::create_promise(realm);
// 9. Let readIntoRequest be a new read-into request with the following items:
// chunk steps, given chunk
// Resolve promise with «[ "value" → chunk, "done" → false ]».
// close steps, given chunk
// Resolve promise with «[ "value" → chunk, "done" → true ]».
// error steps, given e
// Reject promise with e.
auto read_into_request = heap().allocate_without_realm<BYOBReaderReadIntoRequest>(realm, promise_capability);
// 10. Perform ! ReadableStreamBYOBReaderRead(this, view, options["min"], readIntoRequest).
readable_stream_byob_reader_read(*this, *view, options.min, *read_into_request);
// 11. Return promise.
return promise_capability;
}
}

View file

@ -0,0 +1,72 @@
/*
* Copyright (c) 2023, Matthew Olsson <mattco@serenityos.org>
* Copyright (c) 2023, Shannon Booth <shannon@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/Forward.h>
#include <AK/Function.h>
#include <LibJS/Forward.h>
#include <LibWeb/Bindings/PlatformObject.h>
#include <LibWeb/Forward.h>
#include <LibWeb/Streams/ReadableStreamGenericReader.h>
#include <LibWeb/WebIDL/Types.h>
namespace Web::Streams {
// https://streams.spec.whatwg.org/#dictdef-readablestreambyobreaderreadoptions
struct ReadableStreamBYOBReaderReadOptions {
WebIDL::UnsignedLongLong min = 1;
};
// https://streams.spec.whatwg.org/#read-into-request
class ReadIntoRequest : public JS::Cell {
JS_CELL(ReadIntoRequest, JS::Cell);
public:
virtual ~ReadIntoRequest() = default;
// An algorithm taking a chunk, called when a chunk is available for reading
virtual void on_chunk(JS::Value chunk) = 0;
// An algorithm taking a chunk or undefined, called when no chunks are available because the stream is closed
virtual void on_close(JS::Value chunk_or_undefined) = 0;
// An algorithm taking a JavaScript value, called when no chunks are available because the stream is errored
virtual void on_error(JS::Value error) = 0;
};
// https://streams.spec.whatwg.org/#readablestreambyobreader
class ReadableStreamBYOBReader final
: public Bindings::PlatformObject
, public ReadableStreamGenericReaderMixin {
WEB_PLATFORM_OBJECT(ReadableStreamBYOBReader, Bindings::PlatformObject);
JS_DECLARE_ALLOCATOR(ReadableStreamBYOBReader);
public:
static WebIDL::ExceptionOr<JS::NonnullGCPtr<ReadableStreamBYOBReader>> construct_impl(JS::Realm&, JS::NonnullGCPtr<ReadableStream>);
virtual ~ReadableStreamBYOBReader() override = default;
JS::NonnullGCPtr<WebIDL::Promise> read(JS::Handle<WebIDL::ArrayBufferView>&, ReadableStreamBYOBReaderReadOptions options = {});
void release_lock();
Vector<JS::NonnullGCPtr<ReadIntoRequest>>& read_into_requests() { return m_read_into_requests; }
private:
explicit ReadableStreamBYOBReader(JS::Realm&);
virtual void initialize(JS::Realm&) override;
virtual void visit_edges(Cell::Visitor&) override;
// https://streams.spec.whatwg.org/#readablestreambyobreader-readintorequests
// A list of read-into requests, used when a consumer requests chunks sooner than they are available
Vector<JS::NonnullGCPtr<ReadIntoRequest>> m_read_into_requests;
};
}

View file

@ -0,0 +1,18 @@
#import <Streams/ReadableStream.idl>
#import <Streams/ReadableStreamDefaultReader.idl>
#import <Streams/ReadableStreamGenericReader.idl>
// https://streams.spec.whatwg.org/#readablestreambyobreader
[Exposed=*]
interface ReadableStreamBYOBReader {
constructor(ReadableStream stream);
Promise<ReadableStreamReadResult> read(ArrayBufferView view, optional ReadableStreamBYOBReaderReadOptions options = {});
undefined releaseLock();
};
ReadableStreamBYOBReader includes ReadableStreamGenericReader;
dictionary ReadableStreamBYOBReaderReadOptions {
[EnforceRange] unsigned long long min = 1;
};

View file

@ -0,0 +1,82 @@
/*
* Copyright (c) 2023, Matthew Olsson <mattco@serenityos.org>
* Copyright (c) 2023, Shannon Booth <shannon@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibJS/Runtime/TypedArray.h>
#include <LibWeb/Bindings/Intrinsics.h>
#include <LibWeb/Bindings/ReadableStreamBYOBRequestPrototype.h>
#include <LibWeb/Streams/ReadableByteStreamController.h>
#include <LibWeb/Streams/ReadableStreamBYOBRequest.h>
#include <LibWeb/WebIDL/Buffers.h>
namespace Web::Streams {
JS_DEFINE_ALLOCATOR(ReadableStreamBYOBRequest);
// https://streams.spec.whatwg.org/#rs-byob-request-view
JS::GCPtr<WebIDL::ArrayBufferView> ReadableStreamBYOBRequest::view()
{
// 1. Return this.[[view]].
return m_view;
}
ReadableStreamBYOBRequest::ReadableStreamBYOBRequest(JS::Realm& realm)
: Bindings::PlatformObject(realm)
{
}
void ReadableStreamBYOBRequest::initialize(JS::Realm& realm)
{
Base::initialize(realm);
WEB_SET_PROTOTYPE_FOR_INTERFACE(ReadableStreamBYOBRequest);
}
void ReadableStreamBYOBRequest::visit_edges(Cell::Visitor& visitor)
{
Base::visit_edges(visitor);
visitor.visit(m_controller);
visitor.visit(m_view);
}
// https://streams.spec.whatwg.org/#rs-byob-request-respond
WebIDL::ExceptionOr<void> ReadableStreamBYOBRequest::respond(WebIDL::UnsignedLongLong bytes_written)
{
// 1. If this.[[controller]] is undefined, throw a TypeError exception.
if (!m_controller)
return WebIDL::SimpleException { WebIDL::SimpleExceptionType::TypeError, "Controller is undefined"_string };
// 2. If ! IsDetachedBuffer(this.[[view]].[[ArrayBuffer]]) is true, throw a TypeError exception.
if (m_view->viewed_array_buffer()->is_detached())
return WebIDL::SimpleException { WebIDL::SimpleExceptionType::TypeError, "Unable to respond to detached ArrayBuffer"_string };
// 3. Assert: this.[[view]].[[ByteLength]] > 0.
VERIFY(m_view->viewed_array_buffer()->byte_length() > 0);
// 4. Assert: this.[[view]].[[ViewedArrayBuffer]].[[ByteLength]] > 0.
VERIFY(m_view->viewed_array_buffer()->byte_length() > 0);
// 5. Perform ? ReadableByteStreamControllerRespond(this.[[controller]], bytesWritten).
return readable_byte_stream_controller_respond(*m_controller, bytes_written);
}
// https://streams.spec.whatwg.org/#rs-byob-request-respond-with-new-view
WebIDL::ExceptionOr<void> ReadableStreamBYOBRequest::respond_with_new_view(JS::Handle<WebIDL::ArrayBufferView> const& view)
{
auto& realm = this->realm();
// 1. If this.[[controller]] is undefined, throw a TypeError exception.
if (!m_controller)
return WebIDL::SimpleException { WebIDL::SimpleExceptionType::TypeError, "Controller is undefined"_string };
// 2. If ! IsDetachedBuffer(view.[[ViewedArrayBuffer]]) is true, throw a TypeError exception.
if (view->viewed_array_buffer()->is_detached())
return WebIDL::SimpleException { WebIDL::SimpleExceptionType::TypeError, "Unable to respond with a detached ArrayBuffer"_string };
// 3. Return ? ReadableByteStreamControllerRespondWithNewView(this.[[controller]], view).
return TRY(readable_byte_stream_controller_respond_with_new_view(realm, *m_controller, *view));
}
}

View file

@ -0,0 +1,50 @@
/*
* Copyright (c) 2023, Matthew Olsson <mattco@serenityos.org>
* Copyright (c) 2023, Shannon Booth <shannon@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <LibWeb/Bindings/PlatformObject.h>
#include <LibWeb/Forward.h>
#include <LibWeb/Streams/ReadableByteStreamController.h>
#include <LibWeb/WebIDL/Types.h>
namespace Web::Streams {
// https://streams.spec.whatwg.org/#readablestreambyobrequest
class ReadableStreamBYOBRequest : public Bindings::PlatformObject {
WEB_PLATFORM_OBJECT(ReadableStreamBYOBRequest, Bindings::PlatformObject);
JS_DECLARE_ALLOCATOR(ReadableStreamBYOBRequest);
public:
virtual ~ReadableStreamBYOBRequest() override = default;
JS::GCPtr<WebIDL::ArrayBufferView> view();
void set_controller(JS::GCPtr<ReadableByteStreamController> value) { m_controller = value; }
void set_view(JS::GCPtr<WebIDL::ArrayBufferView> value) { m_view = value; }
WebIDL::ExceptionOr<void> respond(WebIDL::UnsignedLongLong bytes_written);
WebIDL::ExceptionOr<void> respond_with_new_view(JS::Handle<WebIDL::ArrayBufferView> const& view);
private:
explicit ReadableStreamBYOBRequest(JS::Realm&);
virtual void initialize(JS::Realm&) override;
virtual void visit_edges(Cell::Visitor&) override;
// https://streams.spec.whatwg.org/#readablestreambyobrequest-controller
// The parent ReadableByteStreamController instance
JS::GCPtr<ReadableByteStreamController> m_controller;
// https://streams.spec.whatwg.org/#readablestreambyobrequest-view
// A typed array representing the destination region to which the controller can write generated data, or null after the BYOB request has been invalidated.
JS::GCPtr<WebIDL::ArrayBufferView> m_view;
};
}

View file

@ -0,0 +1,8 @@
// https://streams.spec.whatwg.org/#readablestreambyobrequest
[Exposed=*]
interface ReadableStreamBYOBRequest {
readonly attribute ArrayBufferView? view;
undefined respond([EnforceRange] unsigned long long bytesWritten);
undefined respondWithNewView(ArrayBufferView view);
};

View file

@ -0,0 +1,142 @@
/*
* Copyright (c) 2023, Matthew Olsson <mattco@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibWeb/Bindings/Intrinsics.h>
#include <LibWeb/Bindings/ReadableStreamDefaultControllerPrototype.h>
#include <LibWeb/Streams/AbstractOperations.h>
#include <LibWeb/Streams/ReadableStream.h>
#include <LibWeb/Streams/ReadableStreamDefaultController.h>
#include <LibWeb/Streams/ReadableStreamDefaultReader.h>
#include <LibWeb/WebIDL/ExceptionOr.h>
#include <LibWeb/WebIDL/Promise.h>
namespace Web::Streams {
JS_DEFINE_ALLOCATOR(ReadableStreamDefaultController);
ReadableStreamDefaultController::ReadableStreamDefaultController(JS::Realm& realm)
: Bindings::PlatformObject(realm)
{
}
// https://streams.spec.whatwg.org/#rs-default-controller-desired-size
Optional<double> ReadableStreamDefaultController::desired_size()
{
// 1. Return ! ReadableStreamDefaultControllerGetDesiredSize(this).
return readable_stream_default_controller_get_desired_size(*this);
}
// https://streams.spec.whatwg.org/#rs-default-controller-close
WebIDL::ExceptionOr<void> ReadableStreamDefaultController::close()
{
// 1. If ! ReadableStreamDefaultControllerCanCloseOrEnqueue(this) is false, throw a TypeError exception.
if (!readable_stream_default_controller_can_close_or_enqueue(*this)) {
return WebIDL::SimpleException { WebIDL::SimpleExceptionType::TypeError, "Stream is not closable"sv };
}
// 2. Perform ! ReadableStreamDefaultControllerClose(this).
readable_stream_default_controller_close(*this);
return {};
}
// https://streams.spec.whatwg.org/#rs-default-controller-enqueue
WebIDL::ExceptionOr<void> ReadableStreamDefaultController::enqueue(JS::Value chunk)
{
// 1. If ! ReadableStreamDefaultControllerCanCloseOrEnqueue(this) is false, throw a TypeError exception.
if (!readable_stream_default_controller_can_close_or_enqueue(*this))
return WebIDL::SimpleException { WebIDL::SimpleExceptionType::TypeError, "Cannot enqueue chunk to stream"sv };
// 2. Perform ? ReadableStreamDefaultControllerEnqueue(this, chunk).
TRY(readable_stream_default_controller_enqueue(*this, chunk));
return {};
}
// https://streams.spec.whatwg.org/#rs-default-controller-error
void ReadableStreamDefaultController::error(JS::Value error)
{
// 1. Perform ! ReadableStreamDefaultControllerError(this, e).
readable_stream_default_controller_error(*this, error);
}
// https://streams.spec.whatwg.org/#rs-default-controller-private-cancel
JS::NonnullGCPtr<WebIDL::Promise> ReadableStreamDefaultController::cancel_steps(JS::Value reason)
{
// 1. Perform ! ResetQueue(this).
reset_queue(*this);
// 2. Let result be the result of performing this.[[cancelAlgorithm]], passing reason.
auto result = cancel_algorithm()->function()(reason);
// 3. Perform ! ReadableStreamDefaultControllerClearAlgorithms(this).
readable_stream_default_controller_clear_algorithms(*this);
// 4. Return result.
return result;
}
// https://streams.spec.whatwg.org/#rs-default-controller-private-pull
void ReadableStreamDefaultController::pull_steps(Web::Streams::ReadRequest& read_request)
{
// 1. Let stream be this.[[stream]].
auto& stream = *m_stream;
// 2. If this.[[queue]] is not empty,
if (!m_queue.is_empty()) {
// 1. Let chunk be ! DequeueValue(this).
auto chunk = dequeue_value(*this);
// 2. If this.[[closeRequested]] is true and this.[[queue]] is empty,
if (m_close_requested && m_queue.is_empty()) {
// 1. Perform ! ReadableStreamDefaultControllerClearAlgorithms(this).
readable_stream_default_controller_clear_algorithms(*this);
// 2. Perform ! ReadableStreamClose(stream).
readable_stream_close(stream);
}
// 3. Otherwise, perform ! ReadableStreamDefaultControllerCallPullIfNeeded(this).
else {
readable_stream_default_controller_can_pull_if_needed(*this);
}
// 4. Perform readRequests chunk steps, given chunk.
read_request.on_chunk(chunk);
}
// 3. Otherwise,
else {
// 1. Perform ! ReadableStreamAddReadRequest(stream, readRequest).
readable_stream_add_read_request(stream, read_request);
// 2. Perform ! ReadableStreamDefaultControllerCallPullIfNeeded(this).
readable_stream_default_controller_can_pull_if_needed(*this);
}
}
// https://streams.spec.whatwg.org/#abstract-opdef-readablestreamdefaultcontroller-releasesteps
void ReadableStreamDefaultController::release_steps()
{
// 1. Return.
}
void ReadableStreamDefaultController::initialize(JS::Realm& realm)
{
Base::initialize(realm);
WEB_SET_PROTOTYPE_FOR_INTERFACE(ReadableStreamDefaultController);
}
void ReadableStreamDefaultController::visit_edges(Cell::Visitor& visitor)
{
Base::visit_edges(visitor);
for (auto const& item : m_queue)
visitor.visit(item.value);
visitor.visit(m_stream);
visitor.visit(m_cancel_algorithm);
visitor.visit(m_pull_algorithm);
visitor.visit(m_strategy_size_algorithm);
}
}

View file

@ -0,0 +1,121 @@
/*
* Copyright (c) 2023, Matthew Olsson <mattco@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/Forward.h>
#include <AK/Optional.h>
#include <AK/SinglyLinkedList.h>
#include <LibJS/Forward.h>
#include <LibWeb/Bindings/PlatformObject.h>
#include <LibWeb/Forward.h>
#include <LibWeb/Streams/AbstractOperations.h>
#include <LibWeb/WebIDL/Promise.h>
namespace Web::Streams {
// https://streams.spec.whatwg.org/#readablestreamdefaultcontroller
class ReadableStreamDefaultController : public Bindings::PlatformObject {
WEB_PLATFORM_OBJECT(ReadableStreamDefaultController, Bindings::PlatformObject);
JS_DECLARE_ALLOCATOR(ReadableStreamDefaultController);
public:
explicit ReadableStreamDefaultController(JS::Realm&);
virtual ~ReadableStreamDefaultController() override = default;
Optional<double> desired_size();
WebIDL::ExceptionOr<void> close();
WebIDL::ExceptionOr<void> enqueue(JS::Value chunk);
void error(JS::Value error);
JS::GCPtr<CancelAlgorithm> cancel_algorithm() { return m_cancel_algorithm; }
void set_cancel_algorithm(JS::GCPtr<CancelAlgorithm> value) { m_cancel_algorithm = value; }
bool close_requested() const { return m_close_requested; }
void set_close_requested(bool value) { m_close_requested = value; }
bool pull_again() const { return m_pull_again; }
void set_pull_again(bool value) { m_pull_again = value; }
JS::GCPtr<PullAlgorithm> pull_algorithm() { return m_pull_algorithm; }
void set_pull_algorithm(JS::GCPtr<PullAlgorithm> value) { m_pull_algorithm = value; }
bool pulling() const { return m_pulling; }
void set_pulling(bool value) { m_pulling = value; }
SinglyLinkedList<ValueWithSize>& queue() { return m_queue; }
double queue_total_size() const { return m_queue_total_size; }
void set_queue_total_size(double value) { m_queue_total_size = value; }
bool started() const { return m_started; }
void set_started(bool value) { m_started = value; }
double strategy_hwm() const { return m_strategy_hwm; }
void set_strategy_hwm(double value) { m_strategy_hwm = value; }
JS::GCPtr<SizeAlgorithm> strategy_size_algorithm() { return m_strategy_size_algorithm; }
void set_strategy_size_algorithm(JS::GCPtr<SizeAlgorithm> value) { m_strategy_size_algorithm = value; }
JS::GCPtr<ReadableStream> stream() { return m_stream; }
void set_stream(JS::GCPtr<ReadableStream> value) { m_stream = value; }
JS::NonnullGCPtr<WebIDL::Promise> cancel_steps(JS::Value reason);
void pull_steps(ReadRequest&);
void release_steps();
private:
virtual void initialize(JS::Realm&) override;
virtual void visit_edges(Cell::Visitor&) override;
// https://streams.spec.whatwg.org/#readablestreamdefaultcontroller-cancelalgorithm
// A promise-returning algorithm, taking one argument (the cancel reason), which communicates a requested cancelation to the underlying source
JS::GCPtr<CancelAlgorithm> m_cancel_algorithm;
// https://streams.spec.whatwg.org/#readablestreamdefaultcontroller-closerequested
// A boolean flag indicating whether the stream has been closed by its underlying source, but still has chunks in its internal queue that have not yet been read
bool m_close_requested { false };
// https://streams.spec.whatwg.org/#readablestreamdefaultcontroller-pullagain
// A boolean flag set to true if the streams mechanisms requested a call to the underlying source's pull algorithm to pull more data, but the pull could not yet be done since a previous call is still executing
bool m_pull_again { false };
// https://streams.spec.whatwg.org/#readablestreamdefaultcontroller-pullalgorithm
// A promise-returning algorithm that pulls data from the underlying source
JS::GCPtr<PullAlgorithm> m_pull_algorithm;
// https://streams.spec.whatwg.org/#readablestreamdefaultcontroller-pulling
// A boolean flag set to true while the underlying source's pull algorithm is executing and the returned promise has not yet fulfilled, used to prevent reentrant calls
bool m_pulling { false };
// https://streams.spec.whatwg.org/#readablestreamdefaultcontroller-queue
// A list representing the streams internal queue of chunks
SinglyLinkedList<ValueWithSize> m_queue;
// https://streams.spec.whatwg.org/#readablestreamdefaultcontroller-queuetotalsize
// The total size of all the chunks stored in [[queue]]
double m_queue_total_size { 0 };
// https://streams.spec.whatwg.org/#readablestreamdefaultcontroller-started
// A boolean flag indicating whether the underlying source has finished starting
bool m_started { false };
// https://streams.spec.whatwg.org/#readablestreamdefaultcontroller-strategyhwm
// A number supplied to the constructor as part of the streams queuing strategy, indicating the point at which the stream will apply backpressure to its underlying source
double m_strategy_hwm { 0 };
// https://streams.spec.whatwg.org/#readablestreamdefaultcontroller-strategysizealgorithm
// An algorithm to calculate the size of enqueued chunks, as part of the streams queuing strategy
JS::GCPtr<SizeAlgorithm> m_strategy_size_algorithm;
// https://streams.spec.whatwg.org/#readablestreamdefaultcontroller-stream
// The ReadableStream instance controlled
JS::GCPtr<ReadableStream> m_stream;
};
}

View file

@ -0,0 +1,9 @@
// https://streams.spec.whatwg.org/#readablestreamdefaultcontroller
[Exposed=*]
interface ReadableStreamDefaultController {
readonly attribute unrestricted double? desiredSize;
undefined close();
undefined enqueue(optional any chunk);
undefined error(optional any e);
};

View file

@ -0,0 +1,265 @@
/*
* Copyright (c) 2023, Matthew Olsson <mattco@serenityos.org>
* Copyright (c) 2023, Shannon Booth <shannon@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibJS/Heap/Heap.h>
#include <LibJS/Runtime/ArrayBuffer.h>
#include <LibJS/Runtime/Error.h>
#include <LibJS/Runtime/Iterator.h>
#include <LibJS/Runtime/PromiseCapability.h>
#include <LibJS/Runtime/Realm.h>
#include <LibJS/Runtime/TypedArray.h>
#include <LibWeb/Bindings/ExceptionOrUtils.h>
#include <LibWeb/Bindings/Intrinsics.h>
#include <LibWeb/Bindings/ReadableStreamDefaultReaderPrototype.h>
#include <LibWeb/Fetch/Infrastructure/IncrementalReadLoopReadRequest.h>
#include <LibWeb/Streams/AbstractOperations.h>
#include <LibWeb/Streams/ReadableStream.h>
#include <LibWeb/Streams/ReadableStreamDefaultReader.h>
#include <LibWeb/WebIDL/ExceptionOr.h>
#include <LibWeb/WebIDL/Promise.h>
namespace Web::Streams {
JS_DEFINE_ALLOCATOR(ReadableStreamDefaultReader);
JS_DEFINE_ALLOCATOR(ReadLoopReadRequest);
void ReadLoopReadRequest::visit_edges(Visitor& visitor)
{
Base::visit_edges(visitor);
visitor.visit(m_realm);
visitor.visit(m_reader);
visitor.visit(m_success_steps);
visitor.visit(m_failure_steps);
visitor.visit(m_chunk_steps);
}
// https://streams.spec.whatwg.org/#default-reader-constructor
WebIDL::ExceptionOr<JS::NonnullGCPtr<ReadableStreamDefaultReader>> ReadableStreamDefaultReader::construct_impl(JS::Realm& realm, JS::NonnullGCPtr<ReadableStream> stream)
{
auto reader = realm.heap().allocate<ReadableStreamDefaultReader>(realm, realm);
// 1. Perform ? SetUpReadableStreamDefaultReader(this, stream);
TRY(set_up_readable_stream_default_reader(reader, *stream));
return reader;
}
ReadableStreamDefaultReader::ReadableStreamDefaultReader(JS::Realm& realm)
: Bindings::PlatformObject(realm)
, ReadableStreamGenericReaderMixin(realm)
{
}
void ReadableStreamDefaultReader::initialize(JS::Realm& realm)
{
Base::initialize(realm);
WEB_SET_PROTOTYPE_FOR_INTERFACE(ReadableStreamDefaultReader);
}
void ReadableStreamDefaultReader::visit_edges(Cell::Visitor& visitor)
{
Base::visit_edges(visitor);
ReadableStreamGenericReaderMixin::visit_edges(visitor);
for (auto& request : m_read_requests)
visitor.visit(request);
}
// https://streams.spec.whatwg.org/#read-loop
ReadLoopReadRequest::ReadLoopReadRequest(JS::VM& vm, JS::Realm& realm, ReadableStreamDefaultReader& reader, JS::NonnullGCPtr<SuccessSteps> success_steps, JS::NonnullGCPtr<FailureSteps> failure_steps, JS::GCPtr<ChunkSteps> chunk_steps)
: m_vm(vm)
, m_realm(realm)
, m_reader(reader)
, m_success_steps(success_steps)
, m_failure_steps(failure_steps)
, m_chunk_steps(chunk_steps)
{
}
// chunk steps, given chunk
void ReadLoopReadRequest::on_chunk(JS::Value chunk)
{
// 1. If chunk is not a Uint8Array object, call failureSteps with a TypeError and abort these steps.
if (!chunk.is_object() || !is<JS::Uint8Array>(chunk.as_object())) {
m_failure_steps->function()(JS::TypeError::create(m_realm, "Chunk data is not Uint8Array"sv));
return;
}
auto const& array = static_cast<JS::Uint8Array const&>(chunk.as_object());
auto const& buffer = array.viewed_array_buffer()->buffer();
// 2. Append the bytes represented by chunk to bytes.
m_bytes.append(buffer);
if (m_chunk_steps) {
// FIXME: Can we move the buffer out of the `chunk`? Unclear if that is safe.
m_chunk_steps->function()(MUST(ByteBuffer::copy(buffer)));
}
// FIXME: As the spec suggests, implement this non-recursively - instead of directly. It is not too big of a deal currently
// as we enqueue the entire blob buffer in one go, meaning that we only recurse a single time. Once we begin queuing
// up more than one chunk at a time, we may run into stack overflow problems.
//
// 3. Read-loop given reader, bytes, successSteps, and failureSteps.
readable_stream_default_reader_read(m_reader, *this);
}
// close steps
void ReadLoopReadRequest::on_close()
{
// 1. Call successSteps with bytes.
m_success_steps->function()(move(m_bytes));
}
// error steps, given e
void ReadLoopReadRequest::on_error(JS::Value error)
{
// 1. Call failureSteps with e.
m_failure_steps->function()(error);
}
class DefaultReaderReadRequest final : public ReadRequest {
JS_CELL(DefaultReaderReadRequest, ReadRequest);
JS_DECLARE_ALLOCATOR(DefaultReaderReadRequest);
public:
DefaultReaderReadRequest(JS::Realm& realm, WebIDL::Promise& promise)
: m_realm(realm)
, m_promise(promise)
{
}
virtual void on_chunk(JS::Value chunk) override
{
WebIDL::resolve_promise(m_realm, m_promise, JS::create_iterator_result_object(m_realm->vm(), chunk, false));
}
virtual void on_close() override
{
WebIDL::resolve_promise(m_realm, m_promise, JS::create_iterator_result_object(m_realm->vm(), JS::js_undefined(), true));
}
virtual void on_error(JS::Value error) override
{
WebIDL::reject_promise(m_realm, m_promise, error);
}
private:
virtual void visit_edges(Visitor& visitor) override
{
Base::visit_edges(visitor);
visitor.visit(m_realm);
visitor.visit(m_promise);
}
JS::NonnullGCPtr<JS::Realm> m_realm;
JS::NonnullGCPtr<WebIDL::Promise> m_promise;
};
JS_DEFINE_ALLOCATOR(DefaultReaderReadRequest);
// https://streams.spec.whatwg.org/#default-reader-read
JS::NonnullGCPtr<WebIDL::Promise> ReadableStreamDefaultReader::read()
{
auto& realm = this->realm();
// 1. If this.[[stream]] is undefined, return a promise rejected with a TypeError exception.
if (!m_stream) {
WebIDL::SimpleException exception { WebIDL::SimpleExceptionType::TypeError, "Cannot read from an empty stream"sv };
return WebIDL::create_rejected_promise_from_exception(realm, move(exception));
}
// 2. Let promise be a new promise.
auto promise_capability = WebIDL::create_promise(realm);
// 3. Let readRequest be a new read request with the following items:
// chunk steps, given chunk
// Resolve promise with «[ "value" → chunk, "done" → false ]».
// close steps
// Resolve promise with «[ "value" → undefined, "done" → true ]».
// error steps, given e
// Reject promise with e.
auto read_request = heap().allocate_without_realm<DefaultReaderReadRequest>(realm, promise_capability);
// 4. Perform ! ReadableStreamDefaultReaderRead(this, readRequest).
readable_stream_default_reader_read(*this, read_request);
// 5. Return promise.
return promise_capability;
}
void ReadableStreamDefaultReader::read_a_chunk(Fetch::Infrastructure::IncrementalReadLoopReadRequest& read_request)
{
// To read a chunk from a ReadableStreamDefaultReader reader, given a read request readRequest,
// perform ! ReadableStreamDefaultReaderRead(reader, readRequest).
readable_stream_default_reader_read(*this, read_request);
}
// https://streams.spec.whatwg.org/#readablestreamdefaultreader-read-all-bytes
void ReadableStreamDefaultReader::read_all_bytes(JS::NonnullGCPtr<ReadLoopReadRequest::SuccessSteps> success_steps, JS::NonnullGCPtr<ReadLoopReadRequest::FailureSteps> failure_steps)
{
auto& realm = this->realm();
auto& vm = realm.vm();
// 1. Let readRequest be a new read request with the following items:
// NOTE: items and steps in ReadLoopReadRequest.
auto read_request = heap().allocate_without_realm<ReadLoopReadRequest>(vm, realm, *this, success_steps, failure_steps);
// 2. Perform ! ReadableStreamDefaultReaderRead(this, readRequest).
readable_stream_default_reader_read(*this, read_request);
}
void ReadableStreamDefaultReader::read_all_chunks(JS::NonnullGCPtr<ReadLoopReadRequest::ChunkSteps> chunk_steps, JS::NonnullGCPtr<ReadLoopReadRequest::SuccessSteps> success_steps, JS::NonnullGCPtr<ReadLoopReadRequest::FailureSteps> failure_steps)
{
// AD-HOC: Some spec steps direct us to "read all chunks" from a stream, but there isn't an AO defined to do that.
// We implement those steps by using the "read all bytes" definition, with a custom callback to receive
// each chunk that is read.
auto& realm = this->realm();
auto& vm = realm.vm();
// 1. Let readRequest be a new read request with the following items:
// NOTE: items and steps in ReadLoopReadRequest.
auto read_request = heap().allocate_without_realm<ReadLoopReadRequest>(vm, realm, *this, success_steps, failure_steps, chunk_steps);
// 2. Perform ! ReadableStreamDefaultReaderRead(this, readRequest).
readable_stream_default_reader_read(*this, read_request);
}
// FIXME: This function is a promise-based wrapper around "read all bytes". The spec changed this function to not use promises
// in https://github.com/whatwg/streams/commit/f894acdd417926a2121710803cef593e15127964 - however, it seems that the
// FileAPI blob specification has not been updated to match, see: https://github.com/w3c/FileAPI/issues/187.
JS::NonnullGCPtr<WebIDL::Promise> ReadableStreamDefaultReader::read_all_bytes_deprecated()
{
auto& realm = this->realm();
auto promise = WebIDL::create_promise(realm);
auto success_steps = JS::create_heap_function(realm.heap(), [promise, &realm](ByteBuffer bytes) {
auto buffer = JS::ArrayBuffer::create(realm, move(bytes));
WebIDL::resolve_promise(realm, promise, buffer);
});
auto failure_steps = JS::create_heap_function(realm.heap(), [promise, &realm](JS::Value error) {
WebIDL::reject_promise(realm, promise, error);
});
read_all_bytes(success_steps, failure_steps);
return promise;
}
// https://streams.spec.whatwg.org/#default-reader-release-lock
void ReadableStreamDefaultReader::release_lock()
{
// 1. If this.[[stream]] is undefined, return.
if (!m_stream)
return;
// 2. Perform ! ReadableStreamDefaultReaderRelease(this).
readable_stream_default_reader_release(*this);
}
}

View file

@ -0,0 +1,101 @@
/*
* Copyright (c) 2023, Matthew Olsson <mattco@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/Function.h>
#include <AK/SinglyLinkedList.h>
#include <LibJS/Forward.h>
#include <LibWeb/Bindings/PlatformObject.h>
#include <LibWeb/Forward.h>
#include <LibWeb/Streams/ReadableStreamGenericReader.h>
namespace Web::Streams {
struct ReadableStreamReadResult {
JS::Value value;
bool done;
};
class ReadRequest : public JS::Cell {
JS_CELL(ReadRequest, JS::Cell);
public:
virtual ~ReadRequest() = default;
virtual void on_chunk(JS::Value chunk) = 0;
virtual void on_close() = 0;
virtual void on_error(JS::Value error) = 0;
};
class ReadLoopReadRequest final : public ReadRequest {
JS_CELL(ReadLoopReadRequest, ReadRequest);
JS_DECLARE_ALLOCATOR(ReadLoopReadRequest);
public:
// successSteps, which is an algorithm accepting a byte sequence
using SuccessSteps = JS::HeapFunction<void(ByteBuffer)>;
// failureSteps, which is an algorithm accepting a JavaScript value
using FailureSteps = JS::HeapFunction<void(JS::Value error)>;
// AD-HOC: callback triggered on every chunk received from the stream.
using ChunkSteps = JS::HeapFunction<void(ByteBuffer)>;
ReadLoopReadRequest(JS::VM& vm, JS::Realm& realm, ReadableStreamDefaultReader& reader, JS::NonnullGCPtr<SuccessSteps> success_steps, JS::NonnullGCPtr<FailureSteps> failure_steps, JS::GCPtr<ChunkSteps> chunk_steps = {});
virtual void on_chunk(JS::Value chunk) override;
virtual void on_close() override;
virtual void on_error(JS::Value error) override;
private:
virtual void visit_edges(Visitor&) override;
JS::VM& m_vm;
JS::NonnullGCPtr<JS::Realm> m_realm;
JS::NonnullGCPtr<ReadableStreamDefaultReader> m_reader;
ByteBuffer m_bytes;
JS::NonnullGCPtr<SuccessSteps> m_success_steps;
JS::NonnullGCPtr<FailureSteps> m_failure_steps;
JS::GCPtr<ChunkSteps> m_chunk_steps;
};
// https://streams.spec.whatwg.org/#readablestreamdefaultreader
class ReadableStreamDefaultReader final
: public Bindings::PlatformObject
, public ReadableStreamGenericReaderMixin {
WEB_PLATFORM_OBJECT(ReadableStreamDefaultReader, Bindings::PlatformObject);
JS_DECLARE_ALLOCATOR(ReadableStreamDefaultReader);
public:
static WebIDL::ExceptionOr<JS::NonnullGCPtr<ReadableStreamDefaultReader>> construct_impl(JS::Realm&, JS::NonnullGCPtr<ReadableStream>);
virtual ~ReadableStreamDefaultReader() override = default;
JS::NonnullGCPtr<WebIDL::Promise> read();
void read_a_chunk(Fetch::Infrastructure::IncrementalReadLoopReadRequest& read_request);
void read_all_bytes(JS::NonnullGCPtr<ReadLoopReadRequest::SuccessSteps>, JS::NonnullGCPtr<ReadLoopReadRequest::FailureSteps>);
void read_all_chunks(JS::NonnullGCPtr<ReadLoopReadRequest::ChunkSteps>, JS::NonnullGCPtr<ReadLoopReadRequest::SuccessSteps>, JS::NonnullGCPtr<ReadLoopReadRequest::FailureSteps>);
JS::NonnullGCPtr<WebIDL::Promise> read_all_bytes_deprecated();
void release_lock();
SinglyLinkedList<JS::NonnullGCPtr<ReadRequest>>& read_requests() { return m_read_requests; }
private:
explicit ReadableStreamDefaultReader(JS::Realm&);
virtual void initialize(JS::Realm&) override;
virtual void visit_edges(Cell::Visitor&) override;
SinglyLinkedList<JS::NonnullGCPtr<ReadRequest>> m_read_requests;
};
}

View file

@ -0,0 +1,17 @@
#import <Streams/ReadableStream.idl>
#import <Streams/ReadableStreamGenericReader.idl>
// https://streams.spec.whatwg.org/#readablestreamdefaultreader
[Exposed=*]
interface ReadableStreamDefaultReader {
constructor(ReadableStream stream);
Promise<ReadableStreamReadResult> read();
undefined releaseLock();
};
ReadableStreamDefaultReader includes ReadableStreamGenericReader;
dictionary ReadableStreamReadResult {
any value;
boolean done;
};

View file

@ -0,0 +1,48 @@
/*
* Copyright (c) 2023, Matthew Olsson <mattco@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibJS/Runtime/Promise.h>
#include <LibJS/Runtime/PromiseCapability.h>
#include <LibWeb/Streams/AbstractOperations.h>
#include <LibWeb/Streams/ReadableStream.h>
#include <LibWeb/Streams/ReadableStreamGenericReader.h>
#include <LibWeb/WebIDL/Promise.h>
namespace Web::Streams {
// https://streams.spec.whatwg.org/#generic-reader-closed
JS::GCPtr<WebIDL::Promise> ReadableStreamGenericReaderMixin::closed()
{
// 1. Return this.[[closedPromise]].
return m_closed_promise;
}
// https://streams.spec.whatwg.org/#generic-reader-cancel
JS::NonnullGCPtr<WebIDL::Promise> ReadableStreamGenericReaderMixin::cancel(JS::Value reason)
{
// 1. If this.[[stream]] is undefined, return a promise rejected with a TypeError exception.
if (!m_stream) {
WebIDL::SimpleException exception { WebIDL::SimpleExceptionType::TypeError, "No stream present to cancel"sv };
return WebIDL::create_rejected_promise_from_exception(m_realm, move(exception));
}
// 2. Return ! ReadableStreamReaderGenericCancel(this, reason).
return readable_stream_reader_generic_cancel(*this, reason);
}
ReadableStreamGenericReaderMixin::ReadableStreamGenericReaderMixin(JS::Realm& realm)
: m_realm(realm)
{
}
void ReadableStreamGenericReaderMixin::visit_edges(JS::Cell::Visitor& visitor)
{
visitor.visit(m_closed_promise);
visitor.visit(m_stream);
visitor.visit(m_realm);
}
}

View file

@ -0,0 +1,48 @@
/*
* Copyright (c) 2023, Matthew Olsson <mattco@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/Forward.h>
#include <LibJS/Forward.h>
#include <LibWeb/Bindings/PlatformObject.h>
#include <LibWeb/Forward.h>
#include <LibWeb/WebIDL/Promise.h>
namespace Web::Streams {
// https://streams.spec.whatwg.org/#readablestreamgenericreader
class ReadableStreamGenericReaderMixin {
public:
virtual ~ReadableStreamGenericReaderMixin() = default;
JS::GCPtr<WebIDL::Promise> closed();
JS::NonnullGCPtr<WebIDL::Promise> cancel(JS::Value reason);
JS::GCPtr<ReadableStream> stream() const { return m_stream; }
void set_stream(JS::GCPtr<ReadableStream> stream) { m_stream = stream; }
JS::GCPtr<WebIDL::Promise> closed_promise_capability() { return m_closed_promise; }
void set_closed_promise_capability(JS::GCPtr<WebIDL::Promise> promise) { m_closed_promise = promise; }
protected:
explicit ReadableStreamGenericReaderMixin(JS::Realm&);
void visit_edges(JS::Cell::Visitor&);
// https://streams.spec.whatwg.org/#readablestreamgenericreader-closedpromise
// A promise returned by the reader's closed getter
JS::GCPtr<WebIDL::Promise> m_closed_promise;
// https://streams.spec.whatwg.org/#readablestreamgenericreader-stream
// A ReadableStream instance that owns this reader
JS::GCPtr<ReadableStream> m_stream;
JS::NonnullGCPtr<JS::Realm> m_realm;
};
}

View file

@ -0,0 +1,6 @@
// https://streams.spec.whatwg.org/#readablestreamgenericreader
interface mixin ReadableStreamGenericReader {
readonly attribute Promise<undefined> closed;
Promise<undefined> cancel(optional any reason);
};

View file

@ -0,0 +1,99 @@
/*
* Copyright (c) 2023, Kenneth Myhra <kennethmyhra@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibWeb/Bindings/Intrinsics.h>
#include <LibWeb/Bindings/TransformStreamPrototype.h>
#include <LibWeb/Streams/AbstractOperations.h>
#include <LibWeb/Streams/TransformStream.h>
#include <LibWeb/Streams/TransformStreamDefaultController.h>
#include <LibWeb/Streams/Transformer.h>
#include <LibWeb/Streams/WritableStream.h>
#include <LibWeb/WebIDL/AbstractOperations.h>
#include <LibWeb/WebIDL/ExceptionOr.h>
namespace Web::Streams {
JS_DEFINE_ALLOCATOR(TransformStream);
// https://streams.spec.whatwg.org/#ts-constructor
WebIDL::ExceptionOr<JS::NonnullGCPtr<TransformStream>> TransformStream::construct_impl(JS::Realm& realm, Optional<JS::Handle<JS::Object>> transformer_object, QueuingStrategy const& writable_strategy, QueuingStrategy const& readable_strategy)
{
auto& vm = realm.vm();
auto stream = realm.heap().allocate<TransformStream>(realm, realm);
// 1. If transformer is missing, set it to null.
auto transformer = transformer_object.has_value() ? JS::Value { transformer_object.value() } : JS::js_null();
// 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).
auto readable_size_algorithm = extract_size_algorithm(vm, readable_strategy);
// 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).
auto writable_size_algorithm = extract_size_algorithm(vm, writable_strategy);
// 9. Let startPromise be a new promise.
auto start_promise = WebIDL::create_promise(realm);
// 10. Perform ! InitializeTransformStream(this, startPromise, writableHighWaterMark, writableSizeAlgorithm, readableHighWaterMark, readableSizeAlgorithm).
initialize_transform_stream(*stream, start_promise, writable_high_water_mark, move(writable_size_algorithm), readable_high_water_mark, move(readable_size_algorithm));
// 11. Perform ? SetUpTransformStreamDefaultControllerFromTransformer(this, transformer, transformerDict).
set_up_transform_stream_default_controller_from_transformer(*stream, transformer, transformer_dict);
// 12. If transformerDict["start"] exists, then resolve startPromise with the result of invoking
// transformerDict["start"] with argument list « this.[[controller]] » and callback this value transformer.
if (transformer_dict.start) {
auto result = TRY(WebIDL::invoke_callback(*transformer_dict.start, transformer, stream->controller())).release_value();
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;
}
TransformStream::TransformStream(JS::Realm& realm)
: Bindings::PlatformObject(realm)
{
}
TransformStream::~TransformStream() = default;
void TransformStream::initialize(JS::Realm& realm)
{
Base::initialize(realm);
WEB_SET_PROTOTYPE_FOR_INTERFACE(TransformStream);
}
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);
}
}

View file

@ -0,0 +1,75 @@
/*
* Copyright (c) 2023, Kenneth Myhra <kennethmyhra@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <LibJS/Forward.h>
#include <LibWeb/Bindings/PlatformObject.h>
#include <LibWeb/Forward.h>
#include <LibWeb/Streams/QueuingStrategy.h>
#include <LibWeb/WebIDL/Promise.h>
namespace Web::Streams {
class TransformStream final : public Bindings::PlatformObject {
WEB_PLATFORM_OBJECT(TransformStream, Bindings::PlatformObject);
JS_DECLARE_ALLOCATOR(TransformStream);
public:
virtual ~TransformStream() override;
static WebIDL::ExceptionOr<JS::NonnullGCPtr<TransformStream>> construct_impl(JS::Realm&, Optional<JS::Handle<JS::Object>> transformer_object = {}, QueuingStrategy const& writable_strategy = {}, QueuingStrategy const& readable_strategy = {});
// https://streams.spec.whatwg.org/#ts-readable
JS::NonnullGCPtr<ReadableStream> readable() { return *m_readable; }
void set_readable(ReadableStream& readable) { m_readable = readable; }
// https://streams.spec.whatwg.org/#ts-writable
JS::NonnullGCPtr<WritableStream> writable() { return *m_writable; }
void set_writable(WritableStream& writable) { m_writable = writable; }
Optional<bool> backpressure() const { return m_backpressure; }
void set_backpressure(Optional<bool> value) { m_backpressure = move(value); }
JS::GCPtr<WebIDL::Promise> backpressure_change_promise() const { return m_backpressure_change_promise; }
void set_backpressure_change_promise(JS::GCPtr<WebIDL::Promise> value) { m_backpressure_change_promise = value; }
JS::GCPtr<TransformStreamDefaultController> controller() const { return m_controller; }
void set_controller(JS::GCPtr<TransformStreamDefaultController> value) { m_controller = value; }
private:
explicit TransformStream(JS::Realm& realm);
virtual void initialize(JS::Realm&) override;
virtual void visit_edges(Cell::Visitor&) override;
// https://streams.spec.whatwg.org/#transformstream-backpressure
// Whether there was backpressure on [[readable]] the last time it was observed
Optional<bool> m_backpressure { false };
// https://streams.spec.whatwg.org/#transformstream-backpressurechangepromise
// A promise which is fulfilled and replaced every time the value of [[backpressure]] changes
JS::GCPtr<WebIDL::Promise> m_backpressure_change_promise;
// https://streams.spec.whatwg.org/#transformstream-controller
// A TransformStreamDefaultController created with the ability to control [[readable]] and [[writable]]
JS::GCPtr<TransformStreamDefaultController> 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
JS::GCPtr<ReadableStream> m_readable;
// https://streams.spec.whatwg.org/#transformstream-writable
// The WritableStream instance controlled by this object
JS::GCPtr<WritableStream> m_writable;
};
}

View file

@ -0,0 +1,12 @@
#import <Streams/QueuingStrategy.idl>
#import <Streams/ReadableStream.idl>
#import <Streams/WritableStream.idl>
// https://streams.spec.whatwg.org/#transformstream
[Exposed=*, Transferable]
interface TransformStream {
constructor(optional object transformer, optional QueuingStrategy writableStrategy = {}, optional QueuingStrategy readableStrategy = {});
readonly attribute ReadableStream readable;
readonly attribute WritableStream writable;
};

View file

@ -0,0 +1,74 @@
/*
* Copyright (c) 2023-2024, Kenneth Myhra <kennethmyhra@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibWeb/Bindings/Intrinsics.h>
#include <LibWeb/Bindings/TransformStreamDefaultControllerPrototype.h>
#include <LibWeb/Streams/TransformStream.h>
#include <LibWeb/Streams/TransformStreamDefaultController.h>
namespace Web::Streams {
JS_DEFINE_ALLOCATOR(TransformStreamDefaultController);
TransformStreamDefaultController::TransformStreamDefaultController(JS::Realm& realm)
: Bindings::PlatformObject(realm)
{
}
TransformStreamDefaultController::~TransformStreamDefaultController() = default;
void TransformStreamDefaultController::initialize(JS::Realm& realm)
{
Base::initialize(realm);
WEB_SET_PROTOTYPE_FOR_INTERFACE(TransformStreamDefaultController);
}
void TransformStreamDefaultController::visit_edges(Cell::Visitor& visitor)
{
Base::visit_edges(visitor);
visitor.visit(m_stream);
visitor.visit(m_cancel_algorithm);
visitor.visit(m_finish_promise);
visitor.visit(m_flush_algorithm);
visitor.visit(m_transform_algorithm);
}
// https://streams.spec.whatwg.org/#ts-default-controller-desired-size
Optional<double> TransformStreamDefaultController::desired_size()
{
VERIFY(stream()->readable()->controller().has_value() && stream()->readable()->controller()->has<JS::NonnullGCPtr<ReadableStreamDefaultController>>());
// 1. Let readableController be this.[[stream]].[[readable]].[[controller]].
auto readable_controller = stream()->readable()->controller()->get<JS::NonnullGCPtr<ReadableStreamDefaultController>>();
// 2. Return ! ReadableStreamDefaultControllerGetDesiredSize(readableController).
return readable_stream_default_controller_get_desired_size(*readable_controller);
}
// https://streams.spec.whatwg.org/#ts-default-controller-enqueue
WebIDL::ExceptionOr<void> TransformStreamDefaultController::enqueue(Optional<JS::Value> chunk)
{
// 1. Perform ? TransformStreamDefaultControllerEnqueue(this, chunk).
TRY(transform_stream_default_controller_enqueue(*this, chunk.has_value() ? chunk.value() : JS::js_undefined()));
return {};
}
// https://streams.spec.whatwg.org/#ts-default-controller-error
void TransformStreamDefaultController::error(Optional<JS::Value> reason)
{
// 1. Perform ? TransformStreamDefaultControllerError(this, e).
transform_stream_default_controller_error(*this, reason.has_value() ? reason.value() : JS::js_undefined());
}
// https://streams.spec.whatwg.org/#ts-default-controller-terminate
void TransformStreamDefaultController::terminate()
{
// 1. Perform ? TransformStreamDefaultControllerTerminate(this).
transform_stream_default_controller_terminate(*this);
}
}

View file

@ -0,0 +1,63 @@
/*
* Copyright (c) 2023-2024, Kenneth Myhra <kennethmyhra@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <LibWeb/Bindings/PlatformObject.h>
#include <LibWeb/Streams/AbstractOperations.h>
namespace Web::Streams {
class TransformStreamDefaultController : public Bindings::PlatformObject {
WEB_PLATFORM_OBJECT(TransformStreamDefaultController, Bindings::PlatformObject);
JS_DECLARE_ALLOCATOR(TransformStreamDefaultController);
public:
explicit TransformStreamDefaultController(JS::Realm&);
virtual ~TransformStreamDefaultController() override;
Optional<double> desired_size();
WebIDL::ExceptionOr<void> enqueue(Optional<JS::Value> chunk);
void error(Optional<JS::Value> reason = {});
void terminate();
JS::GCPtr<CancelAlgorithm> cancel_algorithm() { return m_cancel_algorithm; }
void set_cancel_algorithm(JS::GCPtr<CancelAlgorithm> value) { m_cancel_algorithm = value; }
JS::GCPtr<JS::PromiseCapability> finish_promise() { return m_finish_promise; }
void set_finish_promise(JS::GCPtr<JS::PromiseCapability> value) { m_finish_promise = value; }
JS::GCPtr<FlushAlgorithm> flush_algorithm() { return m_flush_algorithm; }
void set_flush_algorithm(JS::GCPtr<FlushAlgorithm>&& value) { m_flush_algorithm = move(value); }
JS::GCPtr<TransformAlgorithm> transform_algorithm() { return m_transform_algorithm; }
void set_transform_algorithm(JS::GCPtr<TransformAlgorithm>&& value) { m_transform_algorithm = move(value); }
JS::GCPtr<TransformStream> stream() { return m_stream; }
void set_stream(JS::GCPtr<TransformStream> stream) { m_stream = stream; }
private:
virtual void initialize(JS::Realm&) override;
virtual void visit_edges(Cell::Visitor&) override;
// https://streams.spec.whatwg.org/#transformstreamdefaultcontroller-cancelalgorithm
JS::GCPtr<CancelAlgorithm> m_cancel_algorithm;
// https://streams.spec.whatwg.org/#transformstreamdefaultcontroller-finishpromise
JS::GCPtr<JS::PromiseCapability> m_finish_promise;
// https://streams.spec.whatwg.org/#transformstreamdefaultcontroller-flushalgorithm
JS::GCPtr<FlushAlgorithm> m_flush_algorithm;
// https://streams.spec.whatwg.org/#transformstreamdefaultcontroller-transformalgorithm
JS::GCPtr<TransformAlgorithm> m_transform_algorithm;
// https://streams.spec.whatwg.org/#transformstreamdefaultcontroller-stream
JS::GCPtr<TransformStream> m_stream;
};
}

View file

@ -0,0 +1,9 @@
// https://streams.spec.whatwg.org/#transformstreamdefaultcontroller
[Exposed=*]
interface TransformStreamDefaultController {
readonly attribute unrestricted double? desiredSize;
undefined enqueue(optional any chunk);
undefined error(optional any reason);
undefined terminate();
};

View file

@ -0,0 +1,39 @@
/*
* Copyright (c) 2023, Kenneth Myhra <kennethmyhra@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibJS/Runtime/VM.h>
#include <LibWeb/Streams/AbstractOperations.h>
#include <LibWeb/Streams/Transformer.h>
#include <LibWeb/WebIDL/CallbackType.h>
namespace Web::Streams {
JS::ThrowCompletionOr<Transformer> Transformer::from_value(JS::VM& vm, JS::Value value)
{
if (!value.is_object())
return Transformer {};
auto& object = value.as_object();
Transformer transformer {
.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 = {},
};
if (TRY(object.has_property("readableType")))
transformer.readable_type = TRY(object.get("readableType"));
if (TRY(object.has_property("writableType")))
transformer.writable_type = TRY(object.get("writableType"));
return transformer;
}
}

View file

@ -0,0 +1,34 @@
/*
* Copyright (c) 2023, Kenneth Myhra <kennethmyhra@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <LibJS/Forward.h>
#include <LibJS/Heap/Handle.h>
#include <LibWeb/Forward.h>
namespace Web::Streams {
// https://streams.spec.whatwg.org/#dictdef-transformer
struct Transformer {
// https://streams.spec.whatwg.org/#dom-transformer-start
JS::Handle<WebIDL::CallbackType> start;
// https://streams.spec.whatwg.org/#dom-transformer-transform
JS::Handle<WebIDL::CallbackType> transform;
// https://streams.spec.whatwg.org/#dom-transformer-flush
JS::Handle<WebIDL::CallbackType> flush;
// https://streams.spec.whatwg.org/#dom-transformer-cancel
JS::Handle<WebIDL::CallbackType> cancel;
// https://streams.spec.whatwg.org/#dom-transformer-readabletype
Optional<JS::Value> readable_type;
// https://streams.spec.whatwg.org/#dom-transformer-writabletype
Optional<JS::Value> writable_type;
static JS::ThrowCompletionOr<Transformer> from_value(JS::VM&, JS::Value);
};
}

View file

@ -0,0 +1,35 @@
/*
* Copyright (c) 2023, Matthew Olsson <mattco@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibJS/Runtime/VM.h>
#include <LibWeb/Streams/AbstractOperations.h>
#include <LibWeb/Streams/UnderlyingSink.h>
#include <LibWeb/WebIDL/CallbackType.h>
namespace Web::Streams {
JS::ThrowCompletionOr<UnderlyingSink> UnderlyingSink::from_value(JS::VM& vm, JS::Value value)
{
if (!value.is_object())
return UnderlyingSink {};
auto& object = value.as_object();
UnderlyingSink underlying_sink {
.start = TRY(property_to_callback(vm, value, "start", WebIDL::OperationReturnsPromise::No)),
.write = TRY(property_to_callback(vm, value, "write", WebIDL::OperationReturnsPromise::Yes)),
.close = TRY(property_to_callback(vm, value, "close", WebIDL::OperationReturnsPromise::Yes)),
.abort = TRY(property_to_callback(vm, value, "abort", WebIDL::OperationReturnsPromise::Yes)),
.type = {},
};
if (TRY(object.has_property("type")))
underlying_sink.type = TRY(object.get("type"));
return underlying_sink;
}
}

View file

@ -0,0 +1,26 @@
/*
* Copyright (c) 2023, Matthew Olsson <mattco@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/Forward.h>
#include <LibJS/Forward.h>
#include <LibWeb/Forward.h>
namespace Web::Streams {
// https://streams.spec.whatwg.org/#dictdef-underlyingsink
struct UnderlyingSink {
JS::Handle<WebIDL::CallbackType> start;
JS::Handle<WebIDL::CallbackType> write;
JS::Handle<WebIDL::CallbackType> close;
JS::Handle<WebIDL::CallbackType> abort;
Optional<JS::Value> type;
static JS::ThrowCompletionOr<UnderlyingSink> from_value(JS::VM&, JS::Value);
};
}

View file

@ -0,0 +1,49 @@
/*
* Copyright (c) 2023, Matthew Olsson <mattco@serenityos.org>
* Copyright (c) 2023, Shannon Booth <shannon@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibJS/Runtime/VM.h>
#include <LibWeb/Streams/AbstractOperations.h>
#include <LibWeb/Streams/UnderlyingSource.h>
#include <LibWeb/WebIDL/AbstractOperations.h>
#include <LibWeb/WebIDL/CallbackType.h>
#include <LibWeb/WebIDL/Types.h>
namespace Web::Streams {
JS::ThrowCompletionOr<UnderlyingSource> UnderlyingSource::from_value(JS::VM& vm, JS::Value value)
{
if (!value.is_object())
return UnderlyingSource {};
auto& object = value.as_object();
UnderlyingSource underlying_source {
.start = TRY(property_to_callback(vm, value, "start", WebIDL::OperationReturnsPromise::No)),
.pull = TRY(property_to_callback(vm, value, "pull", WebIDL::OperationReturnsPromise::Yes)),
.cancel = TRY(property_to_callback(vm, value, "cancel", WebIDL::OperationReturnsPromise::Yes)),
.type = {},
.auto_allocate_chunk_size = {},
};
auto type_value = TRY(object.get("type"));
if (!type_value.is_undefined()) {
auto type_string = TRY(type_value.to_string(vm));
if (type_string == "bytes"sv)
underlying_source.type = ReadableStreamType::Bytes;
else
return vm.throw_completion<JS::TypeError>(MUST(String::formatted("Unknown stream type '{}'", type_value)));
}
if (TRY(object.has_property("autoAllocateChunkSize"))) {
auto value = TRY(object.get("autoAllocateChunkSize"));
underlying_source.auto_allocate_chunk_size = TRY(WebIDL::convert_to_int<WebIDL::UnsignedLongLong>(vm, value, WebIDL::EnforceRange::Yes));
}
return underlying_source;
}
}

View file

@ -0,0 +1,31 @@
/*
* Copyright (c) 2023, Matthew Olsson <mattco@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/Forward.h>
#include <LibJS/Forward.h>
#include <LibWeb/Bindings/PlatformObject.h>
#include <LibWeb/Forward.h>
#include <LibWeb/WebIDL/Promise.h>
namespace Web::Streams {
enum class ReadableStreamType {
Bytes
};
struct UnderlyingSource {
JS::Handle<WebIDL::CallbackType> start;
JS::Handle<WebIDL::CallbackType> pull;
JS::Handle<WebIDL::CallbackType> cancel;
Optional<ReadableStreamType> type;
Optional<u64> auto_allocate_chunk_size;
static JS::ThrowCompletionOr<UnderlyingSource> from_value(JS::VM&, JS::Value);
};
}

View file

@ -0,0 +1,131 @@
/*
* Copyright (c) 2023, Matthew Olsson <mattco@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibJS/Runtime/PromiseCapability.h>
#include <LibWeb/Bindings/Intrinsics.h>
#include <LibWeb/Bindings/WritableStreamPrototype.h>
#include <LibWeb/Streams/AbstractOperations.h>
#include <LibWeb/Streams/UnderlyingSink.h>
#include <LibWeb/Streams/WritableStream.h>
#include <LibWeb/Streams/WritableStreamDefaultController.h>
#include <LibWeb/Streams/WritableStreamDefaultWriter.h>
#include <LibWeb/WebIDL/ExceptionOr.h>
namespace Web::Streams {
JS_DEFINE_ALLOCATOR(WritableStream);
// https://streams.spec.whatwg.org/#ws-constructor
WebIDL::ExceptionOr<JS::NonnullGCPtr<WritableStream>> WritableStream::construct_impl(JS::Realm& realm, Optional<JS::Handle<JS::Object>> const& underlying_sink_object, QueuingStrategy const& strategy)
{
auto& vm = realm.vm();
auto writable_stream = realm.heap().allocate<WritableStream>(realm, realm);
// 1. If underlyingSink is missing, set it to null.
auto underlying_sink = underlying_sink_object.has_value() ? JS::Value(underlying_sink_object.value()) : JS::js_null();
// 2. Let underlyingSinkDict be underlyingSink, converted to an IDL value of type UnderlyingSink.
auto underlying_sink_dict = TRY(UnderlyingSink::from_value(vm, underlying_sink));
// 3. If underlyingSinkDict["type"] exists, throw a RangeError exception.
if (underlying_sink_dict.type.has_value())
return WebIDL::SimpleException { WebIDL::SimpleExceptionType::RangeError, "Invalid use of reserved key 'type'"sv };
// 4. Perform ! InitializeWritableStream(this).
// Note: This AO configures slot values which are already specified in the class's field initializers.
// 5. Let sizeAlgorithm be ! ExtractSizeAlgorithm(strategy).
auto size_algorithm = extract_size_algorithm(vm, strategy);
// 6. Let highWaterMark be ? ExtractHighWaterMark(strategy, 1).
auto high_water_mark = TRY(extract_high_water_mark(strategy, 1));
// 7. Perform ? SetUpWritableStreamDefaultControllerFromUnderlyingSink(this, underlyingSink, underlyingSinkDict, highWaterMark, sizeAlgorithm).
TRY(set_up_writable_stream_default_controller_from_underlying_sink(*writable_stream, underlying_sink, underlying_sink_dict, high_water_mark, move(size_algorithm)));
return writable_stream;
}
// https://streams.spec.whatwg.org/#ws-locked
bool WritableStream::locked() const
{
// 1. Return ! IsWritableStreamLocked(this).
return is_writable_stream_locked(*this);
}
// https://streams.spec.whatwg.org/#ws-close
JS::GCPtr<WebIDL::Promise> WritableStream::close()
{
auto& realm = this->realm();
// 1. If ! IsWritableStreamLocked(this) is true, return a promise rejected with a TypeError exception.
if (is_writable_stream_locked(*this)) {
auto exception = JS::TypeError::create(realm, "Cannot close a locked stream"sv);
return WebIDL::create_rejected_promise(realm, exception);
}
// 2. If ! WritableStreamCloseQueuedOrInFlight(this) is true, return a promise rejected with a TypeError exception.
if (writable_stream_close_queued_or_in_flight(*this)) {
auto exception = JS::TypeError::create(realm, "Cannot close a stream that is already closed or errored"sv);
return WebIDL::create_rejected_promise(realm, exception);
}
// 3. Return ! WritableStreamClose(this).
return writable_stream_close(*this);
}
// https://streams.spec.whatwg.org/#ws-abort
JS::GCPtr<WebIDL::Promise> WritableStream::abort(JS::Value reason)
{
auto& realm = this->realm();
// 1. If ! IsWritableStreamLocked(this) is true, return a promise rejected with a TypeError exception.
if (is_writable_stream_locked(*this)) {
auto exception = JS::TypeError::create(realm, "Cannot abort a locked stream"sv);
return WebIDL::create_rejected_promise(realm, exception);
}
// 2. Return ! WritableStreamAbort(this, reason).
return writable_stream_abort(*this, reason);
}
// https://streams.spec.whatwg.org/#ws-get-writer
WebIDL::ExceptionOr<JS::NonnullGCPtr<WritableStreamDefaultWriter>> WritableStream::get_writer()
{
// 1. Return ? AcquireWritableStreamDefaultWriter(this).
return acquire_writable_stream_default_writer(*this);
}
WritableStream::WritableStream(JS::Realm& realm)
: Bindings::PlatformObject(realm)
{
}
void WritableStream::initialize(JS::Realm& realm)
{
Base::initialize(realm);
WEB_SET_PROTOTYPE_FOR_INTERFACE(WritableStream);
}
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);
}
}

View file

@ -0,0 +1,140 @@
/*
* Copyright (c) 2023, Matthew Olsson <mattco@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/Forward.h>
#include <AK/SinglyLinkedList.h>
#include <LibJS/Forward.h>
#include <LibWeb/Bindings/PlatformObject.h>
#include <LibWeb/Forward.h>
#include <LibWeb/Streams/QueuingStrategy.h>
#include <LibWeb/WebIDL/Promise.h>
namespace Web::Streams {
// https://streams.spec.whatwg.org/#pending-abort-request
struct PendingAbortRequest {
// https://streams.spec.whatwg.org/#pending-abort-request-promise
// A promise returned from WritableStreamAbort
JS::NonnullGCPtr<WebIDL::Promise> promise;
// https://streams.spec.whatwg.org/#pending-abort-request-reason
// A JavaScript value that was passed as the abort reason to WritableStreamAbort
JS::Value reason;
// https://streams.spec.whatwg.org/#pending-abort-request-was-already-erroring
// A boolean indicating whether or not the stream was in the "erroring" state when WritableStreamAbort was called, which impacts the outcome of the abort request
bool was_already_erroring;
};
// https://streams.spec.whatwg.org/#writablestream
class WritableStream final : public Bindings::PlatformObject {
WEB_PLATFORM_OBJECT(WritableStream, Bindings::PlatformObject);
JS_DECLARE_ALLOCATOR(WritableStream);
public:
enum class State {
Writable,
Closed,
Erroring,
Errored,
};
static WebIDL::ExceptionOr<JS::NonnullGCPtr<WritableStream>> construct_impl(JS::Realm& realm, Optional<JS::Handle<JS::Object>> const& underlying_sink, QueuingStrategy const& = {});
virtual ~WritableStream() = default;
bool locked() const;
JS::GCPtr<WebIDL::Promise> abort(JS::Value reason);
JS::GCPtr<WebIDL::Promise> close();
WebIDL::ExceptionOr<JS::NonnullGCPtr<WritableStreamDefaultWriter>> get_writer();
bool backpressure() const { return m_backpressure; }
void set_backpressure(bool value) { m_backpressure = value; }
JS::GCPtr<WebIDL::Promise const> close_request() const { return m_close_request; }
JS::GCPtr<WebIDL::Promise> close_request() { return m_close_request; }
void set_close_request(JS::GCPtr<WebIDL::Promise> value) { m_close_request = value; }
JS::GCPtr<WritableStreamDefaultController const> controller() const { return m_controller; }
JS::GCPtr<WritableStreamDefaultController> controller() { return m_controller; }
void set_controller(JS::GCPtr<WritableStreamDefaultController> value) { m_controller = value; }
JS::GCPtr<WebIDL::Promise const> in_flight_write_request() const { return m_in_flight_write_request; }
void set_in_flight_write_request(JS::GCPtr<WebIDL::Promise> value) { m_in_flight_write_request = value; }
JS::GCPtr<WebIDL::Promise const> in_flight_close_request() const { return m_in_flight_close_request; }
void set_in_flight_close_request(JS::GCPtr<WebIDL::Promise> value) { m_in_flight_close_request = value; }
Optional<PendingAbortRequest>& pending_abort_request() { return m_pending_abort_request; }
void set_pending_abort_request(Optional<PendingAbortRequest>&& value) { m_pending_abort_request = move(value); }
State state() const { return m_state; }
void set_state(State value) { m_state = value; }
JS::Value stored_error() const { return m_stored_error; }
void set_stored_error(JS::Value value) { m_stored_error = value; }
JS::GCPtr<WritableStreamDefaultWriter const> writer() const { return m_writer; }
JS::GCPtr<WritableStreamDefaultWriter> writer() { return m_writer; }
void set_writer(JS::GCPtr<WritableStreamDefaultWriter> value) { m_writer = value; }
SinglyLinkedList<JS::NonnullGCPtr<WebIDL::Promise>>& write_requests() { return m_write_requests; }
private:
explicit WritableStream(JS::Realm&);
virtual void initialize(JS::Realm&) override;
virtual void visit_edges(Cell::Visitor&) override;
// https://streams.spec.whatwg.org/#writablestream-backpressure
// A boolean indicating the backpressure signal set by the controller
bool m_backpressure { false };
// https://streams.spec.whatwg.org/#writablestream-closerequest
// The promise returned from the writers close() method
JS::GCPtr<WebIDL::Promise> m_close_request;
// https://streams.spec.whatwg.org/#writablestream-controller
// A WritableStreamDefaultController created with the ability to control the state and queue of this stream
JS::GCPtr<WritableStreamDefaultController> 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
JS::GCPtr<WebIDL::Promise> m_in_flight_write_request;
// https://streams.spec.whatwg.org/#writablestream-inflightcloserequest
// A slot set to the promise for the current in-flight close operation while the underlying sink's close algorithm is executing and has not yet fulfilled, used to prevent the abort() method from interrupting close
JS::GCPtr<WebIDL::Promise> m_in_flight_close_request;
// https://streams.spec.whatwg.org/#writablestream-pendingabortrequest
// A pending abort request
Optional<PendingAbortRequest> m_pending_abort_request;
// https://streams.spec.whatwg.org/#writablestream-state
// A string containing the streams current state, used internally; one of "writable", "closed", "erroring", or "errored"
State m_state { State::Writable };
// https://streams.spec.whatwg.org/#writablestream-storederror
// A value indicating how the stream failed, to be given as a failure reason or exception when trying to operate on the stream while in the "errored" state
JS::Value m_stored_error { JS::js_undefined() };
// https://streams.spec.whatwg.org/#writablestream-writer
// A WritableStreamDefaultWriter instance, if the stream is locked to a writer, or undefined if it is not
JS::GCPtr<WritableStreamDefaultWriter> m_writer;
// https://streams.spec.whatwg.org/#writablestream-writerequests
// A list of promises representing the streams internal queue of write requests not yet processed by the underlying sink
SinglyLinkedList<JS::NonnullGCPtr<WebIDL::Promise>> m_write_requests;
};
}

View file

@ -0,0 +1,14 @@
#import <Streams/QueuingStrategy.idl>
#import <Streams/WritableStreamDefaultWriter.idl>
// https://streams.spec.whatwg.org/#writablestream
[Exposed=*, Transferable]
interface WritableStream {
constructor(optional object underlyingSink, optional QueuingStrategy strategy = {});
readonly attribute boolean locked;
Promise<undefined> abort(optional any reason);
Promise<undefined> close();
WritableStreamDefaultWriter getWriter();
};

View file

@ -0,0 +1,68 @@
/*
* Copyright (c) 2023, Matthew Olsson <mattco@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibWeb/Bindings/WritableStreamDefaultControllerPrototype.h>
#include <LibWeb/DOM/AbortSignal.h>
#include <LibWeb/Streams/WritableStream.h>
#include <LibWeb/Streams/WritableStreamDefaultController.h>
namespace Web::Streams {
JS_DEFINE_ALLOCATOR(WritableStreamDefaultController);
void WritableStreamDefaultController::visit_edges(Visitor& visitor)
{
Base::visit_edges(visitor);
visitor.visit(m_signal);
for (auto& value : m_queue)
visitor.visit(value.value);
visitor.visit(m_stream);
visitor.visit(m_abort_algorithm);
visitor.visit(m_close_algorithm);
visitor.visit(m_strategy_size_algorithm);
visitor.visit(m_write_algorithm);
}
// https://streams.spec.whatwg.org/#ws-default-controller-error
void WritableStreamDefaultController::error(JS::Value error)
{
// 1. Let state be this.[[stream]].[[state]].
auto state = m_stream->state();
// 2. If state is not "writable", return.
if (state != WritableStream::State::Writable)
return;
// 3. Perform ! WritableStreamDefaultControllerError(this, e).
writable_stream_default_controller_error(*this, error);
}
// https://streams.spec.whatwg.org/#ws-default-controller-private-abort
JS::NonnullGCPtr<WebIDL::Promise> WritableStreamDefaultController::abort_steps(JS::Value reason)
{
// 1. Let result be the result of performing this.[[abortAlgorithm]], passing reason.
auto result = m_abort_algorithm->function()(reason);
// 2. Perform ! WritableStreamDefaultControllerClearAlgorithms(this).
writable_stream_default_controller_clear_algorithms(*this);
// 3. Return result.
return result;
}
// https://streams.spec.whatwg.org/#ws-default-controller-private-error
void WritableStreamDefaultController::error_steps()
{
// 1. Perform ! ResetQueue(this).
reset_queue(*this);
}
WritableStreamDefaultController::WritableStreamDefaultController(JS::Realm& realm)
: Bindings::PlatformObject(realm)
{
}
}

View file

@ -0,0 +1,102 @@
/*
* Copyright (c) 2023, Matthew Olsson <mattco@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/SinglyLinkedList.h>
#include <LibWeb/Bindings/PlatformObject.h>
#include <LibWeb/Streams/AbstractOperations.h>
namespace Web::Streams {
// https://streams.spec.whatwg.org/#writablestreamdefaultcontroller
class WritableStreamDefaultController final : public Bindings::PlatformObject {
WEB_PLATFORM_OBJECT(WritableStreamDefaultController, Bindings::PlatformObject);
JS_DECLARE_ALLOCATOR(WritableStreamDefaultController);
public:
virtual ~WritableStreamDefaultController() override = default;
void error(JS::Value error);
JS::NonnullGCPtr<DOM::AbortSignal> signal() { return *m_signal; }
void set_signal(JS::NonnullGCPtr<DOM::AbortSignal> value) { m_signal = value; }
JS::GCPtr<AbortAlgorithm> abort_algorithm() { return m_abort_algorithm; }
void set_abort_algorithm(JS::GCPtr<AbortAlgorithm> value) { m_abort_algorithm = value; }
JS::GCPtr<CloseAlgorithm> close_algorithm() { return m_close_algorithm; }
void set_close_algorithm(JS::GCPtr<CloseAlgorithm> value) { m_close_algorithm = value; }
SinglyLinkedList<ValueWithSize>& queue() { return m_queue; }
double queue_total_size() const { return m_queue_total_size; }
void set_queue_total_size(double value) { m_queue_total_size = value; }
bool started() const { return m_started; }
void set_started(bool value) { m_started = value; }
size_t strategy_hwm() const { return m_strategy_hwm; }
void set_strategy_hwm(size_t value) { m_strategy_hwm = value; }
JS::GCPtr<SizeAlgorithm> strategy_size_algorithm() { return m_strategy_size_algorithm; }
void set_strategy_size_algorithm(JS::GCPtr<SizeAlgorithm> value) { m_strategy_size_algorithm = value; }
JS::NonnullGCPtr<WritableStream> stream() { return *m_stream; }
void set_stream(JS::NonnullGCPtr<WritableStream> value) { m_stream = value; }
JS::GCPtr<WriteAlgorithm> write_algorithm() { return m_write_algorithm; }
void set_write_algorithm(JS::GCPtr<WriteAlgorithm> value) { m_write_algorithm = value; }
JS::NonnullGCPtr<WebIDL::Promise> abort_steps(JS::Value reason);
void error_steps();
private:
explicit WritableStreamDefaultController(JS::Realm&);
virtual void visit_edges(Visitor&) override;
// https://streams.spec.whatwg.org/#writablestreamdefaultcontroller-abortalgorithm
// A promise-returning algorithm, taking one argument (the abort reason), which communicates a requested abort to the underlying sink
JS::GCPtr<AbortAlgorithm> m_abort_algorithm;
// https://streams.spec.whatwg.org/#writablestreamdefaultcontroller-closealgorithm
// A promise-returning algorithm which communicates a requested close to the underlying sink
JS::GCPtr<CloseAlgorithm> m_close_algorithm;
// https://streams.spec.whatwg.org/#writablestreamdefaultcontroller-queue
// A list representing the streams internal queue of chunks
SinglyLinkedList<ValueWithSize> m_queue;
// https://streams.spec.whatwg.org/#writablestreamdefaultcontroller-queuetotalsize
// The total size of all the chunks stored in [[queue]]
double m_queue_total_size { 0 };
// https://streams.spec.whatwg.org/#writablestreamdefaultcontroller-signal
// An AbortSignal that can be used to abort the pending write or close operation when the stream is aborted.
JS::GCPtr<DOM::AbortSignal> m_signal;
// https://streams.spec.whatwg.org/#writablestreamdefaultcontroller-started
// A boolean flag indicating whether the underlying sink has finished starting
bool m_started { false };
// https://streams.spec.whatwg.org/#writablestreamdefaultcontroller-strategyhwm
// A number supplied by the creator of the stream as part of the streams queuing strategy, indicating the point at which the stream will apply backpressure to its underlying sink
size_t m_strategy_hwm { 0 };
// https://streams.spec.whatwg.org/#writablestreamdefaultcontroller-strategysizealgorithm
// An algorithm to calculate the size of enqueued chunks, as part of the streams queuing strategy
JS::GCPtr<SizeAlgorithm> m_strategy_size_algorithm;
// https://streams.spec.whatwg.org/#writablestreamdefaultcontroller-stream
// The WritableStream instance controlled
JS::GCPtr<WritableStream> m_stream;
// https://streams.spec.whatwg.org/#writablestreamdefaultcontroller-writealgorithm
// A promise-returning algorithm, taking one argument (the chunk to write), which writes data to the underlying sink
JS::GCPtr<WriteAlgorithm> m_write_algorithm;
};
}

View file

@ -0,0 +1,8 @@
#import <DOM/AbortSignal.idl>
// https://streams.spec.whatwg.org/#writablestreamdefaultcontroller
[Exposed=*]
interface WritableStreamDefaultController {
readonly attribute AbortSignal signal;
undefined error(optional any e);
};

View file

@ -0,0 +1,142 @@
/*
* Copyright (c) 2023, Matthew Olsson <mattco@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibJS/Runtime/PromiseCapability.h>
#include <LibWeb/Bindings/Intrinsics.h>
#include <LibWeb/Bindings/WritableStreamDefaultWriterPrototype.h>
#include <LibWeb/Streams/AbstractOperations.h>
#include <LibWeb/Streams/WritableStream.h>
#include <LibWeb/Streams/WritableStreamDefaultWriter.h>
#include <LibWeb/WebIDL/ExceptionOr.h>
namespace Web::Streams {
JS_DEFINE_ALLOCATOR(WritableStreamDefaultWriter);
WebIDL::ExceptionOr<JS::NonnullGCPtr<WritableStreamDefaultWriter>> WritableStreamDefaultWriter::construct_impl(JS::Realm& realm, JS::NonnullGCPtr<WritableStream> stream)
{
auto writer = realm.heap().allocate<WritableStreamDefaultWriter>(realm, realm);
// 1. Perform ? SetUpWritableStreamDefaultWriter(this, stream).
TRY(set_up_writable_stream_default_writer(*writer, stream));
return writer;
}
// https://streams.spec.whatwg.org/#default-writer-closed
JS::GCPtr<WebIDL::Promise> WritableStreamDefaultWriter::closed()
{
// 1. Return this.[[closedPromise]].
return m_closed_promise;
}
// https://streams.spec.whatwg.org/#default-writer-desired-size
WebIDL::ExceptionOr<Optional<double>> WritableStreamDefaultWriter::desired_size() const
{
// 1. If this.[[stream]] is undefined, throw a TypeError exception.
if (!m_stream)
return WebIDL::SimpleException { WebIDL::SimpleExceptionType::TypeError, "Cannot get desired size of writer that has no locked stream"sv };
// 2. Return ! WritableStreamDefaultWriterGetDesiredSize(this).
return writable_stream_default_writer_get_desired_size(*this);
}
// https://streams.spec.whatwg.org/#default-writer-ready
JS::GCPtr<WebIDL::Promise> WritableStreamDefaultWriter::ready()
{
// 1. Return this.[[readyPromise]].
return m_ready_promise;
}
// https://streams.spec.whatwg.org/#default-writer-abort
JS::GCPtr<WebIDL::Promise> WritableStreamDefaultWriter::abort(JS::Value reason)
{
auto& realm = this->realm();
// 1. If this.[[stream]] is undefined, return a promise rejected with a TypeError exception.
if (!m_stream) {
auto exception = JS::TypeError::create(realm, "Cannot abort a writer that has no locked stream"sv);
return WebIDL::create_rejected_promise(realm, exception);
}
// 2. Return ! WritableStreamDefaultWriterAbort(this, reason).
return writable_stream_default_writer_abort(*this, reason);
}
// https://streams.spec.whatwg.org/#default-writer-close
JS::GCPtr<WebIDL::Promise> WritableStreamDefaultWriter::close()
{
auto& realm = this->realm();
// 1. Let stream be this.[[stream]].
// 2. If stream is undefined, return a promise rejected with a TypeError exception.
if (!m_stream) {
auto exception = JS::TypeError::create(realm, "Cannot close a writer that has no locked stream"sv);
return WebIDL::create_rejected_promise(realm, exception);
}
// 3. If ! WritableStreamCloseQueuedOrInFlight(stream) is true, return a promise rejected with a TypeError exception.
if (writable_stream_close_queued_or_in_flight(*m_stream)) {
auto exception = JS::TypeError::create(realm, "Cannot close a stream that is already closed or errored"sv);
return WebIDL::create_rejected_promise(realm, exception);
}
// 4. Return ! WritableStreamDefaultWriterClose(this).
return writable_stream_default_writer_close(*this);
}
// https://streams.spec.whatwg.org/#default-writer-release-lock
void WritableStreamDefaultWriter::release_lock()
{
// 1. Let stream be this.[[stream]].
// 2. If stream is undefined, return.
if (!m_stream)
return;
// 3. Assert: stream.[[writer]] is not undefined.
VERIFY(m_stream->writer());
// 4. Perform ! WritableStreamDefaultWriterRelease(this).
writable_stream_default_writer_release(*this);
}
// https://streams.spec.whatwg.org/#default-writer-write
JS::GCPtr<WebIDL::Promise> WritableStreamDefaultWriter::write(JS::Value chunk)
{
auto& realm = this->realm();
// 1. If this.[[stream]] is undefined, return a promise rejected with a TypeError exception.
if (!m_stream) {
auto exception = JS::TypeError::create(realm, "Cannot write to a writer that has no locked stream"sv);
return WebIDL::create_rejected_promise(realm, exception);
}
// 2. Return ! WritableStreamDefaultWriterWrite(this, chunk).
return writable_stream_default_writer_write(*this, chunk);
}
WritableStreamDefaultWriter::WritableStreamDefaultWriter(JS::Realm& realm)
: Bindings::PlatformObject(realm)
{
}
void WritableStreamDefaultWriter::initialize(JS::Realm& realm)
{
Base::initialize(realm);
WEB_SET_PROTOTYPE_FOR_INTERFACE(WritableStreamDefaultWriter);
}
void WritableStreamDefaultWriter::visit_edges(Cell::Visitor& visitor)
{
Base::visit_edges(visitor);
visitor.visit(m_closed_promise);
visitor.visit(m_ready_promise);
visitor.visit(m_stream);
}
}

View file

@ -0,0 +1,66 @@
/*
* Copyright (c) 2023, Matthew Olsson <mattco@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/Function.h>
#include <AK/SinglyLinkedList.h>
#include <LibJS/Forward.h>
#include <LibWeb/Bindings/PlatformObject.h>
#include <LibWeb/Forward.h>
#include <LibWeb/WebIDL/Promise.h>
namespace Web::Streams {
// https://streams.spec.whatwg.org/#writablestreamdefaultwriter
class WritableStreamDefaultWriter final : public Bindings::PlatformObject {
WEB_PLATFORM_OBJECT(WritableStreamDefaultWriter, Bindings::PlatformObject);
JS_DECLARE_ALLOCATOR(WritableStreamDefaultWriter);
public:
static WebIDL::ExceptionOr<JS::NonnullGCPtr<WritableStreamDefaultWriter>> construct_impl(JS::Realm&, JS::NonnullGCPtr<WritableStream>);
virtual ~WritableStreamDefaultWriter() override = default;
JS::GCPtr<WebIDL::Promise> closed();
WebIDL::ExceptionOr<Optional<double>> desired_size() const;
JS::GCPtr<WebIDL::Promise> ready();
JS::GCPtr<WebIDL::Promise> abort(JS::Value reason);
JS::GCPtr<WebIDL::Promise> close();
void release_lock();
JS::GCPtr<WebIDL::Promise> write(JS::Value chunk);
JS::GCPtr<WebIDL::Promise> closed_promise() { return m_closed_promise; }
void set_closed_promise(JS::GCPtr<WebIDL::Promise> value) { m_closed_promise = value; }
JS::GCPtr<WebIDL::Promise> ready_promise() { return m_ready_promise; }
void set_ready_promise(JS::GCPtr<WebIDL::Promise> value) { m_ready_promise = value; }
JS::GCPtr<WritableStream const> stream() const { return m_stream; }
JS::GCPtr<WritableStream> stream() { return m_stream; }
void set_stream(JS::GCPtr<WritableStream> value) { m_stream = value; }
private:
explicit WritableStreamDefaultWriter(JS::Realm&);
virtual void initialize(JS::Realm&) override;
virtual void visit_edges(Cell::Visitor&) override;
// https://streams.spec.whatwg.org/#writablestreamdefaultwriter-closedpromise
// A promise returned by the writers closed getter
JS::GCPtr<WebIDL::Promise> m_closed_promise;
// https://streams.spec.whatwg.org/#writablestreamdefaultwriter-readypromise
// A promise returned by the writers ready getter
JS::GCPtr<WebIDL::Promise> m_ready_promise;
// https://streams.spec.whatwg.org/#writablestreamdefaultwriter-stream
// A WritableStream instance that owns this reader
JS::GCPtr<WritableStream> m_stream;
};
}

View file

@ -0,0 +1,16 @@
#import <Streams/WritableStream.idl>
// https://streams.spec.whatwg.org/#writablestreamdefaultwriter
[Exposed=*]
interface WritableStreamDefaultWriter {
constructor(WritableStream stream);
readonly attribute Promise<undefined> closed;
readonly attribute unrestricted double? desiredSize;
readonly attribute Promise<undefined> ready;
Promise<undefined> abort(optional any reason);
Promise<undefined> close();
undefined releaseLock();
Promise<undefined> write(optional any chunk);
};