mirror of
https://github.com/lexmount/moli.git
synced 2026-10-06 16:01:00 +00:00
feat(clone): serialize native DOMRect platform values
This commit is contained in:
@@ -247,7 +247,9 @@ pub(crate) use self::css_stylesheet_runtime::{
|
||||
sync_css_style_sheet_media_list_from_owner,
|
||||
sync_css_style_sheet_shadow_root_adopted_owner_tracking,
|
||||
};
|
||||
pub(crate) use self::dom_rect::build_dom_rect_object;
|
||||
pub(crate) use self::dom_rect::{
|
||||
build_dom_rect_clone_object, build_dom_rect_object, dom_rect_clone_data,
|
||||
};
|
||||
pub(crate) use self::events::{
|
||||
EVENT_DISPATCHING_SLOT, EVENT_PASSIVE_SLOT, EVENT_STOP_IMMEDIATE_PROPAGATION_SLOT,
|
||||
EVENT_STOP_PROPAGATION_SLOT, clear_event_composed_path, event_attribute, event_backing,
|
||||
|
||||
@@ -561,6 +561,37 @@ fn dom_rect_receiver_branded<'s>(
|
||||
web_api_interfaces::DOMRectReadOnly::is_instance(scope, receiver)
|
||||
}
|
||||
|
||||
pub(crate) fn dom_rect_clone_data<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
object: v8::Local<'s, v8::Object>,
|
||||
) -> Option<(bool, [f64; 4])> {
|
||||
if !dom_rect_receiver_branded(scope, object) {
|
||||
return None;
|
||||
}
|
||||
let mutable = web_api_interfaces::DOMRect::is_instance(scope, object);
|
||||
Some((
|
||||
mutable,
|
||||
[
|
||||
dom_rect_slot(object, scope, DOM_RECT_X_SLOT),
|
||||
dom_rect_slot(object, scope, DOM_RECT_Y_SLOT),
|
||||
dom_rect_slot(object, scope, DOM_RECT_WIDTH_SLOT),
|
||||
dom_rect_slot(object, scope, DOM_RECT_HEIGHT_SLOT),
|
||||
],
|
||||
))
|
||||
}
|
||||
|
||||
pub(crate) fn build_dom_rect_clone_object<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
mutable: bool,
|
||||
[x, y, width, height]: [f64; 4],
|
||||
) -> v8::Local<'s, v8::Object> {
|
||||
if mutable {
|
||||
build_dom_rect_object(scope, x, y, width, height)
|
||||
} else {
|
||||
build_dom_rect_readonly_object(scope, x, y, width, height)
|
||||
}
|
||||
}
|
||||
|
||||
const DOM_RECT_WRITABLE_ATTRIBUTE_SLOTS: &[&str] = &[
|
||||
DOM_RECT_X_SLOT,
|
||||
DOM_RECT_Y_SLOT,
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn structured_clone_preserves_native_rectangle_kind_values_and_graph_identity() {
|
||||
let mut vm = new_storage_test_vm("https://rect-clone.test/");
|
||||
assert_eq!(vm.eval(r#"
|
||||
(() => {
|
||||
const check = (ok, message) => { if (!ok) throw Error(message); };
|
||||
if (!document.documentElement) document.appendChild(document.createElement('html'));
|
||||
if (!document.body) document.documentElement.appendChild(document.createElement('body'));
|
||||
const iframe = document.createElement('iframe');
|
||||
document.body.appendChild(iframe);
|
||||
const other = iframe.contentWindow;
|
||||
for (const realm of [window, other]) {
|
||||
for (const name of ['DOMRect', 'DOMRectReadOnly']) {
|
||||
const C = realm[name];
|
||||
const prototype = C.prototype;
|
||||
const values = [-0, NaN, Infinity, -Infinity];
|
||||
const rect = new C(...values);
|
||||
Object.defineProperty(rect, 'x', { enumerable: true, get() { throw Error('public x must not be read'); } });
|
||||
Object.defineProperty(rect, 'toJSON', { enumerable: true, get() { throw Error('toJSON must not be read'); } });
|
||||
const subclass = new (class extends C {})(1, 2, -3, -4);
|
||||
realm[name] = function PoisonedConstructor() { throw Error('public constructor must not be called'); };
|
||||
try {
|
||||
const graph = realm.structuredClone({ rect, again: rect, list: [rect], subclass });
|
||||
const clone = graph.rect;
|
||||
check(clone !== rect && graph.again === clone && graph.list[0] === clone, name + ' cloned graph identity');
|
||||
check(Object.getPrototypeOf(clone) === prototype, name + ' realm-local native prototype');
|
||||
check(Object.keys(clone).length === 0, name + ' ignores expandos');
|
||||
for (const [i, key] of ['x', 'y', 'width', 'height'].entries())
|
||||
check(Object.is(clone[key], values[i]), name + ' preserves ' + key);
|
||||
check(Object.getPrototypeOf(graph.subclass) === prototype, name + ' strips author subclass prototype');
|
||||
check(graph.subclass.x === 1 && graph.subclass.y === 2 && graph.subclass.width === -3 && graph.subclass.height === -4, name + ' subclass native state');
|
||||
if (name === 'DOMRect') {
|
||||
clone.x = 10;
|
||||
check(clone.x === 10, name + ' mutable clone');
|
||||
} else {
|
||||
check(Reflect.set(clone, 'x', 10) === false && Object.is(clone.x, -0), name + ' readonly clone');
|
||||
}
|
||||
} finally { realm[name] = C; }
|
||||
}
|
||||
}
|
||||
const source = new other.DOMRect(5, 6, -7, -8);
|
||||
const cross = structuredClone(source);
|
||||
check(Object.getPrototypeOf(cross) === DOMRect.prototype && cross.x === 5 && cross.width === -7, 'cross-realm reconstruction');
|
||||
let traps = 0;
|
||||
let error;
|
||||
try { structuredClone(new Proxy(source, { ownKeys() { traps++; return []; } })); } catch (caught) { error = caught; }
|
||||
check(error instanceof DOMException && error.name === 'DataCloneError' && traps === 0, 'author Proxy rejection');
|
||||
history.replaceState({ rect: new DOMRectReadOnly(-0, 2, -3, 4) }, '', '#rectangle');
|
||||
const stored = history.state.rect;
|
||||
check(Object.getPrototypeOf(stored) === DOMRectReadOnly.prototype && Object.is(stored.x, -0) && stored.width === -3, 'history storage reconstruction');
|
||||
return 'passed';
|
||||
})()
|
||||
"#).unwrap(), "passed");
|
||||
}
|
||||
@@ -2193,3 +2193,4 @@ mod observer_element_arguments;
|
||||
mod media_device_interfaces;
|
||||
|
||||
mod dom_rect_factory_descriptors;
|
||||
mod dom_rect_structured_clone;
|
||||
|
||||
@@ -11,13 +11,14 @@ use crate::{
|
||||
CryptoKeyAlgorithmClonePayload, CryptoKeyClonePayload, FileSystemFileSnapshotClonePayload,
|
||||
FileSystemHandleClonePayload, ImageDataClonePayload, ReadableStreamClonePayload,
|
||||
TransformStreamClonePayload, WritableStreamClonePayload,
|
||||
attach_file_system_file_snapshot_clone_payload, build_file_object,
|
||||
build_file_system_handle_from_clone_payload, build_image_data_object_from_clone_payload,
|
||||
build_readable_stream_clone_shell, build_transform_stream_clone_shell,
|
||||
build_writable_stream_clone_shell, crypto_key_clone_payload_from_object,
|
||||
crypto_key_object_from_clone_payload, detach_message_port_owner_for_transfer,
|
||||
detach_transferred_message_port, dom_exception_clone_fields,
|
||||
ensure_message_port_wrapper_for_id, file_system_file_snapshot_clone_payload_from_object,
|
||||
attach_file_system_file_snapshot_clone_payload, build_dom_rect_clone_object,
|
||||
build_file_object, build_file_system_handle_from_clone_payload,
|
||||
build_image_data_object_from_clone_payload, build_readable_stream_clone_shell,
|
||||
build_transform_stream_clone_shell, build_writable_stream_clone_shell,
|
||||
crypto_key_clone_payload_from_object, crypto_key_object_from_clone_payload,
|
||||
detach_message_port_owner_for_transfer, detach_transferred_message_port,
|
||||
dom_exception_clone_fields, dom_rect_clone_data, ensure_message_port_wrapper_for_id,
|
||||
file_system_file_snapshot_clone_payload_from_object,
|
||||
file_system_handle_clone_payload_from_object, image_data_clone_payload_from_object,
|
||||
initialize_readable_stream_clone_shell, initialize_transform_stream_clone_shell,
|
||||
initialize_writable_stream_clone_shell, message_port_id_from_object,
|
||||
@@ -44,6 +45,9 @@ pub(crate) const HOST_OBJECT_TAG_FILE_SYSTEM_HANDLE: u32 = 7;
|
||||
const HOST_OBJECT_TAG_QUOTA_EXCEEDED_ERROR: u32 = 8;
|
||||
const HOST_OBJECT_TAG_WRITABLE_STREAM: u32 = 9;
|
||||
const HOST_OBJECT_TAG_TRANSFORM_STREAM: u32 = 10;
|
||||
const HOST_OBJECT_TAG_GEOMETRY: u32 = 11;
|
||||
const GEOMETRY_KIND_DOM_RECT_READONLY: u32 = 2;
|
||||
const GEOMETRY_KIND_DOM_RECT: u32 = 3;
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub(crate) struct V8StructuredClonePayload {
|
||||
@@ -368,6 +372,20 @@ impl v8::ValueSerializerImpl for WireSerializer {
|
||||
return Some(true);
|
||||
}
|
||||
}
|
||||
Some("DOMRect" | "DOMRectReadOnly") => {
|
||||
if let Some((mutable, values)) = dom_rect_clone_data(scope, object) {
|
||||
serializer.write_uint32(HOST_OBJECT_TAG_GEOMETRY);
|
||||
serializer.write_uint32(if mutable {
|
||||
GEOMETRY_KIND_DOM_RECT
|
||||
} else {
|
||||
GEOMETRY_KIND_DOM_RECT_READONLY
|
||||
});
|
||||
for value in values {
|
||||
serializer.write_double(value);
|
||||
}
|
||||
return Some(true);
|
||||
}
|
||||
}
|
||||
Some("ImageData") => {
|
||||
if let Some(payload) = image_data_clone_payload_from_object(scope, object) {
|
||||
write_image_data_payload(serializer, payload);
|
||||
@@ -583,6 +601,25 @@ impl v8::ValueDeserializerImpl for WireDeserializer {
|
||||
}
|
||||
ensure_message_port_wrapper_for_id(scope, port_id)
|
||||
}
|
||||
HOST_OBJECT_TAG_GEOMETRY => {
|
||||
let kind = read_u32(deserializer)?;
|
||||
let mutable = match kind {
|
||||
GEOMETRY_KIND_DOM_RECT_READONLY => false,
|
||||
GEOMETRY_KIND_DOM_RECT => true,
|
||||
_ => {
|
||||
throw_data_clone_exception(scope, "Unsupported geometry clone kind.");
|
||||
return None;
|
||||
}
|
||||
};
|
||||
let mut values = [0.0; 4];
|
||||
for value in &mut values {
|
||||
if !deserializer.read_double(value) {
|
||||
throw_data_clone_exception(scope, "Invalid DOMRect clone payload.");
|
||||
return None;
|
||||
}
|
||||
}
|
||||
Some(build_dom_rect_clone_object(scope, mutable, values))
|
||||
}
|
||||
HOST_OBJECT_TAG_IMAGE_DATA => read_image_data_payload(scope, deserializer),
|
||||
HOST_OBJECT_TAG_CRYPTO_KEY => {
|
||||
read_crypto_key_payload(scope, deserializer).or_else(|| {
|
||||
|
||||
@@ -3028,3 +3028,44 @@ async fn worker_does_not_expose_window_media_device_interfaces() {
|
||||
r#"{"MediaDeviceInfo":false,"InputDeviceInfo":false}"#
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn worker_rectangle_clones_use_native_prototypes_and_message_channel_delivery() {
|
||||
ensure_v8();
|
||||
let mut handle = spawn_worker(
|
||||
r#"
|
||||
(async () => {
|
||||
const check = (ok, label) => { if (!ok) throw Error(label); };
|
||||
for (const name of ['DOMRect', 'DOMRectReadOnly']) {
|
||||
const C = self[name];
|
||||
const prototype = C.prototype;
|
||||
const rect = new C(-0, NaN, Infinity, -Infinity);
|
||||
Object.defineProperty(rect, 'x', {get() { throw Error('must read native state'); }});
|
||||
self[name] = () => { throw Error('must not call public constructor'); };
|
||||
try {
|
||||
const clone = structuredClone({rect, again: rect});
|
||||
check(clone.rect === clone.again, name + ' graph alias');
|
||||
check(Object.getPrototypeOf(clone.rect) === prototype, name + ' intrinsic prototype');
|
||||
check(Object.is(clone.rect.x, -0) && Number.isNaN(clone.rect.y) &&
|
||||
clone.rect.width === Infinity && clone.rect.height === -Infinity, name + ' values');
|
||||
const channel = new MessageChannel();
|
||||
const received = new Promise(resolve => { channel.port2.onmessage = event => resolve(event.data); });
|
||||
channel.port1.postMessage({rect, again: rect});
|
||||
const message = await received;
|
||||
check(message.rect === message.again && message.rect !== rect, name + ' message graph');
|
||||
check(Object.getPrototypeOf(message.rect) === prototype, name + ' message receiver prototype');
|
||||
check(Object.is(message.rect.x, -0) && Number.isNaN(message.rect.y) &&
|
||||
message.rect.width === Infinity && message.rect.height === -Infinity, name + ' message values');
|
||||
channel.port1.close();
|
||||
channel.port2.close();
|
||||
} finally { self[name] = C; }
|
||||
}
|
||||
postMessage('passed');
|
||||
close();
|
||||
})().catch(error => { postMessage(String(error)); close(); });
|
||||
"#
|
||||
.into(),
|
||||
"https://worker-rectangle.test/clone.js".into(),
|
||||
);
|
||||
assert_eq!(recv_post_json(&mut handle).await, r#""passed""#);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user