mirror of https://github.com/boa-dev/boa.git
Browse Source
This PR adds the `ArrayBuffer` rust wrapper. It also provides a capability to construct a `JsArrayBuffer` from a user defined blob of data ( `Vec<u8>` ) and it is not cloned, it is directly used as the internal buffer. This allows us to replace the inifficent `Vec<u8>` to `JsArray` then to `TypedArray` (in typed arrays `from_iter`), with a `JsArrayBuffer` created from user data to `TypedArray`. With this `Vec<u8>` to `JsTypedArray` should be fully fixed as discussed in #2058.pull/2173/head
Halid Odat
2 years ago
5 changed files with 209 additions and 4 deletions
@ -0,0 +1,123 @@ |
|||||||
|
use crate::{ |
||||||
|
builtins::array_buffer::ArrayBuffer, |
||||||
|
context::intrinsics::StandardConstructors, |
||||||
|
object::{ |
||||||
|
internal_methods::get_prototype_from_constructor, JsObject, JsObjectType, ObjectData, |
||||||
|
}, |
||||||
|
Context, JsResult, JsValue, |
||||||
|
}; |
||||||
|
use boa_gc::{Finalize, Trace}; |
||||||
|
use std::ops::Deref; |
||||||
|
|
||||||
|
/// JavaScript `ArrayBuffer` rust object.
|
||||||
|
#[derive(Debug, Clone, Trace, Finalize)] |
||||||
|
pub struct JsArrayBuffer { |
||||||
|
inner: JsObject, |
||||||
|
} |
||||||
|
|
||||||
|
impl JsArrayBuffer { |
||||||
|
/// Create a new array buffer with byte length.
|
||||||
|
#[inline] |
||||||
|
pub fn new(byte_length: usize, context: &mut Context) -> JsResult<Self> { |
||||||
|
let inner = ArrayBuffer::allocate( |
||||||
|
&context |
||||||
|
.intrinsics() |
||||||
|
.constructors() |
||||||
|
.array_buffer() |
||||||
|
.constructor() |
||||||
|
.into(), |
||||||
|
byte_length, |
||||||
|
context, |
||||||
|
)?; |
||||||
|
|
||||||
|
Ok(Self { inner }) |
||||||
|
} |
||||||
|
|
||||||
|
/// Create a new array buffer from byte block.
|
||||||
|
///
|
||||||
|
/// This uses the passed byte block as the internal storage, it does not clone it!
|
||||||
|
///
|
||||||
|
/// The `byte_length` will be set to `byte_block.len()`.
|
||||||
|
#[inline] |
||||||
|
pub fn from_byte_block(byte_block: Vec<u8>, context: &mut Context) -> JsResult<Self> { |
||||||
|
let byte_length = byte_block.len(); |
||||||
|
|
||||||
|
let constructor = context |
||||||
|
.intrinsics() |
||||||
|
.constructors() |
||||||
|
.array_buffer() |
||||||
|
.constructor() |
||||||
|
.into(); |
||||||
|
|
||||||
|
// 1. Let obj be ? OrdinaryCreateFromConstructor(constructor, "%ArrayBuffer.prototype%", « [[ArrayBufferData]], [[ArrayBufferByteLength]], [[ArrayBufferDetachKey]] »).
|
||||||
|
let prototype = get_prototype_from_constructor( |
||||||
|
&constructor, |
||||||
|
StandardConstructors::array_buffer, |
||||||
|
context, |
||||||
|
)?; |
||||||
|
let obj = context.construct_object(); |
||||||
|
obj.set_prototype(prototype.into()); |
||||||
|
|
||||||
|
// 2. Let block be ? CreateByteDataBlock(byteLength).
|
||||||
|
//
|
||||||
|
// NOTE: We skip step 2. because we already have the block
|
||||||
|
// that is passed to us as a function argument.
|
||||||
|
let block = byte_block; |
||||||
|
|
||||||
|
// 3. Set obj.[[ArrayBufferData]] to block.
|
||||||
|
// 4. Set obj.[[ArrayBufferByteLength]] to byteLength.
|
||||||
|
obj.borrow_mut().data = ObjectData::array_buffer(ArrayBuffer { |
||||||
|
array_buffer_data: Some(block), |
||||||
|
array_buffer_byte_length: byte_length, |
||||||
|
array_buffer_detach_key: JsValue::Undefined, |
||||||
|
}); |
||||||
|
|
||||||
|
Ok(Self { inner: obj }) |
||||||
|
} |
||||||
|
|
||||||
|
/// Create a [`JsArrayBuffer`] from a [`JsObject`], if the object is not an array buffer throw a `TypeError`.
|
||||||
|
///
|
||||||
|
/// This does not clone the fields of the array buffer, it only does a shallow clone of the object.
|
||||||
|
#[inline] |
||||||
|
pub fn from_object(object: JsObject, context: &mut Context) -> JsResult<Self> { |
||||||
|
if object.borrow().is_array_buffer() { |
||||||
|
Ok(Self { inner: object }) |
||||||
|
} else { |
||||||
|
context.throw_type_error("object is not an ArrayBuffer") |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/// Returns the byte length of the array buffer.
|
||||||
|
#[inline] |
||||||
|
pub fn byte_length(&self, context: &mut Context) -> usize { |
||||||
|
ArrayBuffer::get_byte_length(&self.inner.clone().into(), &[], context) |
||||||
|
.expect("it should not throw") |
||||||
|
.as_number() |
||||||
|
.expect("expected a number") as usize |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
impl From<JsArrayBuffer> for JsObject { |
||||||
|
#[inline] |
||||||
|
fn from(o: JsArrayBuffer) -> Self { |
||||||
|
o.inner.clone() |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
impl From<JsArrayBuffer> for JsValue { |
||||||
|
#[inline] |
||||||
|
fn from(o: JsArrayBuffer) -> Self { |
||||||
|
o.inner.clone().into() |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
impl Deref for JsArrayBuffer { |
||||||
|
type Target = JsObject; |
||||||
|
|
||||||
|
#[inline] |
||||||
|
fn deref(&self) -> &Self::Target { |
||||||
|
&self.inner |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
impl JsObjectType for JsArrayBuffer {} |
@ -0,0 +1,49 @@ |
|||||||
|
// This example shows how to manipulate a Javascript array using Rust code.
|
||||||
|
|
||||||
|
use boa_engine::{ |
||||||
|
object::{JsArrayBuffer, JsUint32Array, JsUint8Array}, |
||||||
|
property::Attribute, |
||||||
|
Context, JsResult, JsValue, |
||||||
|
}; |
||||||
|
|
||||||
|
fn main() -> JsResult<()> { |
||||||
|
// We create a new `Context` to create a new Javascript executor.
|
||||||
|
let context = &mut Context::default(); |
||||||
|
|
||||||
|
// This create an array buffer of byte length 4
|
||||||
|
let array_buffer = JsArrayBuffer::new(4, context)?; |
||||||
|
|
||||||
|
// We can now create an typed array to access the data.
|
||||||
|
let uint32_typed_array = JsUint32Array::from_array_buffer(array_buffer, context)?; |
||||||
|
|
||||||
|
let value = 0x12345678u32; |
||||||
|
uint32_typed_array.set(0, value, true, context)?; |
||||||
|
|
||||||
|
assert_eq!(uint32_typed_array.get(0, context)?, JsValue::new(value)); |
||||||
|
|
||||||
|
// We can also create array buffers from a user defined block of data.
|
||||||
|
//
|
||||||
|
// NOTE: The block data will not be cloned.
|
||||||
|
let blob_of_data: Vec<u8> = (0..=255).collect(); |
||||||
|
let array_buffer = JsArrayBuffer::from_byte_block(blob_of_data, context)?; |
||||||
|
|
||||||
|
// This the byte length of the new array buffer will be the length of block of data.
|
||||||
|
let byte_length = array_buffer.byte_length(context); |
||||||
|
assert_eq!(byte_length, 256); |
||||||
|
|
||||||
|
// We can now create an typed array to access the data.
|
||||||
|
let uint8_typed_array = JsUint8Array::from_array_buffer(array_buffer.clone(), context)?; |
||||||
|
|
||||||
|
for i in 0..byte_length { |
||||||
|
assert_eq!(uint8_typed_array.get(i, context)?, JsValue::new(i)); |
||||||
|
} |
||||||
|
|
||||||
|
// We can also register it as a global property
|
||||||
|
context.register_global_property( |
||||||
|
"myArrayBuffer", |
||||||
|
array_buffer, |
||||||
|
Attribute::WRITABLE | Attribute::ENUMERABLE | Attribute::CONFIGURABLE, |
||||||
|
); |
||||||
|
|
||||||
|
Ok(()) |
||||||
|
} |
Loading…
Reference in new issue