From 9943e9772640073032a8828cef032a19730a369b Mon Sep 17 00:00:00 2001 From: Taym Haddadi Date: Mon, 27 Jan 2025 16:52:54 +0100 Subject: [PATCH] Script: implement `ReadableStreamBYOBReader::Read` (#35040) * Script: implement ReadableStreamBYOBReader::Read Signed-off-by: Taym Haddadi * fix ReadRequest::close_steps Signed-off-by: Taym Haddadi * fix clippy Signed-off-by: Taym Haddadi * implement viewed_buffer_array_byte_length and byte_length Signed-off-by: Taym Haddadi * fix clippy Signed-off-by: Taym Haddadi * Correct BufferSource implemntation Signed-off-by: Taym Haddadi * Correct detach_buffer implemantation Signed-off-by: Taym Haddadi * fix JS_IsArrayBufferViewObject usage Signed-off-by: Taym Haddadi * Reduce BufferSource to two variants ArrayBuffer and ArrayBufferView Signed-off-by: Taym Haddadi * Add more doc and use promise.reject_error Signed-off-by: Taym Haddadi --------- Signed-off-by: Taym Haddadi --- .../script/dom/bindings/buffer_source.rs | 367 ++++++++---------- components/script/dom/imagedata.rs | 2 +- .../dom/readablebytestreamcontroller.rs | 23 +- components/script/dom/readablestream.rs | 54 ++- .../script/dom/readablestreambyobreader.rs | 167 +++++++- .../script/dom/readablestreamdefaultreader.rs | 10 +- .../webidls/ReadableStreamBYOBReader.webidl | 7 +- 7 files changed, 406 insertions(+), 224 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,54 +25,26 @@ use js::typedarray::{ use crate::dom::globalscope::GlobalScope; use crate::script_runtime::JSContext; -/// -#[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: pub(crate) enum BufferSource { - Int8Array(Box>), - Int16Array(Box>), - Int32Array(Box>), - Uint8Array(Box>), - Uint16Array(Box>), - Uint32Array(Box>), - Uint8ClampedArray(Box>), - BigInt64Array(Box>), - BigUint64Array(Box>), - Float32Array(Box>), - Float64Array(Box>), - DataView(Box>), + /// Represents an `ArrayBufferView` (e.g., `Uint8Array`, `DataView`). + /// See: + ArrayBufferView(Box>), + + /// Represents an `ArrayBuffer`, a fixed-length binary data buffer. + /// See: + #[allow(dead_code)] ArrayBuffer(Box>), + + /// Default variant, used as a placeholder in initialization. Default(Box>), } -pub(crate) struct HeapBufferSource { - buffer_source: BufferSource, - phantom: PhantomData, -} - -unsafe impl crate::dom::bindings::trace::JSTraceable for HeapBufferSource { - #[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( init: HeapTypedArrayInit, ) -> Result, ()> @@ -93,18 +67,7 @@ where let heap_buffer_source = HeapBufferSource::::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 { + buffer_source: BufferSource, + phantom: PhantomData, +} + impl HeapBufferSource where - T: TypedArrayElement + TypedArrayElementCreator, - T::Element: Clone + Copy, + T: TypedArrayElement, { + pub(crate) fn new(buffer_source: BufferSource) -> HeapBufferSource { + HeapBufferSource { + buffer_source, + phantom: PhantomData, + } + } + pub(crate) fn default() -> HeapBufferSource { 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::()); - let _: TypedArray = 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::from(match &self.buffer_source { + BufferSource::ArrayBufferView(buffer) | + BufferSource::ArrayBuffer(buffer) | + BufferSource::Default(buffer) => buffer.get(), + }) + } + + /// + 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> { + 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() + } + + /// + 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 HeapBufferSource +where + T: TypedArrayElement + TypedArrayElementCreator, + T::Element: Clone + Copy, +{ pub(crate) fn acquire_data(&self, cx: JSContext) -> 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() @@ -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 } - /// - 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::()); + let _: TypedArray = 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::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 crate::dom::bindings::trace::JSTraceable for HeapBufferSource { + #[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> { - 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> { let heap_typed_array = match new_initialized_heap_buffer_source::( - 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}; /// #[dom_struct] @@ -23,14 +28,22 @@ impl ReadableByteStreamController { pub(crate) fn set_stream(&self, stream: &ReadableStream) { self.stream.set(Some(stream)) } + + /// + pub(crate) fn perform_pull_into( + &self, + _read_into_request: &ReadIntoRequest, + _view: HeapBufferSource, + _options: &ReadableStreamBYOBReaderReadOptions, + _can_gc: CanGc, + ) { + todo!() + } } impl ReadableByteStreamControllerMethods for ReadableByteStreamController { /// - fn GetByobRequest( - &self, - ) -> Fallible>> - { + fn GetByobRequest(&self) -> Fallible>> { // 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 /// . #[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 + /// + pub(crate) fn perform_pull_into_steps( + &self, + read_into_request: &ReadIntoRequest, + view: HeapBufferSource, + 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)] + /// + 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) { 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}; /// -#[derive(JSTraceable, MallocSizeOf)] +#[derive(Clone, JSTraceable, MallocSizeOf)] pub enum ReadIntoRequest { /// Read(#[ignore_malloc_size_of = "Rc is hard"] Rc), } impl ReadIntoRequest { - /// - pub fn chunk_steps(&self, _chunk: RootedTraceableBox>) { - todo!() + /// + pub fn chunk_steps(&self, chunk: RootedTraceableBox>) { + // 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, + }); + }, + } } - /// - pub fn close_steps(&self, _chunk: Option>>) { - todo!() + /// + pub fn close_steps(&self, chunk: Option>>) { + // 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(&()), + }, + } } - /// - pub(crate) fn error_steps(&self, _e: SafeHandleValue) { - todo!() + /// + 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()) } + /// + pub(crate) fn add_read_into_request(&self, read_request: &ReadIntoRequest) { + self.read_into_requests + .borrow_mut() + .push_back(read_request.clone()); + } + /// pub(crate) fn close(&self) { // If reader is not undefined and reader implements ReadableStreamBYOBReader, @@ -175,6 +208,36 @@ impl ReadableStreamBYOBReader { request.close_steps(None); } } + + /// + pub(crate) fn read( + &self, + view: HeapBufferSource, + 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 for ReadableStreamBYOBReader { @@ -194,9 +257,85 @@ impl ReadableStreamBYOBReaderMethods for ReadableStreamBYO } /// - fn Read(&self, _view: CustomAutoRooterGuard, can_gc: CanGc) -> Rc { - // TODO - Promise::new(&self.reflector_.global(), can_gc) + #[allow(unsafe_code)] + fn Read( + &self, + view: CustomAutoRooterGuard, + options: &ReadableStreamBYOBReaderReadOptions, + can_gc: CanGc, + ) -> Rc { + let view = HeapBufferSource::::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 } /// 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>) { 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 { /// 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(); }, diff --git a/components/script_bindings/webidls/ReadableStreamBYOBReader.webidl b/components/script_bindings/webidls/ReadableStreamBYOBReader.webidl index cae85b43350..0633223a952 100644 --- a/components/script_bindings/webidls/ReadableStreamBYOBReader.webidl +++ b/components/script_bindings/webidls/ReadableStreamBYOBReader.webidl @@ -10,10 +10,15 @@ interface ReadableStreamBYOBReader { constructor(ReadableStream stream); [NewObject] - Promise read(ArrayBufferView view); + Promise read(ArrayBufferView view, + optional ReadableStreamBYOBReaderReadOptions options = {} + ); [Throws] undefined releaseLock(); }; ReadableStreamBYOBReader includes ReadableStreamGenericReader; +dictionary ReadableStreamBYOBReaderReadOptions { + [EnforceRange] unsigned long long min = 1; +};