diff options
author | Taym Haddadi <haddadi.taym@gmail.com> | 2025-01-27 16:52:54 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2025-01-27 15:52:54 +0000 |
commit | 9943e9772640073032a8828cef032a19730a369b (patch) | |
tree | 534e22f50a203ac9b491ff12bbc648982c1a7762 /components/script/dom | |
parent | 177b5b2ceff86b89d87c4536ac4e33e382ffcc11 (diff) | |
download | servo-9943e9772640073032a8828cef032a19730a369b.tar.gz servo-9943e9772640073032a8828cef032a19730a369b.zip |
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>
Diffstat (limited to 'components/script/dom')
-rw-r--r-- | components/script/dom/bindings/buffer_source.rs | 365 | ||||
-rw-r--r-- | components/script/dom/imagedata.rs | 2 | ||||
-rw-r--r-- | components/script/dom/readablebytestreamcontroller.rs | 23 | ||||
-rw-r--r-- | components/script/dom/readablestream.rs | 54 | ||||
-rw-r--r-- | components/script/dom/readablestreambyobreader.rs | 167 | ||||
-rw-r--r-- | components/script/dom/readablestreamdefaultreader.rs | 10 |
6 files changed, 399 insertions, 222 deletions
diff --git a/components/script/dom/bindings/buffer_source.rs b/components/script/dom/bindings/buffer_source.rs index 3e095a1e617..299b46a3c85 100644 --- a/components/script/dom/bindings/buffer_source.rs +++ b/components/script/dom/bindings/buffer_source.rs @@ -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,52 +25,24 @@ 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>>), - ArrayBuffer(Box<Heap<*mut JSObject>>), - Default(Box<Heap<*mut JSObject>>), -} + /// Represents an `ArrayBufferView` (e.g., `Uint8Array`, `DataView`). + /// See: <https://webidl.spec.whatwg.org/#ArrayBufferView> + ArrayBufferView(Box<Heap<*mut JSObject>>), -pub(crate) struct HeapBufferSource<T> { - buffer_source: BufferSource, - phantom: PhantomData<T>, -} + /// 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>>), -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); - }, - } - } + /// Default variant, used as a placeholder in initialization. + Default(Box<Heap<*mut JSObject>>), } pub(crate) fn new_initialized_heap_buffer_source<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(), + } + } + + 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())) }, } - Ok(()) } + 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); + }, } } } diff --git a/components/script/dom/imagedata.rs b/components/script/dom/imagedata.rs index 4b44cd88a93..f8da034179e 100644 --- a/components/script/dom/imagedata.rs +++ b/components/script/dom/imagedata.rs @@ -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), diff --git a/components/script/dom/readablebytestreamcontroller.rs b/components/script/dom/readablebytestreamcontroller.rs index 774cd71149e..8d1c1b91eed 100644 --- a/components/script/dom/readablebytestreamcontroller.rs +++ b/components/script/dom/readablebytestreamcontroller.rs @@ -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) } diff --git a/components/script/dom/readablestream.rs b/components/script/dom/readablestream.rs index a99ae33c799..06a05a6f57f 100644 --- a/components/script/dom/readablestream.rs +++ b/components/script/dom/readablestream.rs @@ -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>) { diff --git a/components/script/dom/readablestreambyobreader.rs b/components/script/dom/readablestreambyobreader.rs index 792be7257d5..32e07af9ab1 100644 --- a/components/script/dom/readablestreambyobreader.rs +++ b/components/script/dom/readablestreambyobreader.rs @@ -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 readIntoRequest’s 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> diff --git a/components/script/dom/readablestreamdefaultreader.rs b/components/script/dom/readablestreamdefaultreader.rs index 9a5283b1baa..7b5e46a3d51 100644 --- a/components/script/dom/readablestreamdefaultreader.rs +++ b/components/script/dom/readablestreamdefaultreader.rs @@ -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(); }, |