boa_engine/object/builtins/
jsdataview.rs

1//! A Rust API wrapper for Boa's `DataView` Builtin ECMAScript Object
2use crate::{
3    Context, JsNativeError, JsResult, JsValue,
4    builtins::{DataView, array_buffer::BufferObject},
5    object::{JsArrayBuffer, JsObject},
6    value::TryFromJs,
7};
8
9use boa_gc::{Finalize, Trace};
10use std::ops::Deref;
11
12/// `JsDataView` provides a wrapper for Boa's implementation of the ECMAScript `DataView` object
13///
14/// # Examples
15/// ```
16/// # use boa_engine::{
17/// #     object::builtins::{JsArrayBuffer, JsDataView},
18/// #     Context, JsValue, JsResult,
19/// # };
20/// # fn main() -> JsResult<()> {
21/// // Create a new context and ArrayBuffer
22/// let context = &mut Context::default();
23/// let array_buffer = JsArrayBuffer::new(4, context)?;
24///
25/// // Create a new Dataview from pre-existing ArrayBuffer
26/// let data_view =
27///     JsDataView::from_js_array_buffer(array_buffer, None, None, context)?;
28///
29/// # Ok(())
30/// # }
31/// ```
32#[derive(Debug, Clone, Trace, Finalize)]
33#[boa_gc(unsafe_no_drop)]
34pub struct JsDataView {
35    inner: JsObject<DataView>,
36}
37
38impl From<JsDataView> for JsObject<DataView> {
39    #[inline]
40    fn from(value: JsDataView) -> Self {
41        value.inner
42    }
43}
44
45impl From<JsObject<DataView>> for JsDataView {
46    #[inline]
47    fn from(value: JsObject<DataView>) -> Self {
48        Self { inner: value }
49    }
50}
51
52impl JsDataView {
53    /// Create a new `JsDataView` object from an existing `JsArrayBuffer`.
54    pub fn from_js_array_buffer(
55        buffer: JsArrayBuffer,
56        offset: Option<u64>,
57        byte_len: Option<u64>,
58        context: &mut Context,
59    ) -> JsResult<Self> {
60        let offset = offset.unwrap_or_default();
61
62        let (buf_byte_len, is_fixed_len) = {
63            let buffer = buffer.borrow();
64            let buffer = buffer.data();
65
66            // 4. If IsDetachedBuffer(buffer) is true, throw a TypeError exception.
67            let Some(slice) = buffer.bytes() else {
68                return Err(JsNativeError::typ()
69                    .with_message("ArrayBuffer is detached")
70                    .into());
71            };
72
73            // 5. Let bufferByteLength be ArrayBufferByteLength(buffer, seq-cst).
74            let buf_len = slice.len() as u64;
75
76            // 6. If offset > bufferByteLength, throw a RangeError exception.
77            if offset > buf_len {
78                return Err(JsNativeError::range()
79                    .with_message("Start offset is outside the bounds of the buffer")
80                    .into());
81            }
82
83            // 7. Let bufferIsFixedLength be IsFixedLengthArrayBuffer(buffer).
84            (buf_len, buffer.is_fixed_len())
85        };
86
87        // 8. If byteLength is undefined, then
88        let view_byte_len = if let Some(byte_len) = byte_len {
89            // 9. Else,
90            //     a. Let viewByteLength be ? ToIndex(byteLength).
91            //     b. If offset + viewByteLength > bufferByteLength, throw a RangeError exception.
92            if offset + byte_len > buf_byte_len {
93                return Err(JsNativeError::range()
94                    .with_message("Invalid data view length")
95                    .into());
96            }
97
98            Some(byte_len)
99        } else {
100            // a. If bufferIsFixedLength is true, then
101            //     i. Let viewByteLength be bufferByteLength - offset.
102            // b. Else,
103            //     i. Let viewByteLength be auto.
104            is_fixed_len.then_some(buf_byte_len - offset)
105        };
106
107        // 10. Let O be ? OrdinaryCreateFromConstructor(NewTarget, "%DataView.prototype%",
108        //     « [[DataView]], [[ViewedArrayBuffer]], [[ByteLength]], [[ByteOffset]] »).
109        let prototype = context.intrinsics().constructors().data_view().prototype();
110
111        // 11. If IsDetachedBuffer(buffer) is true, throw a TypeError exception.
112        // 12. Set bufferByteLength to ArrayBufferByteLength(buffer, seq-cst).
113        let Some(buf_byte_len) = buffer.borrow().data().bytes().map(|s| s.len() as u64) else {
114            return Err(JsNativeError::typ()
115                .with_message("ArrayBuffer is detached")
116                .into());
117        };
118
119        // 13. If offset > bufferByteLength, throw a RangeError exception.
120        if offset > buf_byte_len {
121            return Err(JsNativeError::range()
122                .with_message("DataView offset outside of buffer array bounds")
123                .into());
124        }
125
126        // 14. If byteLength is not undefined, then
127        //     a. If offset + viewByteLength > bufferByteLength, throw a RangeError exception.
128        if let Some(view_byte_len) = view_byte_len
129            && byte_len.is_some()
130            && offset + view_byte_len > buf_byte_len
131        {
132            return Err(JsNativeError::range()
133                .with_message("DataView offset outside of buffer array bounds")
134                .into());
135        }
136
137        let obj = JsObject::new(
138            context.root_shape(),
139            prototype,
140            DataView {
141                // 15. Set O.[[ViewedArrayBuffer]] to buffer.
142                viewed_array_buffer: BufferObject::Buffer(buffer.into()),
143                // 16. Set O.[[ByteLength]] to viewByteLength.
144                byte_length: view_byte_len,
145                // 17. Set O.[[ByteOffset]] to offset.
146                byte_offset: offset,
147            },
148        );
149
150        // 18. Return O.
151        Ok(Self { inner: obj })
152    }
153
154    /// Create a new `JsDataView` object from an existing object.
155    #[inline]
156    pub fn from_object(object: JsObject) -> JsResult<Self> {
157        object
158            .downcast::<DataView>()
159            .map(|inner| Self { inner })
160            .map_err(|_| {
161                JsNativeError::typ()
162                    .with_message("object is not a DataView")
163                    .into()
164            })
165    }
166
167    /// Returns the `viewed_array_buffer` field for [`JsDataView`]
168    #[inline]
169    pub fn buffer(&self, context: &mut Context) -> JsResult<JsValue> {
170        DataView::get_buffer(&self.inner.clone().upcast().into(), &[], context)
171    }
172
173    /// Returns the `byte_length` property of [`JsDataView`] as a u64 integer
174    #[inline]
175    pub fn byte_length(&self, context: &mut Context) -> JsResult<u64> {
176        DataView::get_byte_length(&self.inner.clone().upcast().into(), &[], context)
177            .map(|v| v.as_number().expect("value should be a number") as u64)
178    }
179
180    /// Returns the `byte_offset` field property of [`JsDataView`] as a u64 integer
181    #[inline]
182    pub fn byte_offset(&self, context: &mut Context) -> JsResult<u64> {
183        DataView::get_byte_offset(&self.inner.clone().upcast().into(), &[], context)
184            .map(|v| v.as_number().expect("byte_offset value must be a number") as u64)
185    }
186
187    /// Returns a signed 64-bit integer at the specified offset from the start of the [`JsDataView`]
188    #[inline]
189    pub fn get_big_int64(
190        &self,
191        byte_offset: usize,
192        is_little_endian: bool,
193        context: &mut Context,
194    ) -> JsResult<i64> {
195        DataView::get_big_int64(
196            &self.inner.clone().upcast().into(),
197            &[byte_offset.into(), is_little_endian.into()],
198            context,
199        )
200        .map(|v| v.as_number().expect("value must be a number") as i64)
201    }
202
203    /// Returns an unsigned 64-bit integer at the specified offset from the start of the [`JsDataView`]
204    #[inline]
205    pub fn get_big_uint64(
206        &self,
207        byte_offset: usize,
208        is_little_endian: bool,
209        context: &mut Context,
210    ) -> JsResult<u64> {
211        DataView::get_big_uint64(
212            &self.inner.clone().upcast().into(),
213            &[byte_offset.into(), is_little_endian.into()],
214            context,
215        )
216        .map(|v| v.as_number().expect("value must be a number") as u64)
217    }
218
219    /// Returns a signed 32-bit float integer at the specified offset from the start of the [`JsDataView`]
220    #[inline]
221    pub fn get_float32(
222        &self,
223        byte_offset: usize,
224        is_little_endian: bool,
225        context: &mut Context,
226    ) -> JsResult<f32> {
227        DataView::get_float32(
228            &self.inner.clone().upcast().into(),
229            &[byte_offset.into(), is_little_endian.into()],
230            context,
231        )
232        .map(|v| v.as_number().expect("value must be a number") as f32)
233    }
234
235    /// Returns a signed 64-bit float integer at the specified offset from the start of the [`JsDataView`]
236    #[inline]
237    pub fn get_float64(
238        &self,
239        byte_offset: usize,
240        is_little_endian: bool,
241        context: &mut Context,
242    ) -> JsResult<f64> {
243        DataView::get_float64(
244            &self.inner.clone().upcast().into(),
245            &[byte_offset.into(), is_little_endian.into()],
246            context,
247        )
248        .map(|v| v.as_number().expect("value must be a number"))
249    }
250
251    /// Returns a signed 8-bit integer at the specified offset from the start of the [`JsDataView`]
252    #[inline]
253    pub fn get_int8(
254        &self,
255        byte_offset: usize,
256        is_little_endian: bool,
257        context: &mut Context,
258    ) -> JsResult<i8> {
259        DataView::get_int8(
260            &self.inner.clone().upcast().into(),
261            &[byte_offset.into(), is_little_endian.into()],
262            context,
263        )
264        .map(|v| v.as_number().expect("value must be a number") as i8)
265    }
266
267    /// Returns a signed 16-bit integer at the specified offset from the start of the [`JsDataView`]
268    #[inline]
269    pub fn get_int16(
270        &self,
271        byte_offset: usize,
272        is_little_endian: bool,
273        context: &mut Context,
274    ) -> JsResult<i16> {
275        DataView::get_int16(
276            &self.inner.clone().upcast().into(),
277            &[byte_offset.into(), is_little_endian.into()],
278            context,
279        )
280        .map(|v| v.as_number().expect("value must be a number") as i16)
281    }
282
283    /// Returns a signed 32-bit integer at the specified offset from the start of the [`JsDataView`]
284    #[inline]
285    pub fn get_int32(
286        &self,
287        byte_offset: usize,
288        is_little_endian: bool,
289        context: &mut Context,
290    ) -> JsResult<i32> {
291        DataView::get_int32(
292            &self.inner.clone().upcast().into(),
293            &[byte_offset.into(), is_little_endian.into()],
294            context,
295        )
296        .map(|v| v.as_number().expect("value must be a number") as i32)
297    }
298
299    /// Returns an unsigned 8-bit integer at the specified offset from the start of the [`JsDataView`]
300    #[inline]
301    pub fn get_uint8(
302        &self,
303        byte_offset: usize,
304        is_little_endian: bool,
305        context: &mut Context,
306    ) -> JsResult<u8> {
307        DataView::get_uint8(
308            &self.inner.clone().upcast().into(),
309            &[byte_offset.into(), is_little_endian.into()],
310            context,
311        )
312        .map(|v| v.as_number().expect("value must be a number") as u8)
313    }
314
315    /// Returns an unsigned 16-bit integer at the specified offset from the start of the [`JsDataView`]
316    #[inline]
317    pub fn get_unit16(
318        &self,
319        byte_offset: usize,
320        is_little_endian: bool,
321        context: &mut Context,
322    ) -> JsResult<u16> {
323        DataView::get_uint16(
324            &self.inner.clone().upcast().into(),
325            &[byte_offset.into(), is_little_endian.into()],
326            context,
327        )
328        .map(|v| v.as_number().expect("value must be a number") as u16)
329    }
330
331    /// Returns an unsigned 32-bit integer at the specified offset from the start of the [`JsDataView`]
332    #[inline]
333    pub fn get_uint32(
334        &self,
335        byte_offset: usize,
336        is_little_endian: bool,
337        context: &mut Context,
338    ) -> JsResult<u32> {
339        DataView::get_uint32(
340            &self.inner.clone().upcast().into(),
341            &[byte_offset.into(), is_little_endian.into()],
342            context,
343        )
344        .map(|v| v.as_number().expect("value must be a number") as u32)
345    }
346
347    /// Sets a signed 64-bit integer at the specified offset from the start of the [`JsDataView`]
348    #[inline]
349    pub fn set_big_int64(
350        &self,
351        byte_offset: usize,
352        value: i64,
353        is_little_endian: bool,
354        context: &mut Context,
355    ) -> JsResult<JsValue> {
356        DataView::set_big_int64(
357            &self.inner.clone().upcast().into(),
358            &[byte_offset.into(), value.into(), is_little_endian.into()],
359            context,
360        )
361    }
362
363    /// Sets an unsigned 64-bit integer at the specified offset from the start of the [`JsDataView`]
364    #[inline]
365    pub fn set_big_uint64(
366        &self,
367        byte_offset: usize,
368        value: u64,
369        is_little_endian: bool,
370        context: &mut Context,
371    ) -> JsResult<JsValue> {
372        DataView::set_big_uint64(
373            &self.inner.clone().upcast().into(),
374            &[byte_offset.into(), value.into(), is_little_endian.into()],
375            context,
376        )
377    }
378
379    /// Sets a signed 32-bit integer at the specified offset from the start of the [`JsDataView`]
380    #[inline]
381    pub fn set_float32(
382        &self,
383        byte_offset: usize,
384        value: f32,
385        is_little_endian: bool,
386        context: &mut Context,
387    ) -> JsResult<JsValue> {
388        DataView::set_float32(
389            &self.inner.clone().upcast().into(),
390            &[byte_offset.into(), value.into(), is_little_endian.into()],
391            context,
392        )
393    }
394
395    /// Sets a signed 64-bit integer at the specified offset from the start of the [`JsDataView`]
396    #[inline]
397    pub fn set_float64(
398        &self,
399        byte_offset: usize,
400        value: f64,
401        is_little_endian: bool,
402        context: &mut Context,
403    ) -> JsResult<JsValue> {
404        DataView::set_float64(
405            &self.inner.clone().upcast().into(),
406            &[byte_offset.into(), value.into(), is_little_endian.into()],
407            context,
408        )
409    }
410
411    /// Sets a signed 8-bit integer at the specified offset from the start of the [`JsDataView`]
412    #[inline]
413    pub fn set_int8(
414        &self,
415        byte_offset: usize,
416        value: i8,
417        is_little_endian: bool,
418        context: &mut Context,
419    ) -> JsResult<JsValue> {
420        DataView::set_int8(
421            &self.inner.clone().upcast().into(),
422            &[byte_offset.into(), value.into(), is_little_endian.into()],
423            context,
424        )
425    }
426
427    /// Sets a signed 16-bit integer at the specified offset from the start of the [`JsDataView`]
428    #[inline]
429    pub fn set_int16(
430        &self,
431        byte_offset: usize,
432        value: i16,
433        is_little_endian: bool,
434        context: &mut Context,
435    ) -> JsResult<JsValue> {
436        DataView::set_int16(
437            &self.inner.clone().upcast().into(),
438            &[byte_offset.into(), value.into(), is_little_endian.into()],
439            context,
440        )
441    }
442
443    /// Sets a signed 32-bit integer at the specified offset from the start of the [`JsDataView`]
444    #[inline]
445    pub fn set_int32(
446        &self,
447        byte_offset: usize,
448        value: i32,
449        is_little_endian: bool,
450        context: &mut Context,
451    ) -> JsResult<JsValue> {
452        DataView::set_int32(
453            &self.inner.clone().upcast().into(),
454            &[byte_offset.into(), value.into(), is_little_endian.into()],
455            context,
456        )
457    }
458
459    /// Sets an unsigned 8-bit integer at the specified offset from the start of the [`JsDataView`]
460    #[inline]
461    pub fn set_uint8(
462        &self,
463        byte_offset: usize,
464        value: u8,
465        is_little_endian: bool,
466        context: &mut Context,
467    ) -> JsResult<JsValue> {
468        DataView::set_uint8(
469            &self.inner.clone().upcast().into(),
470            &[byte_offset.into(), value.into(), is_little_endian.into()],
471            context,
472        )
473    }
474
475    /// Sets an unsigned 16-bit integer at the specified offset from the start of the [`JsDataView`]
476    #[inline]
477    pub fn set_unit16(
478        &self,
479        byte_offset: usize,
480        value: u16,
481        is_little_endian: bool,
482        context: &mut Context,
483    ) -> JsResult<JsValue> {
484        DataView::set_uint16(
485            &self.inner.clone().upcast().into(),
486            &[byte_offset.into(), value.into(), is_little_endian.into()],
487            context,
488        )
489    }
490
491    /// Sets an unsigned 32-bit integer at the specified offset from the start of the [`JsDataView`]
492    #[inline]
493    pub fn set_unit32(
494        &self,
495        byte_offset: usize,
496        value: u32,
497        is_little_endian: bool,
498        context: &mut Context,
499    ) -> JsResult<JsValue> {
500        DataView::set_uint32(
501            &self.inner.clone().upcast().into(),
502            &[byte_offset.into(), value.into(), is_little_endian.into()],
503            context,
504        )
505    }
506}
507
508impl From<JsDataView> for JsObject {
509    #[inline]
510    fn from(o: JsDataView) -> Self {
511        o.inner.upcast()
512    }
513}
514
515impl From<JsDataView> for JsValue {
516    #[inline]
517    fn from(o: JsDataView) -> Self {
518        o.inner.upcast().into()
519    }
520}
521
522impl Deref for JsDataView {
523    type Target = JsObject<DataView>;
524
525    #[inline]
526    fn deref(&self) -> &Self::Target {
527        &self.inner
528    }
529}
530
531impl TryFromJs for JsDataView {
532    fn try_from_js(value: &JsValue, _context: &mut Context) -> JsResult<Self> {
533        if let Some(o) = value.as_object() {
534            Self::from_object(o.clone())
535        } else {
536            Err(JsNativeError::typ()
537                .with_message("value is not a DataView object")
538                .into())
539        }
540    }
541}