| /* This Source Code Form is subject to the terms of the Mozilla Public |
| * License, v. 2.0. If a copy of the MPL was not distributed with this |
| * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ |
| |
| #![allow(unsafe_code)] |
| |
| #[cfg(feature = "webgpu")] |
| use std::ffi::c_void; |
| use std::marker::PhantomData; |
| #[cfg(feature = "webgpu")] |
| use std::ops::Range; |
| use std::ptr; |
| #[cfg(feature = "webgpu")] |
| use std::sync::Arc; |
| |
| #[cfg(feature = "webgpu")] |
| use js::jsapi::NewExternalArrayBuffer; |
| use js::jsapi::{ |
| 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, |
| MutableHandleValue as SafeMutableHandleValue, |
| }; |
| #[cfg(feature = "webgpu")] |
| use js::typedarray::{ArrayBuffer, HeapArrayBuffer}; |
| use js::typedarray::{ |
| ArrayBufferU8, ArrayBufferViewU8, CreateWith, TypedArray, TypedArrayElement, |
| TypedArrayElementCreator, |
| }; |
| |
| use crate::dom::bindings::error::{Error, Fallible}; |
| use crate::dom::bindings::trace::RootedTraceableBox; |
| #[cfg(feature = "webgpu")] |
| use crate::dom::globalscope::GlobalScope; |
| use crate::script_runtime::{CanGc, JSContext}; |
| |
| // Represents a `BufferSource` as defined in the WebIDL specification. |
| /// |
| /// A `BufferSource` is either an `ArrayBuffer` or an `ArrayBufferView`, which |
| /// provides a view onto an `ArrayBuffer`. |
| /// |
| /// See: <https://webidl.spec.whatwg.org/#BufferSource> |
| pub(crate) enum BufferSource { |
| /// Represents an `ArrayBufferView` (e.g., `Uint8Array`, `DataView`). |
| /// See: <https://webidl.spec.whatwg.org/#ArrayBufferView> |
| ArrayBufferView(RootedTraceableBox<Heap<*mut JSObject>>), |
| |
| /// Represents an `ArrayBuffer`, a fixed-length binary data buffer. |
| /// See: <https://webidl.spec.whatwg.org/#idl-ArrayBuffer> |
| ArrayBuffer(RootedTraceableBox<Heap<*mut JSObject>>), |
| } |
| |
| pub(crate) fn create_heap_buffer_source_with_length<T>( |
| cx: JSContext, |
| len: u32, |
| can_gc: CanGc, |
| ) -> Fallible<HeapBufferSource<T>> |
| where |
| T: TypedArrayElement + TypedArrayElementCreator, |
| T::Element: Clone + Copy, |
| { |
| rooted!(in (*cx) let mut array = ptr::null_mut::<JSObject>()); |
| let typed_array_result = |
| create_buffer_source_with_length::<T>(cx, len as usize, array.handle_mut(), can_gc); |
| if typed_array_result.is_err() { |
| return Err(Error::JSFailed); |
| } |
| |
| Ok(HeapBufferSource::<T>::new(BufferSource::ArrayBufferView( |
| RootedTraceableBox::from_box(Heap::boxed(*array.handle())), |
| ))) |
| } |
| |
| pub(crate) struct HeapBufferSource<T> { |
| buffer_source: BufferSource, |
| phantom: PhantomData<T>, |
| } |
| |
| impl<T> Eq for HeapBufferSource<T> where T: TypedArrayElement {} |
| |
| impl<T> PartialEq for HeapBufferSource<T> |
| 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) => { |
| std::ptr::eq(heap.get(), from_heap.get()) |
| }, |
| }, |
| } |
| } |
| } |
| |
| impl<T> HeapBufferSource<T> |
| where |
| T: TypedArrayElement, |
| { |
| pub(crate) fn new(buffer_source: BufferSource) -> HeapBufferSource<T> { |
| HeapBufferSource { |
| buffer_source, |
| phantom: PhantomData, |
| } |
| } |
| |
| pub(crate) fn from_view( |
| chunk: CustomAutoRooterGuard<TypedArray<T, *mut JSObject>>, |
| ) -> HeapBufferSource<T> { |
| HeapBufferSource::<T>::new(BufferSource::ArrayBufferView(RootedTraceableBox::from_box( |
| Heap::boxed(unsafe { *chunk.underlying_object() }), |
| ))) |
| } |
| |
| pub(crate) fn default() -> Self { |
| HeapBufferSource { |
| buffer_source: BufferSource::ArrayBufferView(RootedTraceableBox::from_box( |
| 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) => { |
| !buffer.get().is_null() |
| }, |
| } |
| } |
| |
| pub(crate) fn get_typed_array(&self) -> Result<TypedArray<T, *mut JSObject>, ()> { |
| TypedArray::from(match &self.buffer_source { |
| 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<ArrayBufferU8> { |
| match &self.buffer_source { |
| BufferSource::ArrayBufferView(buffer) => unsafe { |
| let mut is_shared = false; |
| rooted!(in (*cx) let view_buffer = |
| JS_GetArrayBufferViewBuffer(*cx, buffer.handle().into(), &mut is_shared)); |
| |
| HeapBufferSource::<ArrayBufferU8>::new(BufferSource::ArrayBuffer( |
| RootedTraceableBox::from_box(Heap::boxed(*view_buffer.handle())), |
| )) |
| }, |
| BufferSource::ArrayBuffer(_) => { |
| unreachable!("BufferSource::ArrayBuffer does not have a view buffer.") |
| }, |
| } |
| } |
| |
| /// <https://tc39.es/ecma262/#sec-detacharraybuffer> |
| pub(crate) fn detach_buffer(&self, cx: JSContext) -> bool { |
| assert!(self.is_initialized()); |
| match &self.buffer_source { |
| BufferSource::ArrayBufferView(buffer) => { |
| let mut is_shared = false; |
| unsafe { |
| // assert buffer is an ArrayBuffer view |
| assert!(JS_IsArrayBufferViewObject(*buffer.handle())); |
| rooted!(in (*cx) let view_buffer = |
| JS_GetArrayBufferViewBuffer(*cx, buffer.handle().into(), &mut is_shared)); |
| // This buffer is always created unshared |
| debug_assert!(!is_shared); |
| // Detach the ArrayBuffer |
| DetachArrayBuffer(*cx, view_buffer.handle()) |
| } |
| }, |
| BufferSource::ArrayBuffer(buffer) => unsafe { |
| DetachArrayBuffer(*cx, Handle::from_raw(buffer.handle().into())) |
| }, |
| } |
| } |
| |
| pub(crate) fn typed_array_to_option(&self) -> Option<TypedArray<T, *mut JSObject>> { |
| if self.is_initialized() { |
| self.get_typed_array().ok() |
| } else { |
| warn!("Buffer not initialized."); |
| None |
| } |
| } |
| |
| pub(crate) fn is_detached_buffer(&self, cx: JSContext) -> bool { |
| assert!(self.is_initialized()); |
| match &self.buffer_source { |
| BufferSource::ArrayBufferView(buffer) => { |
| let mut is_shared = false; |
| unsafe { |
| assert!(JS_IsArrayBufferViewObject(*buffer.handle())); |
| rooted!(in (*cx) let view_buffer = |
| JS_GetArrayBufferViewBuffer(*cx, buffer.handle().into(), &mut is_shared)); |
| debug_assert!(!is_shared); |
| IsDetachedArrayBufferObject(*view_buffer.handle()) |
| } |
| }, |
| BufferSource::ArrayBuffer(buffer) => unsafe { |
| IsDetachedArrayBufferObject(*buffer.handle()) |
| }, |
| } |
| } |
| |
| pub(crate) fn viewed_buffer_array_byte_length(&self, cx: JSContext) -> usize { |
| assert!(self.is_initialized()); |
| match &self.buffer_source { |
| BufferSource::ArrayBufferView(buffer) => { |
| let mut is_shared = false; |
| unsafe { |
| assert!(JS_IsArrayBufferViewObject(*buffer.handle())); |
| rooted!(in (*cx) let view_buffer = |
| JS_GetArrayBufferViewBuffer(*cx, buffer.handle().into(), &mut is_shared)); |
| debug_assert!(!is_shared); |
| GetArrayBufferByteLength(*view_buffer.handle()) |
| } |
| }, |
| BufferSource::ArrayBuffer(buffer) => unsafe { |
| GetArrayBufferByteLength(*buffer.handle()) |
| }, |
| } |
| } |
| |
| pub(crate) fn byte_length(&self) -> usize { |
| match &self.buffer_source { |
| BufferSource::ArrayBufferView(buffer) => unsafe { |
| JS_GetArrayBufferViewByteLength(*buffer.handle()) |
| }, |
| BufferSource::ArrayBuffer(buffer) => unsafe { |
| GetArrayBufferByteLength(*buffer.handle()) |
| }, |
| } |
| } |
| |
| 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.") |
| }, |
| } |
| } |
| |
| /// <https://tc39.es/ecma262/#typedarray> |
| pub(crate) fn has_typed_array_name(&self) -> bool { |
| match &self.buffer_source { |
| 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<T> HeapBufferSource<T> |
| where |
| T: TypedArrayElement + TypedArrayElementCreator, |
| T::Element: Clone + Copy, |
| { |
| pub(crate) fn acquire_data(&self, cx: JSContext) -> Result<Vec<T::Element>, ()> { |
| assert!(self.is_initialized()); |
| |
| typedarray!(in(*cx) let array: TypedArray = match &self.buffer_source { |
| BufferSource::ArrayBufferView(buffer) | BufferSource::ArrayBuffer(buffer) |
| => { |
| buffer.get() |
| }, |
| }); |
| let data = if let Ok(array) = |
| array as Result<CustomAutoRooterGuard<'_, TypedArray<T, *mut JSObject>>, &mut ()> |
| { |
| let data = array.to_vec(); |
| let _ = self.detach_buffer(cx); |
| Ok(data) |
| } else { |
| Err(()) |
| }; |
| |
| match &self.buffer_source { |
| BufferSource::ArrayBufferView(buffer) | BufferSource::ArrayBuffer(buffer) => { |
| buffer.set(ptr::null_mut()); |
| }, |
| } |
| data |
| } |
| |
| pub(crate) fn copy_data_to( |
| &self, |
| cx: JSContext, |
| dest: &mut [T::Element], |
| source_start: usize, |
| length: usize, |
| ) -> Result<(), ()> { |
| assert!(self.is_initialized()); |
| typedarray!(in(*cx) let array: TypedArray = match &self.buffer_source { |
| BufferSource::ArrayBufferView(buffer) | BufferSource::ArrayBuffer(buffer) |
| => { |
| buffer.get() |
| }, |
| }); |
| let Ok(array) = |
| array as Result<CustomAutoRooterGuard<'_, TypedArray<T, *mut JSObject>>, &mut ()> |
| else { |
| return Err(()); |
| }; |
| unsafe { |
| let slice = (*array).as_slice(); |
| dest.copy_from_slice(&slice[source_start..length]); |
| } |
| Ok(()) |
| } |
| |
| pub(crate) fn copy_data_from( |
| &self, |
| cx: JSContext, |
| source: CustomAutoRooterGuard<TypedArray<T, *mut JSObject>>, |
| dest_start: usize, |
| length: usize, |
| ) -> Result<(), ()> { |
| assert!(self.is_initialized()); |
| typedarray!(in(*cx) let mut array: TypedArray = match &self.buffer_source { |
| BufferSource::ArrayBufferView(buffer) | BufferSource::ArrayBuffer(buffer) |
| => { |
| buffer.get() |
| }, |
| }); |
| let Ok(mut array) = |
| array as Result<CustomAutoRooterGuard<'_, TypedArray<T, *mut JSObject>>, &mut ()> |
| else { |
| return Err(()); |
| }; |
| unsafe { |
| let slice = (*array).as_mut_slice(); |
| let (_, dest) = slice.split_at_mut(dest_start); |
| dest[0..length].copy_from_slice(&source.as_slice()[0..length]) |
| } |
| Ok(()) |
| } |
| |
| pub(crate) fn set_data( |
| &self, |
| cx: JSContext, |
| data: &[T::Element], |
| can_gc: CanGc, |
| ) -> Result<(), ()> { |
| rooted!(in (*cx) let mut array = ptr::null_mut::<JSObject>()); |
| let _: TypedArray<T, *mut JSObject> = |
| create_buffer_source(cx, data, array.handle_mut(), can_gc)?; |
| |
| match &self.buffer_source { |
| BufferSource::ArrayBufferView(buffer) | BufferSource::ArrayBuffer(buffer) => { |
| buffer.set(*array); |
| }, |
| } |
| Ok(()) |
| } |
| |
| /// <https://tc39.es/ecma262/#sec-clonearraybuffer> |
| pub(crate) fn clone_array_buffer( |
| &self, |
| cx: JSContext, |
| byte_offset: usize, |
| byte_length: usize, |
| ) -> Option<HeapBufferSource<ArrayBufferU8>> { |
| match &self.buffer_source { |
| BufferSource::ArrayBufferView(heap) | BufferSource::ArrayBuffer(heap) => { |
| let result = unsafe { |
| ArrayBufferClone(*cx, heap.handle().into(), byte_offset, byte_length) |
| }; |
| if result.is_null() { |
| None |
| } else { |
| Some(HeapBufferSource::<ArrayBufferU8>::new( |
| BufferSource::ArrayBuffer(RootedTraceableBox::from_box(Heap::boxed( |
| result, |
| ))), |
| )) |
| } |
| }, |
| } |
| } |
| |
| /// <https://streams.spec.whatwg.org/#abstract-opdef-cancopydatablockbytes> |
| // CanCopyDataBlockBytes(descriptorBuffer, destStart, queueBuffer, queueByteOffset, bytesToCopy) |
| pub(crate) fn can_copy_data_block_bytes( |
| &self, |
| cx: JSContext, |
| to_index: usize, |
| from_buffer: &HeapBufferSource<ArrayBufferU8>, |
| 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) => { |
| if std::ptr::eq(heap.get(), from_heap.get()) { |
| 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<ArrayBufferU8>, |
| 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().into(), |
| dest_start, |
| from_heap.handle().into(), |
| from_byte_offset, |
| bytes_to_copy, |
| ), |
| } |
| }, |
| } |
| } |
| |
| /// <https://streams.spec.whatwg.org/#can-transfer-array-buffer> |
| 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().into(), &mut is_defined) { |
| return false; |
| } |
| }, |
| } |
| |
| !is_defined |
| } |
| |
| /// <https://streams.spec.whatwg.org/#transfer-array-buffer> |
| pub(crate) fn transfer_array_buffer( |
| &self, |
| cx: JSContext, |
| ) -> Fallible<HeapBufferSource<ArrayBufferU8>> { |
| 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().into()) |
| }, |
| }; |
| |
| // 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::<ArrayBufferU8>::new( |
| BufferSource::ArrayBuffer(RootedTraceableBox::from_box(Heap::boxed(unsafe { |
| NewArrayBufferWithContents(*cx, buffer_length, buffer_data) |
| }))), |
| )) |
| } |
| } |
| } |
| |
| unsafe impl<T> crate::dom::bindings::trace::JSTraceable for HeapBufferSource<T> { |
| #[inline] |
| unsafe fn trace(&self, tracer: *mut js::jsapi::JSTracer) { |
| match &self.buffer_source { |
| BufferSource::ArrayBufferView(buffer) | BufferSource::ArrayBuffer(buffer) => { |
| unsafe { buffer.trace(tracer) }; |
| }, |
| } |
| } |
| } |
| |
| /// <https://webidl.spec.whatwg.org/#arraybufferview-create> |
| pub(crate) fn create_buffer_source<T>( |
| cx: JSContext, |
| data: &[T::Element], |
| mut dest: MutableHandleObject, |
| _can_gc: CanGc, |
| ) -> Result<TypedArray<T, *mut JSObject>, ()> |
| where |
| T: TypedArrayElement + TypedArrayElementCreator, |
| { |
| let res = unsafe { |
| TypedArray::<T, *mut JSObject>::create(*cx, CreateWith::Slice(data), dest.reborrow()) |
| }; |
| |
| if res.is_err() { |
| Err(()) |
| } else { |
| TypedArray::from(dest.get()) |
| } |
| } |
| |
| fn create_buffer_source_with_length<T>( |
| cx: JSContext, |
| len: usize, |
| mut dest: MutableHandleObject, |
| _can_gc: CanGc, |
| ) -> Result<TypedArray<T, *mut JSObject>, ()> |
| where |
| T: TypedArrayElement + TypedArrayElementCreator, |
| { |
| let res = unsafe { |
| TypedArray::<T, *mut JSObject>::create(*cx, CreateWith::Length(len), dest.reborrow()) |
| }; |
| |
| if res.is_err() { |
| Err(()) |
| } else { |
| TypedArray::from(dest.get()) |
| } |
| } |
| |
| 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<ArrayBufferU8>, |
| byte_offset: usize, |
| byte_length: usize, |
| ) -> Fallible<HeapBufferSource<ArrayBufferViewU8>> { |
| match &buffer_source.buffer_source { |
| BufferSource::ArrayBuffer(heap) => match constructor { |
| Constructor::DataView => Ok(HeapBufferSource::new(BufferSource::ArrayBufferView( |
| RootedTraceableBox::from_box(Heap::boxed(unsafe { |
| JS_NewDataView(*cx, heap.handle().into(), byte_offset, byte_length) |
| })), |
| ))), |
| Constructor::Name(name_type) => construct_typed_array( |
| cx, |
| name_type, |
| buffer_source, |
| byte_offset, |
| byte_length as i64, |
| ), |
| }, |
| BufferSource::ArrayBufferView(_) => { |
| unreachable!("Can not create a new ArrayBufferView from an existing ArrayBufferView"); |
| }, |
| } |
| } |
| |
| /// Helper function to construct different TypedArray views |
| fn construct_typed_array( |
| cx: JSContext, |
| name_type: &Type, |
| buffer_source: &HeapBufferSource<ArrayBufferU8>, |
| byte_offset: usize, |
| byte_length: i64, |
| ) -> Fallible<HeapBufferSource<ArrayBufferViewU8>> { |
| match &buffer_source.buffer_source { |
| BufferSource::ArrayBuffer(heap) => { |
| let array_view = unsafe { |
| match name_type { |
| Type::Int8 => JS_NewInt8ArrayWithBuffer( |
| *cx, |
| heap.handle().into(), |
| byte_offset, |
| byte_length, |
| ), |
| Type::Uint8 => JS_NewUint8ArrayWithBuffer( |
| *cx, |
| heap.handle().into(), |
| byte_offset, |
| byte_length, |
| ), |
| Type::Uint16 => JS_NewUint16ArrayWithBuffer( |
| *cx, |
| heap.handle().into(), |
| byte_offset, |
| byte_length, |
| ), |
| Type::Int16 => JS_NewInt16ArrayWithBuffer( |
| *cx, |
| heap.handle().into(), |
| byte_offset, |
| byte_length, |
| ), |
| Type::Int32 => JS_NewInt32ArrayWithBuffer( |
| *cx, |
| heap.handle().into(), |
| byte_offset, |
| byte_length, |
| ), |
| Type::Uint32 => JS_NewUint32ArrayWithBuffer( |
| *cx, |
| heap.handle().into(), |
| byte_offset, |
| byte_length, |
| ), |
| Type::Float32 => JS_NewFloat32ArrayWithBuffer( |
| *cx, |
| heap.handle().into(), |
| byte_offset, |
| byte_length, |
| ), |
| Type::Float64 => JS_NewFloat64ArrayWithBuffer( |
| *cx, |
| heap.handle().into(), |
| byte_offset, |
| byte_length, |
| ), |
| Type::Uint8Clamped => JS_NewUint8ClampedArrayWithBuffer( |
| *cx, |
| heap.handle().into(), |
| byte_offset, |
| byte_length, |
| ), |
| Type::BigInt64 => JS_NewBigInt64ArrayWithBuffer( |
| *cx, |
| heap.handle().into(), |
| byte_offset, |
| byte_length, |
| ), |
| Type::BigUint64 => JS_NewBigUint64ArrayWithBuffer( |
| *cx, |
| heap.handle().into(), |
| byte_offset, |
| byte_length, |
| ), |
| Type::Float16 => JS_NewFloat16ArrayWithBuffer( |
| *cx, |
| heap.handle().into(), |
| byte_offset, |
| byte_length, |
| ), |
| Type::Int64 | Type::Simd128 | Type::MaxTypedArrayViewType => { |
| unreachable!("Invalid TypedArray type") |
| }, |
| } |
| }; |
| |
| Ok(HeapBufferSource::new(BufferSource::ArrayBufferView( |
| RootedTraceableBox::from_box(Heap::boxed(array_view)), |
| ))) |
| }, |
| BufferSource::ArrayBufferView(_) => { |
| unreachable!("Can not create a new ArrayBufferView from an existing ArrayBufferView"); |
| }, |
| } |
| } |
| |
| pub(crate) fn create_array_buffer_with_size( |
| cx: JSContext, |
| size: usize, |
| ) -> Fallible<HeapBufferSource<ArrayBufferU8>> { |
| 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::<ArrayBufferU8>::new( |
| BufferSource::ArrayBuffer(RootedTraceableBox::from_box(Heap::boxed(result))), |
| )) |
| } |
| } |
| |
| #[cfg(feature = "webgpu")] |
| #[derive(JSTraceable, MallocSizeOf)] |
| pub(crate) struct DataBlock { |
| #[conditional_malloc_size_of] |
| data: Arc<Box<[u8]>>, |
| /// Data views (mutable subslices of data) |
| data_views: Vec<DataView>, |
| } |
| |
| /// Returns true if two non-inclusive ranges overlap |
| // https://stackoverflow.com/questions/3269434/whats-the-most-efficient-way-to-test-if-two-ranges-overlap |
| #[cfg(feature = "webgpu")] |
| fn range_overlap<T: std::cmp::PartialOrd>(range1: &Range<T>, range2: &Range<T>) -> bool { |
| range1.start < range2.end && range2.start < range1.end |
| } |
| |
| #[cfg(feature = "webgpu")] |
| impl DataBlock { |
| pub(crate) fn new_zeroed(size: usize) -> Self { |
| let data = vec![0; size]; |
| Self { |
| data: Arc::new(data.into_boxed_slice()), |
| data_views: Vec::new(), |
| } |
| } |
| |
| /// Panics if there is any active view or src data is not same length |
| pub(crate) fn load(&mut self, src: &[u8]) { |
| // `Arc::get_mut` ensures there are no views |
| Arc::get_mut(&mut self.data).unwrap().clone_from_slice(src) |
| } |
| |
| /// Panics if there is any active view |
| pub(crate) fn data(&mut self) -> &mut [u8] { |
| // `Arc::get_mut` ensures there are no views |
| Arc::get_mut(&mut self.data).unwrap() |
| } |
| |
| pub(crate) fn clear_views(&mut self) { |
| self.data_views.clear() |
| } |
| |
| /// Returns error if requested range is already mapped |
| pub(crate) fn view(&mut self, range: Range<usize>, _can_gc: CanGc) -> Result<&DataView, ()> { |
| if self |
| .data_views |
| .iter() |
| .any(|view| range_overlap(&view.range, &range)) |
| { |
| return Err(()); |
| } |
| let cx = GlobalScope::get_cx(); |
| /// `freeFunc()` must be threadsafe, should be safely callable from any thread |
| /// without causing conflicts, unexpected behavior. |
| unsafe extern "C" fn free_func(_contents: *mut c_void, free_user_data: *mut c_void) { |
| // Clippy warns about "creating a `Arc` from a void raw pointer" here, but suggests |
| // the exact same line to fix it. Doing the cast is tricky because of the use of |
| // a generic type in this parameter. |
| #[allow(clippy::from_raw_with_void_ptr)] |
| drop(unsafe { Arc::from_raw(free_user_data as *const _) }); |
| } |
| let raw: *mut Box<[u8]> = Arc::into_raw(Arc::clone(&self.data)) as _; |
| rooted!(in(*cx) let object = unsafe { |
| NewExternalArrayBuffer( |
| *cx, |
| range.end - range.start, |
| // SAFETY: This is safe because we have checked there is no overlapping view |
| (&mut (*raw))[range.clone()].as_mut_ptr() as _, |
| Some(free_func), |
| raw as _, |
| ) |
| }); |
| self.data_views.push(DataView { |
| range, |
| buffer: HeapArrayBuffer::from(*object).unwrap(), |
| }); |
| Ok(self.data_views.last().unwrap()) |
| } |
| } |
| |
| #[cfg(feature = "webgpu")] |
| #[derive(JSTraceable, MallocSizeOf)] |
| #[cfg_attr(crown, allow(crown::unrooted_must_root))] |
| pub(crate) struct DataView { |
| #[no_trace] |
| range: Range<usize>, |
| #[ignore_malloc_size_of = "defined in mozjs"] |
| buffer: HeapArrayBuffer, |
| } |
| |
| #[cfg(feature = "webgpu")] |
| impl DataView { |
| pub(crate) fn array_buffer(&self) -> ArrayBuffer { |
| unsafe { ArrayBuffer::from(self.buffer.underlying_object().get()).unwrap() } |
| } |
| } |
| |
| #[cfg(feature = "webgpu")] |
| impl Drop for DataView { |
| #[allow(unsafe_code)] |
| fn drop(&mut self) { |
| let cx = GlobalScope::get_cx(); |
| assert!(unsafe { |
| js::jsapi::DetachArrayBuffer(*cx, self.buffer.underlying_object().handle()) |
| }) |
| } |
| } |