1use crate::{
2 markers::ParallelSend, qjs, Ctx, Error, FromJs, IntoJs, JsLifetime, Object, Result, Value,
3};
4use alloc::boxed::Box;
5use alloc::sync::Arc;
6use alloc::vec::Vec;
7use core::{
8 ffi::c_void,
9 fmt,
10 mem::{self, size_of, ManuallyDrop, MaybeUninit},
11 ops::Deref,
12 ptr::NonNull,
13 result::Result as StdResult,
14 slice,
15};
16
17use super::typed_array::TypedArrayItem;
18
19const FIXED_SIZE: qjs::size_t = 0;
22const FREE: qjs::size_t = 0;
23
24pub unsafe trait ArrayBufferSource {
35 fn as_ptr(&self) -> *mut u8;
36 fn len(&self) -> usize;
37 fn is_empty(&self) -> bool {
38 self.len() == 0
39 }
40}
41
42macro_rules! impl_array_buffer_source {
43 ($($t:ty),* $(,)?) => {
44 $(
45 unsafe impl ArrayBufferSource for $t {
46 fn as_ptr(&self) -> *mut u8 {
47 <[u8]>::as_ptr(self) as *mut u8
48 }
49 fn len(&self) -> usize {
50 <[u8]>::len(self)
51 }
52 }
53 )*
54 };
55}
56
57impl_array_buffer_source!(Vec<u8>, alloc::boxed::Box<[u8]>, Arc<[u8]>, Arc<Vec<u8>>);
58
59#[cfg(feature = "bytes")]
60impl_array_buffer_source!(bytes::Bytes);
61
62#[derive(Debug, Clone, Copy, Eq, PartialEq)]
63pub enum AsSliceError {
64 BufferUsed,
65 InvalidAlignment,
66}
67
68impl fmt::Display for AsSliceError {
69 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
70 match self {
71 AsSliceError::BufferUsed => write!(f, "Buffer was already used"),
72 AsSliceError::InvalidAlignment => {
73 write!(f, "Buffer had a different alignment than was requested")
74 }
75 }
76 }
77}
78
79#[derive(Debug, PartialEq, Clone, Eq, Hash)]
82#[repr(transparent)]
83pub struct ArrayBuffer<'js>(pub(crate) Object<'js>);
84
85unsafe impl<'js> JsLifetime<'js> for ArrayBuffer<'js> {
86 type Changed<'to> = ArrayBuffer<'to>;
87}
88
89impl<'js> ArrayBuffer<'js> {
90 pub fn new<T: Copy>(ctx: Ctx<'js>, src: impl Into<Vec<T>>) -> Result<Self> {
92 let mut src = ManuallyDrop::new(src.into());
93 let ptr = src.as_mut_ptr();
94 let capacity = src.capacity();
95 let size = src.len() * size_of::<T>();
96
97 let capacity_cell = Box::into_raw(Box::new(capacity));
99
100 extern "C" fn realloc_raw<T: Copy>(
101 _rt: *mut qjs::JSRuntime,
102 opaque: *mut c_void,
103 ptr: *mut c_void,
104 size: qjs::size_t,
105 ) -> *mut c_void {
106 let capacity_cell = opaque as *mut usize;
107 let capacity = unsafe { *capacity_cell };
108 let mut vec = ManuallyDrop::new(unsafe {
110 Vec::from_raw_parts(ptr as *mut T, capacity, capacity)
111 });
112
113 if size == FREE {
114 unsafe {
115 ManuallyDrop::drop(&mut vec);
116 drop(Box::from_raw(capacity_cell));
117 }
118 return core::ptr::null_mut();
119 }
120
121 let elem_size = size_of::<T>();
122 if elem_size == 0 {
123 return core::ptr::null_mut();
124 }
125 let new_len = (size as usize).div_ceil(elem_size);
126
127 if new_len > capacity {
128 if vec.try_reserve_exact(new_len - capacity).is_err() {
129 return core::ptr::null_mut();
130 }
131 } else if new_len < capacity {
132 vec.truncate(new_len);
133 vec.shrink_to(new_len);
134 }
135
136 unsafe { *capacity_cell = vec.capacity() };
137 vec.as_mut_ptr() as *mut c_void
138 }
139
140 Ok(Self(Object(unsafe {
141 let val = qjs::JS_NewArrayBuffer(
142 ctx.as_ptr(),
143 ptr as _,
144 size as _,
145 FIXED_SIZE,
146 Some(realloc_raw::<T>),
147 capacity_cell as _,
148 false,
149 );
150 ctx.handle_exception(val).inspect_err(|_| {
151 Vec::from_raw_parts(ptr, capacity, capacity);
153 drop(Box::from_raw(capacity_cell));
154 })?;
155 Value::from_js_value(ctx, val)
156 })))
157 }
158
159 pub fn new_copy<T: Copy>(ctx: Ctx<'js>, src: impl AsRef<[T]>) -> Result<Self> {
161 let src = src.as_ref();
162 let ptr = src.as_ptr();
163 let size = core::mem::size_of_val(src);
164
165 Ok(Self(Object(unsafe {
166 let val = qjs::JS_NewArrayBufferCopy(ctx.as_ptr(), ptr as _, size as _);
167 ctx.handle_exception(val)?;
168 Value::from_js_value(ctx.clone(), val)
169 })))
170 }
171
172 pub fn from_source<S>(ctx: Ctx<'js>, src: S) -> Result<Self>
179 where
180 S: ArrayBufferSource + ParallelSend + 'static,
181 {
182 let ptr = src.as_ptr();
183 let len = src.len();
184 unsafe { Self::from_external(ctx, ptr, len, false, false, move || drop(src)) }
185 }
186
187 pub fn from_source_shared<S>(ctx: Ctx<'js>, src: S) -> Result<Self>
191 where
192 S: ArrayBufferSource + ParallelSend + 'static,
193 {
194 let ptr = src.as_ptr();
195 let len = src.len();
196 unsafe { Self::from_external(ctx, ptr, len, true, false, move || drop(src)) }
197 }
198
199 pub fn from_source_immutable<S>(ctx: Ctx<'js>, src: S) -> Result<Self>
206 where
207 S: ArrayBufferSource + ParallelSend + 'static,
208 {
209 let ptr = src.as_ptr();
210 let len = src.len();
211 unsafe { Self::from_external(ctx, ptr, len, false, true, move || drop(src)) }
212 }
213
214 unsafe fn from_external<F>(
223 ctx: Ctx<'js>,
224 ptr: *mut u8,
225 len: usize,
226 is_shared: bool,
227 immutable: bool,
228 drop_fn: F,
229 ) -> Result<Self>
230 where
231 F: FnOnce() + ParallelSend + 'static,
232 {
233 extern "C" fn shim<F: FnOnce()>(
234 _rt: *mut qjs::JSRuntime,
235 opaque: *mut c_void,
236 _ptr: *mut c_void,
237 size: qjs::size_t,
238 ) -> *mut c_void {
239 if size != FREE {
240 return core::ptr::null_mut();
241 }
242 unsafe {
243 let boxed: Box<F> = Box::from_raw(opaque as *mut F);
244 (*boxed)();
245 }
246 core::ptr::null_mut()
247 }
248
249 let opaque = Box::into_raw(Box::new(drop_fn)) as *mut c_void;
250
251 Ok(Self(Object(unsafe {
252 let val = qjs::JS_NewArrayBuffer(
253 ctx.as_ptr(),
254 ptr,
255 len as _,
256 FIXED_SIZE,
257 Some(shim::<F>),
258 opaque,
259 is_shared,
260 );
261 if let Err(e) = ctx.handle_exception(val) {
262 shim::<F>(
263 qjs::JS_GetRuntime(ctx.as_ptr()),
264 opaque,
265 ptr as *mut c_void,
266 FREE,
267 );
268 return Err(e);
269 }
270 if immutable {
271 qjs::JS_SetImmutableArrayBuffer(val, true);
272 }
273 Value::from_js_value(ctx, val)
274 })))
275 }
276
277 pub fn len(&self) -> usize {
279 Self::get_raw(&self.0).expect("Not an ArrayBuffer").len()
280 }
281
282 pub fn is_empty(&self) -> bool {
284 self.len() == 0
285 }
286
287 pub unsafe fn as_bytes(&self) -> Option<&[u8]> {
297 Some(self.as_raw()?.as_ref())
298 }
299
300 pub unsafe fn as_slice<T: TypedArrayItem>(&self) -> StdResult<&[T], AsSliceError> {
309 let raw = Self::get_raw(&self.0).ok_or(AsSliceError::BufferUsed)?;
310 if raw.cast::<u8>().align_offset(mem::align_of::<T>()) != 0 {
311 return Err(AsSliceError::InvalidAlignment);
312 }
313 let len = raw.len() / size_of::<T>();
314 Ok(slice::from_raw_parts(raw.as_ptr().cast(), len))
315 }
316
317 pub fn detach(&mut self) {
319 unsafe { qjs::JS_DetachArrayBuffer(self.0.ctx.as_ptr(), self.0.as_js_value()) }
320 }
321
322 #[inline]
324 pub fn as_value(&self) -> &Value<'js> {
325 self.0.as_value()
326 }
327
328 #[inline]
330 pub fn into_value(self) -> Value<'js> {
331 self.0.into_value()
332 }
333
334 pub fn from_value(value: Value<'js>) -> Option<Self> {
336 Self::from_object(Object::from_value(value).ok()?)
337 }
338
339 #[inline]
341 pub fn as_object(&self) -> &Object<'js> {
342 &self.0
343 }
344
345 #[inline]
347 pub fn into_object(self) -> Object<'js> {
348 self.0
349 }
350
351 pub fn from_object(object: Object<'js>) -> Option<Self> {
353 if Self::get_raw(&object.0).is_some() {
354 Some(Self(object))
355 } else {
356 None
357 }
358 }
359
360 pub fn as_raw(&self) -> Option<NonNull<[u8]>> {
369 Self::get_raw(self.as_value())
370 }
371
372 pub(crate) fn get_raw(val: &Value<'js>) -> Option<NonNull<[u8]>> {
373 let ctx = val.ctx();
374 let val = val.as_js_value();
375 let mut size = MaybeUninit::<qjs::size_t>::uninit();
376 let ptr = unsafe { qjs::JS_GetArrayBuffer(ctx.as_ptr(), size.as_mut_ptr(), val) };
377
378 if let Some(ptr) = NonNull::new(ptr) {
379 let len = unsafe { size.assume_init() }
380 .try_into()
381 .expect(qjs::SIZE_T_ERROR);
382 Some(NonNull::slice_from_raw_parts(ptr, len))
383 } else {
384 None
385 }
386 }
387}
388
389impl<'js> Deref for ArrayBuffer<'js> {
390 type Target = Object<'js>;
391
392 fn deref(&self) -> &Self::Target {
393 self.as_object()
394 }
395}
396
397impl<'js> AsRef<Object<'js>> for ArrayBuffer<'js> {
398 fn as_ref(&self) -> &Object<'js> {
399 self.as_object()
400 }
401}
402
403impl<'js> AsRef<Value<'js>> for ArrayBuffer<'js> {
404 fn as_ref(&self) -> &Value<'js> {
405 self.as_value()
406 }
407}
408
409impl<'js> FromJs<'js> for ArrayBuffer<'js> {
410 fn from_js(_: &Ctx<'js>, value: Value<'js>) -> Result<Self> {
411 let ty_name = value.type_name();
412 if let Some(v) = Self::from_value(value) {
413 Ok(v)
414 } else {
415 Err(Error::new_from_js(ty_name, "ArrayBuffer"))
416 }
417 }
418}
419
420impl<'js> IntoJs<'js> for ArrayBuffer<'js> {
421 fn into_js(self, _: &Ctx<'js>) -> Result<Value<'js>> {
422 Ok(self.into_value())
423 }
424}
425
426impl<'js> Object<'js> {
427 pub fn is_array_buffer(&self) -> bool {
429 ArrayBuffer::get_raw(&self.0).is_some()
430 }
431
432 pub unsafe fn ref_array_buffer(&self) -> &ArrayBuffer {
437 mem::transmute(self)
438 }
439
440 pub fn as_array_buffer(&self) -> Option<&ArrayBuffer> {
442 self.is_array_buffer()
443 .then_some(unsafe { self.ref_array_buffer() })
444 }
445}
446
447#[cfg(test)]
448mod test {
449 use crate::*;
450 use alloc::sync::Arc;
451
452 #[test]
453 fn from_javascript_i8() {
454 test_with(|ctx| {
455 let val: ArrayBuffer = ctx
456 .eval(
457 r#"
458 new Int8Array([0, -5, 1, 11]).buffer
459 "#,
460 )
461 .unwrap();
462 assert_eq!(val.len(), 4);
463 assert_eq!(
464 unsafe { val.as_slice() }.unwrap() as &[i8],
465 &[0i8, -5, 1, 11]
466 );
467 });
468 }
469
470 #[test]
471 fn into_javascript_i8() {
472 test_with(|ctx| {
473 let val = ArrayBuffer::new(ctx.clone(), [-1i8, 0, 22, 5]).unwrap();
474 ctx.globals().set("a", val).unwrap();
475 let res: i8 = ctx
476 .eval(
477 r#"
478 let v = new Int8Array(a);
479 v.length != 4 ? 1 :
480 v[0] != -1 ? 2 :
481 v[1] != 0 ? 3 :
482 v[2] != 22 ? 4 :
483 v[3] != 5 ? 5 :
484 0
485 "#,
486 )
487 .unwrap();
488 assert_eq!(res, 0);
489 })
490 }
491
492 #[test]
493 fn from_javascript_f32() {
494 test_with(|ctx| {
495 let val: ArrayBuffer = ctx
496 .eval(
497 r#"
498 new Float32Array([0.5, -5.25, 123.125]).buffer
499 "#,
500 )
501 .unwrap();
502 assert_eq!(val.len(), 12);
503 assert_eq!(
504 unsafe { val.as_slice() }.unwrap() as &[f32],
505 &[0.5f32, -5.25, 123.125]
506 );
507 });
508 }
509
510 #[test]
511 fn into_javascript_f32() {
512 test_with(|ctx| {
513 let val = ArrayBuffer::new(ctx.clone(), [-1.5f32, 0.0, 2.25]).unwrap();
514 ctx.globals().set("a", val).unwrap();
515 let res: i8 = ctx
516 .eval(
517 r#"
518 let v = new Float32Array(a);
519 a.byteLength != 12 ? 1 :
520 v.length != 3 ? 2 :
521 v[0] != -1.5 ? 3 :
522 v[1] != 0 ? 4 :
523 v[2] != 2.25 ? 5 :
524 0
525 "#,
526 )
527 .unwrap();
528 assert_eq!(res, 0);
529 })
530 }
531
532 #[test]
533 fn as_bytes() {
534 test_with(|ctx| {
535 let val: ArrayBuffer = ctx
536 .eval(
537 r#"
538 new Uint32Array([0xCAFEDEAD,0xFEEDBEAD]).buffer
539 "#,
540 )
541 .unwrap();
542 let mut res = [0; 8];
543 let bytes_0 = 0xCAFEDEADu32.to_ne_bytes();
544 res[..4].copy_from_slice(&bytes_0);
545 let bytes_1 = 0xFEEDBEADu32.to_ne_bytes();
546 res[4..].copy_from_slice(&bytes_1);
547
548 assert_eq!(unsafe { val.as_bytes() }.unwrap(), &res)
549 });
550 }
551
552 #[test]
553 fn from_source_external_buffer() {
554 use core::sync::atomic::{AtomicBool, Ordering};
555
556 static DROPPED: AtomicBool = AtomicBool::new(false);
557
558 struct Tracker(alloc::boxed::Box<[u8]>);
559 unsafe impl ArrayBufferSource for Tracker {
560 fn as_ptr(&self) -> *mut u8 {
561 self.0.as_ptr()
562 }
563 fn len(&self) -> usize {
564 self.0.len()
565 }
566 }
567 impl Drop for Tracker {
568 fn drop(&mut self) {
569 DROPPED.store(true, Ordering::SeqCst);
570 }
571 }
572
573 let rt = crate::Runtime::new().unwrap();
574 let c = crate::Context::full(&rt).unwrap();
575 c.with(|ctx| {
576 let src = Tracker(alloc::vec![1u8, 2, 3, 4].into_boxed_slice());
577 let ab = ArrayBuffer::from_source(ctx.clone(), src).unwrap();
578 assert_eq!(ab.len(), 4);
579 assert_eq!(unsafe { ab.as_bytes() }.unwrap(), &[1, 2, 3, 4]);
580 });
581 rt.run_gc();
582 assert!(DROPPED.load(Ordering::SeqCst));
583 }
584
585 #[test]
586 fn from_source_error_invokes_drop_fn() {
587 use core::sync::atomic::{AtomicBool, Ordering};
588
589 static DROPPED: AtomicBool = AtomicBool::new(false);
590
591 struct BadSource(#[allow(dead_code)] alloc::boxed::Box<[u8]>);
594 unsafe impl ArrayBufferSource for BadSource {
595 fn as_ptr(&self) -> *mut u8 {
596 self.0.as_ptr()
597 }
598 fn len(&self) -> usize {
599 i64::MAX as usize
600 }
601 }
602 impl Drop for BadSource {
603 fn drop(&mut self) {
604 DROPPED.store(true, Ordering::SeqCst);
605 }
606 }
607
608 let rt = crate::Runtime::new().unwrap();
609 let c = crate::Context::full(&rt).unwrap();
610 c.with(|ctx| {
611 let src = BadSource(alloc::vec![1u8, 2, 3, 4].into_boxed_slice());
612 let err = ArrayBuffer::from_source(ctx.clone(), src);
613 assert!(err.is_err());
614 });
615 assert!(DROPPED.load(Ordering::SeqCst));
616 }
617
618 #[test]
619 fn from_source_immutable_arc_slices() {
620 struct ArcSlice {
621 arc: Arc<Vec<u8>>,
622 offset: usize,
623 len: usize,
624 }
625 unsafe impl ArrayBufferSource for ArcSlice {
626 fn as_ptr(&self) -> *mut u8 {
627 unsafe { self.arc.as_ptr().add(self.offset) }
628 }
629 fn len(&self) -> usize {
630 self.len
631 }
632 }
633
634 let buf: Arc<Vec<u8>> = Arc::new((0u8..16).collect());
635 let weak = Arc::downgrade(&buf);
636
637 let rt = crate::Runtime::new().unwrap();
638 let c = crate::Context::full(&rt).unwrap();
639 c.with(|ctx| {
640 let mk = |offset: usize, len: usize| -> ArrayBuffer<'_> {
641 ArrayBuffer::from_source_immutable(
642 ctx.clone(),
643 ArcSlice {
644 arc: buf.clone(),
645 offset,
646 len,
647 },
648 )
649 .unwrap()
650 };
651 let full = mk(0, 16);
652 let head = mk(0, 4);
653 let tail = mk(12, 4);
654 let middle = mk(4, 8);
655
656 assert_eq!(
657 unsafe { full.as_bytes() }.unwrap(),
658 (0u8..16).collect::<Vec<_>>()
659 );
660 assert_eq!(unsafe { head.as_bytes() }.unwrap(), &[0, 1, 2, 3]);
661 assert_eq!(unsafe { tail.as_bytes() }.unwrap(), &[12, 13, 14, 15]);
662 assert_eq!(
663 unsafe { middle.as_bytes() }.unwrap(),
664 (4u8..12).collect::<Vec<_>>()
665 );
666 assert_eq!(Arc::strong_count(&buf), 5);
667
668 ctx.globals().set("buf", full).unwrap();
669
670 let after: u8 = ctx
671 .eval::<u8, _>(
672 r#"
673 const arr = new Uint8Array(buf);
674 arr[0] = 99;
675 arr[0];
676 "#,
677 )
678 .unwrap();
679 assert_eq!(after, 0, "immutable ArrayBuffer must not accept writes");
680
681 let writer = ctx.eval::<(), _>(
682 r#"
683 "use strict";
684 new DataView(buf).setUint8(0, 99);
685 "#,
686 );
687 assert!(
688 writer.is_err(),
689 "DataView write on immutable buffer must throw"
690 );
691 });
692
693 drop(buf);
694 rt.run_gc();
695 drop(c);
696 drop(rt);
697 assert!(weak.upgrade().is_none());
698 }
699
700 #[test]
701 fn from_source_vec() {
702 let rt = crate::Runtime::new().unwrap();
703 let c = crate::Context::full(&rt).unwrap();
704 c.with(|ctx| {
705 let ab = ArrayBuffer::from_source(ctx.clone(), alloc::vec![1u8, 2, 3, 4]).unwrap();
706 assert_eq!(unsafe { ab.as_bytes() }.unwrap(), &[1, 2, 3, 4]);
707 });
708 }
709
710 #[test]
711 fn transfer_to_different_length_grows_vec_buffer() {
712 test_with(|ctx| {
713 let ab = ArrayBuffer::new(ctx.clone(), alloc::vec![1u8, 2, 3, 4]).unwrap();
714 ctx.globals().set("buf", ab).unwrap();
715
716 ctx.eval::<(), _>("globalThis.grown = buf.transfer(8);")
717 .unwrap();
718
719 let original: Object = ctx.globals().get("buf").unwrap();
721 assert!(
722 ArrayBuffer::from_object(original).is_none(),
723 "source buffer must be detached by transfer"
724 );
725
726 let grown: ArrayBuffer = ctx.globals().get("grown").unwrap();
727 assert_eq!(
728 unsafe { grown.as_bytes() }.unwrap(),
729 &[1, 2, 3, 4, 0, 0, 0, 0]
730 );
731 });
732 }
733
734 #[test]
735 fn transfer_to_different_length_shrinks_vec_buffer() {
736 test_with(|ctx| {
737 let ab = ArrayBuffer::new(ctx.clone(), alloc::vec![1u8, 2, 3, 4]).unwrap();
738 ctx.globals().set("buf", ab).unwrap();
739
740 ctx.eval::<(), _>("globalThis.shrunk = buf.transfer(2);")
741 .unwrap();
742
743 let shrunk: ArrayBuffer = ctx.globals().get("shrunk").unwrap();
744 assert_eq!(unsafe { shrunk.as_bytes() }.unwrap(), &[1, 2]);
745 });
746 }
747
748 #[test]
749 fn transfer_chain_grows_then_shrinks_vec_buffer() {
750 test_with(|ctx| {
751 let ab = ArrayBuffer::new(ctx.clone(), alloc::vec![1u8, 2, 3, 4]).unwrap();
752 ctx.globals().set("buf", ab).unwrap();
753
754 ctx.eval::<(), _>(
756 r#"
757 globalThis.mid = buf.transfer(64);
758 globalThis.small = mid.transfer(3);
759 globalThis.end = small.transfer(50);
760 "#,
761 )
762 .unwrap();
763
764 let end: ArrayBuffer = ctx.globals().get("end").unwrap();
765 let bytes = unsafe { end.as_bytes() }.unwrap();
766 assert_eq!(bytes.len(), 50);
767 assert_eq!(&bytes[..3], &[1, 2, 3]);
768 assert!(bytes[3..].iter().all(|&b| b == 0));
769 });
770 }
771
772 #[test]
773 fn transfer_to_different_length_preserves_external_buffer() {
774 use core::sync::atomic::{AtomicUsize, Ordering};
775
776 struct Tracker {
777 data: alloc::boxed::Box<[u8]>,
778 drops: Arc<AtomicUsize>,
779 }
780
781 unsafe impl ArrayBufferSource for Tracker {
782 fn as_ptr(&self) -> *mut u8 {
783 self.data.as_ptr()
784 }
785
786 fn len(&self) -> usize {
787 self.data.len()
788 }
789 }
790
791 impl Drop for Tracker {
792 fn drop(&mut self) {
793 self.drops.fetch_add(1, Ordering::SeqCst);
794 }
795 }
796
797 let drops = Arc::new(AtomicUsize::new(0));
798 let rt = crate::Runtime::new().unwrap();
799 let c = crate::Context::full(&rt).unwrap();
800 c.with(|ctx| {
801 let src = Tracker {
802 data: alloc::vec![1u8, 2, 3, 4].into_boxed_slice(),
803 drops: drops.clone(),
804 };
805 let ab = ArrayBuffer::from_source(ctx.clone(), src).unwrap();
806 ctx.globals().set("buf", ab).unwrap();
807
808 assert!(ctx.eval::<(), _>("buf.transfer(8)").is_err());
809 drop(ctx.catch());
810 assert_eq!(drops.load(Ordering::SeqCst), 0);
811
812 let ab: ArrayBuffer = ctx.globals().get("buf").unwrap();
813 assert_eq!(unsafe { ab.as_bytes() }.unwrap(), &[1, 2, 3, 4]);
814 });
815
816 drop(c);
817 drop(rt);
818 assert_eq!(drops.load(Ordering::SeqCst), 1);
819 }
820
821 #[test]
822 fn from_source_immutable_arc() {
823 let arc: Arc<[u8]> = Arc::from((0u8..8).collect::<Vec<_>>().into_boxed_slice());
824 let weak = Arc::downgrade(&arc);
825 assert_eq!(Arc::strong_count(&arc), 1);
826
827 let rt = crate::Runtime::new().unwrap();
828 let c = crate::Context::full(&rt).unwrap();
829
830 c.with(|ctx| {
832 let _ab = ArrayBuffer::from_source_immutable(ctx.clone(), arc.clone()).unwrap();
833 assert_eq!(Arc::strong_count(&arc), 2, "Arc cloned into drop closure");
834 });
836 assert_eq!(
837 Arc::strong_count(&arc),
838 1,
839 "drop closure must release its Arc clone when ArrayBuffer is freed"
840 );
841 assert!(
842 weak.upgrade().is_some(),
843 "allocation must stay alive while Rust still holds the Arc"
844 );
845
846 c.with(|ctx| {
848 let ab = ArrayBuffer::from_source_immutable(ctx.clone(), arc.clone()).unwrap();
849 ctx.globals().set("buf", ab).unwrap();
850 assert_eq!(Arc::strong_count(&arc), 2);
851 });
852 drop(arc);
853 assert!(
854 weak.upgrade().is_some(),
855 "allocation must stay alive: JS still holds the buffer via the Arc clone in the closure"
856 );
857 assert_eq!(
858 weak.strong_count(),
859 1,
860 "only the closure's Arc clone should remain"
861 );
862
863 drop(c);
865 drop(rt);
866 assert!(
867 weak.upgrade().is_none(),
868 "allocation must be freed after both Rust and JS release their handles"
869 );
870 }
871
872 #[cfg(feature = "bytes")]
873 #[test]
874 fn from_source_immutable_bytes() {
875 let data: bytes::Bytes = (0u8..8).collect::<Vec<_>>().into();
876 let rt = crate::Runtime::new().unwrap();
877 let c = crate::Context::full(&rt).unwrap();
878 c.with(|ctx| {
879 let ab = ArrayBuffer::from_source_immutable(ctx.clone(), data.clone()).unwrap();
880 assert_eq!(
881 unsafe { ab.as_bytes() }.unwrap(),
882 (0u8..8).collect::<Vec<_>>()
883 );
884 });
885 }
886}