ion/object/typedarray/
view.rs

1/*
2 * This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
5 */
6
7use 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	/// Creates a new [TypedArray] with the given length.
94	pub fn new(cx: &Context, len: usize) -> Option<TypedArray<'_, T>> {
95		TypedArray::create_with(cx, CreateWith::Length(len))
96	}
97
98	/// Creates a new [TypedArray] by copying the contents of the given slice.
99	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	/// Creates a new [TypedArray] by transferring ownership of the values to the JS runtime.
104	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	/// Creates a new [TypedArray] by transferring ownership of the bytes to the JS runtime.
109	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	/// Creates a new [TypedArray] with a view of the contents of an existing [ArrayBuffer].
136	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	/// Returns a pointer and length to the contents of the [TypedArray].
167	///
168	/// The pointer may be invalidated if the underlying [ArrayBuffer] is detached.
169	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	/// Returns a slice to the contents of the [TypedArray].
186	///
187	/// The slice may be invalidated if the underlying [ArrayBuffer] is detached.
188	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	/// Returns a mutable slice to the contents of the [TypedArray].
194	///
195	/// The slice may be invalidated if the underlying [ArrayBuffer] is detached.
196	#[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	/// Returns the offset of the [TypedArray] with respect to the underlying [ArrayBuffer].
203	pub fn offset(&self) -> usize {
204		unsafe { JS_GetArrayBufferViewByteOffset(self.get()) }
205	}
206
207	/// Returns the length of the [TypedArray] in bytes.
208	pub fn byte_length(&self) -> usize {
209		unsafe { JS_GetArrayBufferViewByteLength(self.get()) }
210	}
211
212	/// Checks if the [TypedArray] is larger than the maximum allowed on 32-bit platforms.
213	pub fn is_large(&self) -> bool {
214		unsafe { IsLargeArrayBufferView(self.get()) }
215	}
216
217	/// Checks if the underlying [ArrayBuffer] is shared.
218	pub fn is_shared(&self) -> bool {
219		unsafe { IsArrayBufferViewShared(self.get()) }
220	}
221
222	/// Returns the underlying [ArrayBuffer]. The buffer may be shared and/or detached.
223	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	/// Checks if an object is an array buffer view.
236	#[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}