diff --git a/components/script/dom/audiobuffer.rs b/components/script/dom/audiobuffer.rs index 830daae3946..2df9ef5327f 100644 --- a/components/script/dom/audiobuffer.rs +++ b/components/script/dom/audiobuffer.rs @@ -248,7 +248,7 @@ impl AudioBufferMethods for AudioBuffer { } self.js_channels.borrow()[channel as usize] - .get_buffer() + .get_typed_array() .map_err(|_| Error::JSFailed) } diff --git a/components/script/dom/bindings/buffer_source.rs b/components/script/dom/bindings/buffer_source.rs index 3e5a6101f0f..3c896ecc164 100644 --- a/components/script/dom/bindings/buffer_source.rs +++ b/components/script/dom/bindings/buffer_source.rs @@ -16,15 +16,31 @@ use std::sync::Arc; #[cfg(feature = "webgpu")] use js::jsapi::NewExternalArrayBuffer; use js::jsapi::{ - GetArrayBufferByteLength, Heap, IsDetachedArrayBufferObject, JS_GetArrayBufferViewBuffer, - JS_GetArrayBufferViewByteLength, JS_IsArrayBufferViewObject, JS_IsTypedArrayObject, JSObject, + ArrayBufferClone, ArrayBufferCopyData, GetArrayBufferByteLength, + HasDefinedArrayBufferDetachKey, Heap, IsArrayBufferObject, IsDetachedArrayBufferObject, + JS_ClearPendingException, JS_GetArrayBufferViewBuffer, JS_GetArrayBufferViewByteLength, + JS_GetArrayBufferViewByteOffset, JS_GetArrayBufferViewType, JS_GetPendingException, + JS_GetTypedArrayLength, JS_IsArrayBufferViewObject, JS_IsTypedArrayObject, + JS_NewBigInt64ArrayWithBuffer, JS_NewBigUint64ArrayWithBuffer, JS_NewDataView, + JS_NewFloat16ArrayWithBuffer, JS_NewFloat32ArrayWithBuffer, JS_NewFloat64ArrayWithBuffer, + JS_NewInt8ArrayWithBuffer, JS_NewInt16ArrayWithBuffer, JS_NewInt32ArrayWithBuffer, + JS_NewUint8ArrayWithBuffer, JS_NewUint8ClampedArrayWithBuffer, JS_NewUint16ArrayWithBuffer, + JS_NewUint32ArrayWithBuffer, JSObject, NewArrayBuffer, NewArrayBufferWithContents, + StealArrayBufferContents, Type, }; +use js::jsval::{ObjectValue, UndefinedValue}; use js::rust::wrappers::DetachArrayBuffer; -use js::rust::{CustomAutoRooterGuard, Handle, MutableHandleObject}; -#[cfg(feature = "webgpu")] -use js::typedarray::{ArrayBuffer, HeapArrayBuffer}; -use js::typedarray::{CreateWith, TypedArray, TypedArrayElement, TypedArrayElementCreator}; +use js::rust::{ + CustomAutoRooterGuard, Handle, MutableHandleObject, + MutableHandleValue as SafeMutableHandleValue, +}; +use js::typedarray::{ + ArrayBuffer, ArrayBufferU8, ArrayBufferView, ArrayBufferViewU8, CreateWith, HeapArrayBuffer, + TypedArray, TypedArrayElement, TypedArrayElementCreator, +}; +use crate::dom::bindings::error::Error; +use crate::dom::bindings::import::module::Fallible; #[cfg(feature = "webgpu")] use crate::dom::globalscope::GlobalScope; use crate::script_runtime::{CanGc, JSContext}; @@ -35,6 +51,7 @@ use crate::script_runtime::{CanGc, JSContext}; /// provides a view onto an `ArrayBuffer`. /// /// See: +#[derive(PartialEq)] pub(crate) enum BufferSource { /// Represents an `ArrayBufferView` (e.g., `Uint8Array`, `DataView`). /// See: @@ -42,11 +59,7 @@ pub(crate) enum BufferSource { /// 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) fn new_initialized_heap_buffer_source( @@ -69,16 +82,8 @@ where if typed_array_result.is_err() { return Err(()); } - let heap_buffer_source = HeapBufferSource::::default(); - match &heap_buffer_source.buffer_source { - BufferSource::ArrayBufferView(buffer) | - BufferSource::ArrayBuffer(buffer) | - BufferSource::Default(buffer) => { - buffer.set(*array); - }, - } - heap_buffer_source + HeapBufferSource::::new(BufferSource::ArrayBufferView(Heap::boxed(*array.handle()))) }, }; Ok(heap_buffer_source) @@ -94,6 +99,25 @@ pub(crate) struct HeapBufferSource { phantom: PhantomData, } +impl Eq for HeapBufferSource where T: TypedArrayElement {} + +impl PartialEq for HeapBufferSource +where + T: TypedArrayElement, +{ + fn eq(&self, other: &Self) -> bool { + match &self.buffer_source { + BufferSource::ArrayBufferView(heap) | BufferSource::ArrayBuffer(heap) => match &other + .buffer_source + { + BufferSource::ArrayBufferView(from_heap) | BufferSource::ArrayBuffer(from_heap) => unsafe { + heap.handle() == from_heap.handle() + }, + }, + } + } +} + impl HeapBufferSource where T: TypedArrayElement, @@ -105,34 +129,78 @@ where } } - pub(crate) fn default() -> HeapBufferSource { + pub(crate) fn from_view( + chunk: CustomAutoRooterGuard, + ) -> HeapBufferSource { + HeapBufferSource::::new(BufferSource::ArrayBufferView(Heap::boxed( + unsafe { *chunk.underlying_object() }, + ))) + } + + pub(crate) fn default() -> Self { HeapBufferSource { - buffer_source: BufferSource::Default(Box::default()), + buffer_source: BufferSource::ArrayBufferView(Heap::boxed(std::ptr::null_mut())), phantom: PhantomData, } } pub(crate) fn is_initialized(&self) -> bool { match &self.buffer_source { - BufferSource::ArrayBufferView(buffer) | - BufferSource::ArrayBuffer(buffer) | - BufferSource::Default(buffer) => !buffer.get().is_null(), + BufferSource::ArrayBufferView(buffer) | BufferSource::ArrayBuffer(buffer) => { + !buffer.get().is_null() + }, } } - pub(crate) fn get_buffer(&self) -> Result, ()> { + pub(crate) fn get_typed_array(&self) -> Result, ()> { TypedArray::from(match &self.buffer_source { - BufferSource::ArrayBufferView(buffer) | - BufferSource::ArrayBuffer(buffer) | - BufferSource::Default(buffer) => buffer.get(), + BufferSource::ArrayBufferView(buffer) | BufferSource::ArrayBuffer(buffer) => { + buffer.get() + }, }) } + pub(crate) fn get_buffer_view_value( + &self, + cx: JSContext, + mut handle_mut: SafeMutableHandleValue, + ) { + match &self.buffer_source { + BufferSource::ArrayBufferView(buffer) => { + rooted!(in(*cx) let value = ObjectValue(buffer.get())); + handle_mut.set(*value); + }, + BufferSource::ArrayBuffer(_) => { + unreachable!("BufferSource::ArrayBuffer does not have a view buffer.") + }, + } + } + + pub(crate) fn get_array_buffer_view_buffer( + &self, + cx: JSContext, + ) -> HeapBufferSource { + match &self.buffer_source { + BufferSource::ArrayBufferView(buffer) => unsafe { + let mut is_shared = false; + rooted!(in (*cx) let view_buffer = + JS_GetArrayBufferViewBuffer(*cx, buffer.handle(), &mut is_shared)); + + HeapBufferSource::::new(BufferSource::ArrayBuffer(Heap::boxed( + *view_buffer.handle(), + ))) + }, + BufferSource::ArrayBuffer(_) => { + unreachable!("BufferSource::ArrayBuffer does not have a view buffer.") + }, + } + } + /// pub(crate) fn detach_buffer(&self, cx: JSContext) -> bool { assert!(self.is_initialized()); match &self.buffer_source { - BufferSource::ArrayBufferView(buffer) | BufferSource::Default(buffer) => { + BufferSource::ArrayBufferView(buffer) => { let mut is_shared = false; unsafe { // assert buffer is an ArrayBuffer view @@ -151,9 +219,9 @@ where } } - pub(crate) fn buffer_to_option(&self) -> Option> { + pub(crate) fn typed_array_to_option(&self) -> Option> { if self.is_initialized() { - self.get_buffer().ok() + self.get_typed_array().ok() } else { warn!("Buffer not initialized."); None @@ -163,7 +231,7 @@ where pub(crate) fn is_detached_buffer(&self, cx: JSContext) -> bool { assert!(self.is_initialized()); match &self.buffer_source { - BufferSource::ArrayBufferView(buffer) | BufferSource::Default(buffer) => { + BufferSource::ArrayBufferView(buffer) => { let mut is_shared = false; unsafe { assert!(JS_IsArrayBufferViewObject(*buffer.handle())); @@ -182,7 +250,7 @@ where 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) => { + BufferSource::ArrayBufferView(buffer) => { let mut is_shared = false; unsafe { assert!(JS_IsArrayBufferViewObject(*buffer.handle())); @@ -200,7 +268,7 @@ where pub(crate) fn byte_length(&self) -> usize { match &self.buffer_source { - BufferSource::ArrayBufferView(buffer) | BufferSource::Default(buffer) => unsafe { + BufferSource::ArrayBufferView(buffer) => unsafe { JS_GetArrayBufferViewByteLength(*buffer.handle()) }, BufferSource::ArrayBuffer(buffer) => unsafe { @@ -209,19 +277,54 @@ where } } - pub(crate) fn array_length(&self) -> usize { - self.get_buffer().unwrap().len() + pub(crate) fn get_byte_offset(&self) -> usize { + match &self.buffer_source { + BufferSource::ArrayBufferView(buffer) => unsafe { + JS_GetArrayBufferViewByteOffset(*buffer.handle()) + }, + BufferSource::ArrayBuffer(_) => { + unreachable!("BufferSource::ArrayBuffer does not have a byte offset.") + }, + } + } + + pub(crate) fn get_typed_array_length(&self) -> usize { + match &self.buffer_source { + BufferSource::ArrayBufferView(buffer) => unsafe { + JS_GetTypedArrayLength(*buffer.handle()) + }, + BufferSource::ArrayBuffer(_) => { + unreachable!("BufferSource::ArrayBuffer does not have a length.") + }, + } } /// pub(crate) fn has_typed_array_name(&self) -> bool { match &self.buffer_source { - BufferSource::ArrayBufferView(buffer) | BufferSource::Default(buffer) => unsafe { + BufferSource::ArrayBufferView(buffer) => unsafe { JS_IsTypedArrayObject(*buffer.handle()) }, BufferSource::ArrayBuffer(_) => false, } } + + pub(crate) fn get_array_buffer_view_type(&self) -> Type { + match &self.buffer_source { + BufferSource::ArrayBufferView(buffer) => unsafe { + JS_GetArrayBufferViewType(*buffer.handle()) + }, + BufferSource::ArrayBuffer(_) => unreachable!("ArrayBuffer does not have a view type."), + } + } + + pub(crate) fn is_array_buffer_object(&self) -> bool { + match &self.buffer_source { + BufferSource::ArrayBufferView(heap) | BufferSource::ArrayBuffer(heap) => unsafe { + IsArrayBufferObject(*heap.handle()) + }, + } + } } impl HeapBufferSource @@ -233,9 +336,8 @@ where assert!(self.is_initialized()); typedarray!(in(*cx) let array: TypedArray = match &self.buffer_source { - BufferSource::ArrayBufferView(buffer) | - BufferSource::ArrayBuffer(buffer) | - BufferSource::Default(buffer) => { + BufferSource::ArrayBufferView(buffer) | BufferSource::ArrayBuffer(buffer) + => { buffer.get() }, }); @@ -250,9 +352,7 @@ where }; match &self.buffer_source { - BufferSource::ArrayBufferView(buffer) | - BufferSource::ArrayBuffer(buffer) | - BufferSource::Default(buffer) => { + BufferSource::ArrayBufferView(buffer) | BufferSource::ArrayBuffer(buffer) => { buffer.set(ptr::null_mut()); }, } @@ -268,9 +368,8 @@ where ) -> Result<(), ()> { assert!(self.is_initialized()); typedarray!(in(*cx) let array: TypedArray = match &self.buffer_source { - BufferSource::ArrayBufferView(buffer) | - BufferSource::ArrayBuffer(buffer) | - BufferSource::Default(buffer) => { + BufferSource::ArrayBufferView(buffer) | BufferSource::ArrayBuffer(buffer) + => { buffer.get() }, }); @@ -295,9 +394,8 @@ where ) -> Result<(), ()> { assert!(self.is_initialized()); typedarray!(in(*cx) let mut array: TypedArray = match &self.buffer_source { - BufferSource::ArrayBufferView(buffer) | - BufferSource::ArrayBuffer(buffer) | - BufferSource::Default(buffer) => { + BufferSource::ArrayBufferView(buffer) | BufferSource::ArrayBuffer(buffer) + => { buffer.get() }, }); @@ -325,23 +423,194 @@ where create_buffer_source(cx, data, array.handle_mut(), can_gc)?; match &self.buffer_source { - BufferSource::ArrayBufferView(buffer) | - BufferSource::ArrayBuffer(buffer) | - BufferSource::Default(buffer) => { + BufferSource::ArrayBufferView(buffer) | BufferSource::ArrayBuffer(buffer) => { buffer.set(*array); }, } Ok(()) } + + /// + pub(crate) fn clone_array_buffer( + &self, + cx: JSContext, + byte_offset: usize, + byte_length: usize, + ) -> Option> { + match &self.buffer_source { + BufferSource::ArrayBufferView(heap) | BufferSource::ArrayBuffer(heap) => { + let result = + unsafe { ArrayBufferClone(*cx, heap.handle(), byte_offset, byte_length) }; + if result.is_null() { + None + } else { + Some(HeapBufferSource::::new( + BufferSource::ArrayBuffer(Heap::boxed(result)), + )) + } + }, + } + } + + /// + // CanCopyDataBlockBytes(descriptorBuffer, destStart, queueBuffer, queueByteOffset, bytesToCopy) + pub(crate) fn can_copy_data_block_bytes( + &self, + cx: JSContext, + to_index: usize, + from_buffer: &HeapBufferSource, + from_index: usize, + bytes_to_copy: usize, + ) -> bool { + // Assert: toBuffer is an Object. + // Assert: toBuffer has an [[ArrayBufferData]] internal slot. + assert!(self.is_array_buffer_object()); + + // Assert: fromBuffer is an Object. + // Assert: fromBuffer has an [[ArrayBufferData]] internal slot. + assert!(from_buffer.is_array_buffer_object()); + + // If toBuffer is fromBuffer, return false. + match &self.buffer_source { + BufferSource::ArrayBufferView(heap) | BufferSource::ArrayBuffer(heap) => { + match &from_buffer.buffer_source { + BufferSource::ArrayBufferView(from_heap) | + BufferSource::ArrayBuffer(from_heap) => { + unsafe { + if heap.handle() == from_heap.handle() { + return false; + } + }; + }, + } + }, + } + + // If ! IsDetachedBuffer(toBuffer) is true, return false. + if self.is_detached_buffer(cx) { + return false; + } + + // If ! IsDetachedBuffer(fromBuffer) is true, return false. + if from_buffer.is_detached_buffer(cx) { + return false; + } + + // If toIndex + count > toBuffer.[[ArrayBufferByteLength]], return false. + if to_index + bytes_to_copy > self.byte_length() { + return false; + } + + // If fromIndex + count > fromBuffer.[[ArrayBufferByteLength]], return false. + if from_index + bytes_to_copy > from_buffer.byte_length() { + return false; + } + + // Return true. + true + } + + pub(crate) fn copy_data_block_bytes( + &self, + cx: JSContext, + dest_start: usize, + from_buffer: &HeapBufferSource, + from_byte_offset: usize, + bytes_to_copy: usize, + ) -> bool { + match &self.buffer_source { + BufferSource::ArrayBufferView(heap) | BufferSource::ArrayBuffer(heap) => unsafe { + match &from_buffer.buffer_source { + BufferSource::ArrayBufferView(from_heap) | + BufferSource::ArrayBuffer(from_heap) => ArrayBufferCopyData( + *cx, + heap.handle(), + dest_start, + from_heap.handle(), + from_byte_offset, + bytes_to_copy, + ), + } + }, + } + } + + /// + pub(crate) fn can_transfer_array_buffer(&self, cx: JSContext) -> bool { + // Assert: O is an Object. + // Assert: O has an [[ArrayBufferData]] internal slot. + assert!(self.is_array_buffer_object()); + + // If ! IsDetachedBuffer(O) is true, return false. + if self.is_detached_buffer(cx) { + return false; + } + + // If SameValue(O.[[ArrayBufferDetachKey]], undefined) is false, return false. + // Return true. + let mut is_defined = false; + match &self.buffer_source { + BufferSource::ArrayBufferView(heap) | BufferSource::ArrayBuffer(heap) => unsafe { + if !HasDefinedArrayBufferDetachKey(*cx, heap.handle(), &mut is_defined) { + return false; + } + }, + } + + !is_defined + } + + /// + pub(crate) fn transfer_array_buffer( + &self, + cx: JSContext, + ) -> Fallible> { + assert!(self.is_array_buffer_object()); + + // Assert: ! IsDetachedBuffer(O) is false. + assert!(!self.is_detached_buffer(cx)); + + // Let arrayBufferByteLength be O.[[ArrayBufferByteLength]]. + // Step 3 (Reordered) + let buffer_length = self.byte_length(); + + // Let arrayBufferData be O.[[ArrayBufferData]]. + // Step 2 (Reordered) + let buffer_data = match &self.buffer_source { + BufferSource::ArrayBufferView(buffer) | BufferSource::ArrayBuffer(buffer) => unsafe { + StealArrayBufferContents(*cx, buffer.handle()) + }, + }; + + // Perform ? DetachArrayBuffer(O). + // This will throw an exception if O has an [[ArrayBufferDetachKey]] that is not undefined, + // such as a WebAssembly.Memory’s buffer. [WASM-JS-API-1] + if !self.detach_buffer(cx) { + rooted!(in(*cx) let mut rval = UndefinedValue()); + unsafe { + assert!(JS_GetPendingException(*cx, rval.handle_mut().into())); + JS_ClearPendingException(*cx) + }; + + Err(Error::Type("can't transfer array buffer".to_owned())) + } else { + // Return a new ArrayBuffer object, created in the current Realm, + // whose [[ArrayBufferData]] internal slot value is arrayBufferData and + // whose [[ArrayBufferByteLength]] internal slot value is arrayBufferByteLength. + Ok(HeapBufferSource::::new( + BufferSource::ArrayBuffer(Heap::boxed(unsafe { + NewArrayBufferWithContents(*cx, buffer_length, buffer_data) + })), + )) + } + } } 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) => { + BufferSource::ArrayBufferView(buffer) | BufferSource::ArrayBuffer(buffer) => { buffer.trace(tracer); }, } @@ -385,6 +654,134 @@ where } } +pub(crate) fn byte_size(byte_type: Type) -> u64 { + match byte_type { + Type::Int8 | Type::Uint8 | Type::Uint8Clamped => 1, + Type::Int16 | Type::Uint16 | Type::Float16 => 2, + Type::Int32 | Type::Uint32 | Type::Float32 => 4, + Type::Int64 | Type::Float64 | Type::BigInt64 | Type::BigUint64 => 8, + Type::Simd128 => 16, + _ => unreachable!("invalid scalar type"), + } +} + +#[derive(Clone, Eq, JSTraceable, MallocSizeOf, PartialEq)] +pub(crate) enum Constructor { + DataView, + Name( + #[ignore_malloc_size_of = "mozjs"] + #[no_trace] + Type, + ), +} + +pub(crate) fn create_buffer_source_with_constructor( + cx: JSContext, + constructor: &Constructor, + buffer_source: &HeapBufferSource, + byte_offset: usize, + byte_length: usize, +) -> Fallible> { + let buffer = unsafe { + Heap::boxed( + *buffer_source + .get_typed_array() + .expect("Failed to get typed array") + .underlying_object(), + ) + .handle() + }; + + match constructor { + Constructor::DataView => Ok(HeapBufferSource::new(BufferSource::ArrayBufferView( + Heap::boxed(unsafe { JS_NewDataView(*cx, buffer, byte_offset, byte_length) }), + ))), + Constructor::Name(name_type) => construct_typed_array( + cx, + name_type, + buffer_source, + byte_offset, + byte_length as i64, + ), + } +} + +/// Helper function to construct different TypedArray views +fn construct_typed_array( + cx: JSContext, + name_type: &Type, + buffer_source: &HeapBufferSource, + byte_offset: usize, + byte_length: i64, +) -> Fallible> { + let buffer = unsafe { + Heap::boxed( + *buffer_source + .get_typed_array() + .expect("Failed to get typed array") + .underlying_object(), + ) + .handle() + }; + let array_view = match name_type { + Type::Int8 => unsafe { JS_NewInt8ArrayWithBuffer(*cx, buffer, byte_offset, byte_length) }, + Type::Uint8 => unsafe { JS_NewUint8ArrayWithBuffer(*cx, buffer, byte_offset, byte_length) }, + Type::Uint16 => unsafe { + JS_NewUint16ArrayWithBuffer(*cx, buffer, byte_offset, byte_length) + }, + Type::Int16 => unsafe { JS_NewInt16ArrayWithBuffer(*cx, buffer, byte_offset, byte_length) }, + Type::Int32 => unsafe { JS_NewInt32ArrayWithBuffer(*cx, buffer, byte_offset, byte_length) }, + Type::Uint32 => unsafe { + JS_NewUint32ArrayWithBuffer(*cx, buffer, byte_offset, byte_length) + }, + Type::Float32 => unsafe { + JS_NewFloat32ArrayWithBuffer(*cx, buffer, byte_offset, byte_length) + }, + Type::Float64 => unsafe { + JS_NewFloat64ArrayWithBuffer(*cx, buffer, byte_offset, byte_length) + }, + Type::Uint8Clamped => unsafe { + JS_NewUint8ClampedArrayWithBuffer(*cx, buffer, byte_offset, byte_length) + }, + Type::BigInt64 => unsafe { + JS_NewBigInt64ArrayWithBuffer(*cx, buffer, byte_offset, byte_length) + }, + Type::BigUint64 => unsafe { + JS_NewBigUint64ArrayWithBuffer(*cx, buffer, byte_offset, byte_length) + }, + Type::Float16 => unsafe { + JS_NewFloat16ArrayWithBuffer(*cx, buffer, byte_offset, byte_length) + }, + Type::Int64 | Type::Simd128 | Type::MaxTypedArrayViewType => { + unreachable!("Invalid TypedArray type") + }, + }; + + Ok(HeapBufferSource::new(BufferSource::ArrayBufferView( + Heap::boxed(array_view), + ))) +} + +pub(crate) fn create_array_buffer_with_size( + cx: JSContext, + size: usize, +) -> Fallible> { + let result = unsafe { NewArrayBuffer(*cx, size) }; + if result.is_null() { + rooted!(in(*cx) let mut rval = UndefinedValue()); + unsafe { + assert!(JS_GetPendingException(*cx, rval.handle_mut().into())); + JS_ClearPendingException(*cx) + }; + + Err(Error::Type("can't create array buffer".to_owned())) + } else { + Ok(HeapBufferSource::::new( + BufferSource::ArrayBuffer(Heap::boxed(result)), + )) + } +} + #[cfg(feature = "webgpu")] #[derive(JSTraceable, MallocSizeOf)] pub(crate) struct DataBlock { diff --git a/components/script/dom/defaultteeunderlyingsource.rs b/components/script/dom/defaultteeunderlyingsource.rs index 34c374dd71d..6372e562baa 100644 --- a/components/script/dom/defaultteeunderlyingsource.rs +++ b/components/script/dom/defaultteeunderlyingsource.rs @@ -108,12 +108,12 @@ impl DefaultTeeUnderlyingSource { /// Let pullAlgorithm be the following steps: #[cfg_attr(crown, allow(crown::unrooted_must_root))] pub(crate) fn pull_algorithm(&self, can_gc: CanGc) -> Rc { + let cx = GlobalScope::get_cx(); // If reading is true, if self.reading.get() { // Set readAgain to true. self.read_again.set(true); // Return a promise resolved with undefined. - let cx = GlobalScope::get_cx(); rooted!(in(*cx) let mut rval = UndefinedValue()); return Promise::new_resolved(&self.stream.global(), cx, rval.handle(), can_gc); } @@ -142,17 +142,11 @@ impl DefaultTeeUnderlyingSource { }; // Perform ! ReadableStreamDefaultReaderRead(reader, readRequest). - self.reader.read(&read_request, can_gc); + self.reader.read(cx, &read_request, can_gc); // Return a promise resolved with undefined. - let cx = GlobalScope::get_cx(); rooted!(in(*cx) let mut rval = UndefinedValue()); - Promise::new_resolved( - &self.stream.global(), - GlobalScope::get_cx(), - rval.handle(), - can_gc, - ) + Promise::new_resolved(&self.stream.global(), cx, rval.handle(), can_gc) } /// diff --git a/components/script/dom/gamepad.rs b/components/script/dom/gamepad.rs index f4c649d0c42..6087b2b4ff2 100644 --- a/components/script/dom/gamepad.rs +++ b/components/script/dom/gamepad.rs @@ -186,7 +186,9 @@ impl GamepadMethods for Gamepad { // https://w3c.github.io/gamepad/#dom-gamepad-axes fn Axes(&self, _cx: JSContext) -> Float64Array { - self.axes.get_buffer().expect("Failed to get gamepad axes.") + self.axes + .get_typed_array() + .expect("Failed to get gamepad axes.") } // https://w3c.github.io/gamepad/#dom-gamepad-buttons @@ -277,7 +279,7 @@ impl Gamepad { if normalized_value.is_finite() { let mut axis_vec = self .axes - .buffer_to_option() + .typed_array_to_option() .expect("Axes have not been initialized!"); unsafe { axis_vec.as_mut_slice()[axis_index] = normalized_value; diff --git a/components/script/dom/gamepadpose.rs b/components/script/dom/gamepadpose.rs index 8737a5a7d32..5d55288b02b 100644 --- a/components/script/dom/gamepadpose.rs +++ b/components/script/dom/gamepadpose.rs @@ -52,7 +52,7 @@ impl GamepadPose { impl GamepadPoseMethods for GamepadPose { // https://w3c.github.io/gamepad/extensions.html#dom-gamepadpose-position fn GetPosition(&self, _cx: JSContext) -> Option { - self.position.buffer_to_option() + self.position.typed_array_to_option() } // https://w3c.github.io/gamepad/extensions.html#dom-gamepadpose-hasposition @@ -62,17 +62,17 @@ impl GamepadPoseMethods for GamepadPose { // https://w3c.github.io/gamepad/extensions.html#dom-gamepadpose-linearvelocity fn GetLinearVelocity(&self, _cx: JSContext) -> Option { - self.linear_vel.buffer_to_option() + self.linear_vel.typed_array_to_option() } // https://w3c.github.io/gamepad/extensions.html#dom-gamepadpose-linearacceleration fn GetLinearAcceleration(&self, _cx: JSContext) -> Option { - self.linear_acc.buffer_to_option() + self.linear_acc.typed_array_to_option() } // https://w3c.github.io/gamepad/extensions.html#dom-gamepadpose-orientation fn GetOrientation(&self, _cx: JSContext) -> Option { - self.orientation.buffer_to_option() + self.orientation.typed_array_to_option() } // https://w3c.github.io/gamepad/extensions.html#dom-gamepadpose-orientation @@ -82,11 +82,11 @@ impl GamepadPoseMethods for GamepadPose { // https://w3c.github.io/gamepad/extensions.html#dom-gamepadpose-angularvelocity fn GetAngularVelocity(&self, _cx: JSContext) -> Option { - self.angular_vel.buffer_to_option() + self.angular_vel.typed_array_to_option() } // https://w3c.github.io/gamepad/extensions.html#dom-gamepadpose-angularacceleration fn GetAngularAcceleration(&self, _cx: JSContext) -> Option { - self.angular_acc.buffer_to_option() + self.angular_acc.typed_array_to_option() } } diff --git a/components/script/dom/imagedata.rs b/components/script/dom/imagedata.rs index a7ee406e222..d49d17d3bf9 100644 --- a/components/script/dom/imagedata.rs +++ b/components/script/dom/imagedata.rs @@ -73,7 +73,7 @@ impl ImageData { Err(_) => return Err(Error::JSFailed), }; - let typed_array = match heap_typed_array.get_buffer() { + let typed_array = match heap_typed_array.get_typed_array() { Ok(array) => array, Err(_) => { return Err(Error::Type( @@ -160,7 +160,7 @@ impl ImageData { assert!(self.data.is_initialized()); let internal_data = self .data - .get_buffer() + .get_typed_array() .expect("Failed to get Data from ImageData."); // NOTE(nox): This is just as unsafe as `as_slice` itself even though we // are extending the lifetime of the slice, because the data in @@ -209,6 +209,6 @@ impl ImageDataMethods for ImageData { /// fn GetData(&self, _: JSContext) -> Fallible { - self.data.get_buffer().map_err(|_| Error::JSFailed) + self.data.get_typed_array().map_err(|_| Error::JSFailed) } } diff --git a/components/script/dom/readablebytestreamcontroller.rs b/components/script/dom/readablebytestreamcontroller.rs index 85c2a08fe76..1de27414fd6 100644 --- a/components/script/dom/readablebytestreamcontroller.rs +++ b/components/script/dom/readablebytestreamcontroller.rs @@ -2,29 +2,268 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +use std::cell::Cell; +use std::cmp::min; +use std::collections::VecDeque; +use std::rc::Rc; + use dom_struct::dom_struct; -use js::rust::HandleValue as SafeHandleValue; -use js::typedarray::ArrayBufferViewU8; +use js::jsapi::{Heap, Type}; +use js::jsval::UndefinedValue; +use js::rust::{HandleObject, HandleValue as SafeHandleValue, HandleValue}; +use js::typedarray::{ArrayBufferU8, ArrayBufferViewU8}; use super::bindings::buffer_source::HeapBufferSource; +use super::bindings::cell::DomRefCell; use super::bindings::codegen::Bindings::ReadableStreamBYOBReaderBinding::ReadableStreamBYOBReaderReadOptions; +use super::bindings::reflector::reflect_dom_object; +use super::bindings::root::Dom; use super::readablestreambyobreader::ReadIntoRequest; -use super::types::ReadableStreamBYOBRequest; +use super::readablestreamdefaultreader::ReadRequest; +use super::underlyingsourcecontainer::{UnderlyingSourceContainer, UnderlyingSourceType}; +use crate::dom::bindings::buffer_source::{ + Constructor, byte_size, create_array_buffer_with_size, create_buffer_source_with_constructor, +}; use crate::dom::bindings::codegen::Bindings::ReadableByteStreamControllerBinding::ReadableByteStreamControllerMethods; +use crate::dom::bindings::error::ErrorToJsval; +use crate::dom::bindings::import::module::UnionTypes::ReadableStreamDefaultControllerOrReadableByteStreamController as Controller; use crate::dom::bindings::import::module::{Error, Fallible}; -use crate::dom::bindings::reflector::Reflector; +use crate::dom::bindings::reflector::{DomGlobal, Reflector}; use crate::dom::bindings::root::{DomRoot, MutNullableDom}; +use crate::dom::bindings::trace::RootedTraceableBox; +use crate::dom::globalscope::GlobalScope; +use crate::dom::promise::Promise; +use crate::dom::promisenativehandler::{Callback, PromiseNativeHandler}; use crate::dom::readablestream::ReadableStream; +use crate::dom::readablestreambyobrequest::ReadableStreamBYOBRequest; +use crate::realms::{InRealm, enter_realm}; use crate::script_runtime::{CanGc, JSContext as SafeJSContext}; +/// +#[derive(JSTraceable, MallocSizeOf)] +pub(crate) struct QueueEntry { + /// + #[ignore_malloc_size_of = "HeapBufferSource"] + buffer: HeapBufferSource, + /// + byte_offset: usize, + /// + byte_length: usize, +} + +impl QueueEntry { + pub(crate) fn new( + buffer: HeapBufferSource, + byte_offset: usize, + byte_length: usize, + ) -> QueueEntry { + QueueEntry { + buffer, + byte_offset, + byte_length, + } + } +} + +#[derive(Debug, Eq, JSTraceable, MallocSizeOf, PartialEq)] +pub(crate) enum ReaderType { + /// + Byob, + /// + Default, +} + +/// +#[derive(Eq, JSTraceable, MallocSizeOf, PartialEq)] +pub(crate) struct PullIntoDescriptor { + #[ignore_malloc_size_of = "HeapBufferSource"] + /// + buffer: HeapBufferSource, + /// + buffer_byte_length: u64, + /// + byte_offset: u64, + /// + byte_length: u64, + /// + bytes_filled: Cell, + /// + minimum_fill: u64, + /// + element_size: u64, + /// + view_constructor: Constructor, + /// + reader_type: Option, +} + +/// The fulfillment handler for +/// +#[derive(Clone, JSTraceable, MallocSizeOf)] +#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)] +struct StartAlgorithmFulfillmentHandler { + controller: Dom, +} + +impl Callback for StartAlgorithmFulfillmentHandler { + /// Continuation of + /// Upon fulfillment of startPromise, + fn callback(&self, _cx: SafeJSContext, _v: HandleValue, _realm: InRealm, can_gc: CanGc) { + // Set controller.[[started]] to true. + self.controller.started.set(true); + + // Assert: controller.[[pulling]] is false. + assert!(!self.controller.pulling.get()); + + // Assert: controller.[[pullAgain]] is false. + assert!(!self.controller.pull_again.get()); + + // Perform ! ReadableByteStreamControllerCallPullIfNeeded(controller). + self.controller.call_pull_if_needed(can_gc); + } +} + +/// The rejection handler for +/// +#[derive(Clone, JSTraceable, MallocSizeOf)] +#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)] +struct StartAlgorithmRejectionHandler { + controller: Dom, +} + +impl Callback for StartAlgorithmRejectionHandler { + /// Continuation of + /// Upon rejection of startPromise with reason r, + fn callback(&self, _cx: SafeJSContext, v: HandleValue, _realm: InRealm, can_gc: CanGc) { + // Perform ! ReadableByteStreamControllerError(controller, r). + self.controller.error(v, can_gc); + } +} + +/// The fulfillment handler for +/// +#[derive(Clone, JSTraceable, MallocSizeOf)] +#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)] +struct PullAlgorithmFulfillmentHandler { + controller: Dom, +} + +impl Callback for PullAlgorithmFulfillmentHandler { + /// Continuation of + /// Upon fulfillment of pullPromise + fn callback(&self, _cx: SafeJSContext, _v: HandleValue, _realm: InRealm, can_gc: CanGc) { + // Set controller.[[pulling]] to false. + self.controller.pulling.set(false); + + // If controller.[[pullAgain]] is true, + if self.controller.pull_again.get() { + // Set controller.[[pullAgain]] to false. + self.controller.pull_again.set(false); + + // Perform ! ReadableByteStreamControllerCallPullIfNeeded(controller). + self.controller.call_pull_if_needed(can_gc); + } + } +} + +/// The rejection handler for +/// +#[derive(Clone, JSTraceable, MallocSizeOf)] +#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)] +struct PullAlgorithmRejectionHandler { + controller: Dom, +} + +impl Callback for PullAlgorithmRejectionHandler { + /// Continuation of + /// Upon rejection of pullPromise with reason e. + fn callback(&self, _cx: SafeJSContext, v: HandleValue, _realm: InRealm, can_gc: CanGc) { + // Perform ! ReadableByteStreamControllerError(controller, e). + self.controller.error(v, can_gc); + } +} + /// #[dom_struct] pub(crate) struct ReadableByteStreamController { reflector_: Reflector, + /// + auto_allocate_chunk_size: Option, + /// stream: MutNullableDom, + /// + strategy_hwm: f64, + /// A mutable reference to the underlying source is used to implement these two + /// internal slots: + /// + /// + /// + underlying_source: MutNullableDom, + /// + queue: DomRefCell>, + /// + queue_total_size: Cell, + /// + byob_request: MutNullableDom, + /// + pending_pull_intos: DomRefCell>, + /// + close_requested: Cell, + /// + started: Cell, + /// + pulling: Cell, + /// + pull_again: Cell, } impl ReadableByteStreamController { + #[cfg_attr(crown, allow(crown::unrooted_must_root))] + fn new_inherited( + underlying_source_type: UnderlyingSourceType, + strategy_hwm: f64, + global: &GlobalScope, + can_gc: CanGc, + ) -> ReadableByteStreamController { + let underlying_source_container = + UnderlyingSourceContainer::new(global, underlying_source_type, can_gc); + let auto_allocate_chunk_size = underlying_source_container.auto_allocate_chunk_size(); + ReadableByteStreamController { + reflector_: Reflector::new(), + byob_request: MutNullableDom::new(None), + stream: MutNullableDom::new(None), + underlying_source: MutNullableDom::new(Some(&*underlying_source_container)), + auto_allocate_chunk_size, + pending_pull_intos: DomRefCell::new(Vec::new()), + strategy_hwm, + close_requested: Default::default(), + queue: DomRefCell::new(Default::default()), + queue_total_size: Default::default(), + started: Default::default(), + pulling: Default::default(), + pull_again: Default::default(), + } + } + + #[cfg_attr(crown, allow(crown::unrooted_must_root))] + pub(crate) fn new( + underlying_source_type: UnderlyingSourceType, + strategy_hwm: f64, + global: &GlobalScope, + can_gc: CanGc, + ) -> DomRoot { + reflect_dom_object( + Box::new(ReadableByteStreamController::new_inherited( + underlying_source_type, + strategy_hwm, + global, + can_gc, + )), + global, + can_gc, + ) + } + pub(crate) fn set_stream(&self, stream: &ReadableStream) { self.stream.set(Some(stream)) } @@ -32,59 +271,1735 @@ impl ReadableByteStreamController { /// pub(crate) fn perform_pull_into( &self, - _read_into_request: &ReadIntoRequest, - _view: HeapBufferSource, - _options: &ReadableStreamBYOBReaderReadOptions, - _can_gc: CanGc, + cx: SafeJSContext, + read_into_request: &ReadIntoRequest, + view: HeapBufferSource, + options: &ReadableStreamBYOBReaderReadOptions, + can_gc: CanGc, ) { - todo!() + // Let stream be controller.[[stream]]. + let stream = self.stream.get().unwrap(); + + // Let elementSize be 1. + let mut element_size = 1; + + // Let ctor be %DataView%. + let mut ctor = Constructor::DataView; + + // If view has a [[TypedArrayName]] internal slot (i.e., it is not a DataView), + if view.has_typed_array_name() { + // Set elementSize to the element size specified in the + // typed array constructors table for view.[[TypedArrayName]]. + let view_typw = view.get_array_buffer_view_type(); + element_size = byte_size(view_typw); + + // Set ctor to the constructor specified in the typed array constructors table for view.[[TypedArrayName]]. + ctor = Constructor::Name(view_typw); + } + + // Let minimumFill be min × elementSize. + let minimum_fill = options.min * element_size; + + // Assert: minimumFill ≥ 0 and minimumFill ≤ view.[[ByteLength]]. + assert!(minimum_fill <= (view.byte_length() as u64)); + + // Assert: the remainder after dividing minimumFill by elementSize is 0. + assert_eq!(minimum_fill % element_size, 0); + + // Let byteOffset be view.[[ByteOffset]]. + let byte_offset = view.get_byte_offset(); + + // Let byteLength be view.[[ByteLength]]. + let byte_length = view.byte_length(); + + // Let bufferResult be TransferArrayBuffer(view.[[ViewedArrayBuffer]]). + match view + .get_array_buffer_view_buffer(cx) + .transfer_array_buffer(cx) + { + Ok(buffer) => { + // Let buffer be bufferResult.[[Value]]. + // Let pullIntoDescriptor be a new pull-into descriptor with + // buffer buffer + // buffer byte length buffer.[[ArrayBufferByteLength]] + // byte offset byteOffset + // byte length byteLength + // bytes filled 0 + // minimum fill minimumFill + // element size elementSize + // view constructor ctor + // reader type "byob" + let buffer_byte_length = buffer.byte_length(); + let pull_into_descriptor = PullIntoDescriptor { + buffer, + buffer_byte_length: buffer_byte_length as u64, + byte_offset: byte_offset as u64, + byte_length: byte_length as u64, + bytes_filled: Cell::new(0), + minimum_fill, + element_size, + view_constructor: ctor.clone(), + reader_type: Some(ReaderType::Byob), + }; + + // If controller.[[pendingPullIntos]] is not empty, + { + let mut pending_pull_intos = self.pending_pull_intos.borrow_mut(); + if !pending_pull_intos.is_empty() { + // Append pullIntoDescriptor to controller.[[pendingPullIntos]]. + pending_pull_intos.push(pull_into_descriptor); + + // Perform ! ReadableStreamAddReadIntoRequest(stream, readIntoRequest). + stream.add_read_into_request(read_into_request); + + // Return. + return; + } + } + + // If stream.[[state]] is "closed", + if stream.is_closed() { + // Let emptyView be ! Construct(ctor, « pullIntoDescriptor’s buffer, + // pullIntoDescriptor’s byte offset, 0 »). + if let Ok(empty_view) = create_buffer_source_with_constructor( + cx, + &ctor, + &pull_into_descriptor.buffer, + pull_into_descriptor.byte_offset as usize, + 0, + ) { + // Perform readIntoRequest’s close steps, given emptyView. + let result = RootedTraceableBox::new(Heap::default()); + rooted!(in(*cx) let mut view_value = UndefinedValue()); + empty_view.get_buffer_view_value(cx, view_value.handle_mut()); + result.set(*view_value); + + read_into_request.close_steps(Some(result), can_gc); + + // Return. + return; + } else { + return; + } + } + + // If controller.[[queueTotalSize]] > 0, + if self.queue_total_size.get() > 0.0 { + // If ! ReadableByteStreamControllerFillPullIntoDescriptorFromQueue( + // controller, pullIntoDescriptor) is true, + if self.fill_pull_into_descriptor_from_queue(cx, &pull_into_descriptor) { + // Let filledView be ! ReadableByteStreamControllerConvertPullIntoDescriptor( + // pullIntoDescriptor). + if let Ok(filled_view) = + self.convert_pull_into_descriptor(cx, &pull_into_descriptor) + { + // Perform ! ReadableByteStreamControllerHandleQueueDrain(controller). + self.handle_queue_drain(can_gc); + + // Perform readIntoRequest’s chunk steps, given filledView. + let result = RootedTraceableBox::new(Heap::default()); + rooted!(in(*cx) let mut view_value = UndefinedValue()); + filled_view.get_buffer_view_value(cx, view_value.handle_mut()); + result.set(*view_value); + read_into_request.chunk_steps(result, can_gc); + + // Return. + return; + } else { + return; + } + } + + // If controller.[[closeRequested]] is true, + if self.close_requested.get() { + // Let e be a new TypeError exception. + rooted!(in(*cx) let mut error = UndefinedValue()); + Error::Type("close requested".to_owned()).to_jsval( + cx, + &self.global(), + error.handle_mut(), + ); + + // Perform ! ReadableByteStreamControllerError(controller, e). + self.error(error.handle(), can_gc); + + // Perform readIntoRequest’s error steps, given e. + read_into_request.error_steps(error.handle(), can_gc); + + // Return. + return; + } + } + + // Append pullIntoDescriptor to controller.[[pendingPullIntos]]. + { + self.pending_pull_intos + .borrow_mut() + .push(pull_into_descriptor); + } + // Perform ! ReadableStreamAddReadIntoRequest(stream, readIntoRequest). + stream.add_read_into_request(read_into_request); + + // Perform ! ReadableByteStreamControllerCallPullIfNeeded(controller). + self.call_pull_if_needed(can_gc); + }, + Err(error) => { + // If bufferResult is an abrupt completion, + + // Perform readIntoRequest’s error steps, given bufferResult.[[Value]]. + rooted!(in(*cx) let mut rval = UndefinedValue()); + error + .clone() + .to_jsval(cx, &self.global(), rval.handle_mut()); + read_into_request.error_steps(rval.handle(), can_gc); + + // Return. + }, + } } /// - pub(crate) fn respond(&self, _bytes_written: u64) -> Fallible<()> { - todo!() + pub(crate) fn respond( + &self, + cx: SafeJSContext, + bytes_written: u64, + can_gc: CanGc, + ) -> Fallible<()> { + { + // Assert: controller.[[pendingPullIntos]] is not empty. + let mut pending_pull_intos = self.pending_pull_intos.borrow_mut(); + assert!(!pending_pull_intos.is_empty()); + + // Let firstDescriptor be controller.[[pendingPullIntos]][0]. + let first_descriptor = pending_pull_intos.first_mut().unwrap(); + + // Let state be controller.[[stream]].[[state]]. + let stream = self.stream.get().unwrap(); + + // If state is "closed", + if stream.is_closed() { + // If bytesWritten is not 0, throw a TypeError exception. + if bytes_written != 0 { + return Err(Error::Type( + "bytesWritten not zero on closed stream".to_owned(), + )); + } + } else { + // Assert: state is "readable". + assert!(stream.is_readable()); + + // If bytesWritten is 0, throw a TypeError exception. + if bytes_written == 0 { + return Err(Error::Type("bytesWritten is 0".to_owned())); + } + + // If firstDescriptor’s bytes filled + bytesWritten > firstDescriptor’s byte length, + // throw a RangeError exception. + if first_descriptor.bytes_filled.get() + bytes_written > + first_descriptor.byte_length + { + return Err(Error::Range( + "bytes filled + bytesWritten > byte length".to_owned(), + )); + } + } + + // Set firstDescriptor’s buffer to ! TransferArrayBuffer(firstDescriptor’s buffer). + first_descriptor.buffer = first_descriptor.buffer.transfer_array_buffer(cx)?; + } + + // Perform ? ReadableByteStreamControllerRespondInternal(controller, bytesWritten). + self.respond_internal(cx, bytes_written, can_gc) + } + + /// + pub(crate) fn respond_internal( + &self, + cx: SafeJSContext, + bytes_written: u64, + can_gc: CanGc, + ) -> Fallible<()> { + { + // Let firstDescriptor be controller.[[pendingPullIntos]][0]. + let pending_pull_intos = self.pending_pull_intos.borrow(); + let first_descriptor = pending_pull_intos.first().unwrap(); + + // Assert: ! CanTransferArrayBuffer(firstDescriptor’s buffer) is true + assert!(first_descriptor.buffer.can_transfer_array_buffer(cx)); + } + + // Perform ! ReadableByteStreamControllerInvalidateBYOBRequest(controller). + self.invalidate_byob_request(); + + // Let state be controller.[[stream]].[[state]]. + let stream = self.stream.get().unwrap(); + + // If state is "closed", + if stream.is_closed() { + // Assert: bytesWritten is 0. + assert_eq!(bytes_written, 0); + + // Perform ! ReadableByteStreamControllerRespondInClosedState(controller, firstDescriptor). + self.respond_in_closed_state(cx, can_gc)?; + } else { + // Assert: state is "readable". + assert!(stream.is_readable()); + + // Assert: bytesWritten > 0. + assert!(bytes_written > 0); + + // Perform ? ReadableByteStreamControllerRespondInReadableState(controller, bytesWritten, firstDescriptor). + self.respond_in_readable_state(cx, bytes_written, can_gc)?; + } + + // Perform ! ReadableByteStreamControllerCallPullIfNeeded(controller). + self.call_pull_if_needed(can_gc); + + Ok(()) + } + + /// + pub(crate) fn respond_in_closed_state(&self, cx: SafeJSContext, can_gc: CanGc) -> Fallible<()> { + let pending_pull_intos = self.pending_pull_intos.borrow(); + let first_descriptor = pending_pull_intos.first().unwrap(); + + // Assert: the remainder after dividing firstDescriptor’s bytes filled + // by firstDescriptor’s element size is 0. + assert_eq!( + first_descriptor.bytes_filled.get() % first_descriptor.element_size, + 0 + ); + + // If firstDescriptor’s reader type is "none", + // perform ! ReadableByteStreamControllerShiftPendingPullInto(controller). + let reader_type = first_descriptor.reader_type.is_none(); + + // needed to drop the borrow and avoid BorrowMutError + drop(pending_pull_intos); + + if reader_type { + self.shift_pending_pull_into(); + } + + // Let stream be controller.[[stream]]. + let stream = self.stream.get().unwrap(); + + // If ! ReadableStreamHasBYOBReader(stream) is true, + if stream.has_byob_reader() { + // Let filledPullIntos be a new empty list. + let mut filled_pull_intos = Vec::new(); + + // While filledPullIntos’s size < ! ReadableStreamGetNumReadIntoRequests(stream), + while filled_pull_intos.len() < stream.get_num_read_into_requests() { + // Let pullIntoDescriptor be ! ReadableByteStreamControllerShiftPendingPullInto(controller). + let pull_into_descriptor = self.shift_pending_pull_into(); + + // Append pullIntoDescriptor to filledPullIntos. + filled_pull_intos.push(pull_into_descriptor); + } + + // For each filledPullInto of filledPullIntos, + for filled_pull_into in filled_pull_intos { + // Perform ! ReadableByteStreamControllerCommitPullIntoDescriptor(stream, filledPullInto). + self.commit_pull_into_descriptor(cx, &filled_pull_into, can_gc)?; + } + } + + Ok(()) + } + + /// + pub(crate) fn respond_in_readable_state( + &self, + cx: SafeJSContext, + bytes_written: u64, + can_gc: CanGc, + ) -> Fallible<()> { + let pending_pull_intos = self.pending_pull_intos.borrow(); + let first_descriptor = pending_pull_intos.first().unwrap(); + + // Assert: pullIntoDescriptor’s bytes filled + bytesWritten ≤ pullIntoDescriptor’s byte length. + assert!( + first_descriptor.bytes_filled.get() + bytes_written <= first_descriptor.byte_length + ); + + // Perform ! ReadableByteStreamControllerFillHeadPullIntoDescriptor( + // controller, bytesWritten, pullIntoDescriptor). + self.fill_head_pull_into_descriptor(bytes_written, first_descriptor); + + // If pullIntoDescriptor’s reader type is "none", + if first_descriptor.reader_type.is_none() { + // needed to drop the borrow and avoid BorrowMutError + drop(pending_pull_intos); + + // Perform ? ReadableByteStreamControllerEnqueueDetachedPullIntoToQueue(controller, pullIntoDescriptor). + self.enqueue_detached_pull_into_to_queue(cx, can_gc)?; + + // Let filledPullIntos be the result of performing + // ! ReadableByteStreamControllerProcessPullIntoDescriptorsUsingQueue(controller). + let filled_pull_intos = self.process_pull_into_descriptors_using_queue(cx); + + // For each filledPullInto of filledPullIntos, + for filled_pull_into in filled_pull_intos { + // Perform ! ReadableByteStreamControllerCommitPullIntoDescriptor(controller.[[stream]] + // , filledPullInto). + self.commit_pull_into_descriptor(cx, &filled_pull_into, can_gc)?; + } + + // Return. + return Ok(()); + } + + // If pullIntoDescriptor’s bytes filled < pullIntoDescriptor’s minimum fill, return. + if first_descriptor.bytes_filled.get() < first_descriptor.minimum_fill { + return Ok(()); + } + + // needed to drop the borrow and avoid BorrowMutError + drop(pending_pull_intos); + + // Perform ! ReadableByteStreamControllerShiftPendingPullInto(controller). + let pull_into_descriptor = self.shift_pending_pull_into(); + + // Let remainderSize be the remainder after dividing pullIntoDescriptor’s bytes + // filled by pullIntoDescriptor’s element size. + let remainder_size = + pull_into_descriptor.bytes_filled.get() % pull_into_descriptor.element_size; + + // If remainderSize > 0, + if remainder_size > 0 { + // Let end be pullIntoDescriptor’s byte offset + pullIntoDescriptor’s bytes filled. + let end = pull_into_descriptor.byte_offset + pull_into_descriptor.bytes_filled.get(); + + // Perform ? ReadableByteStreamControllerEnqueueClonedChunkToQueue(controller, + // pullIntoDescriptor’s buffer, end − remainderSize, remainderSize). + self.enqueue_cloned_chunk_to_queue( + cx, + &pull_into_descriptor.buffer, + end - remainder_size, + remainder_size, + can_gc, + )?; + } + + // Set pullIntoDescriptor’s bytes filled to pullIntoDescriptor’s bytes filled − remainderSize. + pull_into_descriptor + .bytes_filled + .set(pull_into_descriptor.bytes_filled.get() - remainder_size); + + // Let filledPullIntos be the result of performing + // ! ReadableByteStreamControllerProcessPullIntoDescriptorsUsingQueue(controller). + let filled_pull_intos = self.process_pull_into_descriptors_using_queue(cx); + + // Perform ! ReadableByteStreamControllerCommitPullIntoDescriptor(controller.[[stream]], pullIntoDescriptor). + self.commit_pull_into_descriptor(cx, &pull_into_descriptor, can_gc)?; + + // For each filledPullInto of filledPullIntos, + for filled_pull_into in filled_pull_intos { + // Perform ! ReadableByteStreamControllerCommitPullIntoDescriptor(controller.[[stream]], filledPullInto). + self.commit_pull_into_descriptor(cx, &filled_pull_into, can_gc)?; + } + + Ok(()) } /// pub(crate) fn respond_with_new_view( &self, - _view: HeapBufferSource, + cx: SafeJSContext, + view: HeapBufferSource, + can_gc: CanGc, ) -> Fallible<()> { - todo!() + let view_byte_length; + { + // Assert: controller.[[pendingPullIntos]] is not empty. + let mut pending_pull_intos = self.pending_pull_intos.borrow_mut(); + assert!(!pending_pull_intos.is_empty()); + + // Assert: ! IsDetachedBuffer(view.[[ViewedArrayBuffer]]) is false. + assert!(!view.is_detached_buffer(cx)); + + // Let firstDescriptor be controller.[[pendingPullIntos]][0]. + let first_descriptor = pending_pull_intos.first_mut().unwrap(); + + // Let state be controller.[[stream]].[[state]]. + let stream = self.stream.get().unwrap(); + + // If state is "closed", + if stream.is_closed() { + // If view.[[ByteLength]] is not 0, throw a TypeError exception. + if view.byte_length() != 0 { + return Err(Error::Type("view byte length is not 0".to_owned())); + } + } else { + // Assert: state is "readable". + assert!(stream.is_readable()); + + // If view.[[ByteLength]] is 0, throw a TypeError exception. + if view.byte_length() == 0 { + return Err(Error::Type("view byte length is 0".to_owned())); + } + } + + // If firstDescriptor’s byte offset + firstDescriptor’ bytes filled is not view.[[ByteOffset]], + // throw a RangeError exception. + if first_descriptor.byte_offset + first_descriptor.bytes_filled.get() != + (view.get_byte_offset() as u64) + { + return Err(Error::Range( + "firstDescriptor's byte offset + bytes filled is not view byte offset" + .to_owned(), + )); + } + + // If firstDescriptor’s buffer byte length is not view.[[ViewedArrayBuffer]].[[ByteLength]], + // throw a RangeError exception. + if first_descriptor.buffer_byte_length != + (view.viewed_buffer_array_byte_length(cx) as u64) + { + return Err(Error::Range( + "firstDescriptor's buffer byte length is not view viewed buffer array byte length" + .to_owned(), + )); + } + + // If firstDescriptor’s bytes filled + view.[[ByteLength]] > firstDescriptor’s byte length, + // throw a RangeError exception. + if first_descriptor.bytes_filled.get() + (view.byte_length()) as u64 > + first_descriptor.byte_length + { + return Err(Error::Range( + "bytes filled + view byte length > byte length".to_owned(), + )); + } + + // Let viewByteLength be view.[[ByteLength]]. + view_byte_length = view.byte_length(); + + // Set firstDescriptor’s buffer to ? TransferArrayBuffer(view.[[ViewedArrayBuffer]]). + first_descriptor.buffer = view + .get_array_buffer_view_buffer(cx) + .transfer_array_buffer(cx)?; + } + + // Perform ? ReadableByteStreamControllerRespondInternal(controller, viewByteLength). + self.respond_internal(cx, view_byte_length as u64, can_gc) + } + + /// + pub(crate) fn get_desired_size(&self) -> Option { + // Let state be controller.[[stream]].[[state]]. + let stream = self.stream.get()?; + + // If state is "errored", return null. + if stream.is_errored() { + return None; + } + + // If state is "closed", return 0. + if stream.is_closed() { + return Some(0.0); + } + + // Return controller.[[strategyHWM]] − controller.[[queueTotalSize]]. + Some(self.strategy_hwm - self.queue_total_size.get()) + } + + /// + pub(crate) fn get_byob_request( + &self, + cx: SafeJSContext, + can_gc: CanGc, + ) -> Fallible>> { + // If controller.[[byobRequest]] is null and controller.[[pendingPullIntos]] is not empty, + let pending_pull_intos = self.pending_pull_intos.borrow(); + if self.byob_request.get().is_none() && !pending_pull_intos.is_empty() { + // Let firstDescriptor be controller.[[pendingPullIntos]][0]. + let first_descriptor = pending_pull_intos.first().unwrap(); + // Let view be ! Construct(%Uint8Array%, « firstDescriptor’s buffer, + // firstDescriptor’s byte offset + firstDescriptor’s bytes filled, + // firstDescriptor’s byte length − firstDescriptor’s bytes filled »). + + let byte_offset = first_descriptor.byte_offset + first_descriptor.bytes_filled.get(); + let byte_length = first_descriptor.byte_length - first_descriptor.bytes_filled.get(); + + let view = create_buffer_source_with_constructor( + cx, + &Constructor::Name(Type::Uint8), + &first_descriptor.buffer, + byte_offset as usize, + byte_length as usize, + )?; + + // Let byobRequest be a new ReadableStreamBYOBRequest. + let byob_request = ReadableStreamBYOBRequest::new(&self.global(), can_gc); + + // Set byobRequest.[[controller]] to controller. + byob_request.set_controller(Some(&DomRoot::from_ref(self))); + + // Set byobRequest.[[view]] to view. + byob_request.set_view(Some(view)); + + // Set controller.[[byobRequest]] to byobRequest. + self.byob_request.set(Some(&byob_request)); + } + + // Return controller.[[byobRequest]]. + Ok(self.byob_request.get()) + } + + /// + pub(crate) fn close(&self, cx: SafeJSContext, can_gc: CanGc) -> Fallible<()> { + // Let stream be controller.[[stream]]. + let stream = self.stream.get().unwrap(); + + // If controller.[[closeRequested]] is true or stream.[[state]] is not "readable", return. + if self.close_requested.get() || !stream.is_readable() { + return Ok(()); + } + + // If controller.[[queueTotalSize]] > 0, + if self.queue_total_size.get() > 0.0 { + // Set controller.[[closeRequested]] to true. + self.close_requested.set(true); + // Return. + return Ok(()); + } + + // If controller.[[pendingPullIntos]] is not empty, + let pending_pull_intos = self.pending_pull_intos.borrow(); + if !pending_pull_intos.is_empty() { + // Let firstPendingPullInto be controller.[[pendingPullIntos]][0]. + let first_pending_pull_into = pending_pull_intos.first().unwrap(); + + // If the remainder after dividing firstPendingPullInto’s bytes filled by + // firstPendingPullInto’s element size is not 0, + if first_pending_pull_into.bytes_filled.get() % first_pending_pull_into.element_size != + 0 + { + // needed to drop the borrow and avoid BorrowMutError + drop(pending_pull_intos); + + // Let e be a new TypeError exception. + let e = Error::Type( + "remainder after dividing firstPendingPullInto's bytes + filled by firstPendingPullInto's element size is not 0" + .to_owned(), + ); + + // Perform ! ReadableByteStreamControllerError(controller, e). + rooted!(in(*cx) let mut error = UndefinedValue()); + e.clone().to_jsval(cx, &self.global(), error.handle_mut()); + self.error(error.handle(), can_gc); + + // Throw e. + return Err(e); + } + } + + // Perform ! ReadableByteStreamControllerClearAlgorithms(controller). + self.clear_algorithms(); + + // Perform ! ReadableStreamClose(stream). + stream.close(can_gc); + Ok(()) + } + + /// + pub(crate) fn error(&self, e: SafeHandleValue, can_gc: CanGc) { + // Let stream be controller.[[stream]]. + let stream = self.stream.get().unwrap(); + + // If stream.[[state]] is not "readable", return. + if !stream.is_readable() { + return; + } + + // Perform ! ReadableByteStreamControllerClearPendingPullIntos(controller). + self.clear_pending_pull_intos(); + + // Perform ! ResetQueue(controller). + self.reset_queue(); + + // Perform ! ReadableByteStreamControllerClearAlgorithms(controller). + self.clear_algorithms(); + + // Perform ! ReadableStreamError(stream, e). + stream.error(e, can_gc); + } + + /// + fn clear_algorithms(&self) { + // Set controller.[[pullAlgorithm]] to undefined. + // Set controller.[[cancelAlgorithm]] to undefined. + self.underlying_source.set(None); + } + + /// + pub(crate) fn reset_queue(&self) { + // Assert: container has [[queue]] and [[queueTotalSize]] internal slots. + + // Set container.[[queue]] to a new empty list. + self.queue.borrow_mut().clear(); + + // Set container.[[queueTotalSize]] to 0. + self.queue_total_size.set(0.0); + } + + /// + pub(crate) fn clear_pending_pull_intos(&self) { + // Perform ! ReadableByteStreamControllerInvalidateBYOBRequest(controller). + self.invalidate_byob_request(); + + // Set controller.[[pendingPullIntos]] to a new empty list. + self.pending_pull_intos.borrow_mut().clear(); + } + + /// + pub(crate) fn invalidate_byob_request(&self) { + if let Some(byob_request) = self.byob_request.get() { + // Set controller.[[byobRequest]].[[controller]] to undefined. + byob_request.set_controller(None); + + // Set controller.[[byobRequest]].[[view]] to null. + byob_request.set_view(None); + + // Set controller.[[byobRequest]] to null. + self.byob_request.set(None); + } + // If controller.[[byobRequest]] is null, return. + } + + /// + pub(crate) fn enqueue( + &self, + cx: SafeJSContext, + chunk: HeapBufferSource, + can_gc: CanGc, + ) -> Fallible<()> { + // Let stream be controller.[[stream]]. + let stream = self.stream.get().unwrap(); + + // If controller.[[closeRequested]] is true or stream.[[state]] is not "readable", return. + if self.close_requested.get() || !stream.is_readable() { + return Ok(()); + } + + // Let buffer be chunk.[[ViewedArrayBuffer]]. + let buffer = chunk.get_array_buffer_view_buffer(cx); + + // Let byteOffset be chunk.[[ByteOffset]]. + let byte_offset = chunk.get_byte_offset(); + + // Let byteLength be chunk.[[ByteLength]]. + let byte_length = chunk.byte_length(); + + // If ! IsDetachedBuffer(buffer) is true, throw a TypeError exception. + if buffer.is_detached_buffer(cx) { + return Err(Error::Type("buffer is detached".to_owned())); + } + + // Let transferredBuffer be ? TransferArrayBuffer(buffer). + let transferred_buffer = buffer.transfer_array_buffer(cx)?; + + // If controller.[[pendingPullIntos]] is not empty, + { + let mut pending_pull_intos = self.pending_pull_intos.borrow_mut(); + if !pending_pull_intos.is_empty() { + // Let firstPendingPullInto be controller.[[pendingPullIntos]][0]. + let first_descriptor = pending_pull_intos.first_mut().unwrap(); + // If ! IsDetachedBuffer(firstPendingPullInto’s buffer) is true, throw a TypeError exception. + if first_descriptor.buffer.is_detached_buffer(cx) { + return Err(Error::Type("buffer is detached".to_owned())); + } + + // Perform ! ReadableByteStreamControllerInvalidateBYOBRequest(controller). + self.invalidate_byob_request(); + + // Set firstPendingPullInto’s buffer to ! TransferArrayBuffer(firstPendingPullInto’s buffer). + first_descriptor.buffer = first_descriptor.buffer.transfer_array_buffer(cx)?; + + // If firstPendingPullInto’s reader type is "none", + if first_descriptor.reader_type.is_none() { + // needed to drop the borrow and avoid BorrowMutError + drop(pending_pull_intos); + + // perform ? ReadableByteStreamControllerEnqueueDetachedPullIntoToQueue( + // controller, firstPendingPullInto). + self.enqueue_detached_pull_into_to_queue(cx, can_gc)?; + } + } + } + + // If ! ReadableStreamHasDefaultReader(stream) is true, + if stream.has_default_reader() { + // Perform ! ReadableByteStreamControllerProcessReadRequestsUsingQueue(controller). + self.process_read_requests_using_queue(cx, can_gc)?; + + // If ! ReadableStreamGetNumReadRequests(stream) is 0, + if stream.get_num_read_requests() == 0 { + // Assert: controller.[[pendingPullIntos]] is empty. + { + assert!(self.pending_pull_intos.borrow().is_empty()); + } + + // Perform ! ReadableByteStreamControllerEnqueueChunkToQueue( + // controller, transferredBuffer, byteOffset, byteLength). + self.enqueue_chunk_to_queue(transferred_buffer, byte_offset, byte_length); + } else { + // Assert: controller.[[queue]] is empty. + assert!(self.queue.borrow().is_empty()); + + // If controller.[[pendingPullIntos]] is not empty, + + let pending_pull_intos = self.pending_pull_intos.borrow(); + if !pending_pull_intos.is_empty() { + // Assert: controller.[[pendingPullIntos]][0]'s reader type is "default". + assert!(matches!( + pending_pull_intos.first().unwrap().reader_type, + Some(ReaderType::Default) + )); + + // needed to drop the borrow and avoid BorrowMutError + drop(pending_pull_intos); + + // Perform ! ReadableByteStreamControllerShiftPendingPullInto(controller). + self.shift_pending_pull_into(); + } + + // Let transferredView be ! Construct(%Uint8Array%, « transferredBuffer, byteOffset, byteLength »). + let transferred_view = create_buffer_source_with_constructor( + cx, + &Constructor::Name(Type::Uint8), + &transferred_buffer, + byte_offset, + byte_length, + )?; + + // Perform ! ReadableStreamFulfillReadRequest(stream, transferredView, false). + rooted!(in(*cx) let mut view_value = UndefinedValue()); + transferred_view.get_buffer_view_value(cx, view_value.handle_mut()); + stream.fulfill_read_request(view_value.handle(), false, can_gc); + } + // Otherwise, if ! ReadableStreamHasBYOBReader(stream) is true, + } else if stream.has_byob_reader() { + // Perform ! ReadableByteStreamControllerEnqueueChunkToQueue( + // controller, transferredBuffer, byteOffset, byteLength). + self.enqueue_chunk_to_queue(transferred_buffer, byte_offset, byte_length); + + // Let filledPullIntos be the result of performing ! + // ReadableByteStreamControllerProcessPullIntoDescriptorsUsingQueue(controller). + let filled_pull_intos = self.process_pull_into_descriptors_using_queue(cx); + + // For each filledPullInto of filledPullIntos, + // Perform ! ReadableByteStreamControllerCommitPullIntoDescriptor(stream, filledPullInto). + for filled_pull_into in filled_pull_intos { + self.commit_pull_into_descriptor(cx, &filled_pull_into, can_gc)?; + } + } else { + // Assert: ! IsReadableStreamLocked(stream) is false. + assert!(!stream.is_locked()); + + // Perform ! ReadableByteStreamControllerEnqueueChunkToQueue + // (controller, transferredBuffer, byteOffset, byteLength). + self.enqueue_chunk_to_queue(transferred_buffer, byte_offset, byte_length); + } + + // Perform ! ReadableByteStreamControllerCallPullIfNeeded(controller). + self.call_pull_if_needed(can_gc); + + Ok(()) + } + + /// + pub(crate) fn commit_pull_into_descriptor( + &self, + cx: SafeJSContext, + pull_into_descriptor: &PullIntoDescriptor, + can_gc: CanGc, + ) -> Fallible<()> { + // Assert: stream.[[state]] is not "errored". + let stream = self.stream.get().unwrap(); + assert!(!stream.is_errored()); + + // Assert: pullIntoDescriptor.reader type is not "none". + assert!(pull_into_descriptor.reader_type.is_some()); + + // Let done be false. + let mut done = false; + + // If stream.[[state]] is "closed", + if stream.is_closed() { + // Assert: the remainder after dividing pullIntoDescriptor’s bytes filled + // by pullIntoDescriptor’s element size is 0. + assert!( + pull_into_descriptor.bytes_filled.get() % pull_into_descriptor.element_size == 0 + ); + + // Set done to true. + done = true; + } + + // Let filledView be ! ReadableByteStreamControllerConvertPullIntoDescriptor(pullIntoDescriptor). + let filled_view = self.convert_pull_into_descriptor(cx, pull_into_descriptor)?; + + rooted!(in(*cx) let mut view_value = UndefinedValue()); + filled_view.get_buffer_view_value(cx, view_value.handle_mut()); + + // If pullIntoDescriptor’s reader type is "default", + if matches!(pull_into_descriptor.reader_type, Some(ReaderType::Default)) { + // Perform ! ReadableStreamFulfillReadRequest(stream, filledView, done). + + stream.fulfill_read_request(view_value.handle(), done, can_gc); + } else { + // Assert: pullIntoDescriptor’s reader type is "byob". + assert!(matches!( + pull_into_descriptor.reader_type, + Some(ReaderType::Byob) + )); + + // Perform ! ReadableStreamFulfillReadIntoRequest(stream, filledView, done). + stream.fulfill_read_into_request(view_value.handle(), done, can_gc); + } + Ok(()) + } + + /// + pub(crate) fn convert_pull_into_descriptor( + &self, + cx: SafeJSContext, + pull_into_descriptor: &PullIntoDescriptor, + ) -> Fallible> { + // Let bytesFilled be pullIntoDescriptor’s bytes filled. + let bytes_filled = pull_into_descriptor.bytes_filled.get(); + + // Let elementSize be pullIntoDescriptor’s element size. + let element_size = pull_into_descriptor.element_size; + + // Assert: bytesFilled ≤ pullIntoDescriptor’s byte length. + assert!(bytes_filled <= pull_into_descriptor.byte_length); + + //Assert: the remainder after dividing bytesFilled by elementSize is 0. + assert!(bytes_filled % element_size == 0); + + // Let buffer be ! TransferArrayBuffer(pullIntoDescriptor’s buffer). + let buffer = pull_into_descriptor.buffer.transfer_array_buffer(cx)?; + + // Return ! Construct(pullIntoDescriptor’s view constructor, + // « buffer, pullIntoDescriptor’s byte offset, bytesFilled ÷ elementSize »). + create_buffer_source_with_constructor( + cx, + &pull_into_descriptor.view_constructor, + &buffer, + pull_into_descriptor.byte_offset as usize, + (bytes_filled / element_size) as usize, + ) + } + + /// + pub(crate) fn process_pull_into_descriptors_using_queue( + &self, + cx: SafeJSContext, + ) -> Vec { + // Assert: controller.[[closeRequested]] is false. + assert!(!self.close_requested.get()); + + // Let filledPullIntos be a new empty list. + let mut filled_pull_intos = Vec::new(); + + // While controller.[[pendingPullIntos]] is not empty, + loop { + // If controller.[[queueTotalSize]] is 0, then break. + if self.queue_total_size.get() == 0.0 { + break; + } + + // Let pullIntoDescriptor be controller.[[pendingPullIntos]][0]. + let fill_pull_result = { + let pending_pull_intos = self.pending_pull_intos.borrow(); + let Some(pull_into_descriptor) = pending_pull_intos.first() else { + break; + }; + self.fill_pull_into_descriptor_from_queue(cx, pull_into_descriptor) + }; + + // If ! ReadableByteStreamControllerFillPullIntoDescriptorFromQueue(controller, pullIntoDescriptor) is true, + if fill_pull_result { + // Perform ! ReadableByteStreamControllerShiftPendingPullInto(controller). + let pull_into_descriptor = self.shift_pending_pull_into(); + + // Append pullIntoDescriptor to filledPullIntos. + filled_pull_intos.push(pull_into_descriptor); + } + } + + // Return filledPullIntos. + filled_pull_intos + } + + /// + pub(crate) fn fill_pull_into_descriptor_from_queue( + &self, + cx: SafeJSContext, + pull_into_descriptor: &PullIntoDescriptor, + ) -> bool { + // Let maxBytesToCopy be min(controller.[[queueTotalSize]], + // pullIntoDescriptor’s byte length − pullIntoDescriptor’s bytes filled). + let max_bytes_to_copy = min( + self.queue_total_size.get() as usize, + (pull_into_descriptor.byte_length - pull_into_descriptor.bytes_filled.get()) as usize, + ); + + // Let maxBytesFilled be pullIntoDescriptor’s bytes filled + maxBytesToCopy. + let max_bytes_filled = pull_into_descriptor.bytes_filled.get() as usize + max_bytes_to_copy; + + // Let totalBytesToCopyRemaining be maxBytesToCopy. + let mut total_bytes_to_copy_remaining = max_bytes_to_copy; + + // Let ready be false. + let mut ready = false; + + // Assert: ! IsDetachedBuffer(pullIntoDescriptor’s buffer) is false. + assert!(!pull_into_descriptor.buffer.is_detached_buffer(cx)); + + // Assert: pullIntoDescriptor’s bytes filled < pullIntoDescriptor’s minimum fill. + assert!(pull_into_descriptor.bytes_filled.get() < pull_into_descriptor.minimum_fill); + + // Let remainderBytes be the remainder after dividing maxBytesFilled by pullIntoDescriptor’s element size. + let remainder_bytes = max_bytes_filled % pull_into_descriptor.element_size as usize; + + // Let maxAlignedBytes be maxBytesFilled − remainderBytes. + let max_aligned_bytes = max_bytes_filled - remainder_bytes; + + // If maxAlignedBytes ≥ pullIntoDescriptor’s minimum fill, + if max_aligned_bytes >= pull_into_descriptor.minimum_fill as usize { + // Set totalBytesToCopyRemaining to maxAlignedBytes − pullIntoDescriptor’s bytes filled. + total_bytes_to_copy_remaining = + max_aligned_bytes - (pull_into_descriptor.bytes_filled.get() as usize); + + // Set ready to true. + ready = true; + } + + // Let queue be controller.[[queue]]. + let mut queue = self.queue.borrow_mut(); + + // While totalBytesToCopyRemaining > 0, + while total_bytes_to_copy_remaining > 0 { + // Let headOfQueue be queue[0]. + let head_of_queue = queue.front_mut().unwrap(); + + // Let bytesToCopy be min(totalBytesToCopyRemaining, headOfQueue’s byte length). + let bytes_to_copy = total_bytes_to_copy_remaining.min(head_of_queue.byte_length); + + // Let destStart be pullIntoDescriptor’s byte offset + pullIntoDescriptor’s bytes filled. + let dest_start = + pull_into_descriptor.byte_offset + pull_into_descriptor.bytes_filled.get(); + + // Let descriptorBuffer be pullIntoDescriptor’s buffer. + let descriptor_buffer = &pull_into_descriptor.buffer; + + // Let queueBuffer be headOfQueue’s buffer. + let queue_buffer = &head_of_queue.buffer; + + // Let queueByteOffset be headOfQueue’s byte offset. + let queue_byte_offset = head_of_queue.byte_offset; + + // Assert: ! CanCopyDataBlockBytes(descriptorBuffer, destStart, + // queueBuffer, queueByteOffset, bytesToCopy) is true. + assert!(descriptor_buffer.can_copy_data_block_bytes( + cx, + dest_start as usize, + queue_buffer, + queue_byte_offset, + bytes_to_copy + )); + + // Perform ! CopyDataBlockBytes(descriptorBuffer.[[ArrayBufferData]], destStart, + // queueBuffer.[[ArrayBufferData]], queueByteOffset, bytesToCopy). + descriptor_buffer.copy_data_block_bytes( + cx, + dest_start as usize, + queue_buffer, + queue_byte_offset, + bytes_to_copy, + ); + + // If headOfQueue’s byte length is bytesToCopy, + if head_of_queue.byte_length == bytes_to_copy { + // Remove queue[0]. + queue.pop_front().unwrap(); + } else { + // Set headOfQueue’s byte offset to headOfQueue’s byte offset + bytesToCopy. + head_of_queue.byte_offset += bytes_to_copy; + + // Set headOfQueue’s byte length to headOfQueue’s byte length − bytesToCopy. + head_of_queue.byte_length -= bytes_to_copy; + } + + // Set controller.[[queueTotalSize]] to controller.[[queueTotalSize]] − bytesToCopy. + self.queue_total_size + .set(self.queue_total_size.get() - (bytes_to_copy as f64)); + + // Perform ! ReadableByteStreamControllerFillHeadPullIntoDescriptor( + // controller, bytesToCopy, pullIntoDescriptor). + self.fill_head_pull_into_descriptor(bytes_to_copy as u64, pull_into_descriptor); + + // Set totalBytesToCopyRemaining to totalBytesToCopyRemaining − bytesToCopy. + total_bytes_to_copy_remaining -= bytes_to_copy; + } + + // If ready is false, + if !ready { + // Assert: controller.[[queueTotalSize]] is 0. + assert!(self.queue_total_size.get() == 0.0); + + // Assert: pullIntoDescriptor’s bytes filled > 0. + assert!(pull_into_descriptor.bytes_filled.get() > 0); + + // Assert: pullIntoDescriptor’s bytes filled < pullIntoDescriptor’s minimum fill. + assert!(pull_into_descriptor.bytes_filled.get() < pull_into_descriptor.minimum_fill); + } + + // Return ready. + ready + } + + /// + pub(crate) fn fill_head_pull_into_descriptor( + &self, + bytes_copied: u64, + pull_into_descriptor: &PullIntoDescriptor, + ) { + // Assert: either controller.[[pendingPullIntos]] is empty, + // or controller.[[pendingPullIntos]][0] is pullIntoDescriptor. + { + let pending_pull_intos = self.pending_pull_intos.borrow(); + assert!( + pending_pull_intos.is_empty() || + pending_pull_intos.first().unwrap() == pull_into_descriptor + ); + } + + // Assert: controller.[[byobRequest]] is null. + assert!(self.byob_request.get().is_none()); + + // Set pullIntoDescriptor’s bytes filled to bytes filled + size. + pull_into_descriptor + .bytes_filled + .set(pull_into_descriptor.bytes_filled.get() + bytes_copied); + } + + /// + pub(crate) fn enqueue_detached_pull_into_to_queue( + &self, + cx: SafeJSContext, + can_gc: CanGc, + ) -> Fallible<()> { + // first_descriptor: &PullIntoDescriptor, + let pending_pull_intos = self.pending_pull_intos.borrow(); + let first_descriptor = pending_pull_intos.first().unwrap(); + + // Assert: pullIntoDescriptor’s reader type is "none". + assert!(first_descriptor.reader_type.is_none()); + + // If pullIntoDescriptor’s bytes filled > 0, perform ? + // ReadableByteStreamControllerEnqueueClonedChunkToQueue(controller, + // pullIntoDescriptor’s buffer, pullIntoDescriptor’s byte offset, pullIntoDescriptor’s bytes filled). + + if first_descriptor.bytes_filled.get() > 0 { + self.enqueue_cloned_chunk_to_queue( + cx, + &first_descriptor.buffer, + first_descriptor.byte_offset, + first_descriptor.bytes_filled.get(), + can_gc, + )?; + } + + // needed to drop the borrow and avoid BorrowMutError + drop(pending_pull_intos); + + // Perform ! ReadableByteStreamControllerShiftPendingPullInto(controller). + self.shift_pending_pull_into(); + + Ok(()) + } + + /// + pub(crate) fn enqueue_cloned_chunk_to_queue( + &self, + cx: SafeJSContext, + buffer: &HeapBufferSource, + byte_offset: u64, + byte_length: u64, + can_gc: CanGc, + ) -> Fallible<()> { + // Let cloneResult be CloneArrayBuffer(buffer, byteOffset, byteLength, %ArrayBuffer%). + if let Some(clone_result) = + buffer.clone_array_buffer(cx, byte_offset as usize, byte_length as usize) + { + // Perform ! ReadableByteStreamControllerEnqueueChunkToQueue + // (controller, cloneResult.[[Value]], 0, byteLength). + self.enqueue_chunk_to_queue(clone_result, 0, byte_length as usize); + + Ok(()) + } else { + // If cloneResult is an abrupt completion, + + // Perform ! ReadableByteStreamControllerError(controller, cloneResult.[[Value]]). + rooted!(in(*cx) let mut rval = UndefinedValue()); + let error = Error::Type("can not clone array buffer".to_owned()); + error + .clone() + .to_jsval(cx, &self.global(), rval.handle_mut()); + self.error(rval.handle(), can_gc); + + // Return cloneResult. + Err(error) + } + } + + /// + pub(crate) fn enqueue_chunk_to_queue( + &self, + buffer: HeapBufferSource, + byte_offset: usize, + byte_length: usize, + ) { + // Let entry be a new ReadableByteStreamQueueEntry object. + let entry = QueueEntry::new(buffer, byte_offset, byte_length); + + // Append entry to controller.[[queue]]. + self.queue.borrow_mut().push_back(entry); + + // Set controller.[[queueTotalSize]] to controller.[[queueTotalSize]] + byteLength. + self.queue_total_size + .set(self.queue_total_size.get() + byte_length as f64); + } + + /// + pub(crate) fn shift_pending_pull_into(&self) -> PullIntoDescriptor { + // Assert: controller.[[byobRequest]] is null. + assert!(self.byob_request.get().is_none()); + + // Let descriptor be controller.[[pendingPullIntos]][0]. + // Remove descriptor from controller.[[pendingPullIntos]]. + let descriptor = self.pending_pull_intos.borrow_mut().remove(0); + + // Return descriptor. + descriptor + } + + /// + pub(crate) fn process_read_requests_using_queue( + &self, + cx: SafeJSContext, + can_gc: CanGc, + ) -> Fallible<()> { + // Let reader be controller.[[stream]].[[reader]]. + // Assert: reader implements ReadableStreamDefaultReader. + let reader = self.stream.get().unwrap().get_default_reader(); + + // Step 3 + reader.process_read_requests(cx, DomRoot::from_ref(self), can_gc) + } + + /// + pub(crate) fn fill_read_request_from_queue( + &self, + cx: SafeJSContext, + read_request: &ReadRequest, + can_gc: CanGc, + ) -> Fallible<()> { + // Assert: controller.[[queueTotalSize]] > 0. + assert!(self.queue_total_size.get() > 0.0); + // Also assert that the queue has a non-zero length; + assert!(!self.queue.borrow().is_empty()); + + // Let entry be controller.[[queue]][0]. + // Remove entry from controller.[[queue]]. + let entry = self.remove_entry(); + + // Set controller.[[queueTotalSize]] to controller.[[queueTotalSize]] − entry’s byte length. + self.queue_total_size + .set(self.queue_total_size.get() - entry.byte_length as f64); + + // Perform ! ReadableByteStreamControllerHandleQueueDrain(controller). + self.handle_queue_drain(can_gc); + + // Let view be ! Construct(%Uint8Array%, « entry’s buffer, entry’s byte offset, entry’s byte length »). + let view = create_buffer_source_with_constructor( + cx, + &Constructor::Name(Type::Uint8), + &entry.buffer, + entry.byte_offset, + entry.byte_length, + )?; + + // Perform readRequest’s chunk steps, given view. + let result = RootedTraceableBox::new(Heap::default()); + rooted!(in(*cx) let mut view_value = UndefinedValue()); + view.get_buffer_view_value(cx, view_value.handle_mut()); + result.set(*view_value); + + read_request.chunk_steps(result, can_gc); + + Ok(()) + } + + /// + pub(crate) fn handle_queue_drain(&self, can_gc: CanGc) { + // Assert: controller.[[stream]].[[state]] is "readable". + assert!(self.stream.get().unwrap().is_readable()); + + // If controller.[[queueTotalSize]] is 0 and controller.[[closeRequested]] is true, + if self.queue_total_size.get() == 0.0 && self.close_requested.get() { + // Perform ! ReadableByteStreamControllerClearAlgorithms(controller). + self.clear_algorithms(); + + // Perform ! ReadableStreamClose(controller.[[stream]]). + self.stream.get().unwrap().close(can_gc); + } else { + // Perform ! ReadableByteStreamControllerCallPullIfNeeded(controller). + self.call_pull_if_needed(can_gc); + } + } + + /// + pub(crate) fn call_pull_if_needed(&self, can_gc: CanGc) { + // Let shouldPull be ! ReadableByteStreamControllerShouldCallPull(controller). + let should_pull = self.should_call_pull(); + // If shouldPull is false, return. + if !should_pull { + return; + } + + // If controller.[[pulling]] is true, + if self.pulling.get() { + // Set controller.[[pullAgain]] to true. + self.pull_again.set(true); + + // Return. + return; + } + + // Assert: controller.[[pullAgain]] is false. + assert!(!self.pull_again.get()); + + // Set controller.[[pulling]] to true. + self.pulling.set(true); + + // Let pullPromise be the result of performing controller.[[pullAlgorithm]]. + // Continues into the resolve and reject handling of the native handler. + let global = self.global(); + let rooted_controller = DomRoot::from_ref(self); + let controller = Controller::ReadableByteStreamController(rooted_controller.clone()); + + if let Some(underlying_source) = self.underlying_source.get() { + let handler = PromiseNativeHandler::new( + &global, + Some(Box::new(PullAlgorithmFulfillmentHandler { + controller: Dom::from_ref(&rooted_controller), + })), + Some(Box::new(PullAlgorithmRejectionHandler { + controller: Dom::from_ref(&rooted_controller), + })), + can_gc, + ); + + let realm = enter_realm(&*global); + let comp = InRealm::Entered(&realm); + let result = underlying_source + .call_pull_algorithm(controller, can_gc) + .unwrap_or_else(|| { + let promise = Promise::new(&global, can_gc); + promise.resolve_native(&(), can_gc); + Ok(promise) + }); + let promise = result.unwrap_or_else(|error| { + let cx = GlobalScope::get_cx(); + rooted!(in(*cx) let mut rval = UndefinedValue()); + // TODO: check if `self.global()` is the right globalscope. + error + .clone() + .to_jsval(cx, &self.global(), rval.handle_mut()); + let promise = Promise::new(&global, can_gc); + promise.reject_native(&rval.handle(), can_gc); + promise + }); + promise.append_native_handler(&handler, comp, can_gc); + } + } + + /// + fn should_call_pull(&self) -> bool { + // Let stream be controller.[[stream]]. + // Note: the spec does not assert that stream is not undefined here, + // so we return false if it is. + let stream = self.stream.get().unwrap(); + + // If stream.[[state]] is not "readable", return false. + if !stream.is_readable() { + return false; + } + + // If controller.[[closeRequested]] is true, return false. + if self.close_requested.get() { + return false; + } + + // If controller.[[started]] is false, return false. + if !self.started.get() { + return false; + } + + // If ! ReadableStreamHasDefaultReader(stream) is true and ! ReadableStreamGetNumReadRequests(stream) > 0 + // , return true. + if stream.has_default_reader() && stream.get_num_read_requests() > 0 { + return true; + } + + // If ! ReadableStreamHasBYOBReader(stream) is true and ! ReadableStreamGetNumReadIntoRequests(stream) > 0 + // , return true. + if stream.has_byob_reader() && stream.get_num_read_into_requests() > 0 { + return true; + } + + // Let desiredSize be ! ReadableByteStreamControllerGetDesiredSize(controller). + let desired_size = self.get_desired_size(); + + // Assert: desiredSize is not null. + assert!(desired_size.is_some()); + + // If desiredSize > 0, return true. + if desired_size.unwrap() > 0. { + return true; + } + + // Return false. + false + } + /// + pub(crate) fn setup( + &self, + global: &GlobalScope, + stream: DomRoot, + can_gc: CanGc, + ) -> Fallible<()> { + // Assert: stream.[[controller]] is undefined. + stream.assert_no_controller(); + + // If autoAllocateChunkSize is not undefined, + if self.auto_allocate_chunk_size.is_some() { + // Assert: ! IsInteger(autoAllocateChunkSize) is true. Implicit + // Assert: autoAllocateChunkSize is positive. (Implicit by type.) + } + + // Set controller.[[stream]] to stream. + self.stream.set(Some(&stream)); + + // Set controller.[[pullAgain]] and controller.[[pulling]] to false. + self.pull_again.set(false); + self.pulling.set(false); + + // Set controller.[[byobRequest]] to null. + self.byob_request.set(None); + + // Perform ! ResetQueue(controller). + self.reset_queue(); + + // Set controller.[[closeRequested]] and controller.[[started]] to false. + self.close_requested.set(false); + self.started.set(false); + + // Set controller.[[strategyHWM]] to highWaterMark. + // Set controller.[[pullAlgorithm]] to pullAlgorithm. + // Set controller.[[cancelAlgorithm]] to cancelAlgorithm. + // Set controller.[[autoAllocateChunkSize]] to autoAllocateChunkSize. + // Set controller.[[pendingPullIntos]] to a new empty list. + // Note: the above steps are done in `new`. + + // Set stream.[[controller]] to controller. + let rooted_byte_controller = DomRoot::from_ref(self); + stream.set_byte_controller(&rooted_byte_controller); + + if let Some(underlying_source) = rooted_byte_controller.underlying_source.get() { + // Let startResult be the result of performing startAlgorithm. (This might throw an exception.) + let start_result = underlying_source + .call_start_algorithm( + Controller::ReadableByteStreamController(rooted_byte_controller.clone()), + can_gc, + ) + .unwrap_or_else(|| { + let promise = Promise::new(global, can_gc); + promise.resolve_native(&(), can_gc); + Ok(promise) + }); + + // Let startPromise be a promise resolved with startResult. + let start_promise = start_result?; + + // Upon fulfillment of startPromise, Upon rejection of startPromise with reason r, + let handler = PromiseNativeHandler::new( + global, + Some(Box::new(StartAlgorithmFulfillmentHandler { + controller: Dom::from_ref(&rooted_byte_controller), + })), + Some(Box::new(StartAlgorithmRejectionHandler { + controller: Dom::from_ref(&rooted_byte_controller), + })), + can_gc, + ); + let realm = enter_realm(global); + let comp = InRealm::Entered(&realm); + start_promise.append_native_handler(&handler, comp, can_gc); + }; + + Ok(()) + } + + // Fallible<()> { + // If this.[[pendingPullIntos]] is not empty, + let mut pending_pull_intos = self.pending_pull_intos.borrow_mut(); + if !pending_pull_intos.is_empty() { + // Let firstPendingPullInto be this.[[pendingPullIntos]][0]. + let mut first_pending_pull_into = pending_pull_intos.remove(0); + + // Set firstPendingPullInto’s reader type to "none". + first_pending_pull_into.reader_type = None; + + // Set this.[[pendingPullIntos]] to the list « firstPendingPullInto » + pending_pull_intos.clear(); + pending_pull_intos.push(first_pending_pull_into); + } + Ok(()) + } + + /// + pub(crate) fn perform_cancel_steps( + &self, + reason: SafeHandleValue, + can_gc: CanGc, + ) -> Rc { + // Perform ! ReadableByteStreamControllerClearPendingPullIntos(this). + self.clear_pending_pull_intos(); + + // Perform ! ResetQueue(this). + self.reset_queue(); + + let underlying_source = self + .underlying_source + .get() + .expect("Controller should have a source when the cancel steps are called into."); + let global = self.global(); + + // Let result be the result of performing this.[[cancelAlgorithm]], passing in reason. + let result = underlying_source + .call_cancel_algorithm(reason, can_gc) + .unwrap_or_else(|| { + let promise = Promise::new(&global, can_gc); + promise.resolve_native(&(), can_gc); + Ok(promise) + }); + + let promise = result.unwrap_or_else(|error| { + let cx = GlobalScope::get_cx(); + rooted!(in(*cx) let mut rval = UndefinedValue()); + // TODO: check if `self.global()` is the right globalscope. + error + .clone() + .to_jsval(cx, &self.global(), rval.handle_mut()); + let promise = Promise::new(&global, can_gc); + promise.reject_native(&rval.handle(), can_gc); + promise + }); + + // Perform ! ReadableByteStreamControllerClearAlgorithms(this). + self.clear_algorithms(); + + // Return result(the promise). + promise + } + + /// + pub(crate) fn perform_pull_steps( + &self, + cx: SafeJSContext, + read_request: &ReadRequest, + can_gc: CanGc, + ) { + // Let stream be this.[[stream]]. + let stream = self.stream.get().unwrap(); + + // Assert: ! ReadableStreamHasDefaultReader(stream) is true. + assert!(stream.has_default_reader()); + + // If this.[[queueTotalSize]] > 0, + if self.queue_total_size.get() > 0.0 { + // Assert: ! ReadableStreamGetNumReadRequests(stream) is 0. + assert_eq!(stream.get_num_read_requests(), 0); + + // Perform ! ReadableByteStreamControllerFillReadRequestFromQueue(this, readRequest). + let _ = self.fill_read_request_from_queue(cx, read_request, can_gc); + + // Return. + return; + } + + // Let autoAllocateChunkSize be this.[[autoAllocateChunkSize]]. + let auto_allocate_chunk_size = self.auto_allocate_chunk_size; + + // If autoAllocateChunkSize is not undefined, + if let Some(auto_allocate_chunk_size) = auto_allocate_chunk_size { + // create_array_buffer_with_size + // Let buffer be Construct(%ArrayBuffer%, « autoAllocateChunkSize »). + match create_array_buffer_with_size(cx, auto_allocate_chunk_size as usize) { + Ok(buffer) => { + // Let pullIntoDescriptor be a new pull-into descriptor with + // buffer buffer.[[Value]] + // buffer byte length autoAllocateChunkSize + // byte offset 0 + // byte length autoAllocateChunkSize + // bytes filled 0 + // minimum fill 1 + // element size 1 + // view constructor %Uint8Array% + // reader type "default" + let pull_into_descriptor = PullIntoDescriptor { + buffer, + buffer_byte_length: auto_allocate_chunk_size, + byte_length: auto_allocate_chunk_size, + byte_offset: 0, + bytes_filled: Cell::new(0), + minimum_fill: 1, + element_size: 1, + view_constructor: Constructor::Name(Type::Uint8), + reader_type: Some(ReaderType::Default), + }; + + // Append pullIntoDescriptor to this.[[pendingPullIntos]]. + self.pending_pull_intos + .borrow_mut() + .push(pull_into_descriptor); + }, + Err(error) => { + // If buffer is an abrupt completion, + // Perform readRequest’s error steps, given buffer.[[Value]]. + + rooted!(in(*cx) let mut rval = UndefinedValue()); + error + .clone() + .to_jsval(cx, &self.global(), rval.handle_mut()); + read_request.error_steps(rval.handle(), can_gc); + + // Return. + return; + }, + } + } + + // Perform ! ReadableStreamAddReadRequest(stream, readRequest). + stream.add_read_request(read_request); + + // Perform ! ReadableByteStreamControllerCallPullIfNeeded(this). + self.call_pull_if_needed(can_gc); + } + + /// Setting the JS object after the heap has settled down. + pub(crate) fn set_underlying_source_this_object(&self, this_object: HandleObject) { + if let Some(underlying_source) = self.underlying_source.get() { + underlying_source.set_underlying_source_this_object(this_object); + } + } + + pub(crate) fn remove_entry(&self) -> QueueEntry { + self.queue + .borrow_mut() + .pop_front() + .expect("Reader must have read request when remove is called into.") + } + + pub(crate) fn get_queue_total_size(&self) -> f64 { + self.queue_total_size.get() } } impl ReadableByteStreamControllerMethods for ReadableByteStreamController { /// - fn GetByobRequest(&self) -> Fallible>> { - // TODO - Err(Error::NotFound) + fn GetByobRequest( + &self, + can_gc: CanGc, + ) -> Fallible>> { + let cx = GlobalScope::get_cx(); + // Return ! ReadableByteStreamControllerGetBYOBRequest(this). + self.get_byob_request(cx, can_gc) } /// fn GetDesiredSize(&self) -> Option { - // TODO - None + // Return ! ReadableByteStreamControllerGetDesiredSize(this). + self.get_desired_size() } /// - fn Close(&self) -> Fallible<()> { - // TODO - Err(Error::NotFound) + fn Close(&self, can_gc: CanGc) -> Fallible<()> { + let cx = GlobalScope::get_cx(); + // If this.[[closeRequested]] is true, throw a TypeError exception. + if self.close_requested.get() { + return Err(Error::Type("closeRequested is true".to_owned())); + } + + // If this.[[stream]].[[state]] is not "readable", throw a TypeError exception. + if !self.stream.get().unwrap().is_readable() { + return Err(Error::Type("stream is not readable".to_owned())); + } + + // Perform ? ReadableByteStreamControllerClose(this). + self.close(cx, can_gc) } /// fn Enqueue( &self, - _chunk: js::gc::CustomAutoRooterGuard, + chunk: js::gc::CustomAutoRooterGuard, + can_gc: CanGc, ) -> Fallible<()> { - // TODO - Err(Error::NotFound) + let cx = GlobalScope::get_cx(); + + let chunk = HeapBufferSource::::from_view(chunk); + + // If chunk.[[ByteLength]] is 0, throw a TypeError exception. + if chunk.byte_length() == 0 { + return Err(Error::Type("chunk.ByteLength is 0".to_owned())); + } + + // If chunk.[[ViewedArrayBuffer]].[[ByteLength]] is 0, throw a TypeError exception. + if chunk.viewed_buffer_array_byte_length(cx) == 0 { + return Err(Error::Type( + "chunk.ViewedArrayBuffer.ByteLength is 0".to_owned(), + )); + } + + // If this.[[closeRequested]] is true, throw a TypeError exception. + if self.close_requested.get() { + return Err(Error::Type("closeRequested is true".to_owned())); + } + + // If this.[[stream]].[[state]] is not "readable", throw a TypeError exception. + if !self.stream.get().unwrap().is_readable() { + return Err(Error::Type("stream is not readable".to_owned())); + } + + // Return ? ReadableByteStreamControllerEnqueue(this, chunk). + self.enqueue(cx, chunk, can_gc) } /// - fn Error(&self, _cx: SafeJSContext, _e: SafeHandleValue) -> Fallible<()> { - // TODO - Err(Error::NotFound) + fn Error(&self, _cx: SafeJSContext, e: SafeHandleValue, can_gc: CanGc) -> Fallible<()> { + // Perform ! ReadableByteStreamControllerError(this, e). + self.error(e, can_gc); + Ok(()) } } diff --git a/components/script/dom/readablestream.rs b/components/script/dom/readablestream.rs index 07c94dbf591..4baccfdba90 100644 --- a/components/script/dom/readablestream.rs +++ b/components/script/dom/readablestream.rs @@ -2,7 +2,7 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -use std::cell::Cell; +use std::cell::{Cell, RefCell}; use std::ptr::{self}; use std::rc::Rc; @@ -114,6 +114,17 @@ pub(crate) enum ReaderType { Default(MutNullableDom), } +impl Eq for ReaderType {} +impl PartialEq for ReaderType { + fn eq(&self, other: &Self) -> bool { + matches!( + (self, other), + (ReaderType::BYOB(_), ReaderType::BYOB(_)) | + (ReaderType::Default(_), ReaderType::Default(_)) + ) + } +} + /// #[cfg_attr(crown, allow(crown::unrooted_must_root))] fn create_readable_stream( @@ -135,12 +146,7 @@ fn create_readable_stream( // Let stream be a new ReadableStream. // Perform ! InitializeReadableStream(stream). - let stream = ReadableStream::new_with_proto( - global, - None, - ControllerType::Default(MutNullableDom::new(None)), - can_gc, - ); + let stream = ReadableStream::new_with_proto(global, None, can_gc); // Let controller be a new ReadableStreamDefaultController. let controller = ReadableStreamDefaultController::new( @@ -169,7 +175,7 @@ pub(crate) struct ReadableStream { /// /// Note: the inner `MutNullableDom` should really be an `Option`, /// because it is never unset once set. - controller: ControllerType, + controller: RefCell>, /// #[ignore_malloc_size_of = "mozjs"] @@ -179,7 +185,7 @@ pub(crate) struct ReadableStream { disturbed: Cell, /// - reader: ReaderType, + reader: RefCell>, /// state: Cell, @@ -188,17 +194,13 @@ pub(crate) struct ReadableStream { impl ReadableStream { #[cfg_attr(crown, allow(crown::unrooted_must_root))] /// - fn new_inherited(controller: ControllerType) -> ReadableStream { - let reader = match &controller { - ControllerType::Default(_) => ReaderType::Default(MutNullableDom::new(None)), - ControllerType::Byte(_) => ReaderType::BYOB(MutNullableDom::new(None)), - }; + fn new_inherited() -> ReadableStream { ReadableStream { reflector_: Reflector::new(), - controller, + controller: RefCell::new(None), stored_error: Heap::default(), disturbed: Default::default(), - reader, + reader: RefCell::new(None), state: Cell::new(Default::default()), } } @@ -207,11 +209,10 @@ impl ReadableStream { fn new_with_proto( global: &GlobalScope, proto: Option, - controller: ControllerType, can_gc: CanGc, ) -> DomRoot { reflect_dom_object_with_proto( - Box::new(ReadableStream::new_inherited(controller)), + Box::new(ReadableStream::new_inherited()), global, proto, can_gc, @@ -221,21 +222,22 @@ impl ReadableStream { /// Used as part of /// pub(crate) fn set_default_controller(&self, controller: &ReadableStreamDefaultController) { - match self.controller { - ControllerType::Default(ref ctrl) => ctrl.set(Some(controller)), - ControllerType::Byte(_) => { - unreachable!("set_default_controller called in setup of default controller.") - }, - } + *self.controller.borrow_mut() = Some(ControllerType::Default(MutNullableDom::new(Some( + controller, + )))); + } + + /// Used as part of + /// + pub(crate) fn set_byte_controller(&self, controller: &ReadableByteStreamController) { + *self.controller.borrow_mut() = + Some(ControllerType::Byte(MutNullableDom::new(Some(controller)))); } /// Used as part of /// pub(crate) fn assert_no_controller(&self) { - let has_no_controller = match self.controller { - ControllerType::Default(ref ctrl) => ctrl.get().is_none(), - ControllerType::Byte(ref ctrl) => ctrl.get().is_none(), - }; + let has_no_controller = self.controller.borrow().is_none(); assert!(has_no_controller); } @@ -264,12 +266,7 @@ impl ReadableStream { can_gc: CanGc, ) -> Fallible> { assert!(source.is_native()); - let stream = ReadableStream::new_with_proto( - global, - None, - ControllerType::Default(MutNullableDom::new(None)), - can_gc, - ); + let stream = ReadableStream::new_with_proto(global, None, can_gc); let controller = ReadableStreamDefaultController::new( global, source, @@ -283,28 +280,43 @@ impl ReadableStream { /// Call into the release steps of the controller, pub(crate) fn perform_release_steps(&self) -> Fallible<()> { - match &self.controller { - ControllerType::Default(controller) => controller - .get() - .map(|controller_ref| controller_ref.perform_release_steps()) - .unwrap_or_else(|| Err(Error::Type("Stream should have controller.".to_string()))), - ControllerType::Byte(_) => todo!(), + match self.controller.borrow().as_ref() { + Some(ControllerType::Default(ref controller)) => { + let controller = controller + .get() + .ok_or_else(|| Error::Type("Stream should have controller.".to_string()))?; + controller.perform_release_steps() + }, + Some(ControllerType::Byte(ref controller)) => { + let controller = controller + .get() + .ok_or_else(|| Error::Type("Stream should have controller.".to_string()))?; + controller.perform_release_steps() + }, + None => Err(Error::Type("Stream should have controller.".to_string())), } } /// Call into the pull steps of the controller, /// as part of /// - pub(crate) fn perform_pull_steps(&self, read_request: &ReadRequest, can_gc: CanGc) { - match self.controller { - ControllerType::Default(ref controller) => controller + pub(crate) fn perform_pull_steps( + &self, + cx: SafeJSContext, + read_request: &ReadRequest, + can_gc: CanGc, + ) { + match self.controller.borrow().as_ref() { + Some(ControllerType::Default(ref controller)) => controller .get() .expect("Stream should have controller.") .perform_pull_steps(read_request, can_gc), - ControllerType::Byte(_) => { - unreachable!( - "Pulling a chunk from a stream with a byte controller using a default reader" - ) + Some(ControllerType::Byte(ref controller)) => controller + .get() + .expect("Stream should have controller.") + .perform_pull_steps(cx, read_request, can_gc), + None => { + unreachable!("Stream does not have a controller."); }, } } @@ -312,29 +324,31 @@ impl ReadableStream { /// Call into the pull steps of the controller, /// as part of /// - pub(crate) fn perform_pull_into_steps( + pub(crate) fn perform_pull_into( &self, + cx: SafeJSContext, read_into_request: &ReadIntoRequest, view: HeapBufferSource, options: &ReadableStreamBYOBReaderReadOptions, can_gc: CanGc, ) { - match self.controller { - ControllerType::Byte(ref controller) => controller + match self.controller.borrow().as_ref() { + Some(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" - ), + .perform_pull_into(cx, read_into_request, view, options, can_gc), + _ => { + unreachable!( + "Pulling a chunk from a stream with a default controller using a BYOB reader" + ) + }, } } /// pub(crate) fn add_read_request(&self, read_request: &ReadRequest) { - match self.reader { - // Assert: stream.[[reader]] implements ReadableStreamDefaultReader. - ReaderType::Default(ref reader) => { + match self.reader.borrow().as_ref() { + Some(ReaderType::Default(ref reader)) => { let Some(reader) = reader.get() else { panic!("Attempt to add a read request without having first acquired a reader."); }; @@ -345,21 +359,17 @@ impl ReadableStream { // Append readRequest to stream.[[reader]].[[readRequests]]. reader.add_read_request(read_request); }, - ReaderType::BYOB(_) => { + _ => { unreachable!("Adding a read request can only be done on a default reader.") }, } } - #[allow(dead_code)] /// pub(crate) fn add_read_into_request(&self, read_request: &ReadIntoRequest) { - match self.reader { + match self.reader.borrow().as_ref() { // 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) => { + Some(ReaderType::BYOB(ref reader)) => { let Some(reader) = reader.get() else { unreachable!( "Attempt to add a read into request without having first acquired a reader." @@ -372,20 +382,25 @@ impl ReadableStream { // Append readRequest to stream.[[reader]].[[readIntoRequests]]. reader.add_read_into_request(read_request); }, + _ => { + unreachable!("Adding a read into request can only be done on a BYOB reader.") + }, } } /// 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, can_gc: CanGc) { - match self.controller { - ControllerType::Default(ref controller) => controller + match self.controller.borrow().as_ref() { + Some(ControllerType::Default(ref controller)) => controller .get() .expect("Stream should have controller.") .enqueue_native(bytes, can_gc), - _ => unreachable!( - "Enqueueing chunk to a stream from Rust on other than default controller" - ), + _ => { + unreachable!( + "Enqueueing chunk to a stream from Rust on other than default controller" + ); + }, } } @@ -393,22 +408,37 @@ impl ReadableStream { pub(crate) fn error(&self, e: SafeHandleValue, can_gc: CanGc) { // Assert: stream.[[state]] is "readable". assert!(self.is_readable()); + // Set stream.[[state]] to "errored". self.state.set(ReadableStreamState::Errored); + // Set stream.[[storedError]] to e. self.stored_error.set(e.get()); // Let reader be stream.[[reader]]. - match self.reader { - ReaderType::Default(ref reader) => { + + match self.reader.borrow().as_ref() { + Some(ReaderType::Default(ref reader)) => { let Some(reader) = reader.get() else { // If reader is undefined, return. return; }; + + // Perform ! ReadableStreamDefaultReaderErrorReadRequests(reader, e). reader.error(e, can_gc); }, - // Perform ! ReadableStreamBYOBReaderErrorReadIntoRequests(reader, e). - _ => todo!(), + Some(ReaderType::BYOB(ref reader)) => { + let Some(reader) = reader.get() else { + // If reader is undefined, return. + return; + }; + + // Perform ! ReadableStreamBYOBReaderErrorReadIntoRequests(reader, e). + reader.error_read_into_requests(e, can_gc); + }, + None => { + // If reader is undefined, return. + }, } } @@ -429,14 +459,14 @@ impl ReadableStream { /// Call into the controller's `Close` method. /// pub(crate) fn controller_close_native(&self, can_gc: CanGc) { - match self.controller { - ControllerType::Default(ref controller) => { + match self.controller.borrow().as_ref() { + Some(ControllerType::Default(ref controller)) => { let _ = controller .get() .expect("Stream should have controller.") .Close(can_gc); }, - ControllerType::Byte(_) => { + _ => { unreachable!("Native closing is only done on default controllers.") }, } @@ -445,26 +475,28 @@ impl ReadableStream { /// Returns a boolean reflecting whether the stream has all data in memory. /// Useful for native source integration only. pub(crate) fn in_memory(&self) -> bool { - match self.controller { - ControllerType::Default(ref controller) => controller + match self.controller.borrow().as_ref() { + Some(ControllerType::Default(ref controller)) => controller .get() .expect("Stream should have controller.") .in_memory(), - ControllerType::Byte(_) => unreachable!( - "Checking if source is in memory for a stream with a non-default controller" - ), + _ => { + unreachable!( + "Checking if source is in memory for a stream with a non-default controller" + ) + }, } } /// Return bytes for synchronous use, if the stream has all data in memory. /// Useful for native source integration only. pub(crate) fn get_in_memory_bytes(&self) -> Option> { - match self.controller { - ControllerType::Default(ref controller) => controller + match self.controller.borrow().as_ref() { + Some(ControllerType::Default(ref controller)) => controller .get() .expect("Stream should have controller.") .get_in_memory_bytes(), - ControllerType::Byte(_) => { + _ => { unreachable!("Getting in-memory bytes for a stream with a non-default controller") }, } @@ -503,13 +535,26 @@ impl ReadableStream { } pub(crate) fn get_default_controller(&self) -> DomRoot { - match self.controller { - ControllerType::Default(ref controller) => { + match self.controller.borrow().as_ref() { + Some(ControllerType::Default(ref controller)) => { controller.get().expect("Stream should have controller.") }, - ControllerType::Byte(_) => unreachable!( - "Getting default controller for a stream with a non-default controller" - ), + _ => { + unreachable!( + "Getting default controller for a stream with a non-default controller" + ) + }, + } + } + + pub(crate) fn get_default_reader(&self) -> DomRoot { + match self.reader.borrow().as_ref() { + Some(ReaderType::Default(ref reader)) => { + reader.get().expect("Stream should have reader.") + }, + _ => { + unreachable!("Getting default reader for a stream with a non-default reader") + }, } } @@ -519,8 +564,8 @@ impl ReadableStream { /// Native call to /// pub(crate) fn read_a_chunk(&self, can_gc: CanGc) -> Rc { - match self.reader { - ReaderType::Default(ref reader) => { + match self.reader.borrow().as_ref() { + Some(ReaderType::Default(ref reader)) => { let Some(reader) = reader.get() else { unreachable!( "Attempt to read stream chunk without having first acquired a reader." @@ -528,7 +573,7 @@ impl ReadableStream { }; reader.Read(can_gc) }, - ReaderType::BYOB(_) => { + _ => { unreachable!("Native reading of a chunk can only be done with a default reader.") }, } @@ -539,14 +584,18 @@ impl ReadableStream { /// Native call to /// pub(crate) fn stop_reading(&self, can_gc: CanGc) { - match self.reader { - ReaderType::Default(ref reader) => { + let reader_ref = self.reader.borrow(); + + match reader_ref.as_ref() { + Some(ReaderType::Default(ref reader)) => { let Some(reader) = reader.get() else { unreachable!("Attempt to stop reading without having first acquired a reader."); }; + + drop(reader_ref); reader.release(can_gc).expect("Reader release cannot fail."); }, - ReaderType::BYOB(_) => { + _ => { unreachable!("Native stop reading can only be done with a default reader.") }, } @@ -554,9 +603,10 @@ impl ReadableStream { /// pub(crate) fn is_locked(&self) -> bool { - match self.reader { - ReaderType::Default(ref reader) => reader.get().is_some(), - ReaderType::BYOB(ref reader) => reader.get().is_some(), + match self.reader.borrow().as_ref() { + Some(ReaderType::Default(ref reader)) => reader.get().is_some(), + Some(ReaderType::BYOB(ref reader)) => reader.get().is_some(), + None => false, } } @@ -581,39 +631,70 @@ impl ReadableStream { } pub(crate) fn has_default_reader(&self) -> bool { - match self.reader { - ReaderType::Default(ref reader) => reader.get().is_some(), - ReaderType::BYOB(_) => false, + match self.reader.borrow().as_ref() { + Some(ReaderType::Default(ref reader)) => reader.get().is_some(), + _ => false, + } + } + + pub(crate) fn has_byob_reader(&self) -> bool { + match self.reader.borrow().as_ref() { + Some(ReaderType::BYOB(ref reader)) => reader.get().is_some(), + _ => false, } } pub(crate) fn has_byte_controller(&self) -> bool { - matches!(self.controller, ControllerType::Byte(_)) + match self.controller.borrow().as_ref() { + Some(ControllerType::Byte(ref controller)) => controller.get().is_some(), + _ => false, + } } /// pub(crate) fn get_num_read_requests(&self) -> usize { - assert!(self.has_default_reader()); - match self.reader { - ReaderType::Default(ref reader) => { + match self.reader.borrow().as_ref() { + Some(ReaderType::Default(ref reader)) => { let reader = reader .get() - .expect("Stream must have a reader when get num read requests is called into."); + .expect("Stream must have a reader when getting the number of read requests."); reader.get_num_read_requests() }, - ReaderType::BYOB(_) => unreachable!( + _ => unreachable!( "Stream must have a default reader when get num read requests is called into." ), } } + /// + pub(crate) fn get_num_read_into_requests(&self) -> usize { + assert!(self.has_byob_reader()); + + match self.reader.borrow().as_ref() { + Some(ReaderType::BYOB(ref reader)) => { + let Some(reader) = reader.get() else { + unreachable!( + "Stream must have a reader when get num read into requests is called into." + ); + }; + reader.get_num_read_into_requests() + }, + _ => { + unreachable!( + "Stream must have a BYOB reader when get num read into requests is called into." + ); + }, + } + } + /// #[cfg_attr(crown, allow(crown::unrooted_must_root))] pub(crate) fn fulfill_read_request(&self, chunk: SafeHandleValue, done: bool, can_gc: CanGc) { // step 1 - Assert: ! ReadableStreamHasDefaultReader(stream) is true. assert!(self.has_default_reader()); - match self.reader { - ReaderType::Default(ref reader) => { + + match self.reader.borrow().as_ref() { + Some(ReaderType::Default(ref reader)) => { // step 2 - Let reader be stream.[[reader]]. let reader = reader .get() @@ -634,12 +715,59 @@ impl ReadableStream { request.chunk_steps(result, can_gc); } }, - ReaderType::BYOB(_) => unreachable!( - "Stream must have a default reader when fulfill read requests is called into." - ), + _ => { + unreachable!( + "Stream must have a default reader when fulfill read requests is called into." + ); + }, } } + /// + pub(crate) fn fulfill_read_into_request( + &self, + chunk: SafeHandleValue, + done: bool, + can_gc: CanGc, + ) { + // Assert: ! ReadableStreamHasBYOBReader(stream) is true. + assert!(self.has_byob_reader()); + + // Let reader be stream.[[reader]]. + match self.reader.borrow().as_ref() { + Some(ReaderType::BYOB(ref reader)) => { + let Some(reader) = reader.get() else { + unreachable!( + "Stream must have a reader when a read into request is fulfilled." + ); + }; + + // Assert: reader.[[readIntoRequests]] is not empty. + assert!(reader.get_num_read_into_requests() > 0); + + // Let readIntoRequest be reader.[[readIntoRequests]][0]. + // Remove readIntoRequest from reader.[[readIntoRequests]]. + let read_into_request = reader.remove_read_into_request(); + + // If done is true, perform readIntoRequest’s close steps, given chunk. + let result = RootedTraceableBox::new(Heap::default()); + if done { + result.set(*chunk); + read_into_request.close_steps(Some(result), can_gc); + } else { + // Otherwise, perform readIntoRequest’s chunk steps, given chunk. + result.set(*chunk); + read_into_request.chunk_steps(result, can_gc); + } + }, + _ => { + unreachable!( + "Stream must have a BYOB reader when fulfill read into requests is called into." + ); + }, + }; + } + /// pub(crate) fn close(&self, can_gc: CanGc) { // Assert: stream.[[state]] is "readable". @@ -647,8 +775,8 @@ impl ReadableStream { // Set stream.[[state]] to "closed". self.state.set(ReadableStreamState::Closed); // Let reader be stream.[[reader]]. - match self.reader { - ReaderType::Default(ref reader) => { + match self.reader.borrow().as_ref() { + Some(ReaderType::Default(ref reader)) => { let Some(reader) = reader.get() else { // If reader is undefined, return. return; @@ -656,7 +784,17 @@ impl ReadableStream { // step 5 & 6 reader.close(can_gc); }, - ReaderType::BYOB(ref _reader) => {}, + Some(ReaderType::BYOB(ref reader)) => { + let Some(reader) = reader.get() else { + // If reader is undefined, return. + return; + }; + + reader.close(can_gc) + }, + None => { + // If reader is undefined, return. + }, } } @@ -685,24 +823,26 @@ impl ReadableStream { self.close(can_gc); // If reader is not undefined and reader implements ReadableStreamBYOBReader, - match self.reader { - ReaderType::BYOB(ref reader) => { - if let Some(reader) = reader.get() { - // step 6.1, 6.2 & 6.3 of https://streams.spec.whatwg.org/#readable-stream-cancel - reader.close(can_gc); - } - }, - ReaderType::Default(ref _reader) => {}, + if let Some(ReaderType::BYOB(ref reader)) = self.reader.borrow().as_ref() { + if let Some(reader) = reader.get() { + // step 6.1, 6.2 & 6.3 of https://streams.spec.whatwg.org/#readable-stream-cancel + reader.cancel(can_gc); + } } // Let sourceCancelPromise be ! stream.[[controller]].[[CancelSteps]](reason). - let source_cancel_promise = match self.controller { - ControllerType::Default(ref controller) => controller + + let source_cancel_promise = match self.controller.borrow().as_ref() { + Some(ControllerType::Default(ref controller)) => controller .get() .expect("Stream should have controller.") .perform_cancel_steps(reason, can_gc), - ControllerType::Byte(_) => { - todo!() + Some(ControllerType::Byte(ref controller)) => controller + .get() + .expect("Stream should have controller.") + .perform_cancel_steps(reason, can_gc), + None => { + panic!("Stream does not have a controller."); }, }; @@ -733,23 +873,7 @@ impl ReadableStream { #[cfg_attr(crown, allow(crown::unrooted_must_root))] pub(crate) fn set_reader(&self, new_reader: Option) { - match (&self.reader, new_reader) { - (ReaderType::Default(ref reader), Some(ReaderType::Default(new_reader))) => { - reader.set(new_reader.get().as_deref()); - }, - (ReaderType::BYOB(ref reader), Some(ReaderType::BYOB(new_reader))) => { - reader.set(new_reader.get().as_deref()); - }, - (ReaderType::Default(ref reader), None) => { - reader.set(None); - }, - (ReaderType::BYOB(ref reader), None) => { - reader.set(None); - }, - (_, _) => { - unreachable!("Setting a mismatched reader type is not allowed."); - }, - } + *self.reader.borrow_mut() = new_reader; } /// @@ -865,18 +989,68 @@ impl ReadableStream { // Assert: stream implements ReadableStream. // Assert: cloneForBranch2 is a boolean. - match self.controller { - ControllerType::Default(ref _controller) => { + match self.controller.borrow().as_ref() { + Some(ControllerType::Default(_)) => { // Return ? ReadableStreamDefaultTee(stream, cloneForBranch2). self.default_tee(clone_for_branch_2, can_gc) }, - ControllerType::Byte(ref _controller) => { + Some(ControllerType::Byte(_)) => { // If stream.[[controller]] implements ReadableByteStreamController, // return ? ReadableByteStreamTee(stream). - todo!() + Err(Error::Type( + "Teeing is not yet supported for byte streams".to_owned(), + )) + }, + None => { + unreachable!("Stream should have a controller."); }, } } + + /// + pub(crate) fn set_up_byte_controller( + &self, + global: &GlobalScope, + underlying_source_dict: JsUnderlyingSource, + underlying_source_handle: SafeHandleObject, + stream: DomRoot, + strategy_hwm: f64, + can_gc: CanGc, + ) -> Fallible<()> { + // Let pullAlgorithm be an algorithm that returns a promise resolved with undefined. + // Let cancelAlgorithm be an algorithm that returns a promise resolved with undefined. + // If underlyingSourceDict["start"] exists, then set startAlgorithm to an algorithm which returns the result + // of invoking underlyingSourceDict["start"] with argument list « controller » + // and callback this value underlyingSource. + // If underlyingSourceDict["pull"] exists, then set pullAlgorithm to an algorithm which returns the result + // of invoking underlyingSourceDict["pull"] with argument list « controller » + // and callback this value underlyingSource. + // If underlyingSourceDict["cancel"] exists, then set cancelAlgorithm to an algorithm which takes an + // argument reason and returns the result of invoking underlyingSourceDict["cancel"] with argument list + // « reason » and callback this value underlyingSource. + + // Let autoAllocateChunkSize be underlyingSourceDict["autoAllocateChunkSize"], + // if it exists, or undefined otherwise. + // If autoAllocateChunkSize is 0, then throw a TypeError exception. + if let Some(0) = underlying_source_dict.autoAllocateChunkSize { + return Err(Error::Type("autoAllocateChunkSize cannot be 0".to_owned())); + } + + let controller = ReadableByteStreamController::new( + UnderlyingSourceType::Js(underlying_source_dict, Heap::default()), + strategy_hwm, + global, + can_gc, + ); + + // Note: this must be done before `setup`, + // otherwise `thisOb` is null in the start callback. + controller.set_underlying_source_this_object(underlying_source_handle); + + // Perform ? SetUpReadableByteStreamController(stream, controller, startAlgorithm, + // pullAlgorithm, cancelAlgorithm, highWaterMark, autoAllocateChunkSize). + controller.setup(global, stream, can_gc) + } } impl ReadableStreamMethods for ReadableStream { @@ -907,25 +1081,29 @@ impl ReadableStreamMethods for ReadableStream { }; // Perform ! InitializeReadableStream(this). - let stream = if underlying_source_dict.type_.is_some() { - ReadableStream::new_with_proto( - global, - proto, - ControllerType::Byte(MutNullableDom::new(None)), - can_gc, - ) - } else { - ReadableStream::new_with_proto( - global, - proto, - ControllerType::Default(MutNullableDom::new(None)), - can_gc, - ) - }; + let stream = ReadableStream::new_with_proto(global, proto, can_gc); if underlying_source_dict.type_.is_some() { - // TODO: If underlyingSourceDict["type"] is "bytes" - return Err(Error::Type("Bytes streams not implemented".to_string())); + // If strategy["size"] exists, throw a RangeError exception. + if strategy.size.is_some() { + return Err(Error::Range( + "size is not supported for byte streams".to_owned(), + )); + } + + // Let highWaterMark be ? ExtractHighWaterMark(strategy, 0). + let strategy_hwm = extract_high_water_mark(strategy, 0.0)?; + + // Perform ? SetUpReadableByteStreamControllerFromUnderlyingSource(this, + // underlyingSource, underlyingSourceDict, highWaterMark). + stream.set_up_byte_controller( + global, + underlying_source_dict, + underlying_source_obj.handle(), + stream.clone(), + strategy_hwm, + can_gc, + )?; } else { // Let highWaterMark be ? ExtractHighWaterMark(strategy, 1). let high_water_mark = extract_high_water_mark(strategy, 1.0)?; diff --git a/components/script/dom/readablestreambyobreader.rs b/components/script/dom/readablestreambyobreader.rs index 8353ddd531b..30e97b6356d 100644 --- a/components/script/dom/readablestreambyobreader.rs +++ b/components/script/dom/readablestreambyobreader.rs @@ -14,7 +14,7 @@ use js::jsval::{JSVal, UndefinedValue}; use js::rust::{HandleObject as SafeHandleObject, HandleValue as SafeHandleValue}; use js::typedarray::{ArrayBufferView, ArrayBufferViewU8}; -use super::bindings::buffer_source::{BufferSource, HeapBufferSource}; +use super::bindings::buffer_source::HeapBufferSource; use super::bindings::codegen::Bindings::ReadableStreamBYOBReaderBinding::ReadableStreamBYOBReaderReadOptions; use super::bindings::codegen::Bindings::ReadableStreamDefaultReaderBinding::ReadableStreamReadResult; use super::bindings::reflector::reflect_dom_object; @@ -69,7 +69,17 @@ impl ReadIntoRequest { }, can_gc, ), - None => promise.resolve_native(&(), can_gc), + None => { + let result = RootedTraceableBox::new(Heap::default()); + result.set(UndefinedValue()); + promise.resolve_native( + &ReadableStreamReadResult { + done: Some(true), + value: result, + }, + can_gc, + ); + }, }, } } @@ -177,14 +187,20 @@ impl ReadableStreamBYOBReader { } /// - fn error_read_into_requests(&self, rval: SafeHandleValue, can_gc: CanGc) { + pub(crate) fn error_read_into_requests(&self, e: SafeHandleValue, can_gc: CanGc) { + // Reject reader.[[closedPromise]] with e. + self.closed_promise.borrow().reject_native(&e, can_gc); + + // Set reader.[[closedPromise]].[[PromiseIsHandled]] to true. + self.closed_promise.borrow().set_promise_is_handled(); + // Let readRequests be reader.[[readRequests]]. let mut read_into_requests = self.take_read_into_requests(); // Set reader.[[readIntoRequests]] to a new empty list. for request in read_into_requests.drain(0..) { // Perform readIntoRequest’s error steps, given e. - request.error_steps(rval, can_gc); + request.error_steps(e, can_gc); } } @@ -200,7 +216,7 @@ impl ReadableStreamBYOBReader { } /// - pub(crate) fn close(&self, can_gc: CanGc) { + pub(crate) fn cancel(&self, can_gc: CanGc) { // If reader is not undefined and reader implements ReadableStreamBYOBReader, // Let readIntoRequests be reader.[[readIntoRequests]]. let mut read_into_requests = self.take_read_into_requests(); @@ -212,9 +228,15 @@ impl ReadableStreamBYOBReader { } } + pub(crate) fn close(&self, can_gc: CanGc) { + // Resolve reader.[[closedPromise]] with undefined. + self.closed_promise.borrow().resolve_native(&(), can_gc); + } + /// pub(crate) fn read( &self, + cx: SafeJSContext, view: HeapBufferSource, options: &ReadableStreamBYOBReaderReadOptions, read_into_request: &ReadIntoRequest, @@ -234,13 +256,25 @@ impl ReadableStreamBYOBReader { 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(), can_gc); } else { // Otherwise, // perform ! ReadableByteStreamControllerPullInto(stream.[[controller]], view, min, readIntoRequest). - stream.perform_pull_into_steps(read_into_request, view, options, can_gc); + stream.perform_pull_into(cx, read_into_request, view, options, can_gc); } } + + pub(crate) fn get_num_read_into_requests(&self) -> usize { + self.read_into_requests.borrow().len() + } + + pub(crate) fn remove_read_into_request(&self) -> ReadIntoRequest { + self.read_into_requests + .borrow_mut() + .pop_front() + .expect("read into requests is empty") + } } impl ReadableStreamBYOBReaderMethods for ReadableStreamBYOBReader { @@ -260,16 +294,13 @@ impl ReadableStreamBYOBReaderMethods for ReadableStreamBYO } /// - #[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 view = HeapBufferSource::::from_view(view); // Let promise be a new promise. let promise = Promise::new(&self.global(), can_gc); @@ -306,9 +337,9 @@ impl ReadableStreamBYOBReaderMethods for ReadableStreamBYO // 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) { + if options.min > (view.get_typed_array_length() as u64) { promise.reject_error( - Error::Type("min is greater than array length".to_owned()), + Error::Range("min is greater than array length".to_owned()), can_gc, ); return promise; @@ -318,7 +349,7 @@ impl ReadableStreamBYOBReaderMethods for ReadableStreamBYO // 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()), + Error::Range("min is greater than byte length".to_owned()), can_gc, ); return promise; @@ -347,7 +378,7 @@ impl ReadableStreamBYOBReaderMethods for ReadableStreamBYO let read_into_request = ReadIntoRequest::Read(promise.clone()); // Perform ! ReadableStreamBYOBReaderRead(this, view, options["min"], readIntoRequest). - self.read(view, options, &read_into_request, can_gc); + self.read(cx, view, options, &read_into_request, can_gc); // Return promise. promise @@ -371,7 +402,7 @@ impl ReadableStreamBYOBReaderMethods for ReadableStreamBYO /// fn Cancel(&self, _cx: SafeJSContext, reason: SafeHandleValue, can_gc: CanGc) -> Rc { - self.cancel(&self.global(), reason, can_gc) + self.generic_cancel(&self.global(), reason, can_gc) } } diff --git a/components/script/dom/readablestreambyobrequest.rs b/components/script/dom/readablestreambyobrequest.rs index 973dede62d9..286de7e892e 100644 --- a/components/script/dom/readablestreambyobrequest.rs +++ b/components/script/dom/readablestreambyobrequest.rs @@ -4,17 +4,19 @@ use dom_struct::dom_struct; use js::gc::CustomAutoRooterGuard; -use js::jsapi::Heap; use js::typedarray::{ArrayBufferView, ArrayBufferViewU8}; -use super::bindings::buffer_source::{BufferSource, HeapBufferSource}; +use super::bindings::buffer_source::HeapBufferSource; +use super::bindings::cell::DomRefCell; +use super::bindings::reflector::reflect_dom_object; +use super::bindings::root::DomRoot; use crate::dom::bindings::codegen::Bindings::ReadableStreamBYOBRequestBinding::ReadableStreamBYOBRequestMethods; use crate::dom::bindings::import::module::{Error, Fallible}; use crate::dom::bindings::reflector::Reflector; use crate::dom::bindings::root::MutNullableDom; use crate::dom::readablebytestreamcontroller::ReadableByteStreamController; use crate::dom::types::GlobalScope; -use crate::script_runtime::JSContext as SafeJSContext; +use crate::script_runtime::{CanGc, JSContext as SafeJSContext}; /// #[dom_struct] @@ -22,18 +24,47 @@ pub(crate) struct ReadableStreamBYOBRequest { reflector_: Reflector, controller: MutNullableDom, #[ignore_malloc_size_of = "mozjs"] - view: HeapBufferSource, + view: DomRefCell>, +} + +impl ReadableStreamBYOBRequest { + fn new_inherited() -> ReadableStreamBYOBRequest { + ReadableStreamBYOBRequest { + reflector_: Reflector::new(), + controller: MutNullableDom::new(None), + view: DomRefCell::new(HeapBufferSource::::default()), + } + } + + pub(crate) fn new(global: &GlobalScope, can_gc: CanGc) -> DomRoot { + reflect_dom_object(Box::new(Self::new_inherited()), global, can_gc) + } + + pub(crate) fn set_controller(&self, controller: Option<&ReadableByteStreamController>) { + self.controller.set(controller); + } + + pub(crate) fn set_view(&self, view: Option>) { + match view { + Some(view) => { + *self.view.borrow_mut() = view; + }, + None => { + *self.view.borrow_mut() = HeapBufferSource::::default(); + }, + } + } } impl ReadableStreamBYOBRequestMethods for ReadableStreamBYOBRequest { /// fn GetView(&self, _cx: SafeJSContext) -> Option { // Return this.[[view]]. - self.view.buffer_to_option() + self.view.borrow().typed_array_to_option() } /// - fn Respond(&self, bytes_written: u64) -> Fallible<()> { + fn Respond(&self, bytes_written: u64, can_gc: CanGc) -> Fallible<()> { let cx = GlobalScope::get_cx(); // If this.[[controller]] is undefined, throw a TypeError exception. @@ -43,27 +74,32 @@ impl ReadableStreamBYOBRequestMethods for ReadableStreamBY return Err(Error::Type("controller is undefined".to_owned())); }; - // If ! IsDetachedBuffer(this.[[view]].[[ArrayBuffer]]) is true, throw a TypeError exception. - if self.view.is_detached_buffer(cx) { - return Err(Error::Type("buffer is detached".to_owned())); + { + let view = self.view.borrow(); + // If ! IsDetachedBuffer(this.[[view]].[[ArrayBuffer]]) is true, throw a TypeError exception. + if view.get_array_buffer_view_buffer(cx).is_detached_buffer(cx) { + return Err(Error::Type("buffer is detached".to_owned())); + } + + // Assert: this.[[view]].[[ByteLength]] > 0. + assert!(view.byte_length() > 0); + + // Assert: this.[[view]].[[ViewedArrayBuffer]].[[ByteLength]] > 0. + assert!(view.viewed_buffer_array_byte_length(cx) > 0); } - // Assert: this.[[view]].[[ByteLength]] > 0. - assert!(self.view.byte_length() > 0); - - // Assert: this.[[view]].[[ViewedArrayBuffer]].[[ByteLength]] > 0. - assert!(self.view.viewed_buffer_array_byte_length(cx) > 0); - // Perform ? ReadableByteStreamControllerRespond(this.[[controller]], bytesWritten). - controller.respond(bytes_written) + controller.respond(cx, bytes_written, can_gc) } /// - #[allow(unsafe_code)] - fn RespondWithNewView(&self, view: CustomAutoRooterGuard) -> Fallible<()> { - let view = HeapBufferSource::::new(BufferSource::ArrayBufferView( - Heap::boxed(unsafe { *view.underlying_object() }), - )); + fn RespondWithNewView( + &self, + view: CustomAutoRooterGuard, + can_gc: CanGc, + ) -> Fallible<()> { + let cx = GlobalScope::get_cx(); + let view = HeapBufferSource::::from_view(view); // If this.[[controller]] is undefined, throw a TypeError exception. let controller = if let Some(controller) = self.controller.get() { @@ -73,11 +109,11 @@ impl ReadableStreamBYOBRequestMethods for ReadableStreamBY }; // If ! IsDetachedBuffer(view.[[ViewedArrayBuffer]]) is true, throw a TypeError exception. - if self.view.is_detached_buffer(GlobalScope::get_cx()) { + if view.is_detached_buffer(cx) { return Err(Error::Type("buffer is detached".to_owned())); } // Return ? ReadableByteStreamControllerRespondWithNewView(this.[[controller]], view). - controller.respond_with_new_view(view) + controller.respond_with_new_view(cx, view, can_gc) } } diff --git a/components/script/dom/readablestreamdefaultcontroller.rs b/components/script/dom/readablestreamdefaultcontroller.rs index b564aa1f171..4eeca3c9126 100644 --- a/components/script/dom/readablestreamdefaultcontroller.rs +++ b/components/script/dom/readablestreamdefaultcontroller.rs @@ -32,7 +32,7 @@ use crate::dom::readablestreamdefaultreader::ReadRequest; use crate::dom::underlyingsourcecontainer::{UnderlyingSourceContainer, UnderlyingSourceType}; use crate::js::conversions::ToJSValConvertible; use crate::realms::{InRealm, enter_realm}; -use crate::script_runtime::{CanGc, JSContext, JSContext as SafeJSContext}; +use crate::script_runtime::{CanGc, JSContext as SafeJSContext}; /// The fulfillment handler for /// @@ -45,7 +45,7 @@ struct PullAlgorithmFulfillmentHandler { impl Callback for PullAlgorithmFulfillmentHandler { /// Continuation of /// Upon fulfillment of pullPromise - fn callback(&self, _cx: JSContext, _v: HandleValue, _realm: InRealm, can_gc: CanGc) { + fn callback(&self, _cx: SafeJSContext, _v: HandleValue, _realm: InRealm, can_gc: CanGc) { // Set controller.[[pulling]] to false. self.controller.pulling.set(false); @@ -71,7 +71,7 @@ struct PullAlgorithmRejectionHandler { impl Callback for PullAlgorithmRejectionHandler { /// Continuation of /// Upon rejection of pullPromise with reason e. - fn callback(&self, _cx: JSContext, v: HandleValue, _realm: InRealm, can_gc: CanGc) { + fn callback(&self, _cx: SafeJSContext, v: HandleValue, _realm: InRealm, can_gc: CanGc) { // Perform ! ReadableStreamDefaultControllerError(controller, e). self.controller.error(v, can_gc); } @@ -88,7 +88,7 @@ struct StartAlgorithmFulfillmentHandler { impl Callback for StartAlgorithmFulfillmentHandler { /// Continuation of /// Upon fulfillment of startPromise, - fn callback(&self, _cx: JSContext, _v: HandleValue, _realm: InRealm, can_gc: CanGc) { + fn callback(&self, _cx: SafeJSContext, _v: HandleValue, _realm: InRealm, can_gc: CanGc) { // Set controller.[[started]] to true. self.controller.started.set(true); @@ -108,7 +108,7 @@ struct StartAlgorithmRejectionHandler { impl Callback for StartAlgorithmRejectionHandler { /// Continuation of /// Upon rejection of startPromise with reason r, - fn callback(&self, _cx: JSContext, v: HandleValue, _realm: InRealm, can_gc: CanGc) { + fn callback(&self, _cx: SafeJSContext, v: HandleValue, _realm: InRealm, can_gc: CanGc) { // Perform ! ReadableStreamDefaultControllerError(controller, r). self.controller.error(v, can_gc); } @@ -499,6 +499,8 @@ impl ReadableStreamDefaultController { /// fn call_pull_if_needed(&self, can_gc: CanGc) { + // Let shouldPull be ! ReadableStreamDefaultControllerShouldCallPull(controller). + // If shouldPull is false, return. if !self.should_call_pull() { return; } diff --git a/components/script/dom/readablestreamdefaultreader.rs b/components/script/dom/readablestreamdefaultreader.rs index 7e374a1f11a..967cdcbf9b4 100644 --- a/components/script/dom/readablestreamdefaultreader.rs +++ b/components/script/dom/readablestreamdefaultreader.rs @@ -14,6 +14,7 @@ use js::rust::{HandleObject as SafeHandleObject, HandleValue as SafeHandleValue} use super::bindings::reflector::reflect_dom_object; use super::bindings::root::MutNullableDom; +use super::readablebytestreamcontroller::ReadableByteStreamController; use super::types::ReadableStreamDefaultController; use crate::dom::bindings::cell::DomRefCell; use crate::dom::bindings::codegen::Bindings::ReadableStreamDefaultReaderBinding::{ @@ -410,7 +411,7 @@ impl ReadableStreamDefaultReader { } /// - pub(crate) fn read(&self, read_request: &ReadRequest, can_gc: CanGc) { + pub(crate) fn read(&self, cx: SafeJSContext, read_request: &ReadRequest, can_gc: CanGc) { // Let stream be reader.[[stream]]. // Assert: stream is not undefined. @@ -435,7 +436,7 @@ impl ReadableStreamDefaultReader { // Assert: stream.[[state]] is "readable". assert!(stream.is_readable()); // Perform ! stream.[[controller]].[[PullSteps]](readRequest). - stream.perform_pull_steps(read_request, can_gc); + stream.perform_pull_steps(cx, read_request, can_gc); } } @@ -505,6 +506,30 @@ impl ReadableStreamDefaultReader { can_gc, ); } + + /// step 3 of + pub(crate) fn process_read_requests( + &self, + cx: SafeJSContext, + controller: DomRoot, + can_gc: CanGc, + ) -> Fallible<()> { + // While reader.[[readRequests]] is not empty, + while !self.read_requests.borrow().is_empty() { + // If controller.[[queueTotalSize]] is 0, return. + if controller.get_queue_total_size() == 0.0 { + return Ok(()); + } + + // Let readRequest be reader.[[readRequests]][0]. + // Remove entry from controller.[[queue]]. + let read_request = self.remove_read_request(); + + // Perform ! ReadableByteStreamControllerFillReadRequestFromQueue(controller, readRequest). + controller.fill_read_request_from_queue(cx, &read_request, can_gc)?; + } + Ok(()) + } } impl ReadableStreamDefaultReaderMethods for ReadableStreamDefaultReader { @@ -525,9 +550,9 @@ impl ReadableStreamDefaultReaderMethods for ReadableStream /// fn Read(&self, can_gc: CanGc) -> Rc { + let cx = GlobalScope::get_cx(); // If this.[[stream]] is undefined, return a promise rejected with a TypeError exception. if self.stream.get().is_none() { - let cx = GlobalScope::get_cx(); rooted!(in(*cx) let mut error = UndefinedValue()); Error::Type("stream is undefined".to_owned()).to_jsval( cx, @@ -555,7 +580,7 @@ impl ReadableStreamDefaultReaderMethods for ReadableStream let read_request = ReadRequest::Read(promise.clone()); // Perform ! ReadableStreamDefaultReaderRead(this, readRequest). - self.read(&read_request, can_gc); + self.read(cx, &read_request, can_gc); // Return promise. promise @@ -579,7 +604,7 @@ impl ReadableStreamDefaultReaderMethods for ReadableStream /// fn Cancel(&self, _cx: SafeJSContext, reason: SafeHandleValue, can_gc: CanGc) -> Rc { - self.cancel(&self.global(), reason, can_gc) + self.generic_cancel(&self.global(), reason, can_gc) } } diff --git a/components/script/dom/readablestreamgenericreader.rs b/components/script/dom/readablestreamgenericreader.rs index 60609e4d9c0..ccd7eca830e 100644 --- a/components/script/dom/readablestreamgenericreader.rs +++ b/components/script/dom/readablestreamgenericreader.rs @@ -62,7 +62,7 @@ pub(crate) trait ReadableStreamGenericReader { } /// - fn generic_cancel(&self, reason: SafeHandleValue, can_gc: CanGc) -> Rc { + fn reader_generic_cancel(&self, reason: SafeHandleValue, can_gc: CanGc) -> Rc { // Let stream be reader.[[stream]]. let stream = self.get_stream(); @@ -84,7 +84,11 @@ pub(crate) trait ReadableStreamGenericReader { if let Some(stream) = self.get_stream() { // Assert: stream.[[reader]] is reader. - assert!(stream.has_default_reader()); + if self.as_default_reader().is_some() { + assert!(stream.has_default_reader()); + } else { + assert!(stream.has_byob_reader()); + } if stream.is_readable() { // If stream.[[state]] is "readable", reject reader.[[closedPromise]] with a TypeError exception. @@ -129,7 +133,12 @@ pub(crate) trait ReadableStreamGenericReader { } // - fn cancel(&self, global: &GlobalScope, reason: SafeHandleValue, can_gc: CanGc) -> Rc { + fn generic_cancel( + &self, + global: &GlobalScope, + reason: SafeHandleValue, + can_gc: CanGc, + ) -> Rc { if self.get_stream().is_none() { // If this.[[stream]] is undefined, // return a promise rejected with a TypeError exception. @@ -138,7 +147,7 @@ pub(crate) trait ReadableStreamGenericReader { promise } else { // Return ! ReadableStreamReaderGenericCancel(this, reason). - self.generic_cancel(reason, can_gc) + self.reader_generic_cancel(reason, can_gc) } } diff --git a/components/script/dom/underlyingsourcecontainer.rs b/components/script/dom/underlyingsourcecontainer.rs index 949510856ee..173cbf922b1 100644 --- a/components/script/dom/underlyingsourcecontainer.rs +++ b/components/script/dom/underlyingsourcecontainer.rs @@ -221,6 +221,14 @@ impl UnderlyingSourceContainer { } } + /// + pub(crate) fn auto_allocate_chunk_size(&self) -> Option { + match &self.underlying_source_type { + UnderlyingSourceType::Js(source, _) => source.autoAllocateChunkSize, + _ => None, + } + } + /// Does the source have all data in memory? pub(crate) fn in_memory(&self) -> bool { self.underlying_source_type.in_memory() diff --git a/components/script/dom/webxr/xrray.rs b/components/script/dom/webxr/xrray.rs index 88690642930..e09df42e96d 100644 --- a/components/script/dom/webxr/xrray.rs +++ b/components/script/dom/webxr/xrray.rs @@ -168,7 +168,7 @@ impl XRRayMethods for XRRay { } self.matrix - .get_buffer() + .get_typed_array() .expect("Failed to get matrix from XRRay.") } } diff --git a/components/script/dom/webxr/xrrigidtransform.rs b/components/script/dom/webxr/xrrigidtransform.rs index 86459bc98d3..ef08088876f 100644 --- a/components/script/dom/webxr/xrrigidtransform.rs +++ b/components/script/dom/webxr/xrrigidtransform.rs @@ -175,7 +175,7 @@ impl XRRigidTransformMethods for XRRigidTransform { } self.matrix - .get_buffer() + .get_typed_array() .expect("Failed to get transform's internal matrix.") } } diff --git a/components/script/dom/webxr/xrview.rs b/components/script/dom/webxr/xrview.rs index 820fa48bca9..0c76a6bd892 100644 --- a/components/script/dom/webxr/xrview.rs +++ b/components/script/dom/webxr/xrview.rs @@ -105,7 +105,7 @@ impl XRViewMethods for XRView { .expect("Failed to set projection matrix.") } self.proj - .get_buffer() + .get_typed_array() .expect("Failed to get projection matrix.") } diff --git a/components/script/dom/xmlhttprequest.rs b/components/script/dom/xmlhttprequest.rs index 3fe0db9cde4..94e966fd12a 100644 --- a/components/script/dom/xmlhttprequest.rs +++ b/components/script/dom/xmlhttprequest.rs @@ -1345,7 +1345,7 @@ impl XMLHttpRequest { } // Return the correct ArrayBuffer - self.response_arraybuffer.get_buffer().ok() + self.response_arraybuffer.get_typed_array().ok() } /// diff --git a/components/script_bindings/codegen/Bindings.conf b/components/script_bindings/codegen/Bindings.conf index 81cccd9769a..d52a3ad42f3 100644 --- a/components/script_bindings/codegen/Bindings.conf +++ b/components/script_bindings/codegen/Bindings.conf @@ -638,6 +638,14 @@ DOMInterfaces = { "canGc": ["Close", "Enqueue", "Error"] }, +"ReadableByteStreamController": { + "canGc": ["GetByobRequest", "Enqueue", "Close", "Error"] +}, + +"ReadableStreamBYOBRequest": { + "canGc": ["Respond", "RespondWithNewView"] +}, + "ReadableStreamBYOBReader": { "canGc": ["Cancel", "Read", "ReleaseLock"] }, diff --git a/tests/wpt/include.ini b/tests/wpt/include.ini index 49936bdd9f3..1e03287256f 100644 --- a/tests/wpt/include.ini +++ b/tests/wpt/include.ini @@ -243,6 +243,8 @@ skip: true skip: true [readable-streams] skip: false + [readable-byte-streams] + skip: false [writable-streams] skip: false [subresource-integrity] diff --git a/tests/wpt/meta/streams/readable-byte-streams/bad-buffers-and-views.any.js.ini b/tests/wpt/meta/streams/readable-byte-streams/bad-buffers-and-views.any.js.ini new file mode 100644 index 00000000000..24ff7a77922 --- /dev/null +++ b/tests/wpt/meta/streams/readable-byte-streams/bad-buffers-and-views.any.js.ini @@ -0,0 +1,23 @@ +[bad-buffers-and-views.any.serviceworker.html] + expected: ERROR + +[bad-buffers-and-views.any.shadowrealm-in-window.html] + expected: ERROR + +[bad-buffers-and-views.any.shadowrealm-in-dedicatedworker.html] + expected: ERROR + +[bad-buffers-and-views.any.shadowrealm-in-shadowrealm.html] + expected: ERROR + +[bad-buffers-and-views.https.any.shadowrealm-in-audioworklet.html] + expected: ERROR + +[bad-buffers-and-views.https.any.shadowrealm-in-serviceworker.html] + expected: ERROR + +[bad-buffers-and-views.any.shadowrealm-in-sharedworker.html] + expected: ERROR + +[bad-buffers-and-views.any.sharedworker.html] + expected: ERROR diff --git a/tests/wpt/meta/streams/readable-byte-streams/construct-byob-request.any.js.ini b/tests/wpt/meta/streams/readable-byte-streams/construct-byob-request.any.js.ini new file mode 100644 index 00000000000..890d9e62a9e --- /dev/null +++ b/tests/wpt/meta/streams/readable-byte-streams/construct-byob-request.any.js.ini @@ -0,0 +1,23 @@ +[construct-byob-request.https.any.shadowrealm-in-serviceworker.html] + expected: ERROR + +[construct-byob-request.https.any.shadowrealm-in-audioworklet.html] + expected: ERROR + +[construct-byob-request.any.shadowrealm-in-sharedworker.html] + expected: ERROR + +[construct-byob-request.any.shadowrealm-in-window.html] + expected: ERROR + +[construct-byob-request.any.shadowrealm-in-dedicatedworker.html] + expected: ERROR + +[construct-byob-request.any.shadowrealm-in-shadowrealm.html] + expected: ERROR + +[construct-byob-request.any.serviceworker.html] + expected: ERROR + +[construct-byob-request.any.sharedworker.html] + expected: ERROR diff --git a/tests/wpt/meta/streams/readable-byte-streams/enqueue-with-detached-buffer.any.js.ini b/tests/wpt/meta/streams/readable-byte-streams/enqueue-with-detached-buffer.any.js.ini new file mode 100644 index 00000000000..2329146b322 --- /dev/null +++ b/tests/wpt/meta/streams/readable-byte-streams/enqueue-with-detached-buffer.any.js.ini @@ -0,0 +1,23 @@ +[enqueue-with-detached-buffer.any.shadowrealm-in-shadowrealm.html] + expected: ERROR + +[enqueue-with-detached-buffer.https.any.shadowrealm-in-audioworklet.html] + expected: ERROR + +[enqueue-with-detached-buffer.any.sharedworker.html] + expected: ERROR + +[enqueue-with-detached-buffer.any.shadowrealm-in-window.html] + expected: ERROR + +[enqueue-with-detached-buffer.any.shadowrealm-in-sharedworker.html] + expected: ERROR + +[enqueue-with-detached-buffer.any.serviceworker.html] + expected: ERROR + +[enqueue-with-detached-buffer.https.any.shadowrealm-in-serviceworker.html] + expected: ERROR + +[enqueue-with-detached-buffer.any.shadowrealm-in-dedicatedworker.html] + expected: ERROR diff --git a/tests/wpt/meta/streams/readable-byte-streams/general.any.js.ini b/tests/wpt/meta/streams/readable-byte-streams/general.any.js.ini new file mode 100644 index 00000000000..e1c1474d498 --- /dev/null +++ b/tests/wpt/meta/streams/readable-byte-streams/general.any.js.ini @@ -0,0 +1,23 @@ +[general.any.shadowrealm-in-shadowrealm.html] + expected: ERROR + +[general.any.shadowrealm-in-sharedworker.html] + expected: ERROR + +[general.https.any.shadowrealm-in-serviceworker.html] + expected: ERROR + +[general.any.sharedworker.html] + expected: ERROR + +[general.any.shadowrealm-in-dedicatedworker.html] + expected: ERROR + +[general.https.any.shadowrealm-in-audioworklet.html] + expected: ERROR + +[general.any.serviceworker.html] + expected: ERROR + +[general.any.shadowrealm-in-window.html] + expected: ERROR diff --git a/tests/wpt/meta/streams/readable-byte-streams/non-transferable-buffers.any.js.ini b/tests/wpt/meta/streams/readable-byte-streams/non-transferable-buffers.any.js.ini new file mode 100644 index 00000000000..fad37ecaa65 --- /dev/null +++ b/tests/wpt/meta/streams/readable-byte-streams/non-transferable-buffers.any.js.ini @@ -0,0 +1,23 @@ +[non-transferable-buffers.any.shadowrealm-in-shadowrealm.html] + expected: ERROR + +[non-transferable-buffers.https.any.shadowrealm-in-audioworklet.html] + expected: ERROR + +[non-transferable-buffers.any.shadowrealm-in-dedicatedworker.html] + expected: ERROR + +[non-transferable-buffers.any.serviceworker.html] + expected: ERROR + +[non-transferable-buffers.https.any.shadowrealm-in-serviceworker.html] + expected: ERROR + +[non-transferable-buffers.any.shadowrealm-in-sharedworker.html] + expected: ERROR + +[non-transferable-buffers.any.sharedworker.html] + expected: ERROR + +[non-transferable-buffers.any.shadowrealm-in-window.html] + expected: ERROR diff --git a/tests/wpt/meta/streams/readable-byte-streams/patched-global.any.js.ini b/tests/wpt/meta/streams/readable-byte-streams/patched-global.any.js.ini new file mode 100644 index 00000000000..d697aa47d2f --- /dev/null +++ b/tests/wpt/meta/streams/readable-byte-streams/patched-global.any.js.ini @@ -0,0 +1,23 @@ +[patched-global.any.shadowrealm-in-sharedworker.html] + expected: ERROR + +[patched-global.any.shadowrealm-in-dedicatedworker.html] + expected: ERROR + +[patched-global.any.sharedworker.html] + expected: ERROR + +[patched-global.any.shadowrealm-in-shadowrealm.html] + expected: ERROR + +[patched-global.any.shadowrealm-in-window.html] + expected: ERROR + +[patched-global.https.any.shadowrealm-in-serviceworker.html] + expected: ERROR + +[patched-global.any.serviceworker.html] + expected: ERROR + +[patched-global.https.any.shadowrealm-in-audioworklet.html] + expected: ERROR diff --git a/tests/wpt/meta/streams/readable-byte-streams/read-min.any.js.ini b/tests/wpt/meta/streams/readable-byte-streams/read-min.any.js.ini new file mode 100644 index 00000000000..82bfaccea71 --- /dev/null +++ b/tests/wpt/meta/streams/readable-byte-streams/read-min.any.js.ini @@ -0,0 +1,31 @@ +[read-min.https.any.shadowrealm-in-audioworklet.html] + expected: ERROR + +[read-min.any.sharedworker.html] + expected: ERROR + +[read-min.any.shadowrealm-in-shadowrealm.html] + expected: ERROR + +[read-min.any.shadowrealm-in-dedicatedworker.html] + expected: ERROR + +[read-min.any.serviceworker.html] + expected: ERROR + +[read-min.any.shadowrealm-in-window.html] + expected: ERROR + +[read-min.https.any.shadowrealm-in-serviceworker.html] + expected: ERROR + +[read-min.any.shadowrealm-in-sharedworker.html] + expected: ERROR + +[read-min.any.html] + [ReadableStream with byte source: tee() with read({ min }) from branch1 and read() from branch2] + expected: FAIL + +[read-min.any.worker.html] + [ReadableStream with byte source: tee() with read({ min }) from branch1 and read() from branch2] + expected: FAIL diff --git a/tests/wpt/meta/streams/readable-byte-streams/respond-after-enqueue.any.js.ini b/tests/wpt/meta/streams/readable-byte-streams/respond-after-enqueue.any.js.ini new file mode 100644 index 00000000000..cc15713cbc6 --- /dev/null +++ b/tests/wpt/meta/streams/readable-byte-streams/respond-after-enqueue.any.js.ini @@ -0,0 +1,23 @@ +[respond-after-enqueue.https.any.shadowrealm-in-audioworklet.html] + expected: ERROR + +[respond-after-enqueue.any.shadowrealm-in-shadowrealm.html] + expected: ERROR + +[respond-after-enqueue.any.shadowrealm-in-sharedworker.html] + expected: ERROR + +[respond-after-enqueue.any.serviceworker.html] + expected: ERROR + +[respond-after-enqueue.any.sharedworker.html] + expected: ERROR + +[respond-after-enqueue.any.shadowrealm-in-dedicatedworker.html] + expected: ERROR + +[respond-after-enqueue.any.shadowrealm-in-window.html] + expected: ERROR + +[respond-after-enqueue.https.any.shadowrealm-in-serviceworker.html] + expected: ERROR diff --git a/tests/wpt/meta/streams/readable-byte-streams/tee.any.js.ini b/tests/wpt/meta/streams/readable-byte-streams/tee.any.js.ini new file mode 100644 index 00000000000..ac2c7274556 --- /dev/null +++ b/tests/wpt/meta/streams/readable-byte-streams/tee.any.js.ini @@ -0,0 +1,255 @@ +[tee.any.shadowrealm-in-sharedworker.html] + expected: ERROR + +[tee.any.shadowrealm-in-window.html] + expected: ERROR + +[tee.any.worker.html] + [ReadableStream teeing with byte source: rs.tee() returns an array of two ReadableStreams] + expected: FAIL + + [ReadableStream teeing with byte source: should be able to read one branch to the end without affecting the other] + expected: FAIL + + [ReadableStream teeing with byte source: chunks should be cloned for each branch] + expected: FAIL + + [ReadableStream teeing with byte source: chunks for BYOB requests from branch 1 should be cloned to branch 2] + expected: FAIL + + [ReadableStream teeing with byte source: errors in the source should propagate to both branches] + expected: FAIL + + [ReadableStream teeing with byte source: canceling branch1 should not impact branch2] + expected: FAIL + + [ReadableStream teeing with byte source: canceling branch2 should not impact branch1] + expected: FAIL + + [ReadableStream teeing with byte source: canceling both branches should aggregate the cancel reasons into an array] + expected: FAIL + + [ReadableStream teeing with byte source: canceling both branches in reverse order should aggregate the cancel reasons into an array] + expected: FAIL + + [ReadableStream teeing with byte source: failing to cancel the original stream should cause cancel() to reject on branches] + expected: FAIL + + [ReadableStream teeing with byte source: erroring a teed stream should properly handle canceled branches] + expected: FAIL + + [ReadableStream teeing with byte source: closing the original should close the branches] + expected: FAIL + + [ReadableStream teeing with byte source: erroring the original should immediately error the branches] + expected: FAIL + + [ReadableStream teeing with byte source: erroring the original should error pending reads from default reader] + expected: FAIL + + [ReadableStream teeing with byte source: erroring the original should error pending reads from BYOB reader] + expected: FAIL + + [ReadableStream teeing with byte source: canceling branch1 should finish when branch2 reads until end of stream] + expected: FAIL + + [ReadableStream teeing with byte source: canceling branch1 should finish when original stream errors] + expected: FAIL + + [ReadableStream teeing with byte source: should not pull any chunks if no branches are reading] + expected: FAIL + + [ReadableStream teeing with byte source: should only pull enough to fill the emptiest queue] + expected: FAIL + + [ReadableStream teeing with byte source: should not pull when original is already errored] + expected: FAIL + + [ReadableStream teeing with byte source: stops pulling when original stream errors while branch 1 is reading] + expected: FAIL + + [ReadableStream teeing with byte source: stops pulling when original stream errors while branch 2 is reading] + expected: FAIL + + [ReadableStream teeing with byte source: stops pulling when original stream errors while both branches are reading] + expected: FAIL + + [ReadableStream teeing with byte source: canceling both branches in sequence with delay] + expected: FAIL + + [ReadableStream teeing with byte source: failing to cancel when canceling both branches in sequence with delay] + expected: FAIL + + [ReadableStream teeing with byte source: read from branch1 and branch2, cancel branch1, cancel branch2] + expected: FAIL + + [ReadableStream teeing with byte source: read from branch1 and branch2, cancel branch2, cancel branch1] + expected: FAIL + + [ReadableStream teeing with byte source: read from branch1 and branch2, cancel branch2, enqueue to branch1] + expected: FAIL + + [ReadableStream teeing with byte source: read from branch1 and branch2, cancel branch1, respond to branch2] + expected: FAIL + + [ReadableStream teeing with byte source: pull with BYOB reader, then pull with default reader] + expected: FAIL + + [ReadableStream teeing with byte source: pull with default reader, then pull with BYOB reader] + expected: FAIL + + [ReadableStream teeing with byte source: read from branch2, then read from branch1] + expected: FAIL + + [ReadableStream teeing with byte source: read from branch1 with default reader, then close while branch2 has pending BYOB read] + expected: FAIL + + [ReadableStream teeing with byte source: read from branch2 with default reader, then close while branch1 has pending BYOB read] + expected: FAIL + + [ReadableStream teeing with byte source: close when both branches have pending BYOB reads] + expected: FAIL + + [ReadableStream teeing with byte source: enqueue() and close() while both branches are pulling] + expected: FAIL + + [ReadableStream teeing with byte source: respond() and close() while both branches are pulling] + expected: FAIL + + [ReadableStream teeing with byte source: reading an array with a byte offset should clone correctly] + expected: FAIL + + +[tee.any.sharedworker.html] + expected: ERROR + +[tee.any.shadowrealm-in-dedicatedworker.html] + expected: ERROR + +[tee.any.html] + [ReadableStream teeing with byte source: rs.tee() returns an array of two ReadableStreams] + expected: FAIL + + [ReadableStream teeing with byte source: should be able to read one branch to the end without affecting the other] + expected: FAIL + + [ReadableStream teeing with byte source: chunks should be cloned for each branch] + expected: FAIL + + [ReadableStream teeing with byte source: chunks for BYOB requests from branch 1 should be cloned to branch 2] + expected: FAIL + + [ReadableStream teeing with byte source: errors in the source should propagate to both branches] + expected: FAIL + + [ReadableStream teeing with byte source: canceling branch1 should not impact branch2] + expected: FAIL + + [ReadableStream teeing with byte source: canceling branch2 should not impact branch1] + expected: FAIL + + [ReadableStream teeing with byte source: canceling both branches should aggregate the cancel reasons into an array] + expected: FAIL + + [ReadableStream teeing with byte source: canceling both branches in reverse order should aggregate the cancel reasons into an array] + expected: FAIL + + [ReadableStream teeing with byte source: failing to cancel the original stream should cause cancel() to reject on branches] + expected: FAIL + + [ReadableStream teeing with byte source: erroring a teed stream should properly handle canceled branches] + expected: FAIL + + [ReadableStream teeing with byte source: closing the original should close the branches] + expected: FAIL + + [ReadableStream teeing with byte source: erroring the original should immediately error the branches] + expected: FAIL + + [ReadableStream teeing with byte source: erroring the original should error pending reads from default reader] + expected: FAIL + + [ReadableStream teeing with byte source: erroring the original should error pending reads from BYOB reader] + expected: FAIL + + [ReadableStream teeing with byte source: canceling branch1 should finish when branch2 reads until end of stream] + expected: FAIL + + [ReadableStream teeing with byte source: canceling branch1 should finish when original stream errors] + expected: FAIL + + [ReadableStream teeing with byte source: should not pull any chunks if no branches are reading] + expected: FAIL + + [ReadableStream teeing with byte source: should only pull enough to fill the emptiest queue] + expected: FAIL + + [ReadableStream teeing with byte source: should not pull when original is already errored] + expected: FAIL + + [ReadableStream teeing with byte source: stops pulling when original stream errors while branch 1 is reading] + expected: FAIL + + [ReadableStream teeing with byte source: stops pulling when original stream errors while branch 2 is reading] + expected: FAIL + + [ReadableStream teeing with byte source: stops pulling when original stream errors while both branches are reading] + expected: FAIL + + [ReadableStream teeing with byte source: canceling both branches in sequence with delay] + expected: FAIL + + [ReadableStream teeing with byte source: failing to cancel when canceling both branches in sequence with delay] + expected: FAIL + + [ReadableStream teeing with byte source: read from branch1 and branch2, cancel branch1, cancel branch2] + expected: FAIL + + [ReadableStream teeing with byte source: read from branch1 and branch2, cancel branch2, cancel branch1] + expected: FAIL + + [ReadableStream teeing with byte source: read from branch1 and branch2, cancel branch2, enqueue to branch1] + expected: FAIL + + [ReadableStream teeing with byte source: read from branch1 and branch2, cancel branch1, respond to branch2] + expected: FAIL + + [ReadableStream teeing with byte source: pull with BYOB reader, then pull with default reader] + expected: FAIL + + [ReadableStream teeing with byte source: pull with default reader, then pull with BYOB reader] + expected: FAIL + + [ReadableStream teeing with byte source: read from branch2, then read from branch1] + expected: FAIL + + [ReadableStream teeing with byte source: read from branch1 with default reader, then close while branch2 has pending BYOB read] + expected: FAIL + + [ReadableStream teeing with byte source: read from branch2 with default reader, then close while branch1 has pending BYOB read] + expected: FAIL + + [ReadableStream teeing with byte source: close when both branches have pending BYOB reads] + expected: FAIL + + [ReadableStream teeing with byte source: enqueue() and close() while both branches are pulling] + expected: FAIL + + [ReadableStream teeing with byte source: respond() and close() while both branches are pulling] + expected: FAIL + + [ReadableStream teeing with byte source: reading an array with a byte offset should clone correctly] + expected: FAIL + + +[tee.any.serviceworker.html] + expected: ERROR + +[tee.https.any.shadowrealm-in-serviceworker.html] + expected: ERROR + +[tee.https.any.shadowrealm-in-audioworklet.html] + expected: ERROR + +[tee.any.shadowrealm-in-shadowrealm.html] + expected: ERROR diff --git a/tests/wpt/meta/streams/readable-byte-streams/templated.any.js.ini b/tests/wpt/meta/streams/readable-byte-streams/templated.any.js.ini new file mode 100644 index 00000000000..07aeb5043a8 --- /dev/null +++ b/tests/wpt/meta/streams/readable-byte-streams/templated.any.js.ini @@ -0,0 +1,32 @@ +[templated.https.any.shadowrealm-in-audioworklet.html] + expected: ERROR + +[templated.any.shadowrealm-in-dedicatedworker.html] + expected: ERROR + +[templated.any.sharedworker.html] + expected: ERROR + +[templated.any.serviceworker.html] + expected: ERROR + +[templated.https.any.shadowrealm-in-serviceworker.html] + expected: ERROR + +[templated.any.shadowrealm-in-window.html] + expected: ERROR + +[templated.any.shadowrealm-in-shadowrealm.html] + expected: ERROR + +[templated.any.shadowrealm-in-sharedworker.html] + expected: ERROR + +[templated.any.worker.html] + [ReadableStream with byte source (empty): instances have the correct methods and properties] + expected: FAIL + + +[templated.any.html] + [ReadableStream with byte source (empty): instances have the correct methods and properties] + expected: FAIL