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 std::sync::Arc;
use js::jsapi::{ 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::wrappers::DetachArrayBuffer;
use js::rust::{CustomAutoRooterGuard, Handle, MutableHandleObject}; use js::rust::{CustomAutoRooterGuard, Handle, MutableHandleObject};
@ -23,54 +25,26 @@ use js::typedarray::{
use crate::dom::globalscope::GlobalScope; use crate::dom::globalscope::GlobalScope;
use crate::script_runtime::JSContext; use crate::script_runtime::JSContext;
/// <https://webidl.spec.whatwg.org/#BufferSource> // Represents a `BufferSource` as defined in the WebIDL specification.
#[allow(dead_code)] ///
/// 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 { pub(crate) enum BufferSource {
Int8Array(Box<Heap<*mut JSObject>>), /// Represents an `ArrayBufferView` (e.g., `Uint8Array`, `DataView`).
Int16Array(Box<Heap<*mut JSObject>>), /// See: <https://webidl.spec.whatwg.org/#ArrayBufferView>
Int32Array(Box<Heap<*mut JSObject>>), ArrayBufferView(Box<Heap<*mut JSObject>>),
Uint8Array(Box<Heap<*mut JSObject>>),
Uint16Array(Box<Heap<*mut JSObject>>), /// Represents an `ArrayBuffer`, a fixed-length binary data buffer.
Uint32Array(Box<Heap<*mut JSObject>>), /// See: <https://webidl.spec.whatwg.org/#idl-ArrayBuffer>
Uint8ClampedArray(Box<Heap<*mut JSObject>>), #[allow(dead_code)]
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>>),
ArrayBuffer(Box<Heap<*mut JSObject>>), ArrayBuffer(Box<Heap<*mut JSObject>>),
/// Default variant, used as a placeholder in initialization.
Default(Box<Heap<*mut JSObject>>), 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>( pub(crate) fn new_initialized_heap_buffer_source<T>(
init: HeapTypedArrayInit, init: HeapTypedArrayInit,
) -> Result<HeapBufferSource<T>, ()> ) -> Result<HeapBufferSource<T>, ()>
@ -93,18 +67,7 @@ where
let heap_buffer_source = HeapBufferSource::<T>::default(); let heap_buffer_source = HeapBufferSource::<T>::default();
match &heap_buffer_source.buffer_source { match &heap_buffer_source.buffer_source {
BufferSource::Int8Array(buffer) | BufferSource::ArrayBufferView(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::ArrayBuffer(buffer) |
BufferSource::Default(buffer) => { BufferSource::Default(buffer) => {
buffer.set(*array); buffer.set(*array);
@ -121,11 +84,22 @@ pub(crate) enum HeapTypedArrayInit {
Info { len: u32, cx: JSContext }, Info { len: u32, cx: JSContext },
} }
pub(crate) struct HeapBufferSource<T> {
buffer_source: BufferSource,
phantom: PhantomData<T>,
}
impl<T> HeapBufferSource<T> impl<T> HeapBufferSource<T>
where where
T: TypedArrayElement + TypedArrayElementCreator, T: TypedArrayElement,
T::Element: Clone + Copy,
{ {
pub(crate) fn new(buffer_source: BufferSource) -> HeapBufferSource<T> {
HeapBufferSource {
buffer_source,
phantom: PhantomData,
}
}
pub(crate) fn default() -> HeapBufferSource<T> { pub(crate) fn default() -> HeapBufferSource<T> {
HeapBufferSource { HeapBufferSource {
buffer_source: BufferSource::Default(Box::default()), buffer_source: BufferSource::Default(Box::default()),
@ -133,47 +107,128 @@ where
} }
} }
pub(crate) fn set_data(&self, cx: JSContext, data: &[T::Element]) -> Result<(), ()> { pub(crate) fn is_initialized(&self) -> bool {
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 { match &self.buffer_source {
BufferSource::Int8Array(buffer) | BufferSource::ArrayBufferView(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::ArrayBuffer(buffer) |
BufferSource::Default(buffer) => { BufferSource::Default(buffer) => !buffer.get().is_null(),
buffer.set(*array);
},
} }
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>, ()> { pub(crate) fn acquire_data(&self, cx: JSContext) -> Result<Vec<T::Element>, ()> {
assert!(self.is_initialized()); assert!(self.is_initialized());
typedarray!(in(*cx) let array: TypedArray = match &self.buffer_source { typedarray!(in(*cx) let array: TypedArray = match &self.buffer_source {
BufferSource::Int8Array(buffer) | BufferSource::ArrayBufferView(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::ArrayBuffer(buffer) |
BufferSource::Default(buffer) => { BufferSource::Default(buffer) => {
buffer.get() buffer.get()
@ -190,18 +245,7 @@ where
}; };
match &self.buffer_source { match &self.buffer_source {
BufferSource::Int8Array(buffer) | BufferSource::ArrayBufferView(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::ArrayBuffer(buffer) |
BufferSource::Default(buffer) => { BufferSource::Default(buffer) => {
buffer.set(ptr::null_mut()); buffer.set(ptr::null_mut());
@ -210,43 +254,6 @@ where
data 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( pub(crate) fn copy_data_to(
&self, &self,
cx: JSContext, cx: JSContext,
@ -256,18 +263,7 @@ where
) -> Result<(), ()> { ) -> Result<(), ()> {
assert!(self.is_initialized()); assert!(self.is_initialized());
typedarray!(in(*cx) let array: TypedArray = match &self.buffer_source { typedarray!(in(*cx) let array: TypedArray = match &self.buffer_source {
BufferSource::Int8Array(buffer) | BufferSource::ArrayBufferView(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::ArrayBuffer(buffer) |
BufferSource::Default(buffer) => { BufferSource::Default(buffer) => {
buffer.get() buffer.get()
@ -294,18 +290,7 @@ where
) -> Result<(), ()> { ) -> Result<(), ()> {
assert!(self.is_initialized()); assert!(self.is_initialized());
typedarray!(in(*cx) let mut array: TypedArray = match &self.buffer_source { typedarray!(in(*cx) let mut array: TypedArray = match &self.buffer_source {
BufferSource::Int8Array(buffer) | BufferSource::ArrayBufferView(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::ArrayBuffer(buffer) |
BufferSource::Default(buffer) => { BufferSource::Default(buffer) => {
buffer.get() buffer.get()
@ -324,50 +309,30 @@ where
Ok(()) 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 { match &self.buffer_source {
BufferSource::Int8Array(buffer) | BufferSource::ArrayBufferView(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::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>, ()> { unsafe impl<T> crate::dom::bindings::trace::JSTraceable for HeapBufferSource<T> {
TypedArray::from(match &self.buffer_source { #[inline]
BufferSource::Int8Array(buffer) | unsafe fn trace(&self, tracer: *mut js::jsapi::JSTracer) {
BufferSource::Int16Array(buffer) | match &self.buffer_source {
BufferSource::Int32Array(buffer) | BufferSource::ArrayBufferView(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::ArrayBuffer(buffer) |
BufferSource::Default(buffer) => buffer.get(), BufferSource::Default(buffer) => {
}) buffer.trace(tracer);
} },
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
} }
} }
} }

View file

@ -66,7 +66,7 @@ impl ImageData {
can_gc: CanGc, can_gc: CanGc,
) -> Fallible<DomRoot<ImageData>> { ) -> Fallible<DomRoot<ImageData>> {
let heap_typed_array = match new_initialized_heap_buffer_source::<ClampedU8>( 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, Ok(heap_typed_array) => heap_typed_array,
Err(_) => return Err(Error::JSFailed), Err(_) => return Err(Error::JSFailed),

View file

@ -4,13 +4,18 @@
use dom_struct::dom_struct; use dom_struct::dom_struct;
use js::rust::HandleValue as SafeHandleValue; 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::codegen::Bindings::ReadableByteStreamControllerBinding::ReadableByteStreamControllerMethods;
use crate::dom::bindings::import::module::{Error, Fallible}; use crate::dom::bindings::import::module::{Error, Fallible};
use crate::dom::bindings::reflector::Reflector; use crate::dom::bindings::reflector::Reflector;
use crate::dom::bindings::root::{DomRoot, MutNullableDom}; use crate::dom::bindings::root::{DomRoot, MutNullableDom};
use crate::dom::readablestream::ReadableStream; 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> /// <https://streams.spec.whatwg.org/#readablebytestreamcontroller>
#[dom_struct] #[dom_struct]
@ -23,14 +28,22 @@ impl ReadableByteStreamController {
pub(crate) fn set_stream(&self, stream: &ReadableStream) { pub(crate) fn set_stream(&self, stream: &ReadableStream) {
self.stream.set(Some(stream)) 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 { impl ReadableByteStreamControllerMethods<crate::DomTypeHolder> for ReadableByteStreamController {
/// <https://streams.spec.whatwg.org/#rbs-controller-byob-request> /// <https://streams.spec.whatwg.org/#rbs-controller-byob-request>
fn GetByobRequest( fn GetByobRequest(&self) -> Fallible<Option<DomRoot<ReadableStreamBYOBRequest>>> {
&self,
) -> Fallible<Option<DomRoot<super::readablestreambyobrequest::ReadableStreamBYOBRequest>>>
{
// TODO // TODO
Err(Error::NotFound) Err(Error::NotFound)
} }

View file

@ -14,6 +14,7 @@ use js::rust::{
HandleObject as SafeHandleObject, HandleValue as SafeHandleValue, HandleObject as SafeHandleObject, HandleValue as SafeHandleValue,
MutableHandleValue as SafeMutableHandleValue, MutableHandleValue as SafeMutableHandleValue,
}; };
use js::typedarray::ArrayBufferViewU8;
use crate::dom::bindings::codegen::Bindings::QueuingStrategyBinding::QueuingStrategy; use crate::dom::bindings::codegen::Bindings::QueuingStrategyBinding::QueuingStrategy;
use crate::dom::bindings::codegen::Bindings::ReadableStreamBinding::{ 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::script_runtime::{CanGc, JSContext as SafeJSContext};
use crate::dom::promisenativehandler::{Callback, PromiseNativeHandler}; 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 /// The fulfillment handler for the reacting to sourceCancelPromise part of
/// <https://streams.spec.whatwg.org/#readable-stream-cancel>. /// <https://streams.spec.whatwg.org/#readable-stream-cancel>.
#[derive(Clone, JSTraceable, MallocSizeOf)] #[derive(Clone, JSTraceable, MallocSizeOf)]
@ -296,7 +301,32 @@ impl ReadableStream {
.get() .get()
.expect("Stream should have controller.") .expect("Stream should have controller.")
.perform_pull_steps(read_request, can_gc), .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. /// Endpoint to enqueue chunks directly from Rust.
/// Note: in other use cases this call happens via the controller. /// Note: in other use cases this call happens via the controller.
pub(crate) fn enqueue_native(&self, bytes: Vec<u8>) { 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::jsapi::Heap;
use js::jsval::{JSVal, UndefinedValue}; use js::jsval::{JSVal, UndefinedValue};
use js::rust::{HandleObject as SafeHandleObject, HandleValue as SafeHandleValue}; 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::bindings::reflector::reflect_dom_object;
use super::readablestreamgenericreader::ReadableStreamGenericReader; use super::readablestreamgenericreader::ReadableStreamGenericReader;
use crate::dom::bindings::cell::DomRefCell; use crate::dom::bindings::cell::DomRefCell;
@ -29,26 +32,49 @@ use crate::dom::readablestream::ReadableStream;
use crate::script_runtime::{CanGc, JSContext as SafeJSContext}; use crate::script_runtime::{CanGc, JSContext as SafeJSContext};
/// <https://streams.spec.whatwg.org/#read-into-request> /// <https://streams.spec.whatwg.org/#read-into-request>
#[derive(JSTraceable, MallocSizeOf)] #[derive(Clone, JSTraceable, MallocSizeOf)]
pub enum ReadIntoRequest { pub enum ReadIntoRequest {
/// <https://streams.spec.whatwg.org/#byob-reader-read> /// <https://streams.spec.whatwg.org/#byob-reader-read>
Read(#[ignore_malloc_size_of = "Rc is hard"] Rc<Promise>), Read(#[ignore_malloc_size_of = "Rc is hard"] Rc<Promise>),
} }
impl ReadIntoRequest { impl ReadIntoRequest {
/// <https://streams.spec.whatwg.org/#read-into-request-chunk-steps> /// <https://streams.spec.whatwg.org/#ref-for-read-into-request-chunk-steps>
pub fn chunk_steps(&self, _chunk: RootedTraceableBox<Heap<JSVal>>) { pub fn chunk_steps(&self, chunk: RootedTraceableBox<Heap<JSVal>>) {
todo!() // 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> /// <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>>>) { pub fn close_steps(&self, chunk: Option<RootedTraceableBox<Heap<JSVal>>>) {
todo!() // 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> /// <https://streams.spec.whatwg.org/#ref-for-read-into-request-error-steps>
pub(crate) fn error_steps(&self, _e: SafeHandleValue) { pub(crate) fn error_steps(&self, e: SafeHandleValue) {
todo!() // 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()) 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> /// <https://streams.spec.whatwg.org/#readable-stream-cancel>
pub(crate) fn close(&self) { pub(crate) fn close(&self) {
// If reader is not undefined and reader implements ReadableStreamBYOBReader, // If reader is not undefined and reader implements ReadableStreamBYOBReader,
@ -175,6 +208,36 @@ impl ReadableStreamBYOBReader {
request.close_steps(None); 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 { impl ReadableStreamBYOBReaderMethods<crate::DomTypeHolder> for ReadableStreamBYOBReader {
@ -194,9 +257,85 @@ impl ReadableStreamBYOBReaderMethods<crate::DomTypeHolder> for ReadableStreamBYO
} }
/// <https://streams.spec.whatwg.org/#byob-reader-read> /// <https://streams.spec.whatwg.org/#byob-reader-read>
fn Read(&self, _view: CustomAutoRooterGuard<ArrayBufferView>, can_gc: CanGc) -> Rc<Promise> { #[allow(unsafe_code)]
// TODO fn Read(
Promise::new(&self.reflector_.global(), can_gc) &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> /// <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>>) { pub(crate) fn chunk_steps(&self, chunk: RootedTraceableBox<Heap<JSVal>>) {
match self { match self {
ReadRequest::Read(promise) => { ReadRequest::Read(promise) => {
// chunk steps, given chunk
// Resolve promise with «[ "value" → chunk, "done" → false ]».
promise.resolve_native(&ReadableStreamReadResult { promise.resolve_native(&ReadableStreamReadResult {
done: Some(false), done: Some(false),
value: chunk, value: chunk,
@ -64,6 +66,8 @@ impl ReadRequest {
pub(crate) fn close_steps(&self) { pub(crate) fn close_steps(&self) {
match self { match self {
ReadRequest::Read(promise) => { ReadRequest::Read(promise) => {
// close steps
// Resolve promise with «[ "value" → undefined, "done" → true ]».
let result = RootedTraceableBox::new(Heap::default()); let result = RootedTraceableBox::new(Heap::default());
result.set(UndefinedValue()); result.set(UndefinedValue());
promise.resolve_native(&ReadableStreamReadResult { promise.resolve_native(&ReadableStreamReadResult {
@ -80,7 +84,11 @@ impl ReadRequest {
/// <https://streams.spec.whatwg.org/#read-request-error-steps> /// <https://streams.spec.whatwg.org/#read-request-error-steps>
pub(crate) fn error_steps(&self, e: SafeHandleValue) { pub(crate) fn error_steps(&self, e: SafeHandleValue) {
match self { 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 } => { ReadRequest::DefaultTee { tee_read_request } => {
tee_read_request.error_steps(); tee_read_request.error_steps();
}, },

View file

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