diff --git a/Tests/LibWeb/Text/expected/Streams/ReadableStreamBYOBReader-read-min.txt b/Tests/LibWeb/Text/expected/Streams/ReadableStreamBYOBReader-read-min.txt
new file mode 100644
index 00000000000..ff260b38035
--- /dev/null
+++ b/Tests/LibWeb/Text/expected/Streams/ReadableStreamBYOBReader-read-min.txt
@@ -0,0 +1,4 @@
+This -⌄
+ will not be processed as one chunk
+This -> will be processed as one chunk
+Stream closed
diff --git a/Tests/LibWeb/Text/input/Streams/ReadableStreamBYOBReader-read-min.html b/Tests/LibWeb/Text/input/Streams/ReadableStreamBYOBReader-read-min.html
new file mode 100644
index 00000000000..e446f8b3ec8
--- /dev/null
+++ b/Tests/LibWeb/Text/input/Streams/ReadableStreamBYOBReader-read-min.html
@@ -0,0 +1,68 @@
+
+
diff --git a/Userland/Libraries/LibWeb/Streams/AbstractOperations.cpp b/Userland/Libraries/LibWeb/Streams/AbstractOperations.cpp
index 26fbe605e41..c1602bd90f3 100644
--- a/Userland/Libraries/LibWeb/Streams/AbstractOperations.cpp
+++ b/Userland/Libraries/LibWeb/Streams/AbstractOperations.cpp
@@ -1133,7 +1133,7 @@ WebIDL::ExceptionOr readable_byte_stream_tee(JS::Realm& real
auto read_into_request = realm.heap().allocate_without_realm(realm, stream, params, cancel_promise, *byob_branch, *other_branch, for_branch2);
// 5. Perform ! ReadableStreamBYOBReaderRead(reader, view, 1, readIntoRequest).
- readable_stream_byob_reader_read(params->reader.get>(), view, read_into_request);
+ readable_stream_byob_reader_read(params->reader.get>(), view, 1, read_into_request);
});
// 17. Let pull1Algorithm be the following steps:
@@ -1656,34 +1656,36 @@ void readable_byte_stream_controller_fill_head_pull_into_descriptor(ReadableByte
// https://streams.spec.whatwg.org/#readable-byte-stream-controller-fill-pull-into-descriptor-from-queue
bool readable_byte_stream_controller_fill_pull_into_descriptor_from_queue(ReadableByteStreamController& controller, PullIntoDescriptor& pull_into_descriptor)
{
- // 1. Let elementSize be pullIntoDescriptor.[[elementSize]].
- auto element_size = pull_into_descriptor.element_size;
-
- // 2. Let currentAlignedBytes be pullIntoDescriptor’s bytes filled − (pullIntoDescriptor’s bytes filled mod elementSize).
- auto current_aligned_bytes = pull_into_descriptor.bytes_filled - (pull_into_descriptor.bytes_filled % pull_into_descriptor.element_size);
-
- // 3. Let maxBytesToCopy be min(controller.[[queueTotalSize]], pullIntoDescriptor’s byte length − pullIntoDescriptor’s bytes filled).
+ // 1. Let maxBytesToCopy be min(controller.[[queueTotalSize]], pullIntoDescriptor’s byte length − pullIntoDescriptor’s bytes filled).
auto max_bytes_to_copy = min(controller.queue_total_size(), pull_into_descriptor.byte_length - pull_into_descriptor.bytes_filled);
- // 4. Let maxBytesFilled be pullIntoDescriptor’s bytes filled + maxBytesToCopy.
+ // 2. Let maxBytesFilled be pullIntoDescriptor’s bytes filled + maxBytesToCopy.
u64 max_bytes_filled = pull_into_descriptor.bytes_filled + max_bytes_to_copy;
- // 5. Let maxAlignedBytes be maxBytesFilled − (maxBytesFilled mod elementSize).
- auto max_aligned_bytes = max_bytes_filled - (max_bytes_filled % element_size);
-
- // 6. Let totalBytesToCopyRemaining be maxBytesToCopy.
+ // 3. Let totalBytesToCopyRemaining be maxBytesToCopy.
auto total_bytes_to_copy_remaining = max_bytes_to_copy;
- // 7. Let ready be false.
+ // 4. Let ready be false.
bool ready = false;
- // 8. If maxAlignedBytes > currentAlignedBytes,
- if (max_aligned_bytes > current_aligned_bytes) {
+ // 5. Assert: pullIntoDescriptor’s bytes filled < pullIntoDescriptor’s minimum fill.
+ VERIFY(pull_into_descriptor.bytes_filled < pull_into_descriptor.minimum_fill);
+
+ // 6. Let remainderBytes be the remainder after dividing maxBytesFilled by pullIntoDescriptor’s element size.
+ auto remainder_bytes = max_bytes_filled % pull_into_descriptor.element_size;
+
+ // 7. Let maxAlignedBytes be maxBytesFilled − remainderBytes.
+ auto max_aligned_bytes = max_bytes_filled - remainder_bytes;
+
+ // 8. If maxAlignedBytes ≥ pullIntoDescriptor’s minimum fill,
+ if (max_aligned_bytes >= pull_into_descriptor.minimum_fill) {
// 1. Set totalBytesToCopyRemaining to maxAlignedBytes − pullIntoDescriptor’s bytes filled.
total_bytes_to_copy_remaining = max_aligned_bytes - pull_into_descriptor.bytes_filled;
// 2. Set ready to true.
ready = true;
+
+ // NOTE: A descriptor for a read() request that is not yet filled up to its minimum length will stay at the head of the queue, so the underlying source can keep filling it.
}
// 9. Let queue be controller.[[queue]].
@@ -1735,8 +1737,8 @@ bool readable_byte_stream_controller_fill_pull_into_descriptor_from_queue(Readab
// 2. Assert: pullIntoDescriptor’s bytes filled > 0.
VERIFY(pull_into_descriptor.bytes_filled > 0);
- // 3. Assert: pullIntoDescriptor’s bytes filled < pullIntoDescriptor’s element size.
- VERIFY(pull_into_descriptor.bytes_filled < pull_into_descriptor.element_size);
+ // 3. Assert: pullIntoDescriptor’s bytes filled < pullIntoDescriptor’s minimum fill.
+ VERIFY(pull_into_descriptor.bytes_filled < pull_into_descriptor.minimum_fill);
}
// 12. Return ready.
@@ -1789,7 +1791,7 @@ JS::Value readable_byte_stream_controller_convert_pull_into_descriptor(JS::Realm
// 3. Assert: bytesFilled ≤ pullIntoDescriptor’s byte length.
VERIFY(bytes_filled <= pull_into_descriptor.byte_length);
- // 4. Assert: bytesFilled mod elementSize is 0.
+ // 4. Assert: the remainder after dividing bytesFilled by elementSize is 0.
VERIFY(bytes_filled % element_size == 0);
// 5. Let buffer be ! TransferArrayBuffer(pullIntoDescriptor’s buffer).
@@ -1800,7 +1802,7 @@ JS::Value readable_byte_stream_controller_convert_pull_into_descriptor(JS::Realm
}
// https://streams.spec.whatwg.org/#readable-byte-stream-controller-pull-into
-void readable_byte_stream_controller_pull_into(ReadableByteStreamController& controller, WebIDL::ArrayBufferView& view, ReadIntoRequest& read_into_request)
+void readable_byte_stream_controller_pull_into(ReadableByteStreamController& controller, WebIDL::ArrayBufferView& view, u64 min, ReadIntoRequest& read_into_request)
{
auto& vm = controller.vm();
auto& realm = controller.realm();
@@ -1832,7 +1834,16 @@ void readable_byte_stream_controller_pull_into(ReadableByteStreamController& con
}
}
- // 5. Let byteOffset be view.[[ByteOffset]].
+ // 5. Let minimumFill be min × elementSize.
+ u64 minimum_fill = min * element_size;
+
+ // 6. Assert: minimumFill ≥ 0 and minimumFill ≤ view.[[ByteLength]].
+ VERIFY(minimum_fill <= view.byte_length());
+
+ // 7. Assert: the remainder after dividing minimumFill by elementSize is 0.
+ VERIFY(minimum_fill % element_size == 0);
+
+ // 8. Let byteOffset be view.[[ByteOffset]].
auto byte_offset = view.byte_offset();
// 6. Let byteLength be view.[[ByteLength]].
@@ -1862,6 +1873,7 @@ void readable_byte_stream_controller_pull_into(ReadableByteStreamController& con
.byte_offset = byte_offset,
.byte_length = byte_length,
.bytes_filled = 0,
+ .minimum_fill = minimum_fill,
.element_size = element_size,
.view_constructor = *ctor,
.reader_type = ReaderType::Byob,
@@ -1935,7 +1947,7 @@ void readable_byte_stream_controller_pull_into(ReadableByteStreamController& con
}
// https://streams.spec.whatwg.org/#readable-stream-byob-reader-read
-void readable_stream_byob_reader_read(ReadableStreamBYOBReader& reader, WebIDL::ArrayBufferView& view, ReadIntoRequest& read_into_request)
+void readable_stream_byob_reader_read(ReadableStreamBYOBReader& reader, WebIDL::ArrayBufferView& view, u64 min, ReadIntoRequest& read_into_request)
{
// 1. Let stream be reader.[[stream]].
auto stream = reader.stream();
@@ -1952,7 +1964,7 @@ void readable_stream_byob_reader_read(ReadableStreamBYOBReader& reader, WebIDL::
}
// 5. Otherwise, perform ! ReadableByteStreamControllerPullInto(stream.[[controller]], view, readIntoRequest).
else {
- readable_byte_stream_controller_pull_into(*stream->controller()->get>(), view, read_into_request);
+ readable_byte_stream_controller_pull_into(*stream->controller()->get>(), view, min, read_into_request);
}
}
@@ -2261,8 +2273,7 @@ WebIDL::ExceptionOr readable_byte_stream_controller_respond_in_readable_st
}
// 4. If pullIntoDescriptor’s bytes filled < pullIntoDescriptor’s minimum fill, return.
- // FIXME: Support minimum fill.
- if (pull_into_descriptor.bytes_filled < pull_into_descriptor.element_size)
+ if (pull_into_descriptor.bytes_filled < pull_into_descriptor.minimum_fill)
return {};
// NOTE: A descriptor for a read() request that is not yet filled up to its minimum length will stay at the head of the queue, so the underlying source can keep filling it.
@@ -2722,8 +2733,8 @@ WebIDL::ExceptionOr readable_byte_stream_controller_close(ReadableByteStre
// 1. Let firstPendingPullInto be controller.[[pendingPullIntos]][0].
auto& first_pending_pull_into = controller.pending_pull_intos().first();
- // 2. If firstPendingPullInto’s bytes filled > 0,
- if (first_pending_pull_into.bytes_filled > 0) {
+ // 2. If the remainder after dividing firstPendingPullInto’s bytes filled by firstPendingPullInto’s element size is not 0,
+ if (first_pending_pull_into.bytes_filled % first_pending_pull_into.element_size != 0) {
// 1. Let e be a new TypeError exception.
auto error = JS::TypeError::create(realm, "Cannot close controller in the middle of processing a write request"sv);
@@ -3454,8 +3465,8 @@ void readable_byte_stream_controller_commit_pull_into_descriptor(ReadableStream&
// 4. If stream.[[state]] is "closed",
if (stream.is_closed()) {
- // 1. Assert: pullIntoDescriptor’s bytes filled is 0.
- VERIFY(pull_into_descriptor.bytes_filled == 0);
+ // 1. Assert: the remainder after dividing pullIntoDescriptor’s bytes filled by pullIntoDescriptor’s element size is 0.
+ VERIFY(pull_into_descriptor.bytes_filled % pull_into_descriptor.element_size == 0);
// 2. Set done to true.
done = true;
diff --git a/Userland/Libraries/LibWeb/Streams/AbstractOperations.h b/Userland/Libraries/LibWeb/Streams/AbstractOperations.h
index f71e1dac1bb..30ca7ed6e38 100644
--- a/Userland/Libraries/LibWeb/Streams/AbstractOperations.h
+++ b/Userland/Libraries/LibWeb/Streams/AbstractOperations.h
@@ -15,6 +15,7 @@
#include
#include
#include
+#include
namespace Web::Streams {
@@ -61,8 +62,8 @@ 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&, ReadIntoRequest&);
-void readable_stream_byob_reader_read(ReadableStreamBYOBReader&, WebIDL::ArrayBufferView&, ReadIntoRequest&);
+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&);
diff --git a/Userland/Libraries/LibWeb/Streams/ReadableByteStreamController.cpp b/Userland/Libraries/LibWeb/Streams/ReadableByteStreamController.cpp
index ea98ee8e3d9..720f8930dd5 100644
--- a/Userland/Libraries/LibWeb/Streams/ReadableByteStreamController.cpp
+++ b/Userland/Libraries/LibWeb/Streams/ReadableByteStreamController.cpp
@@ -155,6 +155,7 @@ void ReadableByteStreamController::pull_steps(JS::NonnullGCPtr read
.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,
diff --git a/Userland/Libraries/LibWeb/Streams/ReadableByteStreamController.h b/Userland/Libraries/LibWeb/Streams/ReadableByteStreamController.h
index 52884bf115b..72bc8a221a6 100644
--- a/Userland/Libraries/LibWeb/Streams/ReadableByteStreamController.h
+++ b/Userland/Libraries/LibWeb/Streams/ReadableByteStreamController.h
@@ -41,6 +41,10 @@ struct PullIntoDescriptor {
// 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;
diff --git a/Userland/Libraries/LibWeb/Streams/ReadableStreamBYOBReader.cpp b/Userland/Libraries/LibWeb/Streams/ReadableStreamBYOBReader.cpp
index 185e273efc1..3861c1f5926 100644
--- a/Userland/Libraries/LibWeb/Streams/ReadableStreamBYOBReader.cpp
+++ b/Userland/Libraries/LibWeb/Streams/ReadableStreamBYOBReader.cpp
@@ -107,7 +107,7 @@ private:
JS_DEFINE_ALLOCATOR(BYOBReaderReadIntoRequest);
// https://streams.spec.whatwg.org/#byob-reader-read
-JS::NonnullGCPtr ReadableStreamBYOBReader::read(JS::Handle& view)
+JS::NonnullGCPtr ReadableStreamBYOBReader::read(JS::Handle& view, ReadableStreamBYOBReaderReadOptions options)
{
auto& realm = this->realm();
@@ -129,16 +129,42 @@ JS::NonnullGCPtr ReadableStreamBYOBReader::read(JS::Handleis_typed_array_base()) {
+ auto const& typed_array = *view->bufferable_object().get>();
+
+ // 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));
}
- // 5. Let promise be a new promise.
+ // 8. Let promise be a new promise.
auto promise_capability = WebIDL::create_promise(realm);
- // 6. Let readIntoRequest be a new read-into request with the following items:
+ // 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
@@ -147,10 +173,10 @@ JS::NonnullGCPtr ReadableStreamBYOBReader::read(JS::Handle(realm, promise_capability);
- // 7. Perform ! ReadableStreamBYOBReaderRead(this, view, readIntoRequest).
- readable_stream_byob_reader_read(*this, *view, *read_into_request);
+ // 10. Perform ! ReadableStreamBYOBReaderRead(this, view, options["min"], readIntoRequest).
+ readable_stream_byob_reader_read(*this, *view, options.min, *read_into_request);
- // 8. Return promise.
+ // 11. Return promise.
return JS::NonnullGCPtr { verify_cast(*promise_capability->promise()) };
}
}
diff --git a/Userland/Libraries/LibWeb/Streams/ReadableStreamBYOBReader.h b/Userland/Libraries/LibWeb/Streams/ReadableStreamBYOBReader.h
index 5f34fbf58be..264ef5437f8 100644
--- a/Userland/Libraries/LibWeb/Streams/ReadableStreamBYOBReader.h
+++ b/Userland/Libraries/LibWeb/Streams/ReadableStreamBYOBReader.h
@@ -13,9 +13,15 @@
#include
#include
#include
+#include
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);
@@ -45,7 +51,7 @@ public:
virtual ~ReadableStreamBYOBReader() override = default;
- JS::NonnullGCPtr read(JS::Handle&);
+ JS::NonnullGCPtr read(JS::Handle&, ReadableStreamBYOBReaderReadOptions options = {});
void release_lock();
diff --git a/Userland/Libraries/LibWeb/Streams/ReadableStreamBYOBReader.idl b/Userland/Libraries/LibWeb/Streams/ReadableStreamBYOBReader.idl
index 4e37bad9b2d..816392f1b92 100644
--- a/Userland/Libraries/LibWeb/Streams/ReadableStreamBYOBReader.idl
+++ b/Userland/Libraries/LibWeb/Streams/ReadableStreamBYOBReader.idl
@@ -7,8 +7,12 @@
interface ReadableStreamBYOBReader {
constructor(ReadableStream stream);
- Promise read(ArrayBufferView view);
+ Promise read(ArrayBufferView view, optional ReadableStreamBYOBReaderReadOptions options = {});
undefined releaseLock();
};
ReadableStreamBYOBReader includes ReadableStreamGenericReader;
+
+dictionary ReadableStreamBYOBReaderReadOptions {
+ [EnforceRange] unsigned long long min = 1;
+};