feat: 端口与通道事件处理器改原型访问器加 dispatchEvent 交付

This commit is contained in:
2026-10-11 09:06:51 +00:00
parent 0ae232c3f5
commit 10caf694b5
6 changed files with 154 additions and 130 deletions
+45 -48
View File
@@ -1,6 +1,7 @@
//! MessageChannel / MessagePort 行为面:`MessageChannel` 构造器(建双向 mpsc
//! 通道与两枚端口对象)、`MessagePort` 原型方法(postMessage 同步交付 /
//! close / start / readyState)与 `onmessage` 属性。
//! close / start / readyState)与 `onmessage` / `onmessageerror` / `onerror`
//! 原型访问器属性(经 dispatchEvent 交付 MessageEvent)。
//!
//! 端口载荷盒(mpsc 双端与对端端口边)存于对象 `native_fn` 槽,向 GC 家族表
//! 暴露 edges/size/drop 三自由函数。
@@ -96,10 +97,12 @@ pub fn drop_message_port_native(obj: &mut JsObject) -> u64 {
/// 2. 建两条 mpsc 通道(A→B 与 B→A),载荷类型 `MessageValue`
/// 3. 建两枚端口对象(proto = %MessagePort.prototype%,载荷盒入 native_fn 槽)
/// 4. 回填双向 peer_port(裸指针写,跨 JS 调用不持借用)
/// 5. 两端口各 define onmessage 数据属性 = null(非枚举)
/// 6. 建 plain 结果对象(proto = %Object.prototype%),define 可枚举
/// 5. 建 plain 结果对象(proto = %Object.prototype%),define 可枚举
/// port1 / port2
///
/// onmessage / onmessageerror / onerror 不在对象上建自有属性(改
/// MessagePort.prototype 访问器属性)。
///
/// # 副作用
/// - 两端口对象经 `alloc_object` 入对象表即成 GC 根;
/// - 双向 peer_port 边闭合环(两端口自然共亡拆环)。
@@ -126,15 +129,9 @@ pub fn message_channel_constructor<H: VmHost>(vm: &mut H, _args: &[u8]) -> Nativ
(*b_inner).peer_port = a_ptr;
}
// onmessage:普通 own 数据属性(非枚举),初值 null。
let si_onmessage = vm.perm_intern("onmessage");
// SAFETY: a_ptr / b_ptr 是本函数刚 alloc_object 的端口对象,存活且本段无别名。
unsafe {
let a_obj = &mut *a_ptr;
let _ = vm.define_data_property(a_obj, si_onmessage, JsValue::null(), PropAttributes::new(true, false, true));
let b_obj = &mut *b_ptr;
let _ = vm.define_data_property(b_obj, si_onmessage, JsValue::null(), PropAttributes::new(true, false, true));
}
// 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));
@@ -226,18 +223,20 @@ pub fn message_port_post_message<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeRe
}
}
/// 排空对端队列:逐条 rehydrate 并触发对端 onmessage。
/// 排空对端队列:逐条 rehydrate 建 MessageEvent 并经 `dispatchEvent` 交付
/// 到对端端口(注册表查监听器,addEventListener 与属性 handler 同权)。
///
/// # 步骤
/// 1. 读对端载荷盒(对端必存活——双向 GC 边),盒缺失防御性返回 Ok
/// 2. try_recv 循环:Empty / Disconnected 退出;`rehydrate_message` 得 data;
/// `ordinary_get` 读 handler(读期用户代码窗口,异常原值透传);handler 是
/// 函数则 `call_function_sync(handler, peer, [data])`(用户异常原值透传,
/// 返回 Err 结束 drain);handler 非函数(null / undefined / 非函数)则静默丢弃该条
/// 建真实 MessageEvent(data + type "message",ports 空数组)
/// 3. 经 `dispatchEvent` 交付(this = 对端端口,实参 = 事件);监听器异常
/// 原值透传(返回 Err 结束 drain);无监听器时 no-op
///
/// # 边界与前提
/// - rehydrate 新对象经 `alloc_object` 入对象表即成根,交付后无引用自然回收;
/// - 同步重入:handler 内再 postMessage 同通道则嵌套 drain(try_recv 非阻塞,无死锁)。
/// - 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 };
@@ -245,27 +244,19 @@ fn drain_port<H: VmHost>(vm: &mut H, peer_port: *mut JsObject) -> Result<(), JsV
return Ok(());
};
let peer_val = JsValue::from_js_object(peer_port);
let si_onmessage = vm.perm_intern("onmessage");
// SAFETY: inner_ptr 是有效载荷盒,rx 的 try_recv 非阻塞、不跨 JS 调用。
while let Ok(mv) = unsafe { (*inner_ptr).rx.try_recv() } {
let data = rehydrate_message(vm, &mv);
let handler = match vm.ordinary_get(peer_obj, si_onmessage, peer_val) {
Ok(h) => h,
Err(e) => {
if let Some(exc) = vm.take_uncaught_value() {
return Err(exc);
}
return Err(crate::error::create_type_error(vm, &e));
}
};
if !crate::iterator::is_callable(handler) {
continue;
}
if let Err(e) = vm.call_function_sync(handler, peer_val, &[data]) {
if let Some(exc) = vm.take_uncaught_value() {
return Err(exc);
}
return Err(crate::iterator::engine_error(vm, &e));
// 建真实 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(())
@@ -456,19 +447,24 @@ mod tests {
}
#[test]
fn port_onmessage_is_own_non_enumerable() {
fn port_onmessage_is_prototype_accessor() {
// onmessage 是原型访问器属性(非自有属性):getOwnPropertyDescriptor
// 返 undefined,原型描述符带 get / set 函数。
assert_script(
r#"
(function() {
var ch = new MessageChannel();
var p = ch.port1;
var desc = Object.getOwnPropertyDescriptor(p, 'onmessage');
var ownDesc = Object.getOwnPropertyDescriptor(p, 'onmessage');
var protoDesc = Object.getOwnPropertyDescriptor(Object.getPrototypeOf(p), 'onmessage');
return (
desc !== undefined &&
desc.value === null &&
desc.writable === true &&
desc.enumerable === false &&
desc.configurable === true
ownDesc === undefined &&
p.onmessage === null &&
protoDesc !== undefined &&
typeof protoDesc.get === 'function' &&
typeof protoDesc.set === 'function' &&
protoDesc.enumerable === false &&
protoDesc.configurable === true
);
})()
"#,
@@ -477,12 +473,13 @@ mod tests {
#[test]
fn basic_delivery_object() {
// handler 收到 MessageEvent,消息值经 e.data 访问。
assert_script(
r#"
(function() {
var ch = new MessageChannel();
var received = null;
ch.port2.onmessage = function(d) { received = d; };
ch.port2.onmessage = function(e) { received = e.data; };
ch.port1.postMessage({a: 1});
return received !== null && received.a === 1;
})()
@@ -497,7 +494,7 @@ mod tests {
(function() {
var ch = new MessageChannel();
var got = null;
ch.port2.onmessage = function(d) { got = d; };
ch.port2.onmessage = function(e) { got = e.data; };
ch.port1.postMessage({
num: 42,
neg: -1.5,
@@ -538,7 +535,7 @@ mod tests {
var ch = new MessageChannel();
var ab = new ArrayBuffer(8);
var received = null;
ch.port2.onmessage = function(d) { received = d; };
ch.port2.onmessage = function(e) { received = e.data; };
ch.port1.postMessage(ab, [ab]);
return ab.byteLength === 0 && received.byteLength === 8;
})()
@@ -555,7 +552,7 @@ mod tests {
var ch = new MessageChannel();
var ab = new ArrayBuffer(4);
var received = null;
ch.port2.onmessage = function(d) { received = d; };
ch.port2.onmessage = function(e) { received = e.data; };
ch.port1.postMessage(ab);
return ab.byteLength === 4 && received.byteLength === 4 && received !== ab;
})()
@@ -751,7 +748,7 @@ mod tests {
var received = null;
(function() {
var p2 = ch.port2;
p2.onmessage = function(d) { received = d; };
p2.onmessage = function(e) { received = e.data; };
})();
$262.gc();
p1.postMessage({x: 42});