1use std::ffi::c_void;
8use std::fmt::{Debug, Formatter};
9use std::marker::PhantomData;
10use std::mem::size_of;
11use std::ops::{Deref, DerefMut};
12use std::{fmt, ptr, slice};
13
14use mozjs::jsapi::{
15 GetArrayBufferViewLengthAndData, HandleObject, IsArrayBufferViewShared, IsLargeArrayBufferView,
16 JS_GetArrayBufferViewBuffer, JS_GetArrayBufferViewByteLength, JS_GetArrayBufferViewByteOffset,
17 JS_GetArrayBufferViewType, JS_IsArrayBufferViewObject, JS_NewFloat32ArrayWithBuffer, JS_NewFloat64ArrayWithBuffer,
18 JS_NewInt8ArrayWithBuffer, JS_NewInt16ArrayWithBuffer, JS_NewInt32ArrayWithBuffer, JS_NewUint8ArrayWithBuffer,
19 JS_NewUint8ClampedArrayWithBuffer, JS_NewUint16ArrayWithBuffer, JS_NewUint32ArrayWithBuffer, JSContext, JSObject,
20 NewExternalArrayBuffer, Type,
21};
22use mozjs::typedarray as jsta;
23use mozjs::typedarray::{
24 ArrayBufferViewU8, ClampedU8, CreateWith, Float32, Float64, Int8, Int16, Int32, Uint8, Uint16, Uint32,
25};
26
27use crate::typedarray::buffer::ArrayBuffer;
28use crate::utils::BoxExt as _;
29use crate::{Context, Local, Object};
30
31pub trait TypedArrayElement: jsta::TypedArrayElement {
32 const NAME: &'static str;
33}
34
35pub trait TypedArrayElementCreator: jsta::TypedArrayElementCreator + TypedArrayElement {
36 unsafe fn create_with_buffer(cx: *mut JSContext, object: HandleObject, offset: usize, length: i64)
37 -> *mut JSObject;
38}
39
40macro_rules! typed_array_elements {
41 ($(($view:ident, $element:ty $(, $create_with_buffer:ident)?)$(,)?)*) => {
42 $(
43 pub type $view<'bv> = TypedArray<'bv, $element>;
44
45 impl TypedArrayElement for $element {
46 const NAME: &'static str = stringify!($view);
47 }
48
49 $(
50 impl TypedArrayElementCreator for $element {
51 unsafe fn create_with_buffer(
52 cx: *mut JSContext, object: HandleObject, offset: usize, length: i64,
53 ) -> *mut JSObject {
54 unsafe {
55 $create_with_buffer(cx, object, offset, length)
56 }
57 }
58 }
59 )?
60 )*
61 };
62
63}
64
65typed_array_elements! {
66 (ArrayBufferView, ArrayBufferViewU8),
67 (Uint8Array, Uint8, JS_NewUint8ArrayWithBuffer),
68 (Uint16Array, Uint16, JS_NewUint16ArrayWithBuffer),
69 (Uint32Array, Uint32, JS_NewUint32ArrayWithBuffer),
70 (Int8Array, Int8, JS_NewInt8ArrayWithBuffer),
71 (Int16Array, Int16, JS_NewInt16ArrayWithBuffer),
72 (Int32Array, Int32, JS_NewInt32ArrayWithBuffer),
73 (Float32Array, Float32, JS_NewFloat32ArrayWithBuffer),
74 (Float64Array, Float64, JS_NewFloat64ArrayWithBuffer),
75 (ClampedUint8Array, ClampedU8, JS_NewUint8ClampedArrayWithBuffer),
76}
77
78pub struct TypedArray<'bv, T: TypedArrayElement> {
79 view: Local<'bv, *mut JSObject>,
80 _phantom: PhantomData<T>,
81}
82
83impl<'bv, T: TypedArrayElementCreator> TypedArray<'bv, T> {
84 pub fn create_with(cx: &'bv Context, with: CreateWith<T::Element>) -> Option<TypedArray<'bv, T>> {
85 let mut view = Object::null(cx);
86 unsafe { jsta::TypedArray::<T, *mut JSObject>::create(cx.as_ptr(), with, view.handle_mut()).ok()? };
87 Some(TypedArray {
88 view: view.into_local(),
89 _phantom: PhantomData,
90 })
91 }
92
93 pub fn new(cx: &Context, len: usize) -> Option<TypedArray<'_, T>> {
95 TypedArray::create_with(cx, CreateWith::Length(len))
96 }
97
98 pub fn copy_from_bytes(cx: &'bv Context, bytes: &[T::Element]) -> Option<TypedArray<'bv, T>> {
100 TypedArray::create_with(cx, CreateWith::Slice(bytes))
101 }
102
103 pub fn from_vec(cx: &Context, bytes: Vec<T::Element>) -> Option<TypedArray<'_, T>> {
105 TypedArray::from_boxed_slice(cx, bytes.into_boxed_slice())
106 }
107
108 pub fn from_boxed_slice(cx: &Context, bytes: Box<[T::Element]>) -> Option<TypedArray<'_, T>> {
110 unsafe extern "C" fn free_external_array_buffer<T: TypedArrayElementCreator>(
111 contents: *mut c_void, data: *mut c_void,
112 ) {
113 let _ = unsafe { Box::from_raw_parts(contents.cast::<T::Element>(), data as usize) };
114 }
115
116 let (ptr, len) = Box::into_raw_parts(bytes);
117 let buffer = unsafe {
118 NewExternalArrayBuffer(
119 cx.as_ptr(),
120 len * size_of::<T::Element>(),
121 ptr.cast(),
122 Some(free_external_array_buffer::<T>),
123 len as *mut c_void,
124 )
125 };
126
127 if buffer.is_null() {
128 return None;
129 }
130
131 let buffer = ArrayBuffer::from(cx.root(buffer)).unwrap();
132 TypedArray::with_array_buffer(cx, &buffer, 0, len)
133 }
134
135 pub fn with_array_buffer(
137 cx: &'bv Context, buffer: &ArrayBuffer, byte_offset: usize, len: usize,
138 ) -> Option<TypedArray<'bv, T>> {
139 let view = unsafe { T::create_with_buffer(cx.as_ptr(), buffer.handle().into(), byte_offset, len as i64) };
140
141 if view.is_null() {
142 None
143 } else {
144 Some(TypedArray {
145 view: cx.root(view),
146 _phantom: PhantomData,
147 })
148 }
149 }
150}
151
152impl<'bv, T: TypedArrayElement> TypedArray<'bv, T> {
153 pub fn from(object: Local<*mut JSObject>) -> Option<TypedArray<T>> {
154 let unwrapped = unsafe { T::unwrap_array(object.get()) };
155 if unwrapped.is_null() {
156 None
157 } else {
158 Some(TypedArray { view: object, _phantom: PhantomData })
159 }
160 }
161
162 pub unsafe fn from_unchecked(object: Local<*mut JSObject>) -> TypedArray<T> {
163 TypedArray { view: object, _phantom: PhantomData }
164 }
165
166 pub fn data(&self) -> (*mut T::Element, usize) {
170 let mut len = 0;
171 let mut shared = false;
172 let mut data = ptr::null_mut();
173 unsafe { GetArrayBufferViewLengthAndData(self.get(), &raw mut len, &raw mut shared, &raw mut data) };
174 (data.cast::<T::Element>(), len / size_of::<T::Element>())
175 }
176
177 pub fn is_empty(&self) -> bool {
178 self.len() == 0
179 }
180
181 pub fn len(&self) -> usize {
182 self.data().1
183 }
184
185 pub unsafe fn as_slice(&self) -> &[T::Element] {
189 let (ptr, len) = self.data();
190 unsafe { slice::from_raw_parts(ptr, len) }
191 }
192
193 #[expect(clippy::mut_from_ref)]
197 pub unsafe fn as_mut_slice(&self) -> &mut [T::Element] {
198 let (ptr, len) = self.data();
199 unsafe { slice::from_raw_parts_mut(ptr, len) }
200 }
201
202 pub fn offset(&self) -> usize {
204 unsafe { JS_GetArrayBufferViewByteOffset(self.get()) }
205 }
206
207 pub fn byte_length(&self) -> usize {
209 unsafe { JS_GetArrayBufferViewByteLength(self.get()) }
210 }
211
212 pub fn is_large(&self) -> bool {
214 unsafe { IsLargeArrayBufferView(self.get()) }
215 }
216
217 pub fn is_shared(&self) -> bool {
219 unsafe { IsArrayBufferViewShared(self.get()) }
220 }
221
222 pub fn buffer<'ab>(&self, cx: &'ab Context) -> ArrayBuffer<'ab> {
224 let mut shared = false;
225 ArrayBuffer::from(
226 cx.root(unsafe { JS_GetArrayBufferViewBuffer(cx.as_ptr(), self.handle().into(), &raw mut shared) }),
227 )
228 .unwrap()
229 }
230
231 pub fn into_local(self) -> Local<'bv, *mut JSObject> {
232 self.view
233 }
234
235 #[expect(clippy::not_unsafe_ptr_arg_deref)]
237 pub fn is_array_buffer_view(object: *mut JSObject) -> bool {
238 unsafe { JS_IsArrayBufferViewObject(object) }
239 }
240}
241
242impl TypedArray<'_, ArrayBufferViewU8> {
243 pub fn view_type(&self) -> Type {
244 unsafe { JS_GetArrayBufferViewType(self.get()) }
245 }
246}
247
248impl<T: TypedArrayElement> Debug for TypedArray<'_, T> {
249 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
250 f.debug_struct("TypedArray").field("view", &self.view).finish()
251 }
252}
253
254impl<'bv, T: TypedArrayElement> Deref for TypedArray<'bv, T> {
255 type Target = Local<'bv, *mut JSObject>;
256
257 fn deref(&self) -> &Self::Target {
258 &self.view
259 }
260}
261
262impl<T: TypedArrayElement> DerefMut for TypedArray<'_, T> {
263 fn deref_mut(&mut self) -> &mut Self::Target {
264 &mut self.view
265 }
266}