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}