ion/object/typedarray/
buffer.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::ops::{Deref, DerefMut};
9use std::{ptr, slice};
10
11use mozjs::jsapi::{
12	ArrayBufferClone, ArrayBufferCopyData, DetachArrayBuffer, GetArrayBufferMaybeSharedLengthAndData,
13	IsArrayBufferObjectMaybeShared, IsDetachedArrayBufferObject, JSObject, NewArrayBufferWithContents,
14	NewExternalArrayBuffer, StealArrayBufferContents,
15};
16use mozjs::typedarray::CreateWith;
17
18use crate::utils::BoxExt as _;
19use crate::{Context, Error, ErrorKind, Local, Object, Result};
20
21#[derive(Debug)]
22pub struct ArrayBuffer<'ab> {
23	buffer: Local<'ab, *mut JSObject>,
24}
25
26impl<'ab> ArrayBuffer<'ab> {
27	fn create_with(cx: &'ab Context, with: CreateWith<u8>) -> Option<ArrayBuffer<'ab>> {
28		let mut buffer = Object::null(cx);
29		unsafe { mozjs::typedarray::ArrayBuffer::create(cx.as_ptr(), with, buffer.handle_mut()).ok()? };
30		Some(ArrayBuffer { buffer: buffer.into_local() })
31	}
32
33	/// Creates a new [ArrayBuffer] with the given length.
34	pub fn new(cx: &Context, len: usize) -> Option<ArrayBuffer<'_>> {
35		ArrayBuffer::create_with(cx, CreateWith::Length(len))
36	}
37
38	/// Creates a new [ArrayBuffer] by copying the contents of the given slice.
39	pub fn copy_from_bytes(cx: &'ab Context, bytes: &[u8]) -> Option<ArrayBuffer<'ab>> {
40		ArrayBuffer::create_with(cx, CreateWith::Slice(bytes))
41	}
42
43	/// Creates a new [ArrayBuffer] by transferring ownership of the bytes to the JS runtime.
44	pub fn from_vec(cx: &Context, bytes: Vec<u8>) -> Option<ArrayBuffer<'_>> {
45		ArrayBuffer::from_boxed_slice(cx, bytes.into_boxed_slice())
46	}
47
48	/// Creates a new [ArrayBuffer] by transferring ownership of the bytes to the JS runtime.
49	pub fn from_boxed_slice(cx: &Context, bytes: Box<[u8]>) -> Option<ArrayBuffer<'_>> {
50		unsafe extern "C" fn free_external_array_buffer(contents: *mut c_void, data: *mut c_void) {
51			let _ = unsafe { Box::from_raw_parts(contents.cast::<u8>(), data as usize) };
52		}
53
54		let (ptr, len) = Box::into_raw_parts(bytes);
55		let buffer = unsafe {
56			NewExternalArrayBuffer(
57				cx.as_ptr(),
58				len,
59				ptr.cast(),
60				Some(free_external_array_buffer),
61				len as *mut c_void,
62			)
63		};
64
65		if buffer.is_null() {
66			None
67		} else {
68			Some(ArrayBuffer { buffer: cx.root(buffer) })
69		}
70	}
71
72	pub fn from(object: Local<*mut JSObject>) -> Option<ArrayBuffer> {
73		ArrayBuffer::is_array_buffer(object.get()).then_some(ArrayBuffer { buffer: object })
74	}
75
76	pub unsafe fn from_unchecked(object: Local<*mut JSObject>) -> ArrayBuffer {
77		ArrayBuffer { buffer: object }
78	}
79
80	/// Returns a pointer and length to the contents of the [ArrayBuffer].
81	///
82	/// The pointer may be invalidated if the [ArrayBuffer] is detached.
83	pub fn data(&self) -> (*mut u8, usize, bool) {
84		let mut len = 0;
85		let mut shared = false;
86		let mut data = ptr::null_mut();
87		unsafe { GetArrayBufferMaybeSharedLengthAndData(self.get(), &raw mut len, &raw mut shared, &raw mut data) };
88		(data, len, shared)
89	}
90
91	pub fn is_empty(&self) -> bool {
92		self.len() == 0
93	}
94
95	pub fn len(&self) -> usize {
96		self.data().1
97	}
98
99	/// Returns a slice to the contents of the [ArrayBuffer].
100	///
101	/// The slice may be invalidated if the [ArrayBuffer] is detached.
102	pub unsafe fn as_slice(&self) -> &[u8] {
103		let (ptr, len, _) = self.data();
104		unsafe { slice::from_raw_parts(ptr, len) }
105	}
106
107	/// Returns a mutable slice to the contents of the [ArrayBuffer].
108	///
109	/// The slice may be invalidated if the [ArrayBuffer] is detached.
110	#[expect(clippy::mut_from_ref)]
111	pub unsafe fn as_mut_slice(&self) -> &mut [u8] {
112		let (ptr, len, _) = self.data();
113		unsafe { slice::from_raw_parts_mut(ptr, len) }
114	}
115
116	/// Clones an [ArrayBuffer].
117	pub fn clone<'cx>(&self, cx: &'cx Context, offset: usize, len: usize) -> Option<ArrayBuffer<'cx>> {
118		let buffer = unsafe { ArrayBufferClone(cx.as_ptr(), self.handle().into(), offset, len) };
119		if buffer.is_null() {
120			None
121		} else {
122			Some(ArrayBuffer { buffer: cx.root(buffer) })
123		}
124	}
125
126	/// Copies data from one [ArrayBuffer] to another.
127	/// Returns `false` if the sizes do not match.
128	pub fn copy_data_to(
129		&self, cx: &Context, to: &ArrayBuffer, from_index: usize, to_index: usize, count: usize,
130	) -> bool {
131		unsafe {
132			ArrayBufferCopyData(
133				cx.as_ptr(),
134				to.handle().into(),
135				to_index,
136				self.handle().into(),
137				from_index,
138				count,
139			)
140		}
141	}
142
143	pub fn detach(&self, cx: &Context) -> bool {
144		unsafe { DetachArrayBuffer(cx.as_ptr(), self.handle().into()) }
145	}
146
147	pub fn transfer<'cx>(&self, cx: &'cx Context) -> Result<ArrayBuffer<'cx>> {
148		let len = self.len();
149		let data = unsafe { StealArrayBufferContents(cx.as_ptr(), self.handle().into()) };
150		if data.is_null() {
151			return Err(Error::new("ArrayBuffer transfer failed", ErrorKind::Normal));
152		}
153		let buffer = cx.root(unsafe { NewArrayBufferWithContents(cx.as_ptr(), len, data) });
154		if buffer.handle().is_null() {
155			return Err(Error::new("ArrayBuffer transfer failed", ErrorKind::Normal));
156		}
157		Ok(ArrayBuffer { buffer })
158	}
159
160	pub fn is_detached(&self) -> bool {
161		unsafe { IsDetachedArrayBufferObject(self.get()) }
162	}
163
164	pub fn is_shared(&self) -> bool {
165		self.data().2
166	}
167
168	/// Checks if an object is an array buffer.
169	#[expect(clippy::not_unsafe_ptr_arg_deref)]
170	pub fn is_array_buffer(object: *mut JSObject) -> bool {
171		unsafe { IsArrayBufferObjectMaybeShared(object) }
172	}
173}
174
175impl<'ab> Deref for ArrayBuffer<'ab> {
176	type Target = Local<'ab, *mut JSObject>;
177
178	fn deref(&self) -> &Self::Target {
179		&self.buffer
180	}
181}
182
183impl DerefMut for ArrayBuffer<'_> {
184	fn deref_mut(&mut self) -> &mut Self::Target {
185		&mut self.buffer
186	}
187}