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