Skip to main content

boa_engine/object/builtins/
jsdataview.rs

1//! A Rust API wrapper for Boa's `DataView` Builtin ECMAScript Object
2use crate::{
3    Context, JsExpect, 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).and_then(|v| {
177            v.as_number()
178                .js_expect("value should be a number")
179                .map(|n| n as u64)
180                .map_err(Into::into)
181        })
182    }
183
184    /// Returns the `byte_offset` field property of [`JsDataView`] as a u64 integer
185    #[inline]
186    pub fn byte_offset(&self, context: &mut Context) -> JsResult<u64> {
187        DataView::get_byte_offset(&self.inner.clone().upcast().into(), &[], context).and_then(|v| {
188            v.as_number()
189                .js_expect("byte_offset value must be a number")
190                .map(|n| n as u64)
191                .map_err(Into::into)
192        })
193    }
194
195    /// Returns a signed 64-bit integer at the specified offset from the start of the [`JsDataView`]
196    #[inline]
197    pub fn get_big_int64(
198        &self,
199        byte_offset: usize,
200        is_little_endian: bool,
201        context: &mut Context,
202    ) -> JsResult<i64> {
203        DataView::get_big_int64(
204            &self.inner.clone().upcast().into(),
205            &[byte_offset.into(), is_little_endian.into()],
206            context,
207        )
208        .and_then(|v| {
209            v.as_number()
210                .js_expect("value must be a number")
211                .map(|n| n as i64)
212                .map_err(Into::into)
213        })
214    }
215
216    /// Returns an unsigned 64-bit integer at the specified offset from the start of the [`JsDataView`]
217    #[inline]
218    pub fn get_big_uint64(
219        &self,
220        byte_offset: usize,
221        is_little_endian: bool,
222        context: &mut Context,
223    ) -> JsResult<u64> {
224        DataView::get_big_uint64(
225            &self.inner.clone().upcast().into(),
226            &[byte_offset.into(), is_little_endian.into()],
227            context,
228        )
229        .and_then(|v| {
230            v.as_number()
231                .js_expect("value must be a number")
232                .map(|n| n as u64)
233                .map_err(Into::into)
234        })
235    }
236
237    /// Returns a signed 32-bit float integer at the specified offset from the start of the [`JsDataView`]
238    #[inline]
239    pub fn get_float32(
240        &self,
241        byte_offset: usize,
242        is_little_endian: bool,
243        context: &mut Context,
244    ) -> JsResult<f32> {
245        DataView::get_float32(
246            &self.inner.clone().upcast().into(),
247            &[byte_offset.into(), is_little_endian.into()],
248            context,
249        )
250        .and_then(|v| {
251            v.as_number()
252                .js_expect("value must be a number")
253                .map(|n| n as f32)
254                .map_err(Into::into)
255        })
256    }
257
258    /// Returns a signed 64-bit float integer at the specified offset from the start of the [`JsDataView`]
259    #[inline]
260    pub fn get_float64(
261        &self,
262        byte_offset: usize,
263        is_little_endian: bool,
264        context: &mut Context,
265    ) -> JsResult<f64> {
266        DataView::get_float64(
267            &self.inner.clone().upcast().into(),
268            &[byte_offset.into(), is_little_endian.into()],
269            context,
270        )
271        .and_then(|v| {
272            v.as_number()
273                .js_expect("value must be a number")
274                .map_err(Into::into)
275        })
276    }
277
278    /// Returns a signed 8-bit integer at the specified offset from the start of the [`JsDataView`]
279    #[inline]
280    pub fn get_int8(
281        &self,
282        byte_offset: usize,
283        is_little_endian: bool,
284        context: &mut Context,
285    ) -> JsResult<i8> {
286        DataView::get_int8(
287            &self.inner.clone().upcast().into(),
288            &[byte_offset.into(), is_little_endian.into()],
289            context,
290        )
291        .and_then(|v| {
292            v.as_number()
293                .js_expect("value must be a number")
294                .map(|n| n as i8)
295                .map_err(Into::into)
296        })
297    }
298
299    /// Returns a signed 16-bit integer at the specified offset from the start of the [`JsDataView`]
300    #[inline]
301    pub fn get_int16(
302        &self,
303        byte_offset: usize,
304        is_little_endian: bool,
305        context: &mut Context,
306    ) -> JsResult<i16> {
307        DataView::get_int16(
308            &self.inner.clone().upcast().into(),
309            &[byte_offset.into(), is_little_endian.into()],
310            context,
311        )
312        .and_then(|v| {
313            v.as_number()
314                .js_expect("value must be a number")
315                .map(|n| n as i16)
316                .map_err(Into::into)
317        })
318    }
319
320    /// Returns a signed 32-bit integer at the specified offset from the start of the [`JsDataView`]
321    #[inline]
322    pub fn get_int32(
323        &self,
324        byte_offset: usize,
325        is_little_endian: bool,
326        context: &mut Context,
327    ) -> JsResult<i32> {
328        DataView::get_int32(
329            &self.inner.clone().upcast().into(),
330            &[byte_offset.into(), is_little_endian.into()],
331            context,
332        )
333        .and_then(|v| {
334            v.as_number()
335                .js_expect("value must be a number")
336                .map(|n| n as i32)
337                .map_err(Into::into)
338        })
339    }
340
341    /// Returns an unsigned 8-bit integer at the specified offset from the start of the [`JsDataView`]
342    #[inline]
343    pub fn get_uint8(
344        &self,
345        byte_offset: usize,
346        is_little_endian: bool,
347        context: &mut Context,
348    ) -> JsResult<u8> {
349        DataView::get_uint8(
350            &self.inner.clone().upcast().into(),
351            &[byte_offset.into(), is_little_endian.into()],
352            context,
353        )
354        .and_then(|v| {
355            v.as_number()
356                .js_expect("value must be a number")
357                .map(|n| n as u8)
358                .map_err(Into::into)
359        })
360    }
361
362    /// Returns an unsigned 16-bit integer at the specified offset from the start of the [`JsDataView`]
363    #[inline]
364    pub fn get_unit16(
365        &self,
366        byte_offset: usize,
367        is_little_endian: bool,
368        context: &mut Context,
369    ) -> JsResult<u16> {
370        DataView::get_uint16(
371            &self.inner.clone().upcast().into(),
372            &[byte_offset.into(), is_little_endian.into()],
373            context,
374        )
375        .and_then(|v| {
376            v.as_number()
377                .js_expect("value must be a number")
378                .map(|n| n as u16)
379                .map_err(Into::into)
380        })
381    }
382
383    /// Returns an unsigned 32-bit integer at the specified offset from the start of the [`JsDataView`]
384    #[inline]
385    pub fn get_uint32(
386        &self,
387        byte_offset: usize,
388        is_little_endian: bool,
389        context: &mut Context,
390    ) -> JsResult<u32> {
391        DataView::get_uint32(
392            &self.inner.clone().upcast().into(),
393            &[byte_offset.into(), is_little_endian.into()],
394            context,
395        )
396        .and_then(|v| {
397            v.as_number()
398                .js_expect("value must be a number")
399                .map(|n| n as u32)
400                .map_err(Into::into)
401        })
402    }
403
404    /// Sets a signed 64-bit integer at the specified offset from the start of the [`JsDataView`]
405    #[inline]
406    pub fn set_big_int64(
407        &self,
408        byte_offset: usize,
409        value: i64,
410        is_little_endian: bool,
411        context: &mut Context,
412    ) -> JsResult<JsValue> {
413        DataView::set_big_int64(
414            &self.inner.clone().upcast().into(),
415            &[byte_offset.into(), value.into(), is_little_endian.into()],
416            context,
417        )
418    }
419
420    /// Sets an unsigned 64-bit integer at the specified offset from the start of the [`JsDataView`]
421    #[inline]
422    pub fn set_big_uint64(
423        &self,
424        byte_offset: usize,
425        value: u64,
426        is_little_endian: bool,
427        context: &mut Context,
428    ) -> JsResult<JsValue> {
429        DataView::set_big_uint64(
430            &self.inner.clone().upcast().into(),
431            &[byte_offset.into(), value.into(), is_little_endian.into()],
432            context,
433        )
434    }
435
436    /// Sets a signed 32-bit integer at the specified offset from the start of the [`JsDataView`]
437    #[inline]
438    pub fn set_float32(
439        &self,
440        byte_offset: usize,
441        value: f32,
442        is_little_endian: bool,
443        context: &mut Context,
444    ) -> JsResult<JsValue> {
445        DataView::set_float32(
446            &self.inner.clone().upcast().into(),
447            &[byte_offset.into(), value.into(), is_little_endian.into()],
448            context,
449        )
450    }
451
452    /// Sets a signed 64-bit integer at the specified offset from the start of the [`JsDataView`]
453    #[inline]
454    pub fn set_float64(
455        &self,
456        byte_offset: usize,
457        value: f64,
458        is_little_endian: bool,
459        context: &mut Context,
460    ) -> JsResult<JsValue> {
461        DataView::set_float64(
462            &self.inner.clone().upcast().into(),
463            &[byte_offset.into(), value.into(), is_little_endian.into()],
464            context,
465        )
466    }
467
468    /// Sets a signed 8-bit integer at the specified offset from the start of the [`JsDataView`]
469    #[inline]
470    pub fn set_int8(
471        &self,
472        byte_offset: usize,
473        value: i8,
474        is_little_endian: bool,
475        context: &mut Context,
476    ) -> JsResult<JsValue> {
477        DataView::set_int8(
478            &self.inner.clone().upcast().into(),
479            &[byte_offset.into(), value.into(), is_little_endian.into()],
480            context,
481        )
482    }
483
484    /// Sets a signed 16-bit integer at the specified offset from the start of the [`JsDataView`]
485    #[inline]
486    pub fn set_int16(
487        &self,
488        byte_offset: usize,
489        value: i16,
490        is_little_endian: bool,
491        context: &mut Context,
492    ) -> JsResult<JsValue> {
493        DataView::set_int16(
494            &self.inner.clone().upcast().into(),
495            &[byte_offset.into(), value.into(), is_little_endian.into()],
496            context,
497        )
498    }
499
500    /// Sets a signed 32-bit integer at the specified offset from the start of the [`JsDataView`]
501    #[inline]
502    pub fn set_int32(
503        &self,
504        byte_offset: usize,
505        value: i32,
506        is_little_endian: bool,
507        context: &mut Context,
508    ) -> JsResult<JsValue> {
509        DataView::set_int32(
510            &self.inner.clone().upcast().into(),
511            &[byte_offset.into(), value.into(), is_little_endian.into()],
512            context,
513        )
514    }
515
516    /// Sets an unsigned 8-bit integer at the specified offset from the start of the [`JsDataView`]
517    #[inline]
518    pub fn set_uint8(
519        &self,
520        byte_offset: usize,
521        value: u8,
522        is_little_endian: bool,
523        context: &mut Context,
524    ) -> JsResult<JsValue> {
525        DataView::set_uint8(
526            &self.inner.clone().upcast().into(),
527            &[byte_offset.into(), value.into(), is_little_endian.into()],
528            context,
529        )
530    }
531
532    /// Sets an unsigned 16-bit integer at the specified offset from the start of the [`JsDataView`]
533    #[inline]
534    pub fn set_unit16(
535        &self,
536        byte_offset: usize,
537        value: u16,
538        is_little_endian: bool,
539        context: &mut Context,
540    ) -> JsResult<JsValue> {
541        DataView::set_uint16(
542            &self.inner.clone().upcast().into(),
543            &[byte_offset.into(), value.into(), is_little_endian.into()],
544            context,
545        )
546    }
547
548    /// Sets an unsigned 32-bit integer at the specified offset from the start of the [`JsDataView`]
549    #[inline]
550    pub fn set_unit32(
551        &self,
552        byte_offset: usize,
553        value: u32,
554        is_little_endian: bool,
555        context: &mut Context,
556    ) -> JsResult<JsValue> {
557        DataView::set_uint32(
558            &self.inner.clone().upcast().into(),
559            &[byte_offset.into(), value.into(), is_little_endian.into()],
560            context,
561        )
562    }
563}
564
565impl From<JsDataView> for JsObject {
566    #[inline]
567    fn from(o: JsDataView) -> Self {
568        o.inner.upcast()
569    }
570}
571
572impl From<JsDataView> for JsValue {
573    #[inline]
574    fn from(o: JsDataView) -> Self {
575        o.inner.upcast().into()
576    }
577}
578
579impl Deref for JsDataView {
580    type Target = JsObject<DataView>;
581
582    #[inline]
583    fn deref(&self) -> &Self::Target {
584        &self.inner
585    }
586}
587
588impl TryFromJs for JsDataView {
589    fn try_from_js(value: &JsValue, _context: &mut Context) -> JsResult<Self> {
590        if let Some(o) = value.as_object() {
591            Self::from_object(o.clone())
592        } else {
593            Err(JsNativeError::typ()
594                .with_message("value is not a DataView object")
595                .into())
596        }
597    }
598}