Skip to main content

boa_engine/object/builtins/
jsarraybuffer.rs

1//! A Rust API wrapper for Boa's `ArrayBuffer` Builtin ECMAScript Object
2use crate::{
3    Context, JsResult, JsValue,
4    builtins::array_buffer::ArrayBuffer,
5    context::intrinsics::StandardConstructors,
6    error::JsNativeError,
7    object::{JsObject, internal_methods::get_prototype_from_constructor},
8    value::TryFromJs,
9};
10use boa_gc::{Finalize, GcRef, GcRefMut, Trace};
11use std::ops::Deref;
12
13#[doc(inline)]
14pub use crate::builtins::array_buffer::AlignedVec;
15
16/// `JsArrayBuffer` provides a wrapper for Boa's implementation of the ECMAScript `ArrayBuffer` object
17#[derive(Debug, Clone, Trace, Finalize)]
18#[boa_gc(unsafe_no_drop)]
19pub struct JsArrayBuffer {
20    inner: JsObject<ArrayBuffer>,
21}
22
23impl From<JsArrayBuffer> for JsObject<ArrayBuffer> {
24    #[inline]
25    fn from(value: JsArrayBuffer) -> Self {
26        value.inner
27    }
28}
29
30impl From<JsObject<ArrayBuffer>> for JsArrayBuffer {
31    #[inline]
32    fn from(value: JsObject<ArrayBuffer>) -> Self {
33        Self { inner: value }
34    }
35}
36
37// TODO: Add constructors that also take the `detach_key` as argument.
38impl JsArrayBuffer {
39    /// Create a new array buffer with byte length.
40    ///
41    /// ```
42    /// # use boa_engine::{
43    /// # object::builtins::JsArrayBuffer,
44    /// # Context, JsResult, JsValue
45    /// # };
46    /// # fn main() -> JsResult<()> {
47    /// # // Initialize context
48    /// # let context = &mut Context::default();
49    /// // Creates a blank array buffer of n bytes
50    /// let array_buffer = JsArrayBuffer::new(4, context)?;
51    ///
52    /// assert_eq!(
53    ///     array_buffer.detach(&JsValue::undefined())?.as_slice(),
54    ///     &[0u8, 0, 0, 0]
55    /// );
56    ///
57    /// # Ok(())
58    /// # }
59    /// ```
60    #[inline]
61    pub fn new(byte_length: usize, context: &mut Context) -> JsResult<Self> {
62        let inner = ArrayBuffer::allocate(
63            &context
64                .intrinsics()
65                .constructors()
66                .array_buffer()
67                .constructor()
68                .into(),
69            byte_length as u64,
70            None,
71            context,
72        )?;
73
74        Ok(Self { inner })
75    }
76
77    /// Create a new array buffer from byte block.
78    ///
79    /// This uses the passed byte block as the internal storage, it does not clone it!
80    ///
81    /// The `byte_length` will be set to `byte_block.len()`.
82    ///
83    /// ```
84    /// # use boa_engine::{
85    /// # object::builtins::{JsArrayBuffer, AlignedVec},
86    /// # Context, JsResult, JsValue,
87    /// # };
88    /// # fn main() -> JsResult<()> {
89    /// # // Initialize context
90    /// # let context = &mut Context::default();
91    ///
92    /// // Create a buffer from a chunk of data
93    /// let data_block = AlignedVec::from_iter(0, 0..5);
94    /// let array_buffer = JsArrayBuffer::from_byte_block(data_block, context)?;
95    ///
96    /// assert_eq!(
97    ///     array_buffer.detach(&JsValue::undefined())?.as_slice(),
98    ///     &[0u8, 1, 2, 3, 4]
99    /// );
100    /// # Ok(())
101    /// # }
102    /// ```
103    pub fn from_byte_block(byte_block: AlignedVec<u8>, context: &mut Context) -> JsResult<Self> {
104        let constructor = context
105            .intrinsics()
106            .constructors()
107            .array_buffer()
108            .constructor()
109            .into();
110
111        // 1. Let obj be ? OrdinaryCreateFromConstructor(constructor, "%ArrayBuffer.prototype%", « [[ArrayBufferData]], [[ArrayBufferByteLength]], [[ArrayBufferDetachKey]] »).
112        let prototype = get_prototype_from_constructor(
113            &constructor,
114            StandardConstructors::array_buffer,
115            context,
116        )?;
117
118        // 2. Let block be ? CreateByteDataBlock(byteLength).
119        //
120        // NOTE: We skip step 2. because we already have the block
121        // that is passed to us as a function argument.
122        let block = byte_block;
123
124        // 3. Set obj.[[ArrayBufferData]] to block.
125        // 4. Set obj.[[ArrayBufferByteLength]] to byteLength.
126        let obj = JsObject::new(
127            context.root_shape(),
128            prototype,
129            ArrayBuffer::from_data(block, JsValue::undefined()),
130        );
131
132        Ok(Self { inner: obj })
133    }
134
135    /// Set a maximum length for the underlying array buffer.
136    #[inline]
137    #[must_use]
138    pub fn with_max_byte_length(self, max_byte_len: u64) -> Self {
139        self.inner
140            .borrow_mut()
141            .data_mut()
142            .set_max_byte_length(max_byte_len);
143        self
144    }
145
146    /// Create a [`JsArrayBuffer`] from a [`JsObject`], if the object is not an array buffer throw a `TypeError`.
147    ///
148    /// This does not clone the fields of the array buffer, it only does a shallow clone of the object.
149    #[inline]
150    pub fn from_object(object: JsObject) -> JsResult<Self> {
151        object
152            .downcast::<ArrayBuffer>()
153            .map(|inner| Self { inner })
154            .map_err(|_| {
155                JsNativeError::typ()
156                    .with_message("object is not an ArrayBuffer")
157                    .into()
158            })
159    }
160
161    /// Returns the byte length of the array buffer.
162    ///
163    /// ```
164    /// # use boa_engine::{
165    /// # object::builtins::{JsArrayBuffer, AlignedVec},
166    /// # Context, JsResult,
167    /// # };
168    /// # fn main() -> JsResult<()> {
169    /// # // Initialize context
170    /// # let context = &mut Context::default();
171    /// // Create a buffer from a chunk of data
172    /// let data_block = AlignedVec::from_iter(0, 0..5);
173    /// let array_buffer = JsArrayBuffer::from_byte_block(data_block, context)?;
174    ///
175    /// // Take the inner buffer
176    /// let buffer_length = array_buffer.byte_length();
177    ///
178    /// assert_eq!(buffer_length, 5);
179    /// # Ok(())
180    /// # }
181    ///  ```
182    #[inline]
183    #[must_use]
184    pub fn byte_length(&self) -> usize {
185        self.inner.borrow().data().len()
186    }
187
188    /// Take the inner `ArrayBuffer`'s `array_buffer_data` field and replace it with `None`
189    ///
190    /// # Note
191    ///
192    /// This tries to detach the pre-existing `JsArrayBuffer`, meaning the original detach
193    /// key is required. By default, the key is set to `undefined`.
194    ///
195    /// ```
196    /// # use boa_engine::{
197    /// # object::builtins::{JsArrayBuffer, AlignedVec},
198    /// # Context, JsResult, JsValue
199    /// # };
200    /// # fn main() -> JsResult<()> {
201    /// # // Initialize context
202    /// # let context = &mut Context::default();
203    /// // Create a buffer from a chunk of data
204    /// let data_block = AlignedVec::from_iter(0, 0..5);
205    /// let array_buffer = JsArrayBuffer::from_byte_block(data_block, context)?;
206    ///
207    /// // Take the inner buffer
208    /// let internal_buffer = array_buffer.detach(&JsValue::undefined())?;
209    ///
210    /// assert_eq!(internal_buffer.as_slice(), &[0u8, 1, 2, 3, 4]);
211    ///
212    /// // Anymore interaction with the buffer will return an error
213    /// let detached_err = array_buffer.detach(&JsValue::undefined());
214    /// assert!(detached_err.is_err());
215    /// # Ok(())
216    /// # }
217    /// ```
218    #[inline]
219    pub fn detach(&self, detach_key: &JsValue) -> JsResult<AlignedVec<u8>> {
220        self.inner
221            .borrow_mut()
222            .data_mut()
223            .detach(detach_key)?
224            .ok_or_else(|| {
225                JsNativeError::typ()
226                    .with_message("ArrayBuffer was already detached")
227                    .into()
228            })
229    }
230
231    /// Get an immutable reference to the [`JsArrayBuffer`]'s data.
232    ///
233    /// Returns `None` if detached.
234    ///
235    /// ```
236    /// # use boa_engine::{
237    /// # object::builtins::{JsArrayBuffer, AlignedVec},
238    /// # Context, JsResult, JsValue,
239    /// # };
240    /// # fn main() -> JsResult<()> {
241    /// # // Initialize context
242    /// # let context = &mut Context::default();
243    /// // Create a buffer from a chunk of data
244    /// let data_block = AlignedVec::from_iter(0, 0..5);
245    /// let array_buffer = JsArrayBuffer::from_byte_block(data_block, context)?;
246    ///
247    /// // Get a reference to the data.
248    /// let internal_buffer = array_buffer.data();
249    ///
250    /// assert_eq!(internal_buffer.as_deref(), Some(&[0u8, 1, 2, 3, 4][..]));
251    /// # Ok(())
252    /// # }
253    /// ```
254    #[inline]
255    #[must_use]
256    pub fn data(&self) -> Option<GcRef<'_, [u8]>> {
257        GcRef::try_map(self.inner.borrow(), |o| o.data().bytes())
258    }
259
260    /// Copies the contents of this [`JsArrayBuffer`] into a new [`Vec<u8>`].
261    ///
262    /// Returns `None` if the buffer has been detached.
263    ///
264    /// See also [`crate::object::builtins::JsUint8Array::to_vec`] and
265    /// [`crate::object::builtins::JsSharedArrayBuffer::to_vec`].
266    ///
267    /// # Example
268    ///
269    /// ```
270    /// # use boa_engine::object::builtins::{AlignedVec, JsArrayBuffer};
271    /// # use boa_engine::{Context, JsResult, JsValue};
272    /// # fn main() -> JsResult<()> {
273    /// let context = &mut Context::default();
274    /// let data = AlignedVec::from_iter(0, [1u8, 2, 3, 4]);
275    /// let buffer = JsArrayBuffer::from_byte_block(data, context)?;
276    /// assert_eq!(buffer.to_vec(), Some(vec![1u8, 2, 3, 4]));
277    ///
278    /// buffer.detach(&JsValue::undefined())?;
279    /// assert_eq!(buffer.to_vec(), None);
280    /// # Ok(())
281    /// # }
282    /// ```
283    #[inline]
284    #[must_use]
285    pub fn to_vec(&self) -> Option<Vec<u8>> {
286        self.data().map(|data| data.to_vec())
287    }
288
289    /// Get a mutable reference to the [`JsArrayBuffer`]'s data.
290    ///
291    /// Returns `None` if detached.
292    ///
293    /// ```
294    /// # use boa_engine::{
295    /// # object::builtins::{JsArrayBuffer, AlignedVec},
296    /// # Context, JsResult, JsValue
297    /// # };
298    /// # fn main() -> JsResult<()> {
299    /// # // Initialize context
300    /// # let context = &mut Context::default();
301    /// // Create a buffer from a chunk of data
302    /// let data_block = AlignedVec::from_iter(0, 0..5);
303    /// let array_buffer = JsArrayBuffer::from_byte_block(data_block, context)?;
304    ///
305    /// // Get a reference to the data.
306    /// let mut internal_buffer = array_buffer
307    ///     .data_mut()
308    ///     .expect("the buffer should not be detached");
309    ///
310    /// internal_buffer.fill(10);
311    ///
312    /// assert_eq!(&*internal_buffer, &[10u8, 10, 10, 10, 10]);
313    /// # Ok(())
314    /// # }
315    /// ```
316    #[inline]
317    #[must_use]
318    pub fn data_mut(&self) -> Option<GcRefMut<'_, [u8]>> {
319        GcRefMut::try_map(self.inner.borrow_mut(), |o| o.data_mut().bytes_mut())
320    }
321}
322
323impl From<JsArrayBuffer> for JsObject {
324    #[inline]
325    fn from(o: JsArrayBuffer) -> Self {
326        o.inner.upcast()
327    }
328}
329
330impl From<JsArrayBuffer> for JsValue {
331    #[inline]
332    fn from(o: JsArrayBuffer) -> Self {
333        o.inner.upcast().into()
334    }
335}
336
337impl Deref for JsArrayBuffer {
338    type Target = JsObject<ArrayBuffer>;
339
340    #[inline]
341    fn deref(&self) -> &Self::Target {
342        &self.inner
343    }
344}
345
346impl TryFromJs for JsArrayBuffer {
347    fn try_from_js(value: &JsValue, _context: &mut Context) -> JsResult<Self> {
348        if let Some(o) = value.as_object() {
349            Self::from_object(o.clone())
350        } else {
351            Err(JsNativeError::typ()
352                .with_message("value is not an ArrayBuffer object")
353                .into())
354        }
355    }
356}
357
358#[cfg(test)]
359mod tests {
360    use super::*;
361    use crate::{Context, JsValue};
362
363    #[test]
364    fn array_buffer_to_vec_roundtrip_and_detach() {
365        let context = &mut Context::default();
366
367        let data = AlignedVec::from_iter(0, [1u8, 2, 3, 4, 5]);
368        let buffer = JsArrayBuffer::from_byte_block(data, context).unwrap();
369
370        assert_eq!(buffer.to_vec(), Some(vec![1u8, 2, 3, 4, 5]));
371
372        buffer.detach(&JsValue::undefined()).unwrap();
373        assert_eq!(buffer.to_vec(), None);
374    }
375
376    #[test]
377    fn array_buffer_to_vec_empty() {
378        let context = &mut Context::default();
379
380        let data = AlignedVec::from_iter(0, []);
381        let buffer = JsArrayBuffer::from_byte_block(data, context).unwrap();
382
383        assert_eq!(buffer.to_vec(), Some(Vec::new()));
384    }
385}