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