feat: Worker 事件处理器改原型访问器加 postMessage transfer 解析

This commit is contained in:
2026-10-11 08:49:17 +00:00
parent c4b441a1a8
commit 0ae232c3f5
6 changed files with 214 additions and 82 deletions
+1 -33
View File
@@ -5,7 +5,6 @@
//! 端口载荷盒(mpsc 双端与对端端口边)存于对象 `native_fn` 槽,向 GC 家族表
//! 暴露 edges/size/drop 三自由函数。
use std::collections::HashSet;
use std::mem;
use oxide_kernel::message_queue;
@@ -15,7 +14,7 @@ use oxide_types::value::JsValue;
use oxide_runtime_api::{NativeResult, ProtoKind, VmHost};
use crate::message_value::{detach_message, rehydrate_message, MessageValue};
use crate::message_value::{detach_message, parse_transfer_list, rehydrate_message, MessageValue};
/// MessagePort 载荷盒:存于对象 `native_fn` 槽(`Box::into_raw`,realm 局部
/// 单线程)。`peer` 是发往对端的 mpsc 发送端(无 GC 边),`rx` 是收自对端的
@@ -169,37 +168,6 @@ fn make_port_object<H: VmHost>(
(ptr, inner)
}
/// 解析 postMessage 的 transfer 参数为 ArrayBuffer 指针集合。
///
/// # 步骤
/// 1. undefined 返空集
/// 2. ToList 迭代一切可迭代(数组 / 字符串 / 生成器 / Map / Set)
/// 3. 每元素须为 ArrayBuffer,重复元素报 DataCloneError
///
/// # 边界
/// - 不可迭代对象经 GetIterator 抛 TypeError(原值透传);
/// - 迭代中途抛错经 IteratorClose 后透传原异常;
/// - 非 AB 元素 / 重复元素报 DataCloneError(与 structuredClone 口径统一)。
fn parse_transfer_list<H: VmHost>(vm: &mut H, transfer: JsValue) -> Result<HashSet<*const JsObject>, JsValue> {
let mut set = HashSet::new();
if transfer.is_undefined() {
return Ok(set);
}
crate::iterator::iterate_elements(vm, transfer, |vm, e| {
if !e.is_object() || !unsafe { &*e.as_js_object_ptr() }.is_array_buffer_obj() {
return Err(crate::structured_clone::data_clone_error(
vm,
"transfer list element is not an ArrayBuffer",
));
}
if !set.insert(e.as_js_object_ptr() as *const JsObject) {
return Err(crate::structured_clone::data_clone_error(vm, "duplicate buffer in transfer list"));
}
Ok(())
})?;
Ok(set)
}
/// MessagePort.postMessage(message, transfer):detach 消息、入队对端 mpsc、
/// 同步 `drain_port` 排空对端队列触发对端 onmessage。
///
@@ -84,6 +84,37 @@ impl DetachState {
}
}
/// 解析 postMessage 的 transfer 参数为 ArrayBuffer 指针集合。
///
/// # 步骤
/// 1. undefined 返空集
/// 2. ToList 迭代一切可迭代(数组 / 字符串 / 生成器 / Map / Set)
/// 3. 每元素须为 ArrayBuffer,重复元素报 DataCloneError
///
/// # 边界
/// - 不可迭代对象经 GetIterator 抛 TypeError(原值透传);
/// - 迭代中途抛错经 IteratorClose 后透传原异常;
/// - 非 AB 元素 / 重复元素报 DataCloneError(与 structuredClone 口径统一)。
pub fn parse_transfer_list<H: VmHost>(vm: &mut H, transfer: JsValue) -> Result<HashSet<*const JsObject>, JsValue> {
let mut set = HashSet::new();
if transfer.is_undefined() {
return Ok(set);
}
crate::iterator::iterate_elements(vm, transfer, |vm, e| {
if !e.is_object() || !unsafe { &*e.as_js_object_ptr() }.is_array_buffer_obj() {
return Err(crate::structured_clone::data_clone_error(
vm,
"transfer list element is not an ArrayBuffer",
));
}
if !set.insert(e.as_js_object_ptr() as *const JsObject) {
return Err(crate::structured_clone::data_clone_error(vm, "duplicate buffer in transfer list"));
}
Ok(())
})?;
Ok(set)
}
/// 源 realm 侧:遍历源值产出 `MessageValue`。
///
/// # 步骤
+37 -1
View File
@@ -9,7 +9,7 @@
use std::sync::Arc;
use crate::bind_constructor;
use crate::bindings::{apply_binding_table, configure_native_constructor};
use crate::bindings::{apply_binding_table, bind_accessor_getset, configure_native_constructor};
use oxide_kernel::kernel::{KernelCore, KernelSession};
use oxide_kernel::shape_forge::EMPTY_SHAPE_ID;
use oxide_types::object::{JsObject, PropAttributes};
@@ -120,6 +120,24 @@ pub fn bind_worker(core: &Arc<KernelCore>, session: &KernelSession, global: &mut
],
);
}
// onmessage / onerror / onmessageerror 访问器属性(getter 读注册表属性条目、
// setter 写)。幂等守卫。
for name in ["onmessage", "onerror", "onmessageerror"] {
let key = sf.intern(name).0;
if sh.lookup_position(proto.shape_id(), key).is_none() {
bind_accessor_getset(
core,
session,
proto,
key,
&format!("get {name}"),
&format!("set {name}"),
crate::worker::bindings::worker_event_handler_getter as *const (),
crate::worker::bindings::worker_event_handler_setter as *const (),
);
}
}
}
// 全局 `Worker` 槽(bind_constructor! 自带既有槽原位更新、无槽新开)。
@@ -186,6 +204,24 @@ pub fn bind_worker(core: &Arc<KernelCore>, session: &KernelSession, global: &mut
global.set_data_meta(pos, PropAttributes::new(true, false, true));
global.bump_generation();
}
// onmessage / onmessageerror / onerror 直绑全局(访问器,worker realm 经
// self === global 可达,self.onmessage = fn 由此成立;主 realm 为无害占位)。
// 幂等守卫。
for name in ["onmessage", "onmessageerror", "onerror"] {
let key = sf.intern(name).0;
if sh.lookup_position(global.shape_id(), key).is_none() {
bind_accessor_getset(
core,
session,
global,
key,
&format!("get {name}"),
&format!("set {name}"),
crate::worker::bindings::worker_event_handler_getter as *const (),
crate::worker::bindings::worker_event_handler_setter as *const (),
);
}
}
// name / location getter(幂等守卫)。
// let si_name_global = sf.intern("name").0;
// if sh.lookup_position(global.shape_id(), si_name_global).is_none() {
+125 -28
View File
@@ -14,10 +14,10 @@
use std::cell::{Cell, RefCell};
use oxide_builtins::message_value::detach_message;
use oxide_builtins::message_value::{detach_message, parse_transfer_list};
use oxide_kernel::message_queue::Sender;
use oxide_kernel::shape_forge::EMPTY_SHAPE_ID;
use oxide_runtime_api::{NativeResult, ProtoKind, VmHost};
use oxide_runtime_api::{ListenerEntry, NativeResult, ProtoKind, VmHost};
use oxide_types::object::{JsObject, PropAttributes};
use oxide_types::value::JsValue;
@@ -65,8 +65,8 @@ pub(crate) fn worker_close_requested() -> bool {
/// 3. 主线程侧读 url 文件(`std::fs::read_to_string`),失败抛 TypeError。
/// 4. `vm.spawn_worker(source)` 派生 worker,得 worker 编号。
/// 5. 建 Worker 对象:PLAIN 对象(`[[Prototype]]` → Worker.prototype)加非枚举
/// `workerId` 数据属性(值为 worker 编号)与 `onmessage`/`onerror`/
/// `onmessageerror` 普通 own 属性(初值 undefined)。无 native 盒、无 GC 家族。
/// `workerId` 数据属性(值为 worker 编号)。`onmessage`/`onerror`/
/// `onmessageerror` 不在对象上建自有属性(改原型访问器属性)。无 native 盒、无 GC 家族。
/// 6. 返回 Worker 对象。
///
/// # 边界与前提
@@ -113,20 +113,8 @@ pub(crate) fn worker_constructor(vm: &mut Vm, args: &[u8]) -> NativeResult {
// SAFETY: ptr 是本函数刚 alloc_object 的对象,存活且本段无别名。
let worker_obj = unsafe { &mut *ptr };
// onmessage / onerror / onmessageerror:普通 own 数据属性(非枚举),初值 undefined。
let si_onmessage = vm.perm_intern("onmessage");
let _ =
vm.define_data_property(worker_obj, si_onmessage, JsValue::undefined(), PropAttributes::new(true, false, true));
let si_onerror = vm.perm_intern("onerror");
let _ =
vm.define_data_property(worker_obj, si_onerror, JsValue::undefined(), PropAttributes::new(true, false, true));
let si_onmessageerror = vm.perm_intern("onmessageerror");
let _ = vm.define_data_property(
worker_obj,
si_onmessageerror,
JsValue::undefined(),
PropAttributes::new(true, false, true),
);
// onmessage / onerror / onmessageerror 不在对象上建自有属性:改 Worker.prototype
// 访问器属性(getter 读注册表属性条目、setter 写),经既有 dispatchEvent 路径交付。
// workerId:非枚举数据属性(值为 worker 编号,数值表示无 GC 边)。
let si_worker_id = vm.perm_intern("workerId");
@@ -198,15 +186,17 @@ fn read_worker_id(vm: &mut Vm, this_val: JsValue) -> Result<u64, JsValue> {
Ok(n.max(0.0) as u64)
}
/// `worker.postMessage(msg)`:读 `workerId`、detach 消息、投递到 worker。
/// `worker.postMessage(msg, transfer)`:读 `workerId`、解析 transfer、detach 消息、投递到 worker。
///
/// # 步骤
/// 1. 读 `this`(`args[0]`)的 `workerId` 自有属性(快路径)。
/// 2. 读 `args[1]` 为 msg,经 `detach_message` 转 `MessageValue`(空 transfer 集合)。
/// 3. 委托 `vm.worker_post_message(id, mv)`(935.3 固有方法,内部 `send`)。
/// 2. 读 `args[1]` 为 msg、`args[2]` 为 transfer,经 `parse_transfer_list` 解析
/// transfer 集合(undefined 返空集、违例报 DataCloneError)。
/// 3. 经 `detach_message` 转 `MessageValue`(transfer 命中的缓冲源 detach)。
/// 4. 委托 `vm.worker_post_message(id, mv)`(935.3 固有方法,内部 `send`)。
///
/// # 边界
/// - DataCloneError 经 `detach_message` 的 `Err` 臂抛出。
/// - DataCloneError 经 `parse_transfer_list` 与 `detach_message` 的 `Err` 臂抛出。
/// - worker 通道已断开(worker 线程退出)时 `vm.worker_post_message` 返 `Err`。
pub(crate) fn worker_post_message(vm: &mut Vm, args: &[u8]) -> NativeResult {
let this_val = vm.reg(if args.is_empty() { 0 } else { args[0] });
@@ -215,7 +205,11 @@ pub(crate) fn worker_post_message(vm: &mut Vm, args: &[u8]) -> NativeResult {
Err(e) => return NativeResult::Err(e),
};
let msg_val = if args.len() > 1 { vm.reg(args[1]) } else { JsValue::undefined() };
let transfer: std::collections::HashSet<*const JsObject> = std::collections::HashSet::new();
let transfer_val = if args.len() > 2 { vm.reg(args[2]) } else { JsValue::undefined() };
let transfer = match parse_transfer_list(vm, transfer_val) {
Ok(s) => s,
Err(e) => return NativeResult::Err(e),
};
let mv = match detach_message(vm, msg_val, &transfer) {
Ok(mv) => mv,
Err(e) => return NativeResult::Err(e),
@@ -242,18 +236,24 @@ pub(crate) fn worker_terminate(vm: &mut Vm, args: &[u8]) -> NativeResult {
}
}
/// `self.postMessage(msg)`(worker 侧):detach 消息、发回主线程。
/// `self.postMessage(msg, transfer)`(worker 侧):解析 transfer、detach 消息、发回主线程。
///
/// # 步骤
/// 1. 读 `args[1]` 为 msg,经 `detach_message` 转 `MessageValue`(空 transfer 集合)。
/// 2. 经 thread-local `WORKER_OUT_TX` 发回主线程(主 realm 无通道时静默丢弃)。
/// 1. 读 `args[1]` 为 msg、`args[2]` 为 transfer,经 `parse_transfer_list` 解析
/// transfer 集合(undefined 返空集、违例报 DataCloneError)。
/// 2. 经 `detach_message` 转 `MessageValue`(transfer 命中的缓冲源 detach)。
/// 3. 经 thread-local `WORKER_OUT_TX` 发回主线程(主 realm 无通道时静默丢弃)。
///
/// # 边界
/// - DataCloneError 经 `detach_message` 的 `Err` 臂抛出。
/// - DataCloneError 经 `parse_transfer_list` 与 `detach_message` 的 `Err` 臂抛出。
/// - 主 realm(无 `WORKER_OUT_TX`)或主线程已 drop 接收端时静默丢弃。
pub(crate) fn self_post_message(vm: &mut Vm, args: &[u8]) -> NativeResult {
let msg_val = if args.len() > 1 { vm.reg(args[1]) } else { JsValue::undefined() };
let transfer: std::collections::HashSet<*const JsObject> = std::collections::HashSet::new();
let transfer_val = if args.len() > 2 { vm.reg(args[2]) } else { JsValue::undefined() };
let transfer = match parse_transfer_list(vm, transfer_val) {
Ok(s) => s,
Err(e) => return NativeResult::Err(e),
};
let mv = match detach_message(vm, msg_val, &transfer) {
Ok(mv) => mv,
Err(e) => return NativeResult::Err(e),
@@ -287,6 +287,103 @@ pub(crate) fn self_location(vm: &mut Vm, _args: &[u8]) -> NativeResult {
NativeResult::Ok(vm.new_string(""))
}
/// 从当前函数对象的 `name` 读事件类型(`get onmessage` / `set onmessage` →
/// `"message"`)。
///
/// # 步骤
/// 1. 读 callee 槽(寄存器 254)为当前函数对象(native 调用收口置位)。
/// 2. 读函数对象 `name` 自有属性,剥 `get ` / `set ` 前缀与 `on` 前缀得事件类型串。
/// 3. intern 事件类型串返 si。
///
/// # 边界
/// - callee 非对象、无 `name` 属性、或非 `get on<type>` / `set on<type>` 形态时返 `None`。
fn read_event_type_si(vm: &mut Vm) -> Option<u32> {
let callee = vm.reg(254);
if !callee.is_object() {
return None;
}
let callee_ptr = callee.as_js_object_ptr();
// SAFETY: callee 是当前函数对象,native 调用期间存活。
let callee_obj = unsafe { &*callee_ptr };
let si_name = vm.perm_intern("name");
let store = vm.get_own_property_slot(callee_obj, si_name)?;
let name_val = callee_obj.get_prop_at(store);
let name = vm.lookup_str(name_val)?;
let rest = name.strip_prefix("get ").or_else(|| name.strip_prefix("set "))?;
let type_text = rest.strip_prefix("on")?;
Some(vm.perm_intern(type_text))
}
/// `onmessage` / `onerror` / `onmessageerror` 访问器 getter(Worker.prototype 与
/// worker global 共用):读注册表属性条目返回调,无则 null。
///
/// # 步骤
/// 1. 读 `this`(`args[0]`)为目标对象(非对象返 null)。
/// 2. 从函数对象 `name` 读事件类型 si。
/// 3. 查注册表取该目标同类型属性监听器条目,返其回调(无则 null)。
///
/// # 边界
/// - 注册表条目按目标对象裸指针为键(弱引用,不保活)。
pub(crate) fn worker_event_handler_getter(vm: &mut Vm, args: &[u8]) -> NativeResult {
let this_val = vm.reg(args[0]);
if !this_val.is_object() {
return NativeResult::Ok(JsValue::null());
}
let this_ptr = this_val.as_js_object_ptr();
let Some(type_si) = read_event_type_si(vm) else {
return NativeResult::Ok(JsValue::null());
};
for entry in vm.et_lookup(this_ptr) {
if entry.is_attribute && entry.type_si == type_si {
return NativeResult::Ok(entry.callback);
}
}
NativeResult::Ok(JsValue::null())
}
/// `onmessage` / `onerror` / `onmessageerror` 访问器 setter(Worker.prototype 与
/// worker global 共用):写注册表属性条目(可调用则登记、null / undefined 则移除)。
///
/// # 步骤
/// 1. 读 `this`(`args[0]`)为目标对象(非对象 no-op)。
/// 2. 从函数对象 `name` 读事件类型 si。
/// 3. 移除该目标同类型的既有属性监听器条目(若有)。
/// 4. 值为可调用时登记新属性监听器条目(`is_attribute` 置位);null / undefined 不登记。
///
/// # 副作用
/// - 写注册表(`et_unregister` / `et_register`)。
pub(crate) fn worker_event_handler_setter(vm: &mut Vm, args: &[u8]) -> NativeResult {
let this_val = vm.reg(args[0]);
if !this_val.is_object() {
return NativeResult::Ok(JsValue::undefined());
}
let this_ptr = this_val.as_js_object_ptr();
let Some(type_si) = read_event_type_si(vm) else {
return NativeResult::Ok(JsValue::undefined());
};
let value = if args.len() > 1 { vm.reg(args[1]) } else { JsValue::undefined() };
// 移除该目标同类型的既有属性监听器条目(若有)。
for entry in vm.et_lookup(this_ptr) {
if entry.is_attribute && entry.type_si == type_si {
vm.et_unregister(this_ptr, entry.type_si, entry.callback, entry.capture);
}
}
// 值为可调用时登记新属性监听器条目。
if oxide_builtins::iterator::is_callable(value) {
vm.et_register(
this_ptr,
ListenerEntry {
type_si,
callback: value,
capture: false,
once: false,
is_attribute: true,
},
);
}
NativeResult::Ok(JsValue::undefined())
}
/// 消息事件构造辅助:建真实 MessageEvent 盒(`data` + `type`,`ports` 空数组)。
///
/// 消息路径(`deliver_worker_messages` / `deliver_self_message`)经本辅助建