Files
OxideJS-New/crates/oxide_builtins/src/message_channel.rs
T

761 lines
30 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//! MessageChannel / MessagePort 行为面:`MessageChannel` 构造器(建双向 mpsc
//! 通道与两枚端口对象)、`MessagePort` 原型方法(postMessage 同步交付 /
//! close / start / readyState)与 `onmessage` / `onmessageerror` / `onerror`
//! 原型访问器属性(经 dispatchEvent 交付 MessageEvent)。
//!
//! 端口载荷盒(mpsc 双端与对端端口边)存于对象 `native_fn` 槽,向 GC 家族表
//! 暴露 edges/size/drop 三自由函数。
use std::mem;
use oxide_kernel::message_queue;
use oxide_kernel::shape_forge::EMPTY_SHAPE_ID;
use oxide_types::object::{JsObject, NativeFnPtr, PropAttributes};
use oxide_types::value::JsValue;
use oxide_runtime_api::{NativeResult, ProtoKind, VmHost};
use crate::message_value::{detach_message, parse_transfer_list, rehydrate_message, MessageValue};
/// MessagePort 载荷盒:存于对象 `native_fn` 槽(`Box::into_raw`,realm 局部
/// 单线程)。`peer` 是发往对端的 mpsc 发送端(无 GC 边),`rx` 是收自对端的
/// mpsc 接收端(无 GC 边),`peer_port` 是对端端口对象的裸指针(双向 GC 对象
/// 边,保对端存活),`closed` 是关闭标志。
///
struct MessagePortInner {
peer: message_queue::Sender<MessageValue>,
rx: message_queue::Receiver<MessageValue>,
peer_port: *mut JsObject,
closed: bool,
}
/// 取载荷盒指针:仅 MessagePort 标签对象返回 `Some`,其余标签返回 `None`。
fn message_port_payload_ptr(obj: &JsObject) -> Option<*mut MessagePortInner> {
if !obj.is_message_port() {
return None;
}
obj.native_fn().map(|ptr| ptr.as_ptr() as *mut MessagePortInner)
}
/// 收集 MessagePort 的对端端口边(GC 根边)。
///
/// `peer_port` 空窗口(构造期回填之前)返回空边集;边函数自守,语义自含。
pub fn message_port_native_edges(obj: &JsObject) -> Vec<JsValue> {
let Some(ptr) = message_port_payload_ptr(obj) else {
return Vec::new();
};
if ptr.is_null() {
return Vec::new();
}
// SAFETY: ptr 指向有效载荷盒(槽位仅构造期写入、释放时置空),
// 只读单个裸指针字段,不搬移载荷。
let peer_port = unsafe { (*ptr).peer_port };
if peer_port.is_null() {
return Vec::new();
}
vec![JsValue::from_js_object(peer_port)]
}
/// 只读核算 MessagePort 载荷盒字节(不释放)。
///
/// mpsc 内部缓冲在 Arc 通道内、盒不可寻址,不计入(有界轻微少计,
/// 只推迟 GC 触发,不影响触发正确性)。
pub fn message_port_native_size(obj: &JsObject) -> u64 {
let Some(ptr) = message_port_payload_ptr(obj) else {
return 0;
};
if ptr.is_null() {
return 0;
}
mem::size_of::<MessagePortInner>() as u64
}
/// 释放 MessagePort 载荷盒(`Box<MessagePortInner>`),返回释放字节数。
///
/// 盒 drop 时发送端与接收端各减一次 mpsc 引用计数;`peer_port` 裸指针
/// 无 drop。槽置空保证重复调用零释放(幂等)。
pub fn drop_message_port_native(obj: &mut JsObject) -> u64 {
let bytes = message_port_native_size(obj);
if bytes == 0 {
return 0;
}
let Some(ptr) = message_port_payload_ptr(obj) else {
return 0;
};
// SAFETY: ptr 非空(message_port_native_size 已验证),Box::from_raw 恰好释放一次。
unsafe { drop(Box::from_raw(ptr)) };
obj.set_native_fn(None);
bytes
}
/// MessageChannel 构造器:建两条 mpsc 通道与两枚端口对象,回填双向
/// peer_port 边,返回 `{port1, port2}` 结果对象。
///
/// # 步骤
/// 1. 非构造形态(`!constructing_native`)抛 TypeError(HTML 规范只有
/// [[Construct]],无 [[Call]])
/// 2. 建两条 mpsc 通道(A→B 与 B→A),载荷类型 `MessageValue`
/// 3. 建两枚端口对象(proto = %MessagePort.prototype%,载荷盒入 native_fn 槽)
/// 4. 回填双向 peer_port(裸指针写,跨 JS 调用不持借用)
/// 5. 建 plain 结果对象(proto = %Object.prototype%),define 可枚举
/// port1 / port2
///
/// onmessage / onmessageerror / onerror 不在对象上建自有属性(改
/// MessagePort.prototype 访问器属性)。
///
/// # 副作用
/// - 两端口对象经 `alloc_object` 入对象表即成 GC 根;
/// - 双向 peer_port 边闭合环(两端口自然共亡拆环)。
///
/// # 注意事项
/// - 端口固定挂 %MessagePort.prototype%,不读 new.target(与 HTML 规范一致);
/// - 构造器不占 BuiltinWorld P 字段(绑定层就地填充占位)。
pub fn message_channel_constructor<H: VmHost>(vm: &mut H, _args: &[u8]) -> NativeResult {
if !vm.constructing_native() {
return NativeResult::Err(crate::error::create_type_error(vm, "MessageChannel constructor requires new"));
}
let (a_to_b_tx, b_rx) = message_queue::channel::<MessageValue>();
let (b_to_a_tx, a_rx) = message_queue::channel::<MessageValue>();
let proto_val = JsValue::from_js_object(vm.builtin_proto(ProtoKind::MessagePortProto));
let (a_ptr, a_inner) = make_port_object(vm, proto_val, a_to_b_tx, a_rx);
let (b_ptr, b_inner) = make_port_object(vm, proto_val, b_to_a_tx, b_rx);
// 回填双向 peer_port:peer_port 空窗口的唯一来源,回填后双向 GC 边环闭合。
// SAFETY: a_inner / b_inner 是刚建的载荷盒,本段存活,只写单个裸指针字段。
unsafe {
(*a_inner).peer_port = b_ptr;
(*b_inner).peer_port = a_ptr;
}
// onmessage / onmessageerror / onerror 不在对象上建自有属性:改
// MessagePort.prototype 访问器属性(getter 读注册表属性条目、setter 写),
// 经既有 dispatchEvent 路径交付。
// 结果对象:plain 对象(proto = %Object.prototype%),两枚可枚举 port1 / port2。
let object_proto = JsValue::from_js_object(vm.builtin_proto(ProtoKind::ObjectProto));
let result_ptr = vm.alloc_object(JsObject::new_empty(EMPTY_SHAPE_ID, object_proto));
// SAFETY: result_ptr 是本函数刚 alloc_object 的对象,存活且本段无别名。
let result_obj = unsafe { &mut *result_ptr };
let si_port1 = vm.perm_intern("port1");
let _ = vm.define_data_property(result_obj, si_port1, JsValue::from_js_object(a_ptr), PropAttributes::DEFAULT_DATA);
let si_port2 = vm.perm_intern("port2");
let _ = vm.define_data_property(result_obj, si_port2, JsValue::from_js_object(b_ptr), PropAttributes::DEFAULT_DATA);
NativeResult::Ok(JsValue::from_js_object(result_ptr))
}
/// 建单枚端口对象:空形状对象挂载荷盒,`alloc_object` 入对象表。
fn make_port_object<H: VmHost>(
vm: &mut H, proto_val: JsValue, peer: message_queue::Sender<MessageValue>,
rx: message_queue::Receiver<MessageValue>,
) -> (*mut JsObject, *mut MessagePortInner) {
let mut obj = JsObject::new_empty(EMPTY_SHAPE_ID, proto_val);
obj.type_tag = JsObject::OBJ_TYPE_MESSAGE_PORT;
let inner = Box::into_raw(Box::new(MessagePortInner {
peer,
rx,
peer_port: std::ptr::null_mut(),
closed: false,
}));
// SAFETY: 端口对象不可调用,native_fn 存不透明 `Box<MessagePortInner>` 指针,
// 与 ArrayBuffer / RegExp 的类型化存储模式一致。
obj.set_native_fn(Some(unsafe { NativeFnPtr::from_raw(inner as *const ()) }));
let ptr = vm.alloc_object(obj);
(ptr, inner)
}
/// MessagePort.postMessage(message, transfer):detach 消息、入队对端 mpsc、
/// 同步 `drain_port` 排空对端队列触发对端 onmessage。
///
/// # 步骤
/// 1. this 须为 MessagePort,closed 则 TypeError
/// 2. 解析 transfer 列表(undefined → 空集;违例报 DataCloneError)
/// 3. `detach_message` 产 `MessageValue`(失败原值透传)
/// 4. 入队对端 mpsc(SendError 理论不达,防御性报 TypeError)
/// 5. `drain_port` 排空对端队列并触发对端 onmessage
///
/// # 边界与前提
/// - 源对象由调用方寄存器保活;
/// - 同步重入:handler 内再 postMessage 同通道则嵌套 drain(try_recv 非阻塞,无死锁)。
pub fn message_port_post_message<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
let this_val = vm.reg(args[0]);
if !this_val.is_object() {
return NativeResult::Err(crate::error::create_type_error(vm, "postMessage called on a non-object"));
}
let this_ptr = this_val.as_js_object_ptr();
// SAFETY: is_object 保证非空指针;对象在 native 执行期间被根保持、不被回收。
let this_obj = unsafe { &*this_ptr };
let Some(inner_ptr) = message_port_payload_ptr(this_obj) else {
return NativeResult::Err(crate::error::create_type_error(vm, "postMessage called on a non-MessagePort"));
};
// SAFETY: inner_ptr 经 message_port_payload_ptr 校验为存活载荷盒;标量读取
// 短命,不跨 JS 调用。
if unsafe { (*inner_ptr).closed } {
return NativeResult::Err(crate::error::create_type_error(vm, "port is closed"));
}
let message = if args.len() > 1 { vm.reg(args[1]) } else { JsValue::undefined() };
let transfer = if args.len() > 2 { vm.reg(args[2]) } else { JsValue::undefined() };
let transfer_set = match parse_transfer_list(vm, transfer) {
Ok(s) => s,
Err(e) => return NativeResult::Err(e),
};
let mv = match detach_message(vm, message, &transfer_set) {
Ok(m) => m,
Err(e) => return NativeResult::Err(e),
};
// SAFETY: 同 inner_ptr 校验;peer_port 构造期回填完成,指针有效。
let peer_port = unsafe { (*inner_ptr).peer_port };
if peer_port.is_null() {
return NativeResult::Err(crate::error::create_type_error(vm, "port has no peer"));
}
// SAFETY: 对端 rx 由对端载荷盒保活,对端存活则接收端存活,SendError 理论
// 不达(防御性报 TypeError)。
if unsafe { (*inner_ptr).peer.send(mv) }.is_err() {
return NativeResult::Err(crate::error::create_type_error(vm, "peer port is gone"));
}
match drain_port(vm, peer_port) {
Ok(()) => NativeResult::Ok(JsValue::undefined()),
Err(e) => NativeResult::Err(e),
}
}
/// 排空对端队列:逐条 rehydrate 建 MessageEvent 并经 `dispatchEvent` 交付
/// 到对端端口(注册表查监听器,addEventListener 与属性 handler 同权)。
///
/// # 步骤
/// 1. 读对端载荷盒(对端必存活——双向 GC 边),盒缺失防御性返回 Ok
/// 2. try_recv 循环:Empty / Disconnected 退出;`rehydrate_message` 得 data;
/// 建真实 MessageEvent(data + type "message",ports 空数组)
/// 3. 经 `dispatchEvent` 交付(this = 对端端口,实参 = 事件);监听器异常
/// 原值透传(返回 Err 结束 drain);无监听器时 no-op
///
/// # 边界与前提
/// - rehydrate 新对象与事件盒经 `alloc_object` 入对象表即成根,交付后无引用自然回收;
/// - 同步重入:handler 内再 postMessage 同通道则嵌套 drain(try_recv 非阻塞,无死锁);
/// - messageerror 交付路径为前向钩子(rehydrate 当前不可失败,触发点不可达)。
fn drain_port<H: VmHost>(vm: &mut H, peer_port: *mut JsObject) -> Result<(), JsValue> {
// SAFETY: peer_port 是有效端口对象(双向 GC 边保活)。
let peer_obj = unsafe { &*peer_port };
let Some(inner_ptr) = message_port_payload_ptr(peer_obj) else {
return Ok(());
};
let peer_val = JsValue::from_js_object(peer_port);
// SAFETY: inner_ptr 是有效载荷盒,rx 的 try_recv 非阻塞、不跨 JS 调用。
while let Ok(mv) = unsafe { (*inner_ptr).rx.try_recv() } {
let data = rehydrate_message(vm, &mv);
// 建真实 MessageEvent(data + type "message",ports 空数组)。
let event_ptr = crate::event::create_message_event_box(vm, data, "message");
let event_val = JsValue::from_js_object(event_ptr);
// 经 dispatchEvent 交付(注册表查监听器,addEventListener 与属性 handler 同权)。
vm.set_reg(0, peer_val);
vm.set_reg(1, event_val);
match crate::event_target::event_target_dispatch_event(vm, &[0, 1]) {
NativeResult::Ok(_) => {}
NativeResult::Err(e) => return Err(e),
NativeResult::TailCall { .. } => {}
}
}
Ok(())
}
/// MessagePort.close():置 closed 标志(幂等,重复关闭 no-op)。
///
/// # 边界
/// - this 须为 MessagePort;
/// - 不拆 peer_port 环(两端口自然共亡拆环);对端 close 事件延后。
pub fn message_port_close<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
let this_val = vm.reg(args[0]);
if !this_val.is_object() {
return NativeResult::Err(crate::error::create_type_error(vm, "close called on a non-object"));
}
let this_ptr = this_val.as_js_object_ptr();
// SAFETY: is_object 保证非空指针;对象在 native 执行期间被根保持、不被回收。
let this_obj = unsafe { &*this_ptr };
let Some(inner_ptr) = message_port_payload_ptr(this_obj) else {
return NativeResult::Err(crate::error::create_type_error(vm, "close called on a non-MessagePort"));
};
// SAFETY: inner_ptr 是有效载荷盒,标志写短命、不跨 JS 调用。
unsafe { (*inner_ptr).closed = true };
NativeResult::Ok(JsValue::undefined())
}
/// MessagePort.start():no-op(端口自动激活,无需显式启动)。
pub fn message_port_start<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
let this_val = vm.reg(args[0]);
if !this_val.is_object() {
return NativeResult::Err(crate::error::create_type_error(vm, "start called on a non-object"));
}
let this_ptr = this_val.as_js_object_ptr();
// SAFETY: is_object 保证非空指针;对象在 native 执行期间被根保持、不被回收。
let this_obj = unsafe { &*this_ptr };
if message_port_payload_ptr(this_obj).is_none() {
return NativeResult::Err(crate::error::create_type_error(vm, "start called on a non-MessagePort"));
}
NativeResult::Ok(JsValue::undefined())
}
/// MessagePort.readyState:读 closed 标志返 "open" / "closed"。
pub fn message_port_ready_state<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
let this_val = vm.reg(args[0]);
if !this_val.is_object() {
return NativeResult::Err(crate::error::create_type_error(vm, "readyState called on a non-object"));
}
let this_ptr = this_val.as_js_object_ptr();
// SAFETY: is_object 保证非空指针;对象在 native 执行期间被根保持、不被回收。
let this_obj = unsafe { &*this_ptr };
let Some(inner_ptr) = message_port_payload_ptr(this_obj) else {
return NativeResult::Err(crate::error::create_type_error(vm, "readyState called on a non-MessagePort"));
};
// SAFETY: inner_ptr 是有效载荷盒,标志读短命、不跨 JS 调用。
let closed = unsafe { (*inner_ptr).closed };
NativeResult::Ok(vm.new_string(if closed { "closed" } else { "open" }))
}
#[cfg(test)]
mod tests {
use super::*;
use oxide_kernel::shape_forge::EMPTY_SHAPE_ID;
use oxide_types::object::NativeFnPtr;
/// 构造真实通道并把双端移入载荷盒。
fn port_payload() -> MessagePortInner {
let (peer, rx) = message_queue::channel::<MessageValue>();
MessagePortInner {
peer,
rx,
peer_port: std::ptr::null_mut(),
closed: false,
}
}
/// 手工构造带载荷盒的 MessagePort 对象(不经 Vm,只验 GC 三自由函数)。
fn port_object_with_payload(inner: MessagePortInner) -> JsObject {
let mut obj = JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::undefined());
obj.type_tag = JsObject::OBJ_TYPE_MESSAGE_PORT;
let payload_ptr = Box::into_raw(Box::new(inner));
// SAFETY: 载荷盒形态与 native_fn 槽存储约定一致,测试结束前恰好释放一次。
obj.set_native_fn(Some(unsafe { NativeFnPtr::from_raw(payload_ptr as *const ()) }));
obj
}
#[test]
fn size_and_drop_idempotent() {
let mut obj = port_object_with_payload(port_payload());
let struct_bytes = mem::size_of::<MessagePortInner>() as u64;
assert_eq!(message_port_native_size(&obj), struct_bytes);
// 首次 drop 释放结构体;槽置空后二次 drop 零释放。
assert_eq!(drop_message_port_native(&mut obj), struct_bytes);
assert_eq!(drop_message_port_native(&mut obj), 0);
}
#[test]
fn edges_peer_port_null_and_present() {
let mut obj = port_object_with_payload(port_payload());
// peer_port 空窗口(构造期回填之前):边函数返回零条边。
assert!(message_port_native_edges(&obj).is_empty());
// 回填对端端口边:一条对象边。
let peer = Box::leak(Box::new(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::undefined())));
peer.type_tag = JsObject::OBJ_TYPE_MESSAGE_PORT;
let ptr = message_port_payload_ptr(&obj).unwrap();
// SAFETY: peer 是测试局部对象,测试期间存活,边只读不搬移。
unsafe { (*ptr).peer_port = peer as *mut JsObject };
let edges = message_port_native_edges(&obj);
assert_eq!(edges.len(), 1);
assert!(edges[0].is_object());
// 对端对象是测试局部泄漏,不随端口 drop 释放(裸指针无 drop)。
drop_message_port_native(&mut obj);
}
#[test]
fn channel_roundtrip_through_box() {
let mut obj = port_object_with_payload(port_payload());
let ptr = message_port_payload_ptr(&obj).unwrap();
// SAFETY: 载荷盒测试期间存活。
unsafe {
assert!(!(*ptr).closed);
(*ptr).peer.send(MessageValue::Number(1.0)).unwrap();
assert!(matches!((*ptr).rx.try_recv().unwrap(), MessageValue::Number(n) if n == 1.0));
}
drop_message_port_native(&mut obj);
}
#[test]
fn payload_ptr_brand_guard() {
// 非 MessagePort 类型标签(PLAIN)对象取载荷指针恒 None。
let mut obj = JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::undefined());
let payload_ptr = Box::into_raw(Box::new(port_payload()));
// SAFETY: 同 port_object_with_payload 的载荷盒形态。
obj.set_native_fn(Some(unsafe { NativeFnPtr::from_raw(payload_ptr as *const ()) }));
assert!(message_port_payload_ptr(&obj).is_none());
// SAFETY: 测试构造的盒,恰好释放一次。
unsafe { drop(Box::from_raw(payload_ptr)) };
}
// ---- 端到端行为面(经 Vm 跑脚本,验证构造器与方法绑定)----
use oxide_vm::vm::Vm;
/// 编译并执行单脚本,返回 VM 与完成值。
fn eval(source: &str) -> Result<(Vm, JsValue), String> {
let allocator = oxide_parser::Allocator::default();
let program = oxide_parser::parse(&allocator, source).map_err(|e| format!("parse: {:?}", e))?;
let module = oxide_compiler::compiler::Compiler::new()
.compile(&program)
.map_err(|e| format!("compile: {e}"))?;
let mut vm = Vm::new();
let result = vm.run(&std::sync::Arc::new(module))?;
Ok((vm, result))
}
/// 跑一段返回布尔的断言脚本,断言为真。
fn assert_script(source: &str) {
let (_vm, result) = eval(source).unwrap();
assert!(result.as_bool(), "script failed: {source}");
}
#[test]
fn constructor_shape_and_identity() {
assert_script(
r#"
(function() {
var ch = new MessageChannel();
return (
typeof ch.port1 === 'object' &&
typeof ch.port2 === 'object' &&
ch.port1 !== ch.port2 &&
MessageChannel.length === 0 &&
MessageChannel.name === 'MessageChannel' &&
Object.getPrototypeOf(ch.port1).constructor === MessageChannel
);
})()
"#,
);
}
#[test]
fn port_type_tag_is_message_port() {
let (_vm, result) = eval("new MessageChannel().port1").unwrap();
assert!(result.is_object());
// SAFETY: result 是脚本返回的端口对象,测试期间存活。
let obj = unsafe { &*result.as_js_object_ptr() };
assert_eq!(obj.type_tag, JsObject::OBJ_TYPE_MESSAGE_PORT);
}
#[test]
fn port_onmessage_is_prototype_accessor() {
// onmessage 是原型访问器属性(非自有属性):getOwnPropertyDescriptor
// 返 undefined,原型描述符带 get / set 函数。
assert_script(
r#"
(function() {
var ch = new MessageChannel();
var p = ch.port1;
var ownDesc = Object.getOwnPropertyDescriptor(p, 'onmessage');
var protoDesc = Object.getOwnPropertyDescriptor(Object.getPrototypeOf(p), 'onmessage');
return (
ownDesc === undefined &&
p.onmessage === null &&
protoDesc !== undefined &&
typeof protoDesc.get === 'function' &&
typeof protoDesc.set === 'function' &&
protoDesc.enumerable === false &&
protoDesc.configurable === true
);
})()
"#,
);
}
#[test]
fn basic_delivery_object() {
// handler 收到 MessageEvent,消息值经 e.data 访问。
assert_script(
r#"
(function() {
var ch = new MessageChannel();
var received = null;
ch.port2.onmessage = function(e) { received = e.data; };
ch.port1.postMessage({a: 1});
return received !== null && received.a === 1;
})()
"#,
);
}
#[test]
fn roundtrip_all_cloneable_types() {
assert_script(
r#"
(function() {
var ch = new MessageChannel();
var got = null;
ch.port2.onmessage = function(e) { got = e.data; };
ch.port1.postMessage({
num: 42,
neg: -1.5,
big: 10n,
str: 'hello',
bool: true,
nil: null,
undef: undefined,
arr: [1, 2, 3],
obj: {x: 1, y: 'z'},
map: new Map([['k', 1]]),
set: new Set([1, 2]),
date: new Date(1700000000000),
re: /ab+c/g,
err: new Error('boom')
});
if (got.num !== 42 || got.neg !== -1.5 || got.big !== 10n) return false;
if (got.str !== 'hello' || got.bool !== true || got.nil !== null) return false;
if (!('undef' in got) || got.undef !== undefined) return false;
if (got.arr.length !== 3 || got.arr[1] !== 2) return false;
if (got.obj.x !== 1 || got.obj.y !== 'z') return false;
if (got.map.get('k') !== 1) return false;
if (got.set.size !== 2 || !got.set.has(2)) return false;
if (got.date.getTime() !== 1700000000000) return false;
if (got.re.source !== 'ab+c' || got.re.flags !== 'g') return false;
if (got.err.name !== 'Error' || got.err.message !== 'boom') return false;
return true;
})()
"#,
);
}
#[test]
fn transfer_detaches_source() {
assert_script(
r#"
(function() {
var ch = new MessageChannel();
var ab = new ArrayBuffer(8);
var received = null;
ch.port2.onmessage = function(e) { received = e.data; };
ch.port1.postMessage(ab, [ab]);
return ab.byteLength === 0 && received.byteLength === 8;
})()
"#,
);
}
#[test]
fn clone_path_keeps_source_alive() {
// 未列入 transfer 的 ArrayBuffer 走克隆路径:源不 detach。
assert_script(
r#"
(function() {
var ch = new MessageChannel();
var ab = new ArrayBuffer(4);
var received = null;
ch.port2.onmessage = function(e) { received = e.data; };
ch.port1.postMessage(ab);
return ab.byteLength === 4 && received.byteLength === 4 && received !== ab;
})()
"#,
);
}
#[test]
fn data_clone_error_on_function() {
assert_script(
r#"
(function() {
var ch = new MessageChannel();
try {
ch.port1.postMessage(function(){});
return false;
} catch (e) {
return e.name === 'DataCloneError';
}
})()
"#,
);
}
#[test]
fn data_clone_error_on_circular() {
assert_script(
r#"
(function() {
var ch = new MessageChannel();
var a = {};
a.self = a;
try {
ch.port1.postMessage(a);
return false;
} catch (e) {
return e.name === 'DataCloneError';
}
})()
"#,
);
}
#[test]
fn data_clone_error_on_shared_array_buffer() {
assert_script(
r#"
(function() {
var ch = new MessageChannel();
try {
ch.port1.postMessage(new SharedArrayBuffer(8));
return false;
} catch (e) {
return e.name === 'DataCloneError';
}
})()
"#,
);
}
#[test]
fn transfer_violation_is_data_clone_error() {
// 非 AB 元素与重复元素均报 DataCloneError(与 structuredClone 口径统一)。
assert_script(
r#"
(function() {
var ch = new MessageChannel();
var ab = new ArrayBuffer(4);
try {
ch.port1.postMessage(1, [ab, 'not-a-buffer']);
return false;
} catch (e) {
if (e.name !== 'DataCloneError') return false;
}
try {
ch.port1.postMessage(1, [ab, ab]);
return false;
} catch (e) {
return e.name === 'DataCloneError';
}
})()
"#,
);
}
#[test]
fn close_semantics() {
assert_script(
r#"
(function() {
var ch = new MessageChannel();
var p = ch.port1;
if (p.readyState !== 'open') return false;
p.close();
if (p.readyState !== 'closed') return false;
// 重复关闭幂等 no-op。
p.close();
try {
p.postMessage(1);
return false;
} catch (e) {
return e.name === 'TypeError';
}
})()
"#,
);
}
#[test]
fn start_is_noop() {
assert_script(
r#"
(function() {
var ch = new MessageChannel();
ch.port1.start();
ch.port2.start();
return ch.port1.readyState === 'open' && ch.port2.readyState === 'open';
})()
"#,
);
}
#[test]
fn no_handler_silently_drops() {
// 未设 handler 时 postMessage 不抛错、不派发。
assert_script(
r#"
(function() {
var ch = new MessageChannel();
ch.port1.postMessage({a: 1});
ch.port1.postMessage('x');
return ch.port1.readyState === 'open';
})()
"#,
);
}
#[test]
fn non_construct_form_type_error() {
assert_script(
r#"
(function() {
try {
MessageChannel();
return false;
} catch (e) {
return e.name === 'TypeError';
}
})()
"#,
);
}
#[test]
fn non_port_receiver_type_error() {
// 对非对象 this 直调 native 入口抛 TypeError。
let (mut vm, _result) = eval("new MessageChannel()").unwrap();
vm.set_reg(0, JsValue::int(1));
vm.set_reg(1, JsValue::int(42));
let args = [0u8, 1];
match message_port_post_message(&mut vm, &args) {
NativeResult::Err(e) => {
assert!(e.is_object());
}
_ => panic!("expected TypeError"),
}
}
#[test]
fn non_port_object_type_error() {
// 对象但非 MessagePort(plain 对象)调 postMessage 抛 TypeError。
let (mut vm, plain) = eval("({})").unwrap();
vm.set_reg(0, plain);
vm.set_reg(1, JsValue::int(42));
let args = [0u8, 1];
match message_port_post_message(&mut vm, &args) {
NativeResult::Err(e) => {
assert!(e.is_object());
}
_ => panic!("expected TypeError"),
}
}
#[test]
fn gc_edge_keeps_peer_alive() {
// 仅持 port1 时 port2 不被回收:双向 GC 边保活对端,
// 强制收集后对端 handler 仍可触发。
assert_script(
r#"
(function() {
var ch = new MessageChannel();
var p1 = ch.port1;
var received = null;
(function() {
var p2 = ch.port2;
p2.onmessage = function(e) { received = e.data; };
})();
$262.gc();
p1.postMessage({x: 42});
return received !== null && received.x === 42;
})()
"#,
);
}
}