Skip to main content

boa_engine/object/builtins/
jssharedarraybuffer.rs

1//! A Rust API wrapper for Boa's `SharedArrayBuffer` Builtin ECMAScript Object
2use crate::{
3    Context, JsResult, JsValue,
4    builtins::array_buffer::{SharedArrayBuffer, utils::SliceRef},
5    error::JsNativeError,
6    object::JsObject,
7    value::TryFromJs,
8};
9use boa_gc::{Finalize, Trace};
10use std::{ops::Deref, sync::atomic::Ordering};
11
12/// `JsSharedArrayBuffer` provides a wrapper for Boa's implementation of the ECMAScript `ArrayBuffer` object
13#[derive(Debug, Clone, Trace, Finalize)]
14#[boa_gc(unsafe_no_drop)]
15pub struct JsSharedArrayBuffer {
16    inner: JsObject<SharedArrayBuffer>,
17}
18
19impl From<JsSharedArrayBuffer> for JsObject<SharedArrayBuffer> {
20    #[inline]
21    fn from(value: JsSharedArrayBuffer) -> Self {
22        value.inner
23    }
24}
25
26impl From<JsObject<SharedArrayBuffer>> for JsSharedArrayBuffer {
27    #[inline]
28    fn from(value: JsObject<SharedArrayBuffer>) -> Self {
29        JsSharedArrayBuffer { inner: value }
30    }
31}
32
33impl JsSharedArrayBuffer {
34    /// Creates a new [`JsSharedArrayBuffer`] with `byte_length` bytes of allocated space.
35    #[inline]
36    pub fn new(byte_length: usize, context: &mut Context) -> JsResult<Self> {
37        let inner = SharedArrayBuffer::allocate(
38            &context
39                .intrinsics()
40                .constructors()
41                .shared_array_buffer()
42                .constructor()
43                .into(),
44            byte_length as u64,
45            None,
46            context,
47        )?;
48
49        Ok(Self { inner })
50    }
51
52    /// Creates a [`JsSharedArrayBuffer`] from a shared raw buffer.
53    #[inline]
54    pub fn from_buffer(buffer: SharedArrayBuffer, context: &mut Context) -> Self {
55        let proto = context
56            .intrinsics()
57            .constructors()
58            .shared_array_buffer()
59            .prototype();
60
61        let inner = JsObject::new(context.root_shape(), proto, buffer);
62
63        Self { inner }
64    }
65
66    /// Creates a [`JsSharedArrayBuffer`] from a [`JsObject`], throwing a `TypeError` if the object
67    /// is not a shared array buffer.
68    ///
69    /// This does not clone the fields of the shared array buffer, it only does a shallow clone of
70    /// the object.
71    #[inline]
72    pub fn from_object(object: JsObject) -> JsResult<Self> {
73        object
74            .downcast::<SharedArrayBuffer>()
75            .map(|inner| Self { inner })
76            .map_err(|_| {
77                JsNativeError::typ()
78                    .with_message("object is not a SharedArrayBuffer")
79                    .into()
80            })
81    }
82
83    /// Returns the byte length of the array buffer.
84    #[inline]
85    #[must_use]
86    pub fn byte_length(&self) -> usize {
87        self.borrow().data().len(Ordering::SeqCst)
88    }
89
90    /// Copies the contents of this [`JsSharedArrayBuffer`] into a new [`Vec<u8>`].
91    ///
92    /// Each byte is loaded with `SeqCst` ordering into the returned buffer.
93    /// GC-safe and safe for concurrent access within Boa's memory model.
94    ///
95    /// # Example
96    ///
97    /// ```
98    /// # use boa_engine::{Context, JsResult, object::builtins::JsSharedArrayBuffer};
99    /// # fn main() -> JsResult<()> {
100    /// let context = &mut Context::default();
101    /// let sab = JsSharedArrayBuffer::new(64, context)?;
102    /// let bytes = sab.to_vec();
103    /// assert_eq!(bytes.len(), 64);
104    /// # Ok(())
105    /// # }
106    /// ```
107    #[must_use]
108    pub fn to_vec(&self) -> Vec<u8> {
109        let obj = self.borrow();
110        let src = obj.data().bytes(Ordering::SeqCst);
111        let src = SliceRef::AtomicSlice(src);
112        src.to_vec()
113    }
114
115    /// Gets the raw buffer of this `JsSharedArrayBuffer`.
116    #[inline]
117    #[must_use]
118    pub fn inner(&self) -> SharedArrayBuffer {
119        self.borrow().data().clone()
120    }
121}
122
123impl From<JsSharedArrayBuffer> for JsObject {
124    #[inline]
125    fn from(o: JsSharedArrayBuffer) -> Self {
126        o.inner.upcast()
127    }
128}
129
130impl From<JsSharedArrayBuffer> for JsValue {
131    #[inline]
132    fn from(o: JsSharedArrayBuffer) -> Self {
133        o.inner.upcast().into()
134    }
135}
136
137impl Deref for JsSharedArrayBuffer {
138    type Target = JsObject<SharedArrayBuffer>;
139
140    #[inline]
141    fn deref(&self) -> &Self::Target {
142        &self.inner
143    }
144}
145
146impl TryFromJs for JsSharedArrayBuffer {
147    fn try_from_js(value: &JsValue, _context: &mut Context) -> JsResult<Self> {
148        if let Some(o) = value.as_object() {
149            Self::from_object(o.clone())
150        } else {
151            Err(JsNativeError::typ()
152                .with_message("value is not a SharedArrayBuffer object")
153                .into())
154        }
155    }
156}
157
158#[cfg(test)]
159mod tests {
160    use super::*;
161
162    #[test]
163    fn shared_array_buffer_to_vec_roundtrip() {
164        let context = &mut Context::default();
165        let len = 64;
166        let sab = JsSharedArrayBuffer::new(len, context).unwrap();
167        assert_eq!(sab.byte_length(), len);
168
169        // Write a pattern at multiple indices and ensure `to_vec` observes it.
170        let inner = sab.inner();
171        let atoms = inner.bytes(Ordering::SeqCst);
172        atoms[0].store(1, Ordering::SeqCst);
173        atoms[1].store(2, Ordering::SeqCst);
174        atoms[len - 1].store(255, Ordering::SeqCst);
175
176        let bytes = sab.to_vec();
177        assert_eq!(bytes.len(), len);
178        assert_eq!(bytes[0], 1);
179        assert_eq!(bytes[1], 2);
180        assert_eq!(bytes[len - 1], 255);
181    }
182
183    #[test]
184    fn shared_array_buffer_to_vec_zero_length() {
185        let context = &mut Context::default();
186        let sab = JsSharedArrayBuffer::new(0, context).unwrap();
187        assert_eq!(sab.byte_length(), 0);
188
189        let bytes = sab.to_vec();
190        assert!(bytes.is_empty());
191    }
192}