Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 25 additions & 3 deletions core/runtime/src/store/from.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ fn try_from_js_object(
/// Transfer an object into a store instead of cloning it. See [mdn].
///
/// Only [transferable objects][to] can be transferred. Anything else will return an
/// error. Since any object t
/// error.
///
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Transferable_objects
/// [to]: https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Transferable_objects#supported_objects
Expand Down Expand Up @@ -151,6 +151,28 @@ fn clone_typed_array(
Ok(dolly)
}

fn clone_dataview(
original: &JsObject,
dataview: &JsDataView,
transfer: &FxHashSet<JsObject>,
seen: &mut SeenMap,
context: &mut Context,
) -> JsResult<JsValueStore> {
let byte_length = dataview.byte_length(context)?;
let byte_offset = dataview.byte_offset(context)?;

let buffer_value = dataview.buffer(context)?;
let buffer = try_from_js_value(&buffer_value, transfer, seen, context)?;

let dolly = JsValueStore::new(ValueStoreInner::DataView {
buffer,
byte_length,
byte_offset,
});
seen.insert(original, dolly.clone());
Ok(dolly)
}

fn clone_date(
original: &JsObject,
date: &JsDate,
Expand Down Expand Up @@ -262,8 +284,8 @@ fn try_from_js_object_clone(
return Err(js_error!(TypeError: "Errors are not supported yet."));
} else if let Ok(ref regexp) = JsRegExp::from_object(object.clone()) {
return clone_regexp(object, regexp, seen, context);
} else if let Ok(_dataview) = JsDataView::from_object(object.clone()) {
return Err(js_error!(TypeError: "Data views are not supported yet."));
} else if let Ok(dataview) = JsDataView::from_object(object.clone()) {
return clone_dataview(object, &dataview, transfer, seen, context);
} else if object.is_callable() {
// Functions are invalid.
return Err(unsupported_type());
Expand Down
4 changes: 3 additions & 1 deletion core/runtime/src/store/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,6 @@ enum ValueStoreInner {
SharedArrayBuffer(SharedArrayBuffer),

/// Dataview.
#[expect(unused)]
DataView {
buffer: JsValueStore,
byte_length: u64,
Expand Down Expand Up @@ -217,3 +216,6 @@ impl JsValueStore {
Ok(v)
}
}

#[cfg(test)]
mod tests;
88 changes: 88 additions & 0 deletions core/runtime/src/store/tests.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
use super::JsValueStore;
use boa_engine::object::builtins::{JsArrayBuffer, JsDataView};
use boa_engine::value::TryIntoJs;
use boa_engine::Context;

#[test]
fn dataview_clone_non_zero_offset() {
let mut context = Context::default();

// Create an ArrayBuffer of 32 bytes
let array_buffer = JsArrayBuffer::new(32, &mut context).unwrap();

// Create DataView covering 16 bytes starting at offset 8
let data_view = JsDataView::from_js_array_buffer(
array_buffer.clone(),
Some(8),
Some(16),
&mut context,
)
.unwrap();

// Store
let store = JsValueStore::try_from_js(&data_view.into(), &mut context, vec![]).unwrap();

// Restore
let restored = store.try_into_js(&mut context).unwrap();

// Assertions
let restored_dv = JsDataView::from_object(restored.as_object().unwrap().clone()).unwrap();

assert_eq!(restored_dv.byte_offset(&mut context).unwrap(), 8);
assert_eq!(restored_dv.byte_length(&mut context).unwrap(), 16);

let restored_buffer_val = restored_dv.buffer(&mut context).unwrap();
let restored_buffer =
JsArrayBuffer::from_object(restored_buffer_val.as_object().unwrap().clone()).unwrap();

// The underlying buffer must be correctly cloned and sized to the original buffer size (32)
let buffer_len = restored_buffer.borrow().data().bytes().unwrap().len();
assert_eq!(buffer_len, 32);

// Also, it should NOT be the exact same buffer instance
assert_ne!(
std::ptr::from_ref(array_buffer.as_object().as_ref()).addr(),
std::ptr::from_ref(restored_buffer.as_object().as_ref()).addr()
);
}

#[test]
fn dataview_transfer() {
let mut context = Context::default();

// Create an ArrayBuffer of 32 bytes
let array_buffer = JsArrayBuffer::new(32, &mut context).unwrap();

// Create DataView
let data_view = JsDataView::from_js_array_buffer(
array_buffer.clone(),
Some(4),
Some(8),
&mut context,
)
.unwrap();

// Store, but transfer the ArrayBuffer
let store = JsValueStore::try_from_js(
&data_view.into(),
&mut context,
vec![array_buffer.clone().into()],
)
.unwrap();

// Original buffer should be detached
assert!(array_buffer.borrow().data().bytes().is_none());

// Restore
let restored = store.try_into_js(&mut context).unwrap();

let restored_dv = JsDataView::from_object(restored.as_object().unwrap().clone()).unwrap();
assert_eq!(restored_dv.byte_offset(&mut context).unwrap(), 4);
assert_eq!(restored_dv.byte_length(&mut context).unwrap(), 8);

let restored_buffer_val = restored_dv.buffer(&mut context).unwrap();
let restored_buffer =
JsArrayBuffer::from_object(restored_buffer_val.as_object().unwrap().clone()).unwrap();
let buffer_len = restored_buffer.borrow().data().bytes().unwrap().len();
assert_eq!(buffer_len, 32);
}
54 changes: 54 additions & 0 deletions core/runtime/tests/clone/dataview.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
// https://developer.mozilla.org/en-US/docs/Web/API/Window/structuredClone

{
// Test basic DataView cloning
const buffer = new ArrayBuffer(16);
const dataview = new DataView(buffer);
dataview.setInt8(0, 42);

const clone = structuredClone(dataview);

assertNEq(clone, dataview);
assertNEq(clone.buffer, dataview.buffer);
assertEq(clone.byteLength, 16);
assertEq(clone.byteOffset, 0);
assertEq(clone.getInt8(0), 42);

// modifying clone doesn't affect original
clone.setInt8(0, 10);
assertEq(dataview.getInt8(0), 42);
}

{
// Test DataView with byteOffset and byteLength
const buffer = new ArrayBuffer(32);
const dataview = new DataView(buffer, 8, 16);
dataview.setInt16(0, 1234);

const clone = structuredClone(dataview);

assertNEq(clone, dataview);
assertNEq(clone.buffer, dataview.buffer);
assertEq(clone.byteLength, 16);
assertEq(clone.byteOffset, 8);
assertEq(clone.getInt16(0), 1234);
}

{
// Test transferring the underlying buffer of a DataView
const buffer = new ArrayBuffer(16);
const dataview = new DataView(buffer, 4, 8);
dataview.setInt32(0, 98765);

const object1 = {
dataview,
};

const object2 = structuredClone(object1, { transfer: [buffer] });

assert(object2.dataview !== dataview);
assertEq(object1.dataview.byteLength, 0); // Original dataview is detached
assertEq(object2.dataview.byteLength, 8);
assertEq(object2.dataview.byteOffset, 4);
assertEq(object2.dataview.getInt32(0), 98765);
}
Loading