Browse Source

Implement `JsDataView` (#2308)

<!---
Thank you for contributing to Boa! Please fill out the template below, and remove or add any
information as you feel necessary.
--->

This Pull Request is related to #2098 .

It changes the following:

- Implements a wrapper for `DataView`
- Adds an example of `JsDataView` to the `JsArrayBuffer` example file under boa_examples


Co-authored-by: jedel1043 <jedel0124@gmail.com>
pull/2322/head
Kevin 2 years ago
parent
commit
eaf94284bd
  1. 6
      boa_engine/src/builtins/dataview/mod.rs
  2. 476
      boa_engine/src/object/jsdataview.rs
  3. 2
      boa_engine/src/object/mod.rs
  4. 17
      boa_examples/src/bin/jsarraybuffer.rs

6
boa_engine/src/builtins/dataview/mod.rs

@ -15,9 +15,9 @@ use tap::{Conv, Pipe};
#[derive(Debug, Clone, Trace, Finalize)]
pub struct DataView {
viewed_array_buffer: JsObject,
byte_length: u64,
byte_offset: u64,
pub(crate) viewed_array_buffer: JsObject,
pub(crate) byte_length: u64,
pub(crate) byte_offset: u64,
}
impl BuiltIn for DataView {

476
boa_engine/src/object/jsdataview.rs

@ -0,0 +1,476 @@
//! This module implements a wrapper for the `DataView` Builtin Javascript Object
use crate::{
builtins::DataView,
context::intrinsics::StandardConstructors,
object::{
internal_methods::get_prototype_from_constructor, JsArrayBuffer, JsObject, JsObjectType,
ObjectData,
},
Context, JsResult, JsValue,
};
use boa_gc::{Finalize, Trace};
use std::ops::Deref;
/// `JsDataView` Provides a wrapper for Boa's implementation of the Javascript `DataView` object
///
/// # Examples
/// ```
/// # use boa_engine::{
/// # object::{JsArrayBuffer, JsDataView},
/// # Context, JsValue
/// # };
///
/// // Create a new context and ArrayBuffer
/// let context = &mut Context::default();
/// let array_buffer = JsArrayBuffer::new(4, context).unwrap();
///
/// // Create a new Dataview from pre-existing ArrayBuffer
/// let data_view = JsDataView::from_js_array_buffer(&array_buffer, None, None, context).unwrap();
/// ```
#[derive(Debug, Clone, Trace, Finalize)]
pub struct JsDataView {
inner: JsObject,
}
impl JsDataView {
#[inline]
pub fn from_js_array_buffer(
array_buffer: &JsArrayBuffer,
offset: Option<u64>,
byte_length: Option<u64>,
context: &mut Context,
) -> JsResult<Self> {
let (byte_offset, byte_length) = {
let borrowed_buffer = array_buffer.borrow();
let buffer = borrowed_buffer
.as_array_buffer()
.ok_or_else(|| context.construct_type_error("buffer must be an ArrayBuffer"))?;
let provided_offset = offset.unwrap_or(0_u64);
// Check if buffer is detached.
if buffer.is_detached_buffer() {
return context.throw_type_error("ArrayBuffer is detached");
};
let array_buffer_length = buffer.array_buffer_byte_length();
if provided_offset > array_buffer_length {
return context
.throw_range_error("Provided offset is outside the bounds of the buffer");
}
let view_byte_length = if let Some(..) = byte_length {
// Get the provided length
let provided_length = byte_length.expect("byte_length must be a u64");
// Check that the provided length and offset does not exceed the bounds of the ArrayBuffer
if provided_offset + provided_length > array_buffer_length {
return context.throw_range_error("Invalid data view length");
}
provided_length
} else {
array_buffer_length - provided_offset
};
(provided_offset, view_byte_length)
};
let constructor = context
.intrinsics()
.constructors()
.data_view()
.constructor()
.into();
let prototype =
get_prototype_from_constructor(&constructor, StandardConstructors::data_view, context)?;
let obj = JsObject::from_proto_and_data(
prototype,
ObjectData::data_view(DataView {
viewed_array_buffer: (**array_buffer).clone(),
byte_length,
byte_offset,
}),
);
Ok(Self { inner: obj })
}
#[inline]
pub fn from_object(object: JsObject, context: &mut Context) -> JsResult<Self> {
if object.borrow().is_data_view() {
Ok(Self { inner: object })
} else {
context.throw_type_error("object is not a DataView")
}
}
/// Returns the `viewed_array_buffer` field for [`JsDataView`]
#[inline]
pub fn buffer(&self, context: &mut Context) -> JsResult<JsValue> {
DataView::get_buffer(&self.inner.clone().into(), &[], context)
}
/// Returns the `byte_length` property of [`JsDataView`] as a u64 integer
#[inline]
pub fn byte_length(&self, context: &mut Context) -> JsResult<u64> {
DataView::get_byte_length(&self.inner.clone().into(), &[], context)
.map(|v| v.as_number().expect("value should be a number") as u64)
}
/// Returns the `byte_offset` field property of [`JsDataView`] as a u64 integer
#[inline]
pub fn byte_offset(&self, context: &mut Context) -> JsResult<u64> {
DataView::get_byte_offset(&self.inner.clone().into(), &[], context)
.map(|v| v.as_number().expect("byte_offset value must be a number") as u64)
}
/// Returns a signed 64-bit integer at the specified offset from the start of the [`JsDataView`]
#[inline]
pub fn get_big_int64(
&self,
byte_offset: usize,
is_little_endian: bool,
context: &mut Context,
) -> JsResult<i64> {
DataView::get_big_int64(
&self.inner.clone().into(),
&[byte_offset.into(), is_little_endian.into()],
context,
)
.map(|v| v.as_number().expect("value must be a number") as i64)
}
/// Returns an unsigned 64-bit integer at the specified offset from the start of the [`JsDataView`]
#[inline]
pub fn get_big_uint64(
&self,
byte_offset: usize,
is_little_endian: bool,
context: &mut Context,
) -> JsResult<u64> {
DataView::get_big_uint64(
&self.inner.clone().into(),
&[byte_offset.into(), is_little_endian.into()],
context,
)
.map(|v| v.as_number().expect("value must be a number") as u64)
}
/// Returns a signed 32-bit float integer at the specified offset from the start of the [`JsDataView`]
#[inline]
pub fn get_float32(
&self,
byte_offset: usize,
is_little_endian: bool,
context: &mut Context,
) -> JsResult<f32> {
DataView::get_float32(
&self.inner.clone().into(),
&[byte_offset.into(), is_little_endian.into()],
context,
)
.map(|v| v.as_number().expect("value must be a number") as f32)
}
/// Returns a signed 64-bit float integer at the specified offset from the start of the [`JsDataView`]
#[inline]
pub fn get_float64(
&self,
byte_offset: usize,
is_little_endian: bool,
context: &mut Context,
) -> JsResult<f64> {
DataView::get_float64(
&self.inner.clone().into(),
&[byte_offset.into(), is_little_endian.into()],
context,
)
.map(|v| v.as_number().expect("value must be a number"))
}
/// Returns a signed 8-bit integer at the specified offset from the start of the [`JsDataView`]
#[inline]
pub fn get_int8(
&self,
byte_offset: usize,
is_little_endian: bool,
context: &mut Context,
) -> JsResult<i8> {
DataView::get_int8(
&self.inner.clone().into(),
&[byte_offset.into(), is_little_endian.into()],
context,
)
.map(|v| v.as_number().expect("value must be a number") as i8)
}
/// Returns a signed 16-bit integer at the specified offset from the start of the [`JsDataView`]
#[inline]
pub fn get_int16(
&self,
byte_offset: usize,
is_little_endian: bool,
context: &mut Context,
) -> JsResult<i16> {
DataView::get_int16(
&self.inner.clone().into(),
&[byte_offset.into(), is_little_endian.into()],
context,
)
.map(|v| v.as_number().expect("value must be a number") as i16)
}
/// Returns a signed 32-bit integer at the specified offset from the start of the [`JsDataView`]
#[inline]
pub fn get_int32(
&self,
byte_offset: usize,
is_little_endian: bool,
context: &mut Context,
) -> JsResult<i32> {
DataView::get_int32(
&self.inner.clone().into(),
&[byte_offset.into(), is_little_endian.into()],
context,
)
.map(|v| v.as_number().expect("value must be a number") as i32)
}
/// Returns an unsigned 8-bit integer at the specified offset from the start of the [`JsDataView`]
#[inline]
pub fn get_uint8(
&self,
byte_offset: usize,
is_little_endian: bool,
context: &mut Context,
) -> JsResult<u8> {
DataView::get_uint8(
&self.inner.clone().into(),
&[byte_offset.into(), is_little_endian.into()],
context,
)
.map(|v| v.as_number().expect("value must be a number") as u8)
}
/// Returns an unsigned 16-bit integer at the specified offset from the start of the [`JsDataView`]
#[inline]
pub fn get_unit16(
&self,
byte_offset: usize,
is_little_endian: bool,
context: &mut Context,
) -> JsResult<u16> {
DataView::get_uint16(
&self.inner.clone().into(),
&[byte_offset.into(), is_little_endian.into()],
context,
)
.map(|v| v.as_number().expect("value must be a number") as u16)
}
/// Returns an unsigned 32-bit integer at the specified offset from the start of the [`JsDataView`]
#[inline]
pub fn get_uint32(
&self,
byte_offset: usize,
is_little_endian: bool,
context: &mut Context,
) -> JsResult<u32> {
DataView::get_uint32(
&self.inner.clone().into(),
&[byte_offset.into(), is_little_endian.into()],
context,
)
.map(|v| v.as_number().expect("value must be a number") as u32)
}
/// Sets a signed 64-bit integer at the specified offset from the start of the [`JsDataView`]
#[inline]
pub fn set_big_int64(
&self,
byte_offset: usize,
value: i64,
is_little_endian: bool,
context: &mut Context,
) -> JsResult<JsValue> {
DataView::set_big_int64(
&self.inner.clone().into(),
&[byte_offset.into(), value.into(), is_little_endian.into()],
context,
)
}
/// Sets an unsigned 64-bit integer at the specified offset from the start of the [`JsDataView`]
#[inline]
pub fn set_big_uint64(
&self,
byte_offset: usize,
value: u64,
is_little_endian: bool,
context: &mut Context,
) -> JsResult<JsValue> {
DataView::set_big_uint64(
&self.inner.clone().into(),
&[byte_offset.into(), value.into(), is_little_endian.into()],
context,
)
}
/// Sets a signed 32-bit integer at the specified offset from the start of the [`JsDataView`]
#[inline]
pub fn set_float32(
&self,
byte_offset: usize,
value: f32,
is_little_endian: bool,
context: &mut Context,
) -> JsResult<JsValue> {
DataView::set_float32(
&self.inner.clone().into(),
&[byte_offset.into(), value.into(), is_little_endian.into()],
context,
)
}
/// Sets a signed 64-bit integer at the specified offset from the start of the [`JsDataView`]
#[inline]
pub fn set_float64(
&self,
byte_offset: usize,
value: f64,
is_little_endian: bool,
context: &mut Context,
) -> JsResult<JsValue> {
DataView::set_float64(
&self.inner.clone().into(),
&[byte_offset.into(), value.into(), is_little_endian.into()],
context,
)
}
/// Sets a signed 8-bit integer at the specified offset from the start of the [`JsDataView`]
#[inline]
pub fn set_int8(
&self,
byte_offset: usize,
value: i8,
is_little_endian: bool,
context: &mut Context,
) -> JsResult<JsValue> {
DataView::set_int8(
&self.inner.clone().into(),
&[byte_offset.into(), value.into(), is_little_endian.into()],
context,
)
}
/// Sets a signed 16-bit integer at the specified offset from the start of the [`JsDataView`]
#[inline]
pub fn set_int16(
&self,
byte_offset: usize,
value: i16,
is_little_endian: bool,
context: &mut Context,
) -> JsResult<JsValue> {
DataView::set_int16(
&self.inner.clone().into(),
&[byte_offset.into(), value.into(), is_little_endian.into()],
context,
)
}
/// Sets a signed 32-bit integer at the specified offset from the start of the [`JsDataView`]
#[inline]
pub fn set_int32(
&self,
byte_offset: usize,
value: i32,
is_little_endian: bool,
context: &mut Context,
) -> JsResult<JsValue> {
DataView::set_int32(
&self.inner.clone().into(),
&[byte_offset.into(), value.into(), is_little_endian.into()],
context,
)
}
/// Sets an unsigned 8-bit integer at the specified offset from the start of the [`JsDataView`]
#[inline]
pub fn set_uint8(
&self,
byte_offset: usize,
value: u8,
is_little_endian: bool,
context: &mut Context,
) -> JsResult<JsValue> {
DataView::set_uint8(
&self.inner.clone().into(),
&[byte_offset.into(), value.into(), is_little_endian.into()],
context,
)
}
/// Sets an unsigned 16-bit integer at the specified offset from the start of the [`JsDataView`]
#[inline]
pub fn set_unit16(
&self,
byte_offset: usize,
value: u16,
is_little_endian: bool,
context: &mut Context,
) -> JsResult<JsValue> {
DataView::set_uint16(
&self.inner.clone().into(),
&[byte_offset.into(), value.into(), is_little_endian.into()],
context,
)
}
/// Sets an unsigned 32-bit integer at the specified offset from the start of the [`JsDataView`]
#[inline]
pub fn set_unit32(
&self,
byte_offset: usize,
value: u32,
is_little_endian: bool,
context: &mut Context,
) -> JsResult<JsValue> {
DataView::set_uint32(
&self.inner.clone().into(),
&[byte_offset.into(), value.into(), is_little_endian.into()],
context,
)
}
}
impl From<JsDataView> for JsObject {
#[inline]
fn from(o: JsDataView) -> Self {
o.inner.clone()
}
}
impl From<JsDataView> for JsValue {
#[inline]
fn from(o: JsDataView) -> Self {
o.inner.clone().into()
}
}
impl Deref for JsDataView {
type Target = JsObject;
#[inline]
fn deref(&self) -> &Self::Target {
&self.inner
}
}
impl JsObjectType for JsDataView {}

2
boa_engine/src/object/mod.rs

@ -65,6 +65,7 @@ mod tests;
pub(crate) mod internal_methods;
mod jsarray;
mod jsarraybuffer;
mod jsdataview;
mod jsfunction;
mod jsmap;
mod jsmap_iterator;
@ -78,6 +79,7 @@ mod property_map;
pub use jsarray::*;
pub use jsarraybuffer::*;
pub use jsdataview::*;
pub use jsfunction::*;
pub use jsmap::*;
pub use jsmap_iterator::*;

17
boa_examples/src/bin/jsarraybuffer.rs

@ -1,7 +1,7 @@
// This example shows how to manipulate a Javascript array using Rust code.
use boa_engine::{
object::{JsArrayBuffer, JsUint32Array, JsUint8Array},
object::{JsArrayBuffer, JsDataView, JsUint32Array, JsUint8Array},
property::Attribute,
Context, JsResult, JsValue,
};
@ -17,9 +17,9 @@ fn main() -> JsResult<()> {
let uint32_typed_array = JsUint32Array::from_array_buffer(array_buffer, context)?;
let value = 0x12345678u32;
uint32_typed_array.set(0, value, true, context)?;
uint32_typed_array.set(0_u64, value, true, context)?;
assert_eq!(uint32_typed_array.get(0, context)?, JsValue::new(value));
assert_eq!(uint32_typed_array.get(0_u64, context)?, JsValue::new(value));
// We can also create array buffers from a user defined block of data.
//
@ -38,6 +38,17 @@ fn main() -> JsResult<()> {
assert_eq!(uint8_typed_array.get(i, context)?, JsValue::new(i));
}
// We can create a Dataview from a JsArrayBuffer
let dataview = JsDataView::from_js_array_buffer(&array_buffer, None, Some(100_u64), context)?;
let dataview_length = dataview.byte_length(context)?;
assert_eq!(dataview_length, 100);
let second_byte = dataview.get_uint8(2, true, context)?;
assert_eq!(second_byte, 2_u8);
// We can also register it as a global property
context.register_global_property(
"myArrayBuffer",

Loading…
Cancel
Save