1use crate::{qjs, Ctx, Error, FromJs, IntoJs, JsLifetime, Object, Result, Value};
2use alloc::vec::Vec;
3use core::{
4 ffi::c_void,
5 fmt,
6 mem::{self, size_of, ManuallyDrop, MaybeUninit},
7 ops::Deref,
8 ptr::NonNull,
9 result::Result as StdResult,
10 slice,
11};
12
13use super::typed_array::TypedArrayItem;
14
15pub struct RawArrayBuffer {
16 pub len: usize,
17 pub ptr: NonNull<u8>,
18}
19
20#[derive(Debug, Clone, Copy, Eq, PartialEq)]
21pub enum AsSliceError {
22 BufferUsed,
23 InvalidAlignment,
24}
25
26impl fmt::Display for AsSliceError {
27 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
28 match self {
29 AsSliceError::BufferUsed => write!(f, "Buffer was already used"),
30 AsSliceError::InvalidAlignment => {
31 write!(f, "Buffer had a different alignment than was requested")
32 }
33 }
34 }
35}
36
37#[derive(Debug, PartialEq, Clone, Eq, Hash)]
40#[repr(transparent)]
41pub struct ArrayBuffer<'js>(pub(crate) Object<'js>);
42
43unsafe impl<'js> JsLifetime<'js> for ArrayBuffer<'js> {
44 type Changed<'to> = ArrayBuffer<'to>;
45}
46
47impl<'js> ArrayBuffer<'js> {
48 pub fn new<T: Copy>(ctx: Ctx<'js>, src: impl Into<Vec<T>>) -> Result<Self> {
50 let mut src = ManuallyDrop::new(src.into());
51 let ptr = src.as_mut_ptr();
52 let capacity = src.capacity();
53 let size = src.len() * size_of::<T>();
54
55 extern "C" fn drop_raw<T>(_rt: *mut qjs::JSRuntime, opaque: *mut c_void, ptr: *mut c_void) {
56 let ptr = ptr as *mut T;
57 let capacity = opaque as usize;
58 unsafe { Vec::from_raw_parts(ptr, capacity, capacity) };
61 }
62
63 Ok(Self(Object(unsafe {
64 let val = qjs::JS_NewArrayBuffer(
65 ctx.as_ptr(),
66 ptr as _,
67 size as _,
68 Some(drop_raw::<T>),
69 capacity as _,
70 false,
71 );
72 ctx.handle_exception(val).inspect_err(|_| {
73 Vec::from_raw_parts(ptr, capacity, capacity);
75 })?;
76 Value::from_js_value(ctx, val)
77 })))
78 }
79
80 pub fn new_copy<T: Copy>(ctx: Ctx<'js>, src: impl AsRef<[T]>) -> Result<Self> {
82 let src = src.as_ref();
83 let ptr = src.as_ptr();
84 let size = core::mem::size_of_val(src);
85
86 Ok(Self(Object(unsafe {
87 let val = qjs::JS_NewArrayBufferCopy(ctx.as_ptr(), ptr as _, size as _);
88 ctx.handle_exception(val)?;
89 Value::from_js_value(ctx.clone(), val)
90 })))
91 }
92
93 pub fn len(&self) -> usize {
95 Self::get_raw(&self.0).expect("Not an ArrayBuffer").len
96 }
97
98 pub fn is_empty(&self) -> bool {
100 self.len() == 0
101 }
102
103 pub fn as_bytes(&self) -> Option<&[u8]> {
107 let raw = Self::get_raw(self.as_value())?;
108 Some(unsafe { slice::from_raw_parts_mut(raw.ptr.as_ptr(), raw.len) })
109 }
110
111 pub fn as_slice<T: TypedArrayItem>(&self) -> StdResult<&[T], AsSliceError> {
114 let raw = Self::get_raw(&self.0).ok_or(AsSliceError::BufferUsed)?;
115 if raw.ptr.as_ptr().align_offset(mem::align_of::<T>()) != 0 {
116 return Err(AsSliceError::InvalidAlignment);
117 }
118 let len = raw.len / size_of::<T>();
119 Ok(unsafe { slice::from_raw_parts(raw.ptr.as_ptr().cast(), len) })
120 }
121
122 pub fn detach(&mut self) {
124 unsafe { qjs::JS_DetachArrayBuffer(self.0.ctx.as_ptr(), self.0.as_js_value()) }
125 }
126
127 #[inline]
129 pub fn as_value(&self) -> &Value<'js> {
130 self.0.as_value()
131 }
132
133 #[inline]
135 pub fn into_value(self) -> Value<'js> {
136 self.0.into_value()
137 }
138
139 pub fn from_value(value: Value<'js>) -> Option<Self> {
141 Self::from_object(Object::from_value(value).ok()?)
142 }
143
144 #[inline]
146 pub fn as_object(&self) -> &Object<'js> {
147 &self.0
148 }
149
150 #[inline]
152 pub fn into_object(self) -> Object<'js> {
153 self.0
154 }
155
156 pub fn from_object(object: Object<'js>) -> Option<Self> {
158 if Self::get_raw(&object.0).is_some() {
159 Some(Self(object))
160 } else {
161 None
162 }
163 }
164
165 pub fn as_raw(&self) -> Option<RawArrayBuffer> {
169 Self::get_raw(self.as_value())
170 }
171
172 pub(crate) fn get_raw(val: &Value<'js>) -> Option<RawArrayBuffer> {
173 let ctx = val.ctx();
174 let val = val.as_js_value();
175 let mut size = MaybeUninit::<qjs::size_t>::uninit();
176 let ptr = unsafe { qjs::JS_GetArrayBuffer(ctx.as_ptr(), size.as_mut_ptr(), val) };
177
178 if let Some(ptr) = NonNull::new(ptr) {
179 let len = unsafe { size.assume_init() }
180 .try_into()
181 .expect(qjs::SIZE_T_ERROR);
182 Some(RawArrayBuffer { len, ptr })
183 } else {
184 None
185 }
186 }
187}
188
189impl<'js, T: TypedArrayItem> AsRef<[T]> for ArrayBuffer<'js> {
190 fn as_ref(&self) -> &[T] {
191 self.as_slice().expect("ArrayBuffer was detached")
192 }
193}
194
195impl<'js> Deref for ArrayBuffer<'js> {
196 type Target = Object<'js>;
197
198 fn deref(&self) -> &Self::Target {
199 self.as_object()
200 }
201}
202
203impl<'js> AsRef<Object<'js>> for ArrayBuffer<'js> {
204 fn as_ref(&self) -> &Object<'js> {
205 self.as_object()
206 }
207}
208
209impl<'js> AsRef<Value<'js>> for ArrayBuffer<'js> {
210 fn as_ref(&self) -> &Value<'js> {
211 self.as_value()
212 }
213}
214
215impl<'js> FromJs<'js> for ArrayBuffer<'js> {
216 fn from_js(_: &Ctx<'js>, value: Value<'js>) -> Result<Self> {
217 let ty_name = value.type_name();
218 if let Some(v) = Self::from_value(value) {
219 Ok(v)
220 } else {
221 Err(Error::new_from_js(ty_name, "ArrayBuffer"))
222 }
223 }
224}
225
226impl<'js> IntoJs<'js> for ArrayBuffer<'js> {
227 fn into_js(self, _: &Ctx<'js>) -> Result<Value<'js>> {
228 Ok(self.into_value())
229 }
230}
231
232impl<'js> Object<'js> {
233 pub fn is_array_buffer(&self) -> bool {
235 ArrayBuffer::get_raw(&self.0).is_some()
236 }
237
238 pub unsafe fn ref_array_buffer(&self) -> &ArrayBuffer {
243 mem::transmute(self)
244 }
245
246 pub fn as_array_buffer(&self) -> Option<&ArrayBuffer> {
248 self.is_array_buffer()
249 .then_some(unsafe { self.ref_array_buffer() })
250 }
251}
252
253#[cfg(test)]
254mod test {
255 use crate::*;
256
257 #[test]
258 fn from_javascript_i8() {
259 test_with(|ctx| {
260 let val: ArrayBuffer = ctx
261 .eval(
262 r#"
263 new Int8Array([0, -5, 1, 11]).buffer
264 "#,
265 )
266 .unwrap();
267 assert_eq!(val.len(), 4);
268 assert_eq!(val.as_ref() as &[i8], &[0i8, -5, 1, 11]);
269 });
270 }
271
272 #[test]
273 fn into_javascript_i8() {
274 test_with(|ctx| {
275 let val = ArrayBuffer::new(ctx.clone(), [-1i8, 0, 22, 5]).unwrap();
276 ctx.globals().set("a", val).unwrap();
277 let res: i8 = ctx
278 .eval(
279 r#"
280 let v = new Int8Array(a);
281 v.length != 4 ? 1 :
282 v[0] != -1 ? 2 :
283 v[1] != 0 ? 3 :
284 v[2] != 22 ? 4 :
285 v[3] != 5 ? 5 :
286 0
287 "#,
288 )
289 .unwrap();
290 assert_eq!(res, 0);
291 })
292 }
293
294 #[test]
295 fn from_javascript_f32() {
296 test_with(|ctx| {
297 let val: ArrayBuffer = ctx
298 .eval(
299 r#"
300 new Float32Array([0.5, -5.25, 123.125]).buffer
301 "#,
302 )
303 .unwrap();
304 assert_eq!(val.len(), 12);
305 assert_eq!(val.as_ref() as &[f32], &[0.5f32, -5.25, 123.125]);
306 });
307 }
308
309 #[test]
310 fn into_javascript_f32() {
311 test_with(|ctx| {
312 let val = ArrayBuffer::new(ctx.clone(), [-1.5f32, 0.0, 2.25]).unwrap();
313 ctx.globals().set("a", val).unwrap();
314 let res: i8 = ctx
315 .eval(
316 r#"
317 let v = new Float32Array(a);
318 a.byteLength != 12 ? 1 :
319 v.length != 3 ? 2 :
320 v[0] != -1.5 ? 3 :
321 v[1] != 0 ? 4 :
322 v[2] != 2.25 ? 5 :
323 0
324 "#,
325 )
326 .unwrap();
327 assert_eq!(res, 0);
328 })
329 }
330
331 #[test]
332 fn as_bytes() {
333 test_with(|ctx| {
334 let val: ArrayBuffer = ctx
335 .eval(
336 r#"
337 new Uint32Array([0xCAFEDEAD,0xFEEDBEAD]).buffer
338 "#,
339 )
340 .unwrap();
341 let mut res = [0; 8];
342 let bytes_0 = 0xCAFEDEADu32.to_ne_bytes();
343 res[..4].copy_from_slice(&bytes_0);
344 let bytes_1 = 0xFEEDBEADu32.to_ne_bytes();
345 res[4..].copy_from_slice(&bytes_1);
346
347 assert_eq!(val.as_bytes().unwrap(), &res)
348 });
349 }
350}