/* 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, ArrayBufferView, ArrayBufferViewU8, CreateWith, TypedArray, TypedArrayElement, TypedArrayElementCreator, }; use crate::dom::bindings::error::{Error, Fallible}; #[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: #[derive(PartialEq)] pub(crate) enum BufferSource { /// Represents an `ArrayBufferView` (e.g., `Uint8Array`, `DataView`). /// See: ArrayBufferView(Box>), /// Represents an `ArrayBuffer`, a fixed-length binary data buffer. /// See: ArrayBuffer(Box>), } pub(crate) fn new_initialized_heap_buffer_source( init: HeapTypedArrayInit, can_gc: CanGc, ) -> Result, ()> where T: TypedArrayElement + TypedArrayElementCreator, T::Element: Clone + Copy, { let heap_buffer_source = match init { HeapTypedArrayInit::Buffer(buffer_source) => HeapBufferSource { buffer_source, phantom: PhantomData, }, HeapTypedArrayInit::Info { len, cx } => { rooted!(in (*cx) let mut array = ptr::null_mut::()); let typed_array_result = create_buffer_source_with_length::(cx, len as usize, array.handle_mut(), can_gc); if typed_array_result.is_err() { return Err(()); } HeapBufferSource::::new(BufferSource::ArrayBufferView(Heap::boxed(*array.handle()))) }, }; Ok(heap_buffer_source) } pub(crate) enum HeapTypedArrayInit { Buffer(BufferSource), Info { len: u32, cx: JSContext }, } pub(crate) struct HeapBufferSource { buffer_source: BufferSource, 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, { pub(crate) fn new(buffer_source: BufferSource) -> HeapBufferSource { HeapBufferSource { buffer_source, phantom: PhantomData, } } 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::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) => { !buffer.get().is_null() }, } } pub(crate) fn get_typed_array(&self) -> Result, ()> { 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 { 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) => { let mut is_shared = false; unsafe { // assert buffer is an ArrayBuffer view assert!(JS_IsArrayBufferViewObject(*buffer.handle())); rooted!(in (*cx) let view_buffer = JS_GetArrayBufferViewBuffer(*cx, buffer.handle(), &mut is_shared)); // This buffer is always created unshared debug_assert!(!is_shared); // Detach the ArrayBuffer DetachArrayBuffer(*cx, view_buffer.handle()) } }, BufferSource::ArrayBuffer(buffer) => unsafe { DetachArrayBuffer(*cx, Handle::from_raw(buffer.handle())) }, } } pub(crate) fn typed_array_to_option(&self) -> Option> { 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(), &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(), &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.") }, } } /// 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 HeapBufferSource where T: TypedArrayElement + TypedArrayElementCreator, T::Element: Clone + Copy, { pub(crate) fn acquire_data(&self, cx: JSContext) -> Result, ()> { assert!(self.is_initialized()); typedarray!(in(*cx) let array: TypedArray = match &self.buffer_source { BufferSource::ArrayBufferView(buffer) | BufferSource::ArrayBuffer(buffer) => { buffer.get() }, }); let data = if let Ok(array) = array as Result>, &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>, &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>, 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>, &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::()); let _: TypedArray = create_buffer_source(cx, data, array.handle_mut(), can_gc)?; match &self.buffer_source { 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) => { buffer.trace(tracer); }, } } } /// pub(crate) fn create_buffer_source( cx: JSContext, data: &[T::Element], mut dest: MutableHandleObject, _can_gc: CanGc, ) -> Result, ()> where T: TypedArrayElement + TypedArrayElementCreator, { let res = unsafe { TypedArray::::create(*cx, CreateWith::Slice(data), dest.reborrow()) }; if res.is_err() { Err(()) } else { TypedArray::from(dest.get()) } } fn create_buffer_source_with_length( cx: JSContext, len: usize, mut dest: MutableHandleObject, _can_gc: CanGc, ) -> Result, ()> where T: TypedArrayElement + TypedArrayElementCreator, { let res = unsafe { TypedArray::::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, byte_offset: usize, byte_length: usize, ) -> Fallible> { match &buffer_source.buffer_source { BufferSource::ArrayBuffer(heap) => match constructor { Constructor::DataView => Ok(HeapBufferSource::new(BufferSource::ArrayBufferView( Heap::boxed(unsafe { JS_NewDataView(*cx, heap.handle(), 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, byte_offset: usize, byte_length: i64, ) -> Fallible> { match &buffer_source.buffer_source { BufferSource::ArrayBuffer(heap) => { let array_view = unsafe { match name_type { Type::Int8 => { JS_NewInt8ArrayWithBuffer(*cx, heap.handle(), byte_offset, byte_length) }, Type::Uint8 => { JS_NewUint8ArrayWithBuffer(*cx, heap.handle(), byte_offset, byte_length) }, Type::Uint16 => { JS_NewUint16ArrayWithBuffer(*cx, heap.handle(), byte_offset, byte_length) }, Type::Int16 => { JS_NewInt16ArrayWithBuffer(*cx, heap.handle(), byte_offset, byte_length) }, Type::Int32 => { JS_NewInt32ArrayWithBuffer(*cx, heap.handle(), byte_offset, byte_length) }, Type::Uint32 => { JS_NewUint32ArrayWithBuffer(*cx, heap.handle(), byte_offset, byte_length) }, Type::Float32 => { JS_NewFloat32ArrayWithBuffer(*cx, heap.handle(), byte_offset, byte_length) }, Type::Float64 => { JS_NewFloat64ArrayWithBuffer(*cx, heap.handle(), byte_offset, byte_length) }, Type::Uint8Clamped => JS_NewUint8ClampedArrayWithBuffer( *cx, heap.handle(), byte_offset, byte_length, ), Type::BigInt64 => { JS_NewBigInt64ArrayWithBuffer(*cx, heap.handle(), byte_offset, byte_length) }, Type::BigUint64 => { JS_NewBigUint64ArrayWithBuffer(*cx, heap.handle(), byte_offset, byte_length) }, Type::Float16 => { JS_NewFloat16ArrayWithBuffer(*cx, heap.handle(), byte_offset, byte_length) }, Type::Int64 | Type::Simd128 | Type::MaxTypedArrayViewType => { unreachable!("Invalid TypedArray type") }, } }; Ok(HeapBufferSource::new(BufferSource::ArrayBufferView( 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> { 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 { #[ignore_malloc_size_of = "Arc"] data: Arc>, /// Data views (mutable subslices of data) data_views: Vec, } /// 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(range1: &Range, range2: &Range) -> 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, _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(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 (*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)] pub(crate) struct DataView { #[no_trace] range: Range, #[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()) }) } }