Script: implement ReadableStreamBYOBReader::Read (#35040)

* Script: implement ReadableStreamBYOBReader::Read

Signed-off-by: Taym Haddadi <haddadi.taym@gmail.com>

* fix ReadRequest::close_steps

Signed-off-by: Taym Haddadi <haddadi.taym@gmail.com>

* fix clippy

Signed-off-by: Taym Haddadi <haddadi.taym@gmail.com>

* implement viewed_buffer_array_byte_length and byte_length

Signed-off-by: Taym Haddadi <haddadi.taym@gmail.com>

* fix clippy

Signed-off-by: Taym Haddadi <haddadi.taym@gmail.com>

* Correct BufferSource implemntation

Signed-off-by: Taym Haddadi <haddadi.taym@gmail.com>

* Correct detach_buffer implemantation

Signed-off-by: Taym Haddadi <haddadi.taym@gmail.com>

* fix JS_IsArrayBufferViewObject usage

Signed-off-by: Taym Haddadi <haddadi.taym@gmail.com>

* Reduce BufferSource to two variants ArrayBuffer and ArrayBufferView

Signed-off-by: Taym Haddadi <haddadi.taym@gmail.com>

* Add more doc and use promise.reject_error

Signed-off-by: Taym Haddadi <haddadi.taym@gmail.com>

---------

Signed-off-by: Taym Haddadi <haddadi.taym@gmail.com>
This commit is contained in:
Taym Haddadi 2025-01-27 16:52:54 +01:00 committed by GitHub
parent 177b5b2cef
commit 9943e97726
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 406 additions and 224 deletions

View file

@ -11,7 +11,9 @@ use std::ptr;
use std::sync::Arc;
use js::jsapi::{
Heap, JSObject, JS_GetArrayBufferViewBuffer, JS_IsArrayBufferViewObject, NewExternalArrayBuffer,
GetArrayBufferByteLength, Heap, IsDetachedArrayBufferObject, JSObject,
JS_GetArrayBufferViewBuffer, JS_GetArrayBufferViewByteLength, JS_IsArrayBufferViewObject,
JS_IsTypedArrayObject, NewExternalArrayBuffer,
};
use js::rust::wrappers::DetachArrayBuffer;
use js::rust::{CustomAutoRooterGuard, Handle, MutableHandleObject};
@ -23,54 +25,26 @@ use js::typedarray::{
use crate::dom::globalscope::GlobalScope;
use crate::script_runtime::JSContext;
/// <https://webidl.spec.whatwg.org/#BufferSource>
#[allow(dead_code)]
// Represents a `BufferSource` as defined in the WebIDL specification.
///
/// A `BufferSource` is either an `ArrayBuffer` or an `ArrayBufferView`, which
/// provides a view onto an `ArrayBuffer`.
///
/// See: <https://webidl.spec.whatwg.org/#BufferSource>
pub(crate) enum BufferSource {
Int8Array(Box<Heap<*mut JSObject>>),
Int16Array(Box<Heap<*mut JSObject>>),
Int32Array(Box<Heap<*mut JSObject>>),
Uint8Array(Box<Heap<*mut JSObject>>),
Uint16Array(Box<Heap<*mut JSObject>>),
Uint32Array(Box<Heap<*mut JSObject>>),
Uint8ClampedArray(Box<Heap<*mut JSObject>>),
BigInt64Array(Box<Heap<*mut JSObject>>),
BigUint64Array(Box<Heap<*mut JSObject>>),
Float32Array(Box<Heap<*mut JSObject>>),
Float64Array(Box<Heap<*mut JSObject>>),
DataView(Box<Heap<*mut JSObject>>),
/// Represents an `ArrayBufferView` (e.g., `Uint8Array`, `DataView`).
/// See: <https://webidl.spec.whatwg.org/#ArrayBufferView>
ArrayBufferView(Box<Heap<*mut JSObject>>),
/// Represents an `ArrayBuffer`, a fixed-length binary data buffer.
/// See: <https://webidl.spec.whatwg.org/#idl-ArrayBuffer>
#[allow(dead_code)]
ArrayBuffer(Box<Heap<*mut JSObject>>),
/// Default variant, used as a placeholder in initialization.
Default(Box<Heap<*mut JSObject>>),
}
pub(crate) struct HeapBufferSource<T> {
buffer_source: BufferSource,
phantom: PhantomData<T>,
}
unsafe impl<T> crate::dom::bindings::trace::JSTraceable for HeapBufferSource<T> {
#[inline]
unsafe fn trace(&self, tracer: *mut js::jsapi::JSTracer) {
match &self.buffer_source {
BufferSource::Int8Array(buffer) |
BufferSource::Int16Array(buffer) |
BufferSource::Int32Array(buffer) |
BufferSource::Uint8Array(buffer) |
BufferSource::Uint16Array(buffer) |
BufferSource::Uint32Array(buffer) |
BufferSource::Uint8ClampedArray(buffer) |
BufferSource::BigInt64Array(buffer) |
BufferSource::BigUint64Array(buffer) |
BufferSource::Float32Array(buffer) |
BufferSource::Float64Array(buffer) |
BufferSource::DataView(buffer) |
BufferSource::ArrayBuffer(buffer) |
BufferSource::Default(buffer) => {
buffer.trace(tracer);
},
}
}
}
pub(crate) fn new_initialized_heap_buffer_source<T>(
init: HeapTypedArrayInit,
) -> Result<HeapBufferSource<T>, ()>
@ -93,18 +67,7 @@ where
let heap_buffer_source = HeapBufferSource::<T>::default();
match &heap_buffer_source.buffer_source {
BufferSource::Int8Array(buffer) |
BufferSource::Int16Array(buffer) |
BufferSource::Int32Array(buffer) |
BufferSource::Uint8Array(buffer) |
BufferSource::Uint16Array(buffer) |
BufferSource::Uint32Array(buffer) |
BufferSource::Uint8ClampedArray(buffer) |
BufferSource::BigInt64Array(buffer) |
BufferSource::BigUint64Array(buffer) |
BufferSource::Float32Array(buffer) |
BufferSource::Float64Array(buffer) |
BufferSource::DataView(buffer) |
BufferSource::ArrayBufferView(buffer) |
BufferSource::ArrayBuffer(buffer) |
BufferSource::Default(buffer) => {
buffer.set(*array);
@ -121,11 +84,22 @@ pub(crate) enum HeapTypedArrayInit {
Info { len: u32, cx: JSContext },
}
pub(crate) struct HeapBufferSource<T> {
buffer_source: BufferSource,
phantom: PhantomData<T>,
}
impl<T> HeapBufferSource<T>
where
T: TypedArrayElement + TypedArrayElementCreator,
T::Element: Clone + Copy,
T: TypedArrayElement,
{
pub(crate) fn new(buffer_source: BufferSource) -> HeapBufferSource<T> {
HeapBufferSource {
buffer_source,
phantom: PhantomData,
}
}
pub(crate) fn default() -> HeapBufferSource<T> {
HeapBufferSource {
buffer_source: BufferSource::Default(Box::default()),
@ -133,47 +107,128 @@ where
}
}
pub(crate) fn set_data(&self, cx: JSContext, data: &[T::Element]) -> Result<(), ()> {
rooted!(in (*cx) let mut array = ptr::null_mut::<JSObject>());
let _: TypedArray<T, *mut JSObject> = create_buffer_source(cx, data, array.handle_mut())?;
pub(crate) fn is_initialized(&self) -> bool {
match &self.buffer_source {
BufferSource::Int8Array(buffer) |
BufferSource::Int16Array(buffer) |
BufferSource::Int32Array(buffer) |
BufferSource::Uint8Array(buffer) |
BufferSource::Uint16Array(buffer) |
BufferSource::Uint32Array(buffer) |
BufferSource::Uint8ClampedArray(buffer) |
BufferSource::BigInt64Array(buffer) |
BufferSource::BigUint64Array(buffer) |
BufferSource::Float32Array(buffer) |
BufferSource::Float64Array(buffer) |
BufferSource::DataView(buffer) |
BufferSource::ArrayBufferView(buffer) |
BufferSource::ArrayBuffer(buffer) |
BufferSource::Default(buffer) => {
buffer.set(*array);
},
BufferSource::Default(buffer) => !buffer.get().is_null(),
}
Ok(())
}
pub(crate) fn get_buffer(&self) -> Result<TypedArray<T, *mut JSObject>, ()> {
TypedArray::from(match &self.buffer_source {
BufferSource::ArrayBufferView(buffer) |
BufferSource::ArrayBuffer(buffer) |
BufferSource::Default(buffer) => buffer.get(),
})
}
/// <https://tc39.es/ecma262/#sec-detacharraybuffer>
pub(crate) fn detach_buffer(&self, cx: JSContext) -> bool {
assert!(self.is_initialized());
match &self.buffer_source {
BufferSource::ArrayBufferView(buffer) | BufferSource::Default(buffer) => {
let mut is_shared = false;
unsafe {
// assert buffer is an ArrayBuffer view
assert!(JS_IsArrayBufferViewObject(*buffer.handle()));
rooted!(in (*cx) let view_buffer =
JS_GetArrayBufferViewBuffer(*cx, buffer.handle(), &mut is_shared));
// This buffer is always created unshared
debug_assert!(!is_shared);
// Detach the ArrayBuffer
DetachArrayBuffer(*cx, view_buffer.handle())
}
},
BufferSource::ArrayBuffer(buffer) => unsafe {
DetachArrayBuffer(*cx, Handle::from_raw(buffer.handle()))
},
}
}
pub(crate) fn buffer_to_option(&self) -> Option<TypedArray<T, *mut JSObject>> {
if self.is_initialized() {
self.get_buffer().ok()
} else {
warn!("Buffer not initialized.");
None
}
}
pub(crate) fn is_detached_buffer(&self, cx: JSContext) -> bool {
assert!(self.is_initialized());
match &self.buffer_source {
BufferSource::ArrayBufferView(buffer) | BufferSource::Default(buffer) => {
let mut is_shared = false;
unsafe {
assert!(JS_IsArrayBufferViewObject(*buffer.handle()));
rooted!(in (*cx) let view_buffer =
JS_GetArrayBufferViewBuffer(*cx, buffer.handle(), &mut is_shared));
debug_assert!(!is_shared);
IsDetachedArrayBufferObject(*view_buffer.handle())
}
},
BufferSource::ArrayBuffer(buffer) => unsafe {
IsDetachedArrayBufferObject(*buffer.handle())
},
}
}
pub(crate) fn viewed_buffer_array_byte_length(&self, cx: JSContext) -> usize {
assert!(self.is_initialized());
match &self.buffer_source {
BufferSource::ArrayBufferView(buffer) | BufferSource::Default(buffer) => {
let mut is_shared = false;
unsafe {
assert!(JS_IsArrayBufferViewObject(*buffer.handle()));
rooted!(in (*cx) let view_buffer =
JS_GetArrayBufferViewBuffer(*cx, buffer.handle(), &mut is_shared));
debug_assert!(!is_shared);
GetArrayBufferByteLength(*view_buffer.handle())
}
},
BufferSource::ArrayBuffer(buffer) => unsafe {
GetArrayBufferByteLength(*buffer.handle())
},
}
}
pub(crate) fn byte_length(&self) -> usize {
match &self.buffer_source {
BufferSource::ArrayBufferView(buffer) | BufferSource::Default(buffer) => unsafe {
JS_GetArrayBufferViewByteLength(*buffer.handle())
},
BufferSource::ArrayBuffer(buffer) => unsafe {
GetArrayBufferByteLength(*buffer.handle())
},
}
}
pub(crate) fn array_length(&self) -> usize {
self.get_buffer().unwrap().len()
}
/// <https://tc39.es/ecma262/#typedarray>
pub(crate) fn has_typed_array_name(&self) -> bool {
match &self.buffer_source {
BufferSource::ArrayBufferView(buffer) | BufferSource::Default(buffer) => unsafe {
JS_IsTypedArrayObject(*buffer.handle())
},
BufferSource::ArrayBuffer(_) => false,
}
}
}
impl<T> HeapBufferSource<T>
where
T: TypedArrayElement + TypedArrayElementCreator,
T::Element: Clone + Copy,
{
pub(crate) fn acquire_data(&self, cx: JSContext) -> Result<Vec<T::Element>, ()> {
assert!(self.is_initialized());
typedarray!(in(*cx) let array: TypedArray = match &self.buffer_source {
BufferSource::Int8Array(buffer) |
BufferSource::Int16Array(buffer) |
BufferSource::Int32Array(buffer) |
BufferSource::Uint8Array(buffer) |
BufferSource::Uint16Array(buffer) |
BufferSource::Uint32Array(buffer) |
BufferSource::Uint8ClampedArray(buffer) |
BufferSource::BigInt64Array(buffer) |
BufferSource::BigUint64Array(buffer) |
BufferSource::Float32Array(buffer) |
BufferSource::Float64Array(buffer) |
BufferSource::DataView(buffer) |
BufferSource::ArrayBufferView(buffer) |
BufferSource::ArrayBuffer(buffer) |
BufferSource::Default(buffer) => {
buffer.get()
@ -190,18 +245,7 @@ where
};
match &self.buffer_source {
BufferSource::Int8Array(buffer) |
BufferSource::Int16Array(buffer) |
BufferSource::Int32Array(buffer) |
BufferSource::Uint8Array(buffer) |
BufferSource::Uint16Array(buffer) |
BufferSource::Uint32Array(buffer) |
BufferSource::Uint8ClampedArray(buffer) |
BufferSource::BigInt64Array(buffer) |
BufferSource::BigUint64Array(buffer) |
BufferSource::Float32Array(buffer) |
BufferSource::Float64Array(buffer) |
BufferSource::DataView(buffer) |
BufferSource::ArrayBufferView(buffer) |
BufferSource::ArrayBuffer(buffer) |
BufferSource::Default(buffer) => {
buffer.set(ptr::null_mut());
@ -210,43 +254,6 @@ where
data
}
/// <https://tc39.es/ecma262/#sec-detacharraybuffer>
pub(crate) fn detach_buffer(&self, cx: JSContext) -> bool {
match &self.buffer_source {
BufferSource::Int8Array(buffer) |
BufferSource::Int16Array(buffer) |
BufferSource::Int32Array(buffer) |
BufferSource::Uint8Array(buffer) |
BufferSource::Uint16Array(buffer) |
BufferSource::Uint32Array(buffer) |
BufferSource::Uint8ClampedArray(buffer) |
BufferSource::BigInt64Array(buffer) |
BufferSource::BigUint64Array(buffer) |
BufferSource::Float32Array(buffer) |
BufferSource::Float64Array(buffer) |
BufferSource::DataView(buffer) |
BufferSource::ArrayBuffer(buffer) |
BufferSource::Default(buffer) => {
assert!(self.is_initialized());
let mut is_shared = false;
unsafe {
if JS_IsArrayBufferViewObject(*buffer.handle()) {
// If it is an ArrayBuffer view, get the buffer using JS_GetArrayBufferViewBuffer
rooted!(in (*cx) let view_buffer =
JS_GetArrayBufferViewBuffer(*cx, buffer.handle(), &mut is_shared));
// This buffer is always created unshared
debug_assert!(!is_shared);
// Detach the ArrayBuffer
DetachArrayBuffer(*cx, view_buffer.handle())
} else {
// If it's not an ArrayBuffer view, Detach the buffer directly
DetachArrayBuffer(*cx, Handle::from_raw(buffer.handle()))
}
}
},
}
}
pub(crate) fn copy_data_to(
&self,
cx: JSContext,
@ -256,18 +263,7 @@ where
) -> Result<(), ()> {
assert!(self.is_initialized());
typedarray!(in(*cx) let array: TypedArray = match &self.buffer_source {
BufferSource::Int8Array(buffer) |
BufferSource::Int16Array(buffer) |
BufferSource::Int32Array(buffer) |
BufferSource::Uint8Array(buffer) |
BufferSource::Uint16Array(buffer) |
BufferSource::Uint32Array(buffer) |
BufferSource::Uint8ClampedArray(buffer) |
BufferSource::BigInt64Array(buffer) |
BufferSource::BigUint64Array(buffer) |
BufferSource::Float32Array(buffer) |
BufferSource::Float64Array(buffer) |
BufferSource::DataView(buffer) |
BufferSource::ArrayBufferView(buffer) |
BufferSource::ArrayBuffer(buffer) |
BufferSource::Default(buffer) => {
buffer.get()
@ -294,18 +290,7 @@ where
) -> Result<(), ()> {
assert!(self.is_initialized());
typedarray!(in(*cx) let mut array: TypedArray = match &self.buffer_source {
BufferSource::Int8Array(buffer) |
BufferSource::Int16Array(buffer) |
BufferSource::Int32Array(buffer) |
BufferSource::Uint8Array(buffer) |
BufferSource::Uint16Array(buffer) |
BufferSource::Uint32Array(buffer) |
BufferSource::Uint8ClampedArray(buffer) |
BufferSource::BigInt64Array(buffer) |
BufferSource::BigUint64Array(buffer) |
BufferSource::Float32Array(buffer) |
BufferSource::Float64Array(buffer) |
BufferSource::DataView(buffer) |
BufferSource::ArrayBufferView(buffer) |
BufferSource::ArrayBuffer(buffer) |
BufferSource::Default(buffer) => {
buffer.get()
@ -324,50 +309,30 @@ where
Ok(())
}
pub(crate) fn is_initialized(&self) -> bool {
pub(crate) fn set_data(&self, cx: JSContext, data: &[T::Element]) -> Result<(), ()> {
rooted!(in (*cx) let mut array = ptr::null_mut::<JSObject>());
let _: TypedArray<T, *mut JSObject> = create_buffer_source(cx, data, array.handle_mut())?;
match &self.buffer_source {
BufferSource::Int8Array(buffer) |
BufferSource::Int16Array(buffer) |
BufferSource::Int32Array(buffer) |
BufferSource::Uint8Array(buffer) |
BufferSource::Uint16Array(buffer) |
BufferSource::Uint32Array(buffer) |
BufferSource::Uint8ClampedArray(buffer) |
BufferSource::BigInt64Array(buffer) |
BufferSource::BigUint64Array(buffer) |
BufferSource::Float32Array(buffer) |
BufferSource::Float64Array(buffer) |
BufferSource::DataView(buffer) |
BufferSource::ArrayBufferView(buffer) |
BufferSource::ArrayBuffer(buffer) |
BufferSource::Default(buffer) => !buffer.get().is_null(),
BufferSource::Default(buffer) => {
buffer.set(*array);
},
}
Ok(())
}
}
pub(crate) fn get_buffer(&self) -> Result<TypedArray<T, *mut JSObject>, ()> {
TypedArray::from(match &self.buffer_source {
BufferSource::Int8Array(buffer) |
BufferSource::Int16Array(buffer) |
BufferSource::Int32Array(buffer) |
BufferSource::Uint8Array(buffer) |
BufferSource::Uint16Array(buffer) |
BufferSource::Uint32Array(buffer) |
BufferSource::Uint8ClampedArray(buffer) |
BufferSource::BigInt64Array(buffer) |
BufferSource::BigUint64Array(buffer) |
BufferSource::Float32Array(buffer) |
BufferSource::Float64Array(buffer) |
BufferSource::DataView(buffer) |
unsafe impl<T> crate::dom::bindings::trace::JSTraceable for HeapBufferSource<T> {
#[inline]
unsafe fn trace(&self, tracer: *mut js::jsapi::JSTracer) {
match &self.buffer_source {
BufferSource::ArrayBufferView(buffer) |
BufferSource::ArrayBuffer(buffer) |
BufferSource::Default(buffer) => buffer.get(),
})
}
pub(crate) fn buffer_to_option(&self) -> Option<TypedArray<T, *mut JSObject>> {
if self.is_initialized() {
Some(self.get_buffer().expect("Failed to get buffer."))
} else {
warn!("Buffer not initialized.");
None
BufferSource::Default(buffer) => {
buffer.trace(tracer);
},
}
}
}

View file

@ -66,7 +66,7 @@ impl ImageData {
can_gc: CanGc,
) -> Fallible<DomRoot<ImageData>> {
let heap_typed_array = match new_initialized_heap_buffer_source::<ClampedU8>(
HeapTypedArrayInit::Buffer(BufferSource::Uint8ClampedArray(Heap::boxed(jsobject))),
HeapTypedArrayInit::Buffer(BufferSource::ArrayBufferView(Heap::boxed(jsobject))),
) {
Ok(heap_typed_array) => heap_typed_array,
Err(_) => return Err(Error::JSFailed),

View file

@ -4,13 +4,18 @@
use dom_struct::dom_struct;
use js::rust::HandleValue as SafeHandleValue;
use js::typedarray::ArrayBufferViewU8;
use super::bindings::buffer_source::HeapBufferSource;
use super::bindings::codegen::Bindings::ReadableStreamBYOBReaderBinding::ReadableStreamBYOBReaderReadOptions;
use super::readablestreambyobreader::ReadIntoRequest;
use super::types::ReadableStreamBYOBRequest;
use crate::dom::bindings::codegen::Bindings::ReadableByteStreamControllerBinding::ReadableByteStreamControllerMethods;
use crate::dom::bindings::import::module::{Error, Fallible};
use crate::dom::bindings::reflector::Reflector;
use crate::dom::bindings::root::{DomRoot, MutNullableDom};
use crate::dom::readablestream::ReadableStream;
use crate::script_runtime::JSContext as SafeJSContext;
use crate::script_runtime::{CanGc, JSContext as SafeJSContext};
/// <https://streams.spec.whatwg.org/#readablebytestreamcontroller>
#[dom_struct]
@ -23,14 +28,22 @@ impl ReadableByteStreamController {
pub(crate) fn set_stream(&self, stream: &ReadableStream) {
self.stream.set(Some(stream))
}
/// <https://streams.spec.whatwg.org/#readable-byte-stream-controller-pull-into>
pub(crate) fn perform_pull_into(
&self,
_read_into_request: &ReadIntoRequest,
_view: HeapBufferSource<ArrayBufferViewU8>,
_options: &ReadableStreamBYOBReaderReadOptions,
_can_gc: CanGc,
) {
todo!()
}
}
impl ReadableByteStreamControllerMethods<crate::DomTypeHolder> for ReadableByteStreamController {
/// <https://streams.spec.whatwg.org/#rbs-controller-byob-request>
fn GetByobRequest(
&self,
) -> Fallible<Option<DomRoot<super::readablestreambyobrequest::ReadableStreamBYOBRequest>>>
{
fn GetByobRequest(&self) -> Fallible<Option<DomRoot<ReadableStreamBYOBRequest>>> {
// TODO
Err(Error::NotFound)
}

View file

@ -14,6 +14,7 @@ use js::rust::{
HandleObject as SafeHandleObject, HandleValue as SafeHandleValue,
MutableHandleValue as SafeMutableHandleValue,
};
use js::typedarray::ArrayBufferViewU8;
use crate::dom::bindings::codegen::Bindings::QueuingStrategyBinding::QueuingStrategy;
use crate::dom::bindings::codegen::Bindings::ReadableStreamBinding::{
@ -45,6 +46,10 @@ use crate::realms::{enter_realm, InRealm};
use crate::script_runtime::{CanGc, JSContext as SafeJSContext};
use crate::dom::promisenativehandler::{Callback, PromiseNativeHandler};
use super::bindings::buffer_source::HeapBufferSource;
use super::bindings::codegen::Bindings::ReadableStreamBYOBReaderBinding::ReadableStreamBYOBReaderReadOptions;
use super::readablestreambyobreader::ReadIntoRequest;
/// The fulfillment handler for the reacting to sourceCancelPromise part of
/// <https://streams.spec.whatwg.org/#readable-stream-cancel>.
#[derive(Clone, JSTraceable, MallocSizeOf)]
@ -296,7 +301,32 @@ impl ReadableStream {
.get()
.expect("Stream should have controller.")
.perform_pull_steps(read_request, can_gc),
ControllerType::Byte(_) => todo!(),
ControllerType::Byte(_) => {
unreachable!(
"Pulling a chunk from a stream with a byte controller using a default reader"
)
},
}
}
/// Call into the pull steps of the controller,
/// as part of
/// <https://streams.spec.whatwg.org/#readable-stream-byob-reader-read>
pub(crate) fn perform_pull_into_steps(
&self,
read_into_request: &ReadIntoRequest,
view: HeapBufferSource<ArrayBufferViewU8>,
options: &ReadableStreamBYOBReaderReadOptions,
can_gc: CanGc,
) {
match self.controller {
ControllerType::Byte(ref controller) => controller
.get()
.expect("Stream should have controller.")
.perform_pull_into(read_into_request, view, options, can_gc),
ControllerType::Default(_) => unreachable!(
"Pulling a chunk from a stream with a default controller using a BYOB reader"
),
}
}
@ -321,6 +351,28 @@ impl ReadableStream {
}
}
#[allow(dead_code)]
/// <https://streams.spec.whatwg.org/#readable-stream-add-read-into-request>
pub(crate) fn add_read_into_request(&self, read_request: &ReadIntoRequest) {
match self.reader {
// Assert: stream.[[reader]] implements ReadableStreamBYOBReader.
ReaderType::Default(_) => {
unreachable!("Adding a read into request can only be done on a BYOB reader.")
},
ReaderType::BYOB(ref reader) => {
let Some(reader) = reader.get() else {
unreachable!("Attempt to add a read into request without having first acquired a reader.");
};
// Assert: stream.[[state]] is "readable" or "closed".
assert!(self.is_readable() || self.is_closed());
// Append readRequest to stream.[[reader]].[[readIntoRequests]].
reader.add_read_into_request(read_request);
},
}
}
/// Endpoint to enqueue chunks directly from Rust.
/// Note: in other use cases this call happens via the controller.
pub(crate) fn enqueue_native(&self, bytes: Vec<u8>) {

View file

@ -12,8 +12,11 @@ use js::gc::CustomAutoRooterGuard;
use js::jsapi::Heap;
use js::jsval::{JSVal, UndefinedValue};
use js::rust::{HandleObject as SafeHandleObject, HandleValue as SafeHandleValue};
use js::typedarray::ArrayBufferView;
use js::typedarray::{ArrayBufferView, ArrayBufferViewU8};
use super::bindings::buffer_source::{BufferSource, HeapBufferSource};
use super::bindings::codegen::Bindings::ReadableStreamBYOBReaderBinding::ReadableStreamBYOBReaderReadOptions;
use super::bindings::codegen::Bindings::ReadableStreamDefaultReaderBinding::ReadableStreamReadResult;
use super::bindings::reflector::reflect_dom_object;
use super::readablestreamgenericreader::ReadableStreamGenericReader;
use crate::dom::bindings::cell::DomRefCell;
@ -29,26 +32,49 @@ use crate::dom::readablestream::ReadableStream;
use crate::script_runtime::{CanGc, JSContext as SafeJSContext};
/// <https://streams.spec.whatwg.org/#read-into-request>
#[derive(JSTraceable, MallocSizeOf)]
#[derive(Clone, JSTraceable, MallocSizeOf)]
pub enum ReadIntoRequest {
/// <https://streams.spec.whatwg.org/#byob-reader-read>
Read(#[ignore_malloc_size_of = "Rc is hard"] Rc<Promise>),
}
impl ReadIntoRequest {
/// <https://streams.spec.whatwg.org/#read-into-request-chunk-steps>
pub fn chunk_steps(&self, _chunk: RootedTraceableBox<Heap<JSVal>>) {
todo!()
/// <https://streams.spec.whatwg.org/#ref-for-read-into-request-chunk-steps>
pub fn chunk_steps(&self, chunk: RootedTraceableBox<Heap<JSVal>>) {
// chunk steps, given chunk
// Resolve promise with «[ "value" → chunk, "done" → false ]».
match self {
ReadIntoRequest::Read(promise) => {
promise.resolve_native(&ReadableStreamReadResult {
done: Some(false),
value: chunk,
});
},
}
}
/// <https://streams.spec.whatwg.org/#read-into-request-close-steps>
pub fn close_steps(&self, _chunk: Option<RootedTraceableBox<Heap<JSVal>>>) {
todo!()
/// <https://streams.spec.whatwg.org/#ref-for-read-into-request-close-steps%E2%91%A0>
pub fn close_steps(&self, chunk: Option<RootedTraceableBox<Heap<JSVal>>>) {
// close steps, given chunk
// Resolve promise with «[ "value" → chunk, "done" → true ]».
match self {
ReadIntoRequest::Read(promise) => match chunk {
Some(chunk) => promise.resolve_native(&ReadableStreamReadResult {
done: Some(true),
value: chunk,
}),
None => promise.resolve_native(&()),
},
}
}
/// <https://streams.spec.whatwg.org/#read-into-request-error-steps>
pub(crate) fn error_steps(&self, _e: SafeHandleValue) {
todo!()
/// <https://streams.spec.whatwg.org/#ref-for-read-into-request-error-steps>
pub(crate) fn error_steps(&self, e: SafeHandleValue) {
// error steps, given e
// Reject promise with e.
match self {
ReadIntoRequest::Read(promise) => promise.reject_native(&e),
}
}
}
@ -163,6 +189,13 @@ impl ReadableStreamBYOBReader {
mem::take(&mut *self.read_into_requests.borrow_mut())
}
/// <https://streams.spec.whatwg.org/#readable-stream-add-read-into-request>
pub(crate) fn add_read_into_request(&self, read_request: &ReadIntoRequest) {
self.read_into_requests
.borrow_mut()
.push_back(read_request.clone());
}
/// <https://streams.spec.whatwg.org/#readable-stream-cancel>
pub(crate) fn close(&self) {
// If reader is not undefined and reader implements ReadableStreamBYOBReader,
@ -175,6 +208,36 @@ impl ReadableStreamBYOBReader {
request.close_steps(None);
}
}
/// <https://streams.spec.whatwg.org/#readable-stream-byob-reader-read>
pub(crate) fn read(
&self,
view: HeapBufferSource<ArrayBufferViewU8>,
options: &ReadableStreamBYOBReaderReadOptions,
read_into_request: &ReadIntoRequest,
can_gc: CanGc,
) {
// Let stream be reader.[[stream]].
// Assert: stream is not undefined.
assert!(self.stream.get().is_some());
let stream = self.stream.get().unwrap();
// Set stream.[[disturbed]] to true.
stream.set_is_disturbed(true);
// If stream.[[state]] is "errored", perform readIntoRequests error steps given stream.[[storedError]].
if stream.is_errored() {
let cx = GlobalScope::get_cx();
rooted!(in(*cx) let mut error = UndefinedValue());
stream.get_stored_error(error.handle_mut());
read_into_request.error_steps(error.handle());
} else {
// Otherwise,
// perform ! ReadableByteStreamControllerPullInto(stream.[[controller]], view, min, readIntoRequest).
stream.perform_pull_into_steps(read_into_request, view, options, can_gc);
}
}
}
impl ReadableStreamBYOBReaderMethods<crate::DomTypeHolder> for ReadableStreamBYOBReader {
@ -194,9 +257,85 @@ impl ReadableStreamBYOBReaderMethods<crate::DomTypeHolder> for ReadableStreamBYO
}
/// <https://streams.spec.whatwg.org/#byob-reader-read>
fn Read(&self, _view: CustomAutoRooterGuard<ArrayBufferView>, can_gc: CanGc) -> Rc<Promise> {
// TODO
Promise::new(&self.reflector_.global(), can_gc)
#[allow(unsafe_code)]
fn Read(
&self,
view: CustomAutoRooterGuard<ArrayBufferView>,
options: &ReadableStreamBYOBReaderReadOptions,
can_gc: CanGc,
) -> Rc<Promise> {
let view = HeapBufferSource::<ArrayBufferViewU8>::new(BufferSource::ArrayBufferView(
Heap::boxed(unsafe { *view.underlying_object() }),
));
// Let promise be a new promise.
let promise = Promise::new(&self.global(), can_gc);
let cx = GlobalScope::get_cx();
// If view.[[ByteLength]] is 0, return a promise rejected with a TypeError exception.
if view.byte_length() == 0 {
promise.reject_error(Error::Type("view byte length is 0".to_owned()));
return promise;
}
// If view.[[ViewedArrayBuffer]].[[ArrayBufferByteLength]] is 0,
// return a promise rejected with a TypeError exception.
if view.viewed_buffer_array_byte_length(cx) == 0 {
promise.reject_error(Error::Type("viewed buffer byte length is 0".to_owned()));
return promise;
}
// If ! IsDetachedBuffer(view.[[ViewedArrayBuffer]]) is true,
// return a promise rejected with a TypeError exception.
if view.is_detached_buffer(cx) {
promise.reject_error(Error::Type("view is detached".to_owned()));
return promise;
}
// If options["min"] is 0, return a promise rejected with a TypeError exception.
if options.min == 0 {
promise.reject_error(Error::Type("min is 0".to_owned()));
return promise;
}
// If view has a [[TypedArrayName]] internal slot,
if view.has_typed_array_name() {
// If options["min"] > view.[[ArrayLength]], return a promise rejected with a RangeError exception.
if options.min > (view.array_length() as u64) {
promise.reject_error(Error::Type("min is greater than array length".to_owned()));
return promise;
}
} else {
// Otherwise (i.e., it is a DataView),
// If options["min"] > view.[[ByteLength]], return a promise rejected with a RangeError exception.
if options.min > (view.byte_length() as u64) {
promise.reject_error(Error::Type("min is greater than byte length".to_owned()));
return promise;
}
}
// If this.[[stream]] is undefined, return a promise rejected with a TypeError exception.
if self.stream.get().is_none() {
promise.reject_error(Error::Type("min is greater than byte length".to_owned()));
return promise;
}
// 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
let read_into_request = ReadIntoRequest::Read(promise.clone());
// Perform ! ReadableStreamBYOBReaderRead(this, view, options["min"], readIntoRequest).
self.read(view, options, &read_into_request, can_gc);
// Return promise.
promise
}
/// <https://streams.spec.whatwg.org/#byob-reader-release-lock>

View file

@ -49,6 +49,8 @@ impl ReadRequest {
pub(crate) fn chunk_steps(&self, chunk: RootedTraceableBox<Heap<JSVal>>) {
match self {
ReadRequest::Read(promise) => {
// chunk steps, given chunk
// Resolve promise with «[ "value" → chunk, "done" → false ]».
promise.resolve_native(&ReadableStreamReadResult {
done: Some(false),
value: chunk,
@ -64,6 +66,8 @@ impl ReadRequest {
pub(crate) fn close_steps(&self) {
match self {
ReadRequest::Read(promise) => {
// close steps
// Resolve promise with «[ "value" → undefined, "done" → true ]».
let result = RootedTraceableBox::new(Heap::default());
result.set(UndefinedValue());
promise.resolve_native(&ReadableStreamReadResult {
@ -80,7 +84,11 @@ impl ReadRequest {
/// <https://streams.spec.whatwg.org/#read-request-error-steps>
pub(crate) fn error_steps(&self, e: SafeHandleValue) {
match self {
ReadRequest::Read(promise) => promise.reject_native(&e),
ReadRequest::Read(promise) => {
// error steps, given e
// Reject promise with e.
promise.reject_native(&e)
},
ReadRequest::DefaultTee { tee_read_request } => {
tee_read_request.error_steps();
},

View file

@ -10,10 +10,15 @@ interface ReadableStreamBYOBReader {
constructor(ReadableStream stream);
[NewObject]
Promise<ReadableStreamReadResult> read(ArrayBufferView view);
Promise<ReadableStreamReadResult> read(ArrayBufferView view,
optional ReadableStreamBYOBReaderReadOptions options = {}
);
[Throws]
undefined releaseLock();
};
ReadableStreamBYOBReader includes ReadableStreamGenericReader;
dictionary ReadableStreamBYOBReaderReadOptions {
[EnforceRange] unsigned long long min = 1;
};