mirror of
https://github.com/pjh456/OxideJS.git
synced 2026-10-25 19:21:11 +08:00
feat: 事件目标弱引用注册表加三方法加原型链
This commit is contained in:
@@ -85,9 +85,9 @@ pub fn bind_broadcast_channel(core: &Arc<KernelCore>, session: &KernelSession, g
|
||||
proto.bump_generation();
|
||||
}
|
||||
|
||||
// [[Prototype]] → Object.prototype(幂等,裸写槽)。
|
||||
let object_proto_val = JsValue::from_js_object(world.object_proto.as_ptr() as *mut JsObject);
|
||||
let _ = proto.set_proto(object_proto_val);
|
||||
// [[Prototype]] → EventTarget.prototype(事件三方法经原型链可达;幂等,裸写槽)。
|
||||
let et_proto_val = JsValue::from_js_object(world.event_target_proto.as_ptr() as *mut JsObject);
|
||||
let _ = proto.set_proto(et_proto_val);
|
||||
|
||||
// 原型方法:postMessage / close(每方法先 lookup_position 守卫)。
|
||||
let si_post_message = sf.intern("postMessage").0;
|
||||
|
||||
@@ -162,4 +162,39 @@ pub fn bind_event(core: &Arc<KernelCore>, session: &KernelSession, global: &mut
|
||||
bind_accessor_getter(core, session, proto, name, fn_ptr);
|
||||
}
|
||||
}
|
||||
|
||||
// ---- 填 EventTarget 原型占位(无构造器,仅暴露原型与三方法)----
|
||||
// 幂等:三方法经 lookup_position 守卫,[[Prototype]] 裸写槽幂等。
|
||||
let et_proto_ptr = world.event_target_proto.as_ptr() as *mut JsObject;
|
||||
let et_proto = unsafe { &mut *et_proto_ptr };
|
||||
// [[Prototype]] → Object.prototype(幂等,裸写槽)。
|
||||
let object_proto_val = JsValue::from_js_object(world.object_proto.as_ptr() as *mut JsObject);
|
||||
let _ = et_proto.set_proto(object_proto_val);
|
||||
// 三方法:addEventListener / removeEventListener / dispatchEvent(每方法先
|
||||
// lookup_position 守卫)。
|
||||
let si_add = sf.intern("addEventListener").0;
|
||||
if sh.lookup_position(et_proto.shape_id(), si_add).is_none() {
|
||||
apply_binding_table(
|
||||
world,
|
||||
et_proto,
|
||||
core,
|
||||
&[
|
||||
(
|
||||
"addEventListener",
|
||||
oxide_builtins::event_target::event_target_add_event_listener::<crate::vm::Vm> as *const (),
|
||||
3,
|
||||
),
|
||||
(
|
||||
"removeEventListener",
|
||||
oxide_builtins::event_target::event_target_remove_event_listener::<crate::vm::Vm> as *const (),
|
||||
3,
|
||||
),
|
||||
(
|
||||
"dispatchEvent",
|
||||
oxide_builtins::event_target::event_target_dispatch_event::<crate::vm::Vm> as *const (),
|
||||
1,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -85,9 +85,9 @@ pub fn bind_message_channel(core: &Arc<KernelCore>, session: &KernelSession, glo
|
||||
proto.bump_generation();
|
||||
}
|
||||
|
||||
// [[Prototype]] → Object.prototype(幂等,裸写槽)。
|
||||
let object_proto_val = JsValue::from_js_object(world.object_proto.as_ptr() as *mut JsObject);
|
||||
let _ = proto.set_proto(object_proto_val);
|
||||
// [[Prototype]] → EventTarget.prototype(事件三方法经原型链可达;幂等,裸写槽)。
|
||||
let et_proto_val = JsValue::from_js_object(world.event_target_proto.as_ptr() as *mut JsObject);
|
||||
let _ = proto.set_proto(et_proto_val);
|
||||
|
||||
// 原型方法:postMessage / close / start(每方法先 lookup_position 守卫)。
|
||||
let si_post_message = sf.intern("postMessage").0;
|
||||
|
||||
@@ -75,8 +75,10 @@ pub fn bind_worker(core: &Arc<KernelCore>, session: &KernelSession, global: &mut
|
||||
match sh.lookup_position(ctor.shape_id(), si_prototype) {
|
||||
Some(pos) if ctor.get_prop_at(pos).is_object() => ctor.get_prop_at(pos).as_js_object_ptr(),
|
||||
_ => {
|
||||
let object_proto = world.object_proto.as_ptr() as *mut JsObject;
|
||||
let proto = JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::from_js_object(object_proto));
|
||||
// Worker.prototype 的 [[Prototype]] 指向 EventTarget.prototype
|
||||
// (事件三方法经原型链可达;EventTarget 无构造器,仅暴露原型)。
|
||||
let et_proto = world.event_target_proto.as_ptr() as *mut JsObject;
|
||||
let proto = JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::from_js_object(et_proto));
|
||||
let ptr = Box::into_raw(Box::new(proto));
|
||||
world.track_leaked_object(ptr);
|
||||
// 回填构造器 prototype 槽(描述符 { f,f,f })。
|
||||
@@ -145,6 +147,33 @@ pub fn bind_worker(core: &Arc<KernelCore>, session: &KernelSession, global: &mut
|
||||
],
|
||||
);
|
||||
}
|
||||
// EventTarget 三方法直绑全局(worker realm 经 self === global 可达;全局原型链
|
||||
// 是 Object.prototype,首版不改全局原型,三方法直绑全局等价可见)。幂等守卫。
|
||||
let si_self_add = sf.intern("addEventListener").0;
|
||||
if sh.lookup_position(global.shape_id(), si_self_add).is_none() {
|
||||
apply_binding_table(
|
||||
world,
|
||||
global,
|
||||
core,
|
||||
&[
|
||||
(
|
||||
"addEventListener",
|
||||
oxide_builtins::event_target::event_target_add_event_listener::<crate::vm::Vm> as *const (),
|
||||
3,
|
||||
),
|
||||
(
|
||||
"removeEventListener",
|
||||
oxide_builtins::event_target::event_target_remove_event_listener::<crate::vm::Vm> as *const (),
|
||||
3,
|
||||
),
|
||||
(
|
||||
"dispatchEvent",
|
||||
oxide_builtins::event_target::event_target_dispatch_event::<crate::vm::Vm> as *const (),
|
||||
1,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
// self 全局属性:值为 global 对象本身(worker realm 经 self === global 可达,
|
||||
// self.onmessage / self.postMessage 由此成立)。幂等守卫。
|
||||
let si_self = sf.intern("self").0;
|
||||
|
||||
@@ -706,6 +706,21 @@ impl SessionGc {
|
||||
.broadcast_channels
|
||||
.retain(|_, channels| !channels.is_empty());
|
||||
|
||||
// EventTarget 监听器注册表剪枝:按 mark 位移除死目标对象(弱键,同
|
||||
// broadcast_channels 时序——死目标在随后的分流循环被释放,注册表已先
|
||||
// 移除其条目,无悬垂)。
|
||||
vm.realm
|
||||
.gc
|
||||
.borrow_mut()
|
||||
.event_targets
|
||||
.retain(|&ptr, _| {
|
||||
if ptr.is_null() {
|
||||
return false;
|
||||
}
|
||||
// SAFETY: ptr 来自 session 对象表登记,sweep 运行期间有效。
|
||||
unsafe { (*ptr).is_gc_marked() }
|
||||
});
|
||||
|
||||
// 按 mark 位分流:存活保留清位,死对象释放本体、堆区与 upvalue 出表。
|
||||
let mut dead = 0u64;
|
||||
let mut freed_bytes = 0u64;
|
||||
|
||||
@@ -42,11 +42,12 @@ pub(crate) enum RootGroup {
|
||||
Global,
|
||||
WorkerObjects,
|
||||
DisposeStack,
|
||||
EventTargets,
|
||||
}
|
||||
|
||||
impl RootGroup {
|
||||
/// 根组总数(与变体数同源,绑定逐组计数数组的长度)。
|
||||
pub const COUNT: usize = 27;
|
||||
pub const COUNT: usize = 28;
|
||||
}
|
||||
|
||||
impl Vm {
|
||||
@@ -180,6 +181,14 @@ impl Vm {
|
||||
for &worker_obj in self.worker_objects.values() {
|
||||
f(RootGroup::WorkerObjects, worker_obj);
|
||||
}
|
||||
// EventTarget 监听器注册表:监听器回调是 GC 根(存活目标的回调随目标存活,
|
||||
// 漏根 → 回调被 sweep 释放 → 派发解引用悬垂)。弱键剪枝见 sweep;此处
|
||||
// 无条件标活全部回调(死目标的回调多活一轮收集,非泄漏,sweep 已剪其条目)。
|
||||
for state in self.realm.gc.borrow().event_targets.values() {
|
||||
for entry in &state.listeners {
|
||||
f(RootGroup::EventTargets, entry.callback);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// GC 根统一枚举入口:遍历的字段清单与 `for_each_value` 相同。
|
||||
|
||||
@@ -4,7 +4,7 @@ use std::ffi::c_void;
|
||||
use std::sync::Arc;
|
||||
|
||||
use oxide_kernel::KernelCore;
|
||||
use oxide_runtime_api::{ProtoKind, ShapeNode};
|
||||
use oxide_runtime_api::{ListenerEntry, ProtoKind, ShapeNode};
|
||||
use oxide_types::mem::P;
|
||||
use oxide_types::object::{Cell, JsObject, PropAttributes};
|
||||
use oxide_types::value::JsValue;
|
||||
@@ -449,4 +449,27 @@ impl oxide_runtime_api::VmHost for Vm {
|
||||
fn bc_lookup(&self, name: &str) -> Vec<*mut JsObject> {
|
||||
self.realm.gc.borrow().broadcast_channels.get(name).cloned().unwrap_or_default()
|
||||
}
|
||||
fn et_register(&self, target: *mut JsObject, entry: ListenerEntry) {
|
||||
self.realm
|
||||
.gc
|
||||
.borrow_mut()
|
||||
.event_targets
|
||||
.entry(target)
|
||||
.or_default()
|
||||
.listeners
|
||||
.push(entry);
|
||||
}
|
||||
fn et_unregister(&self, target: *mut JsObject, type_si: u32, callback: JsValue, capture: bool) {
|
||||
let mut gc = self.realm.gc.borrow_mut();
|
||||
let should_remove = gc.event_targets.get_mut(&target).is_some_and(|state| {
|
||||
state.listeners.retain(|e| !(e.type_si == type_si && e.capture == capture && e.callback == callback));
|
||||
state.listeners.is_empty()
|
||||
});
|
||||
if should_remove {
|
||||
gc.event_targets.remove(&target);
|
||||
}
|
||||
}
|
||||
fn et_lookup(&self, target: *mut JsObject) -> Vec<ListenerEntry> {
|
||||
self.realm.gc.borrow().event_targets.get(&target).map(|s| s.listeners.clone()).unwrap_or_default()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ use std::collections::HashMap;
|
||||
use crate::session_gc::SessionGc;
|
||||
use crate::vm::ForInIter;
|
||||
use oxide_builtins::iterator::BuiltinIterKind;
|
||||
use oxide_runtime_api::EventTargetState;
|
||||
use oxide_types::object::{Cell as UpvalueCell, JsObject, JsString};
|
||||
use oxide_types::private_key::WELL_KNOWN_SYMBOL_COUNT;
|
||||
use oxide_types::value::JsValue;
|
||||
@@ -65,6 +66,11 @@ pub(crate) struct GcState {
|
||||
/// (弱引用,不保活)。构造器分配后登记、close 注销、GC sweep 按 mark 位
|
||||
/// 剪枝、full_reset 随 session 对象一并清表。
|
||||
pub(crate) broadcast_channels: HashMap<String, Vec<*mut JsObject>>,
|
||||
/// EventTarget per-realm 弱引用监听器注册表:目标对象裸指针 → 监听器集合
|
||||
/// (弱键,不保活)。addEventListener 登记、removeEventListener 注销、GC
|
||||
/// sweep 按 mark 位剪枝、full_reset 随 session 对象一并清表。值持监听器
|
||||
/// 回调的 GC 对象边,mark 期经根枚举标活(存活目标的回调随目标存活)。
|
||||
pub(crate) event_targets: HashMap<*mut JsObject, EventTargetState>,
|
||||
}
|
||||
|
||||
impl GcState {
|
||||
|
||||
@@ -103,6 +103,7 @@ impl Vm {
|
||||
pending_forced_collect: false,
|
||||
gc_pressure_mode: gc_pressure_mode_from_env(),
|
||||
broadcast_channels: std::collections::HashMap::new(),
|
||||
event_targets: std::collections::HashMap::new(),
|
||||
}),
|
||||
symbols: RefCell::new(SymbolState {
|
||||
symbol_descriptions: Vec::new(),
|
||||
@@ -269,6 +270,7 @@ impl Vm {
|
||||
pending_forced_collect: false,
|
||||
gc_pressure_mode: gc_pressure_mode_from_env(),
|
||||
broadcast_channels: std::collections::HashMap::new(),
|
||||
event_targets: std::collections::HashMap::new(),
|
||||
}),
|
||||
symbols: RefCell::new(SymbolState {
|
||||
symbol_descriptions: Vec::new(),
|
||||
@@ -490,6 +492,9 @@ impl Vm {
|
||||
// BroadcastChannel 注册表持 session 对象裸指针:teardown 释放全部
|
||||
// session 对象前须清表,否则残留悬垂指针。
|
||||
self.realm.gc.borrow_mut().broadcast_channels.clear();
|
||||
// EventTarget 监听器注册表同持 session 对象裸指针(弱键):teardown 释放
|
||||
// 全部 session 对象前须清表,否则残留悬垂指针。
|
||||
self.realm.gc.borrow_mut().event_targets.clear();
|
||||
// Worker 对象注册表持 session 对象指针:teardown 释放全部 session 对象
|
||||
// 前须清表,否则残留悬垂指针。
|
||||
self.worker_objects.clear();
|
||||
|
||||
@@ -287,42 +287,43 @@ pub(crate) fn self_location(vm: &mut Vm, _args: &[u8]) -> NativeResult {
|
||||
NativeResult::Ok(vm.new_string(""))
|
||||
}
|
||||
|
||||
/// 事件构造辅助:建事件对象(`data` + `type` + `message` 属性)。
|
||||
/// 事件构造辅助:建真实 Event 盒(`type` 字段)加 `data` / `message` 普通属性。
|
||||
///
|
||||
/// 消息事件与错误事件同型,共用本辅助:消息事件 `message` 为 undefined,
|
||||
/// 错误事件 `data` 为 undefined、`message` 为错误串。
|
||||
///
|
||||
/// # 步骤
|
||||
/// 1. 建 PLAIN 对象(`[[Prototype]]` → `%Object.prototype%`)。
|
||||
/// 2. define `data` 属性(rehydrate 后的消息值,可枚举)。
|
||||
/// 3. define `type` 属性(事件类型串,可枚举)。
|
||||
/// 4. define `message` 属性(错误消息串,可枚举;消息事件为 undefined)。
|
||||
/// 5. 返回对象。
|
||||
/// 1. 经 `create_event_box` 建真实 Event 盒(`type` 取 `type_val` 的字符串形态,
|
||||
/// `[[Prototype]]` → `Event.prototype`),使 `dispatchEvent` 品牌守卫通过。
|
||||
/// 2. define `data` 属性(rehydrate 后的消息值,可枚举;GC 边)。
|
||||
/// 3. `message` 非 undefined 时 define `message` 属性(错误消息串,可枚举)。
|
||||
/// 4. 返回对象。
|
||||
///
|
||||
/// # 边界与前提
|
||||
/// - `data` 是 rehydrate 后的消息值(GC 边,经 `alloc_object` 入对象表即成根)。
|
||||
/// - `data` 是 rehydrate 后的消息值(GC 边,经属性区入 mark)。
|
||||
/// - `type` 是事件类型串(`"message"`/`"error"`/`"messageerror"`)。
|
||||
/// - `message` 是错误消息串(仅错误事件定义,消息事件为 undefined)。
|
||||
/// - Event 盒无 `data` 字段(8.4 的 MessageEvent 才补),`data` 作盒对象普通属性。
|
||||
///
|
||||
/// # 副作用
|
||||
/// - 新建一个事件对象(经 `alloc_object` 入对象表)。
|
||||
/// - 新建一个 Event 盒对象(经 `alloc_object` 入对象表)。
|
||||
pub(crate) fn message_event_constructor(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
let data_val = if args.len() > 1 { vm.reg(args[1]) } else { JsValue::undefined() };
|
||||
let type_val = if args.len() > 2 { vm.reg(args[2]) } else { JsValue::undefined() };
|
||||
let message_val = if args.len() > 3 { vm.reg(args[3]) } else { JsValue::undefined() };
|
||||
|
||||
let object_proto = JsValue::from_js_object(vm.builtin_proto(ProtoKind::ObjectProto));
|
||||
let obj = JsObject::new_empty(EMPTY_SHAPE_ID, object_proto);
|
||||
let ptr = vm.alloc_object(obj);
|
||||
// SAFETY: ptr 是本函数刚 alloc_object 的对象,存活且本段无别名。
|
||||
// 建真实 Event 盒(type 取 type_val 的字符串形态)。
|
||||
let type_text = vm.lookup_str(type_val).unwrap_or_default();
|
||||
let ptr = oxide_builtins::event::create_event_box(vm, &type_text);
|
||||
// SAFETY: ptr 是 create_event_box 刚 alloc_object 的对象,存活且本段无别名。
|
||||
let event_obj = unsafe { &mut *ptr };
|
||||
|
||||
let si_data = vm.perm_intern("data");
|
||||
let _ = vm.define_data_property(event_obj, si_data, data_val, PropAttributes::DEFAULT_DATA);
|
||||
let si_type = vm.perm_intern("type");
|
||||
let _ = vm.define_data_property(event_obj, si_type, type_val, PropAttributes::DEFAULT_DATA);
|
||||
let si_message = vm.perm_intern("message");
|
||||
let _ = vm.define_data_property(event_obj, si_message, message_val, PropAttributes::DEFAULT_DATA);
|
||||
if !message_val.is_undefined() {
|
||||
let si_message = vm.perm_intern("message");
|
||||
let _ = vm.define_data_property(event_obj, si_message, message_val, PropAttributes::DEFAULT_DATA);
|
||||
}
|
||||
|
||||
NativeResult::Ok(JsValue::from_js_object(ptr))
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ use std::time::Duration;
|
||||
use oxide_builtins::message_value::{rehydrate_message, MessageValue};
|
||||
use oxide_kernel::kernel::KernelCore;
|
||||
use oxide_kernel::message_queue::{channel, Receiver, Sender, Timeout};
|
||||
use oxide_runtime_api::{CompilerService, NativeResult, VmHost};
|
||||
use oxide_runtime_api::{CompilerService, NativeResult};
|
||||
use oxide_types::object::JsObject;
|
||||
use oxide_types::value::JsValue;
|
||||
|
||||
@@ -345,30 +345,23 @@ impl Vm {
|
||||
let Some(worker_obj) = self.worker_object(id) else {
|
||||
continue;
|
||||
};
|
||||
// 错误面:逐条交付 onerror(编译 / 执行失败)。
|
||||
// 错误面:逐条经 dispatchEvent 交付 error 事件(编译 / 执行失败)。
|
||||
for err in &poll.errors {
|
||||
if self.worker_object(id).is_none() {
|
||||
break;
|
||||
}
|
||||
self.deliver_error_event(worker_obj, "onerror", "error", err);
|
||||
self.deliver_error_event(worker_obj, "error", err);
|
||||
}
|
||||
// 消息交付失败面:逐条交付 onmessageerror(当前架构不可达,前向钩子)。
|
||||
// 消息交付失败面:逐条经 dispatchEvent 交付 messageerror 事件
|
||||
// (当前架构不可达,前向钩子)。
|
||||
for msg_err in &poll.message_errors {
|
||||
if self.worker_object(id).is_none() {
|
||||
break;
|
||||
}
|
||||
self.deliver_error_event(worker_obj, "onmessageerror", "messageerror", msg_err);
|
||||
}
|
||||
// 数据面:读 onmessage 属性(undefined 或非可调用即静默跳过)。
|
||||
let si_onmessage = self.perm_intern("onmessage");
|
||||
let handler = match self.ordinary_get(unsafe { &*worker_obj.as_js_object_ptr() }, si_onmessage, worker_obj)
|
||||
{
|
||||
Ok(h) => h,
|
||||
Err(_) => continue,
|
||||
};
|
||||
if !is_callable(handler) {
|
||||
continue;
|
||||
self.deliver_error_event(worker_obj, "messageerror", msg_err);
|
||||
}
|
||||
// 数据面:经 dispatchEvent 交付(注册表查监听器,addEventListener 与
|
||||
// 属性 handler 同权;首版属性 handler 尚不支持,8.7 完成)。
|
||||
for data in poll.messages {
|
||||
// 交付前二次校验存活(GC 防护):worker 在事件循环中被终止
|
||||
// (注册表条目移除)即停止交付。
|
||||
@@ -376,40 +369,46 @@ impl Vm {
|
||||
break;
|
||||
}
|
||||
let event = self.build_message_event(data, "message", None);
|
||||
let _ = self.execute_task(|vm| vm.call_function_sync(handler, worker_obj, &[event]));
|
||||
self.dispatch_event_on(worker_obj, event);
|
||||
self.drain_microtasks();
|
||||
}
|
||||
}
|
||||
any
|
||||
}
|
||||
|
||||
/// 交付错误事件:读 handler 属性、建事件、调用(不可调用时静默)。
|
||||
/// 经 `dispatchEvent` 交付事件到目标对象(注册表查监听器,按登记序调用)。
|
||||
///
|
||||
/// # 步骤
|
||||
/// 1. 读 `worker_obj` 的 `handler_name` 属性。
|
||||
/// 2. 不可调用时静默返回(浏览器语义:无 handler 即丢弃)。
|
||||
/// 3. 建错误事件(`type` = `event_type`、`message` = `message`)。
|
||||
/// 4. 经 `execute_task` + `call_function_sync` 触发,后 `drain_microtasks`。
|
||||
/// 1. 目标与事件钉入寄存器 0 / 1(GC 根,防交付窗口内被回收)。
|
||||
/// 2. 调 `event_target_dispatch_event`(this = 目标,实参 = 事件)。
|
||||
///
|
||||
/// # 边界与前提
|
||||
/// - `handler_name` 是 `onerror` 或 `onmessageerror`。
|
||||
/// - `event_type` 是 `"error"` 或 `"messageerror"`。
|
||||
/// - `worker_obj` 与 handler 均为 `JsValue`(Copy),在 `execute_task` 前提取,
|
||||
/// 不跨 `&mut self` 持引用。
|
||||
/// - 监听器回调的异常原值上抛(不吞);无监听器时 no-op(返回 true)。
|
||||
///
|
||||
/// # 副作用
|
||||
/// - 触发 handler 回调与微任务 drain。
|
||||
fn deliver_error_event(&mut self, worker_obj: JsValue, handler_name: &str, event_type: &str, message: &str) {
|
||||
let si_handler = self.perm_intern(handler_name);
|
||||
let handler = match self.ordinary_get(unsafe { &*worker_obj.as_js_object_ptr() }, si_handler, worker_obj) {
|
||||
Ok(h) => h,
|
||||
Err(_) => return,
|
||||
};
|
||||
if !is_callable(handler) {
|
||||
return;
|
||||
}
|
||||
/// - 触发监听器回调与 `once` 条目移除;写事件载荷盒(target / 阶段)。
|
||||
fn dispatch_event_on(&mut self, target: JsValue, event: JsValue) {
|
||||
self.set_reg(0, target);
|
||||
self.set_reg(1, event);
|
||||
let _ = oxide_builtins::event_target::event_target_dispatch_event(self, &[0, 1]);
|
||||
}
|
||||
|
||||
/// 交付错误事件:建事件、经 `dispatchEvent` 交付(注册表查监听器)。
|
||||
///
|
||||
/// # 步骤
|
||||
/// 1. 建错误事件(`type` = `event_type`、`message` = `message`、`data` = undefined)。
|
||||
/// 2. 经 `dispatch_event_on` 交付到 `worker_obj`,后 `drain_microtasks`。
|
||||
///
|
||||
/// # 边界与前提
|
||||
/// - `event_type` 是 `"error"` 或 `"messageerror"`。
|
||||
/// - `worker_obj` 是 `JsValue`(Copy),不跨 `&mut self` 持引用。
|
||||
/// - 无监听器时 `dispatchEvent` 返回 true(no-op)。
|
||||
///
|
||||
/// # 副作用
|
||||
/// - 触发监听器回调与微任务 drain。
|
||||
fn deliver_error_event(&mut self, worker_obj: JsValue, event_type: &str, message: &str) {
|
||||
let event = self.build_message_event(JsValue::undefined(), event_type, Some(message));
|
||||
let _ = self.execute_task(|vm| vm.call_function_sync(handler, worker_obj, &[event]));
|
||||
self.dispatch_event_on(worker_obj, event);
|
||||
self.drain_microtasks();
|
||||
}
|
||||
|
||||
@@ -463,21 +462,20 @@ impl Vm {
|
||||
}
|
||||
}
|
||||
|
||||
/// worker 侧消息交付:rehydrate 进 worker realm、建 MessageEvent、交付到
|
||||
/// `self.onmessage`(worker global 的 `onmessage` 属性)。
|
||||
/// worker 侧消息交付:rehydrate 进 worker realm、建 MessageEvent、经
|
||||
/// `dispatchEvent` 交付到 worker global(注册表查监听器)。
|
||||
///
|
||||
/// # 步骤
|
||||
/// 1. rehydrate 消息值进 worker realm。
|
||||
/// 2. 建 MessageEvent(data + type "message")。
|
||||
/// 3. 读 worker global 的 `onmessage` 属性。
|
||||
/// 4. 可调用时经 `execute_task` + `call_function_sync` 触发,后 `drain_microtasks`。
|
||||
/// 3. 取 worker global 对象,经 `dispatch_event_on` 交付,后 `drain_microtasks`。
|
||||
///
|
||||
/// # 边界与前提
|
||||
/// - `onmessage` 缺失或非可调用时静默跳过(消息已消费,符合浏览器
|
||||
/// - 无监听器时 `dispatchEvent` 返回 true(消息已消费,符合浏览器
|
||||
/// "无 handler 即丢弃"语义)。
|
||||
///
|
||||
/// # 副作用
|
||||
/// - 触发 `self.onmessage` 回调与微任务 drain。
|
||||
/// - 触发监听器回调与微任务 drain。
|
||||
pub(crate) fn deliver_self_message(&mut self, value: MessageValue) {
|
||||
let data = rehydrate_message(self, &value);
|
||||
let event = self.build_message_event(data, "message", None);
|
||||
@@ -487,29 +485,13 @@ impl Vm {
|
||||
session.global_object().as_ptr() as *mut JsObject
|
||||
};
|
||||
// SAFETY: global_ptr 是当前 session 的 global 对象,存活。
|
||||
let global_obj = unsafe { &*global_ptr };
|
||||
let si_onmessage = self.perm_intern("onmessage");
|
||||
let handler = match self.ordinary_get(global_obj, si_onmessage, JsValue::from_js_object(global_ptr)) {
|
||||
Ok(h) => h,
|
||||
Err(_) => return,
|
||||
};
|
||||
if !is_callable(handler) {
|
||||
return;
|
||||
}
|
||||
let _ = self.execute_task(|vm| vm.call_function_sync(handler, JsValue::from_js_object(global_ptr), &[event]));
|
||||
let global_val = JsValue::from_js_object(global_ptr);
|
||||
// 经 dispatchEvent 交付(注册表查监听器,属性 handler 与 addEventListener 同权)。
|
||||
self.dispatch_event_on(global_val, event);
|
||||
self.drain_microtasks();
|
||||
}
|
||||
}
|
||||
|
||||
/// 判定值是否可调用(对象且函数对象,header bit 31)。
|
||||
///
|
||||
/// # 边界与前提
|
||||
/// - 非对象值恒不可调用。
|
||||
/// - 对象经 `as_js_object_ptr` 解引用读函数位(unsafe,惯例形态)。
|
||||
fn is_callable(val: JsValue) -> bool {
|
||||
val.is_object() && unsafe { &*val.as_js_object_ptr() }.is_function()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
@@ -545,9 +527,9 @@ mod tests {
|
||||
fn worker_round_trip() {
|
||||
let mut vm = vm_with_compiler();
|
||||
|
||||
// worker 脚本设 onmessage 回显(onmessage 语义替换早期回显语义)。
|
||||
// worker 脚本设 addEventListener 回显(注册表交付替换早期 onmessage 属性语义)。
|
||||
let id = vm
|
||||
.spawn_worker("self.onmessage = function(e) { self.postMessage(e.data); };")
|
||||
.spawn_worker("self.addEventListener('message', function(e) { self.postMessage(e.data); });")
|
||||
.expect("worker 应派生成功");
|
||||
assert_eq!(vm.active_workers(), vec![id], "应有唯一活跃 worker");
|
||||
|
||||
@@ -581,12 +563,12 @@ mod tests {
|
||||
fn multiple_workers_isolated() {
|
||||
let mut vm = vm_with_compiler();
|
||||
|
||||
// 两 worker 各设 onmessage 回显(onmessage 语义)。
|
||||
// 两 worker 各设 addEventListener 回显(注册表交付语义)。
|
||||
let id_a = vm
|
||||
.spawn_worker("self.onmessage = function(e) { self.postMessage(e.data); };")
|
||||
.spawn_worker("self.addEventListener('message', function(e) { self.postMessage(e.data); });")
|
||||
.expect("worker A 应派生成功");
|
||||
let id_b = vm
|
||||
.spawn_worker("self.onmessage = function(e) { self.postMessage(e.data); };")
|
||||
.spawn_worker("self.addEventListener('message', function(e) { self.postMessage(e.data); });")
|
||||
.expect("worker B 应派生成功");
|
||||
assert_eq!(vm.active_workers(), vec![id_a, id_b], "应有两个活跃 worker");
|
||||
|
||||
@@ -647,10 +629,11 @@ mod tests {
|
||||
let mut vm = vm_with_compiler();
|
||||
|
||||
// worker 脚本:收到消息翻倍后回发。
|
||||
let worker_path = write_worker_script("self.onmessage = function(e) { self.postMessage(e.data * 2); };");
|
||||
// 主脚本:建 Worker、设 onmessage、投递 21。
|
||||
let worker_path =
|
||||
write_worker_script("self.addEventListener('message', function(e) { self.postMessage(e.data * 2); });");
|
||||
// 主脚本:建 Worker、设 addEventListener、投递 21。
|
||||
let main_script = format!(
|
||||
"var w = new Worker('{}'); w.onmessage = function(e) {{ globalThis.received = e.data; }}; w.postMessage(21);",
|
||||
"var w = new Worker('{}'); w.addEventListener('message', function(e) {{ globalThis.received = e.data; }}); w.postMessage(21);",
|
||||
worker_path.display()
|
||||
);
|
||||
vm.run(&Arc::new(compile_script(&main_script))).expect("主脚本应运行");
|
||||
@@ -750,9 +733,9 @@ mod tests {
|
||||
|
||||
// worker 脚本:语法错误(编译失败)。
|
||||
let worker_path = write_worker_script("function { 语法错误");
|
||||
// 主脚本:建 Worker、设 onerror,记录 e.message 与 e.type 是否为 "error"。
|
||||
// 主脚本:建 Worker、设 addEventListener('error'),记录 e.message 与 e.type 是否为 "error"。
|
||||
let main_script = format!(
|
||||
"var w = new Worker('{}'); w.onerror = function(e) {{ globalThis.err = e.message; globalThis.errIsErrorType = (e.type === \"error\"); }};",
|
||||
"var w = new Worker('{}'); w.addEventListener('error', function(e) {{ globalThis.err = e.message; globalThis.errIsErrorType = (e.type === \"error\"); }});",
|
||||
worker_path.display()
|
||||
);
|
||||
vm.run(&Arc::new(compile_script(&main_script))).expect("主脚本应运行");
|
||||
@@ -795,9 +778,9 @@ mod tests {
|
||||
|
||||
// worker 脚本:顶层 throw(执行失败)。
|
||||
let worker_path = write_worker_script("throw new Error('boom');");
|
||||
// 主脚本:建 Worker、设 onerror,记录 e.message。
|
||||
// 主脚本:建 Worker、设 addEventListener('error'),记录 e.message。
|
||||
let main_script = format!(
|
||||
"var w = new Worker('{}'); w.onerror = function(e) {{ globalThis.err = e.message; }};",
|
||||
"var w = new Worker('{}'); w.addEventListener('error', function(e) {{ globalThis.err = e.message; }});",
|
||||
worker_path.display()
|
||||
);
|
||||
vm.run(&Arc::new(compile_script(&main_script))).expect("主脚本应运行");
|
||||
@@ -832,17 +815,17 @@ mod tests {
|
||||
fn onmessageerror_delivery_helper() {
|
||||
let mut vm = vm_with_compiler();
|
||||
|
||||
// 建 Worker 对象、设 onmessageerror handler(记录 e.message 与 e.type)。
|
||||
// 建 Worker 对象、设 addEventListener('messageerror')(记录 e.message 与 e.type)。
|
||||
let worker_path = write_worker_script("1");
|
||||
let main_script = format!(
|
||||
"var w = new Worker('{}'); w.onmessageerror = function(e) {{ globalThis.me = e.message; globalThis.meIsMessageErrorType = (e.type === \"messageerror\"); }}; globalThis.w = w;",
|
||||
"var w = new Worker('{}'); w.addEventListener('messageerror', function(e) {{ globalThis.me = e.message; globalThis.meIsMessageErrorType = (e.type === \"messageerror\"); }}); globalThis.w = w;",
|
||||
worker_path.display()
|
||||
);
|
||||
vm.run(&Arc::new(compile_script(&main_script))).expect("主脚本应运行");
|
||||
|
||||
// 取 Worker 对象,直接调交付辅助(模拟 rehydrate 失败)。
|
||||
// 取 Worker 对象,直接调交付辅助(模拟 rehydrate 失败,经 dispatchEvent 交付)。
|
||||
let worker_obj = global_value_opt(&vm, "w").expect("w 应被设置");
|
||||
vm.deliver_error_event(worker_obj, "onmessageerror", "messageerror", "合成 rehydrate 失败");
|
||||
vm.deliver_error_event(worker_obj, "messageerror", "合成 rehydrate 失败");
|
||||
|
||||
// 断言 handler 被调用、message 为错误串、type 为 "messageerror"。
|
||||
let me = global_value_opt(&vm, "me").expect("me 应被设置");
|
||||
|
||||
Reference in New Issue
Block a user