diff --git a/Cargo.lock b/Cargo.lock index 2b0b3903..01c44ed7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1160,7 +1160,7 @@ dependencies = [ [[package]] name = "oxide_builtins" -version = "0.14.10" +version = "0.14.11" dependencies = [ "chrono", "fast-float", @@ -1185,7 +1185,7 @@ dependencies = [ [[package]] name = "oxide_bytecode" -version = "0.14.10" +version = "0.14.11" dependencies = [ "num-bigint", "oxide_log", @@ -1193,7 +1193,7 @@ dependencies = [ [[package]] name = "oxide_cfg" -version = "0.14.10" +version = "0.14.11" dependencies = [ "oxide_bytecode", "oxide_emit", @@ -1204,7 +1204,7 @@ dependencies = [ [[package]] name = "oxide_cli" -version = "0.14.10" +version = "0.14.11" dependencies = [ "ansi_term", "bumpalo", @@ -1226,7 +1226,7 @@ dependencies = [ [[package]] name = "oxide_code_cache" -version = "0.14.10" +version = "0.14.11" dependencies = [ "lru", "oxide_bytecode", @@ -1235,7 +1235,7 @@ dependencies = [ [[package]] name = "oxide_compiler" -version = "0.14.10" +version = "0.14.11" dependencies = [ "oxide_bytecode", "oxide_cfg", @@ -1255,7 +1255,7 @@ dependencies = [ [[package]] name = "oxide_dce" -version = "0.14.10" +version = "0.14.11" dependencies = [ "oxide_bytecode", "oxide_cfg", @@ -1269,7 +1269,7 @@ dependencies = [ [[package]] name = "oxide_emit" -version = "0.14.10" +version = "0.14.11" dependencies = [ "num-bigint", "oxc_allocator", @@ -1284,7 +1284,7 @@ dependencies = [ [[package]] name = "oxide_engine" -version = "0.14.10" +version = "0.14.11" dependencies = [ "oxide_bytecode", "oxide_compiler", @@ -1296,7 +1296,7 @@ dependencies = [ [[package]] name = "oxide_ir" -version = "0.14.10" +version = "0.14.11" dependencies = [ "oxide_bytecode", "oxide_log", @@ -1305,7 +1305,7 @@ dependencies = [ [[package]] name = "oxide_kernel" -version = "0.14.10" +version = "0.14.11" dependencies = [ "bumpalo", "dashmap", @@ -1319,7 +1319,7 @@ dependencies = [ [[package]] name = "oxide_liveness" -version = "0.14.10" +version = "0.14.11" dependencies = [ "oxide_bytecode", "oxide_cfg", @@ -1331,7 +1331,7 @@ dependencies = [ [[package]] name = "oxide_log" -version = "0.14.10" +version = "0.14.11" dependencies = [ "tracing", "tracing-appender", @@ -1340,7 +1340,7 @@ dependencies = [ [[package]] name = "oxide_parser" -version = "0.14.10" +version = "0.14.11" dependencies = [ "oxc_allocator", "oxc_ast", @@ -1355,7 +1355,7 @@ dependencies = [ [[package]] name = "oxide_regalloc" -version = "0.14.10" +version = "0.14.11" dependencies = [ "oxide_bytecode", "oxide_cfg", @@ -1371,7 +1371,7 @@ dependencies = [ [[package]] name = "oxide_runtime_api" -version = "0.14.10" +version = "0.14.11" dependencies = [ "num-bigint", "num-traits", @@ -1384,7 +1384,7 @@ dependencies = [ [[package]] name = "oxide_test262" -version = "0.14.10" +version = "0.14.11" dependencies = [ "oxide_bytecode", "oxide_compiler", @@ -1402,7 +1402,7 @@ dependencies = [ [[package]] name = "oxide_types" -version = "0.14.10" +version = "0.14.11" dependencies = [ "bumpalo", "dashmap", @@ -1415,7 +1415,7 @@ dependencies = [ [[package]] name = "oxide_vm" -version = "0.14.10" +version = "0.14.11" dependencies = [ "bumpalo", "chrono", diff --git a/Cargo.toml b/Cargo.toml index 3d548250..05ad2374 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,7 +23,7 @@ members = [ ] [workspace.package] -version = "0.14.10" +version = "0.14.11" edition = "2021" license = "MIT" rust-version = "1.80" diff --git a/crates/oxide_builtins/src/event.rs b/crates/oxide_builtins/src/event.rs index 20628d51..72d781be 100644 --- a/crates/oxide_builtins/src/event.rs +++ b/crates/oxide_builtins/src/event.rs @@ -1,9 +1,12 @@ -//! Event 基类行为面:`Event` 构造器(`new Event(type, init)`)、原型 getter -//! (type / target / currentTarget / eventPhase / isTrusted / bubbles / -//! cancelable / defaultPrevented / composed)与原型方法(preventDefault / -//! stopPropagation / stopImmediatePropagation / composedPath)。 +//! Event 基类与 MessageEvent 派生类行为面:`Event` 构造器(`new Event(type, +//! init)`)、原型 getter(type / target / currentTarget / eventPhase / +//! isTrusted / bubbles / cancelable / defaultPrevented / composed)与原型方法 +//! (preventDefault / stopPropagation / stopImmediatePropagation / +//! composedPath);`MessageEvent` 构造器(`new MessageEvent(type, init)`)与 +//! 原型 getter(data / origin / lastEpoch / source / ports)。 //! -//! 事件载荷盒(type 串与九项状态位)存于对象 `native_fn` 槽,向 GC 家族表 +//! 事件载荷盒(type 串与九项状态位,MessageEvent 加 data / origin / +//! lastEpoch / source / ports 五字段)存于对象 `native_fn` 槽,向 GC 家族表 //! 暴露 edges/size/drop 三自由函数。 use std::mem; @@ -17,6 +20,10 @@ use oxide_runtime_api::{NativeResult, ProtoKind, VmHost}; /// Event 载荷盒:存于对象 `native_fn` 槽(`Box::into_raw`,realm 局部单线程)。 /// `r#type` 是事件类型串(GC 字符串边),`target` / `current_target` 是目标 /// 对象(GC 对象边,初始 null),其余为简单状态位。 +/// +/// `#[repr(C)]` 固定字段按声明序、首字段偏移 0:派生类盒以本结构为首字段嵌入, +/// 派发路径把盒指针转基类指针读首字段,依赖该偏移稳定。 +#[repr(C)] struct EventInner { r#type: JsValue, target: JsValue, @@ -31,6 +38,33 @@ struct EventInner { composed: bool, } +/// MessageEvent 载荷盒:事件基类字段(`EventInner` 嵌入,位于偏移 0)加消息 +/// 五字段(data / origin / lastEpoch / source / ports)。存于对象 `native_fn` +/// 槽(`Box::into_raw`,realm 局部单线程)。 +/// +/// `#[repr(C)]` 固定 `base` 位于偏移 0:派生类盒指针转基类指针读首字段依赖该 +/// 偏移稳定(与 `EventInner` 同型约束)。 +#[repr(C)] +struct MessageEventInner { + base: EventInner, + data: JsValue, + origin: JsValue, + last_epoch: u32, + source: JsValue, + ports: Vec, +} + +/// 是否事件系对象(Event / MessageEvent / ErrorEvent / CustomEvent 四标签)。 +pub fn is_event_family_obj(obj: &JsObject) -> bool { + matches!( + obj.type_tag, + JsObject::OBJ_TYPE_EVENT + | JsObject::OBJ_TYPE_MESSAGE_EVENT + | JsObject::OBJ_TYPE_ERROR_EVENT + | JsObject::OBJ_TYPE_CUSTOM_EVENT + ) +} + /// 取载荷盒指针:仅 Event 标签对象返回 `Some`,其余标签返回 `None`。 fn event_payload_ptr(obj: &JsObject) -> Option<*mut EventInner> { if !obj.is_event_obj() { @@ -39,6 +73,17 @@ fn event_payload_ptr(obj: &JsObject) -> Option<*mut EventInner> { obj.native_fn().map(|ptr| ptr.as_ptr() as *mut EventInner) } +/// 取事件系基类字段指针:仅事件系标签对象返回 `Some`,其余标签返回 `None`。 +/// +/// 派生类对象的 `native_fn` 槽存 `Box<派生类Inner>`,其首字段是 `EventInner` +/// (基类字段位于偏移 0),指针可安全转基类字段指针。 +fn event_family_base_ptr(obj: &JsObject) -> Option<*mut EventInner> { + if !is_event_family_obj(obj) { + return None; + } + obj.native_fn().map(|ptr| ptr.as_ptr() as *mut EventInner) +} + /// 收集 Event 的引用边(type 串边与 target / current_target 对象边)。 /// /// `target` / `current_target` 空窗口(构造期回填之前)为 null,边函数自守, @@ -86,11 +131,11 @@ pub fn drop_event_native(obj: &mut JsObject) -> u64 { bytes } -/// 读事件类型串(GC 字符串边)。非 Event 对象或无载荷盒返回 None。 +/// 读事件类型串(GC 字符串边)。非事件系对象或无载荷盒返回 None。 /// /// 供派发路径(`dispatchEvent`)取事件类型串、intern 后查监听器。 pub fn event_type_value(obj: &JsObject) -> Option { - let ptr = event_payload_ptr(obj)?; + let ptr = event_family_base_ptr(obj)?; if ptr.is_null() { return None; } @@ -99,11 +144,11 @@ pub fn event_type_value(obj: &JsObject) -> Option { } /// 设派发目标与阶段:`target` / `current_target` 置 `target`、`event_phase` 置 -/// `phase`。非 Event 对象或无载荷盒时 no-op。 +/// `phase`。非事件系对象或无载荷盒时 no-op。 /// /// 供派发路径(`dispatchEvent`)在调用监听器前设目标与 AT_TARGET 阶段。 pub fn event_set_dispatch(obj: &mut JsObject, target: JsValue, phase: u32) { - let Some(ptr) = event_payload_ptr(obj) else { + let Some(ptr) = event_family_base_ptr(obj) else { return; }; if ptr.is_null() { @@ -116,9 +161,9 @@ pub fn event_set_dispatch(obj: &mut JsObject, target: JsValue, phase: u32) { inner.event_phase = phase; } -/// 读 `defaultPrevented` 标志。非 Event 对象或无载荷盒返回 None。 +/// 读 `defaultPrevented` 标志。非事件系对象或无载荷盒返回 None。 pub fn event_default_prevented(obj: &JsObject) -> Option { - let ptr = event_payload_ptr(obj)?; + let ptr = event_family_base_ptr(obj)?; if ptr.is_null() { return None; } @@ -126,9 +171,9 @@ pub fn event_default_prevented(obj: &JsObject) -> Option { Some(unsafe { &*ptr }.default_prevented) } -/// 读 `stopImmediatePropagation` 标志。非 Event 对象或无载荷盒返回 None。 +/// 读 `stopImmediatePropagation` 标志。非事件系对象或无载荷盒返回 None。 pub fn event_immediate_stopped(obj: &JsObject) -> Option { - let ptr = event_payload_ptr(obj)?; + let ptr = event_family_base_ptr(obj)?; if ptr.is_null() { return None; } @@ -136,7 +181,7 @@ pub fn event_immediate_stopped(obj: &JsObject) -> Option { Some(unsafe { &*ptr }.immediate_stopped) } -/// 解析 this 为 Event 载荷盒共享引用:非 Event 对象或无载荷盒返回 None。 +/// 解析 this 为事件系基类字段共享引用:非事件系对象或无载荷盒返回 None。 /// /// # 注意事项 /// 返回引用按 `'a` 声称为存活,实际存活至载荷盒被 GC 释放;调用方须在无 GC @@ -151,10 +196,7 @@ fn event_inner_of<'a>(this_val: JsValue) -> Option<&'a EventInner> { } // SAFETY: this_val 已确认是对象值,obj_ptr 指向合法 JsObject。 let obj = unsafe { &*obj_ptr }; - if !obj.is_event_obj() { - return None; - } - let box_ptr = event_payload_ptr(obj)?; + let box_ptr = event_family_base_ptr(obj)?; if box_ptr.is_null() { return None; } @@ -163,7 +205,7 @@ fn event_inner_of<'a>(this_val: JsValue) -> Option<&'a EventInner> { Some(unsafe { &*box_ptr }) } -/// 解析 this 为 Event 载荷盒可变引用:非 Event 对象或无载荷盒返回 None。 +/// 解析 this 为事件系基类字段可变引用:非事件系对象或无载荷盒返回 None。 /// /// # 注意事项 /// 返回引用按 `'a` 声称为存活,实际存活至载荷盒被 GC 释放;调用方须在无 GC @@ -178,10 +220,7 @@ fn event_inner_of_mut<'a>(this_val: JsValue) -> Option<&'a mut EventInner> { } // SAFETY: this_val 已确认是对象值,obj_ptr 指向合法 JsObject。 let obj = unsafe { &mut *obj_ptr }; - if !obj.is_event_obj() { - return None; - } - let box_ptr = event_payload_ptr(obj)?; + let box_ptr = event_family_base_ptr(obj)?; if box_ptr.is_null() { return None; } @@ -487,3 +526,359 @@ pub fn event_composed_path(vm: &mut H, args: &[u8]) -> NativeResult { } NativeResult::Ok(JsValue::from_js_object(arr)) } + +// —— MessageEvent 派生类 —— + +/// 取 MessageEvent 载荷盒指针:仅 MessageEvent 标签对象返回 `Some`,其余标签 +/// 返回 `None`。 +fn message_event_payload_ptr(obj: &JsObject) -> Option<*mut MessageEventInner> { + if obj.type_tag != JsObject::OBJ_TYPE_MESSAGE_EVENT { + return None; + } + obj.native_fn().map(|ptr| ptr.as_ptr() as *mut MessageEventInner) +} + +/// 收集 MessageEvent 的引用边(基类 type / target / current_target 边加 +/// data / source / ports 边)。 +/// +/// `target` / `current_target` 空窗口(构造期回填之前)为 null,边函数自守, +/// 语义自含。 +pub fn message_event_native_edges(obj: &JsObject) -> Vec { + let Some(ptr) = message_event_payload_ptr(obj) else { + return Vec::new(); + }; + if ptr.is_null() { + return Vec::new(); + } + // SAFETY: ptr 指向有效载荷盒(槽位仅构造期写入、释放时置空),只读字段。 + let inner = unsafe { &*ptr }; + let mut edges = vec![inner.base.r#type, inner.base.target, inner.base.current_target]; + edges.push(inner.data); + edges.push(inner.source); + edges.extend(inner.ports.iter().copied()); + edges +} + +/// 只读核算 MessageEvent 载荷盒字节(不释放)。 +pub fn message_event_native_size(obj: &JsObject) -> u64 { + let Some(ptr) = message_event_payload_ptr(obj) else { + return 0; + }; + if ptr.is_null() { + return 0; + } + mem::size_of::() as u64 +} + +/// 释放 MessageEvent 载荷盒(`Box`),返回释放字节数。 +/// +/// 盒 drop 时基类字段与 data / origin / source / ports 的 JsValue 边随盒释放 +/// (GC 已判定死对象);槽置空保证重复调用零释放(幂等)。 +pub fn drop_message_event_native(obj: &mut JsObject) -> u64 { + let bytes = message_event_native_size(obj); + if bytes == 0 { + return 0; + } + let Some(ptr) = message_event_payload_ptr(obj) else { + return 0; + }; + // SAFETY: ptr 非空(message_event_native_size 已验证),Box::from_raw 恰好 + // 释放一次。 + unsafe { + drop(Box::from_raw(ptr)); + } + obj.set_native_fn(None); + bytes +} + +/// 建一个 MessageEvent 载荷盒对象(`type` 串与 `data` 值),返回对象指针。 +/// +/// 供交付路径(消息事件)建真实 MessageEvent 盒,使 `dispatchEvent` 品牌守卫 +/// 通过。盒的 `origin` 为空串、`lastEpoch` 为 0、`source` 为 null、`ports` +/// 为空数组;`[[Prototype]]` 指向 `MessageEvent.prototype` +/// (`e instanceof MessageEvent` 与 `e instanceof Event` 均成立)。 +/// +/// # 副作用 +/// - 新建一个 MessageEvent 盒对象(经 `alloc_object` 入对象表)。 +pub fn create_message_event_box(vm: &mut H, data: JsValue, type_str: &str) -> *mut JsObject { + let type_js = vm.new_string(type_str); + let origin_js = vm.new_string(""); + let inner = Box::into_raw(Box::new(MessageEventInner { + base: EventInner { + r#type: type_js, + target: JsValue::null(), + current_target: JsValue::null(), + event_phase: 0, + is_trusted: false, + bubbles: false, + cancelable: false, + default_prevented: false, + propagation_stopped: false, + immediate_stopped: false, + composed: false, + }, + data, + origin: origin_js, + last_epoch: 0, + source: JsValue::null(), + ports: Vec::new(), + })); + let me_proto = JsValue::from_js_object(vm.builtin_proto(ProtoKind::MessageEventProto)); + let obj = JsObject::new_empty(EMPTY_SHAPE_ID, me_proto); + let ptr = vm.alloc_object(obj); + // SAFETY: ptr 是本函数刚 alloc_object 的对象,存活且本段无别名;native_fn 存 + // 不透明 `Box` 指针,与 ArrayBuffer / RegExp 的类型化 + // 存储模式一致。 + let me_obj = unsafe { &mut *ptr }; + me_obj.type_tag = JsObject::OBJ_TYPE_MESSAGE_EVENT; + me_obj.set_native_fn(Some(unsafe { NativeFnPtr::from_raw(inner as *const ()) })); + ptr +} + +/// MessageEvent 构造器:`new MessageEvent(type, init)`。 +/// +/// # 步骤 +/// 1. 非构造形态(`!constructing_native`)抛 TypeError(HTML 规范只有 +/// [[Construct]],无 [[Call]]) +/// 2. type 经完整 ToString 转串;空串抛 SyntaxError +/// 3. init 字典读 data / origin / lastEpoch / source / ports(缺省:data 为 +/// undefined、origin 为空串、lastEpoch 为 0、source 为 null、ports 为空数组) +/// 4. 建载荷盒入 receiver 的 `native_fn` 槽,置 type_tag +/// 5. 返回 receiver +/// +/// # 边界与前提 +/// - receiver(`args[0]`)是 VM 按构造器 prototype 新分配的对象,proto 已 +/// 指向 %MessageEvent.prototype%; +/// - init 缺省(undefined)或非对象时对应字段取缺省。 +/// +/// # 副作用 +/// - receiver 的 `native_fn` 槽写入载荷盒、`type_tag` 置 MessageEvent 标签。 +pub fn message_event_constructor(vm: &mut H, args: &[u8]) -> NativeResult { + if !vm.constructing_native() { + return NativeResult::Err(crate::error::create_type_error(vm, "MessageEvent constructor requires new")); + } + + let this_val = vm.reg(args[0]); + if !this_val.is_object() { + return NativeResult::Err(crate::error::create_type_error(vm, "MessageEvent constructor requires new")); + } + + // type 经完整 ToString 转串(对象经 ToPrimitive,Symbol 抛 TypeError)。 + let type_val = if args.len() > 1 { vm.reg(args[1]) } else { JsValue::undefined() }; + let type_str = match oxide_runtime_api::to_string_full(type_val, vm) { + Ok(s) => s, + Err(_) => { + if let Some(exc) = vm.take_uncaught_value() { + return NativeResult::Err(exc); + } + return NativeResult::Err(crate::error::create_type_error(vm, "Cannot convert value to a string")); + } + }; + if type_str.is_empty() { + return NativeResult::Err(crate::error::create_syntax_error(vm, "The provided type is empty")); + } + + // init 字典读 data / origin / lastEpoch / source / ports(缺省或非对象 + // 时取缺省)。 + let mut data = JsValue::undefined(); + let mut origin = JsValue::undefined(); + let mut last_epoch = 0u32; + let mut source = JsValue::null(); + let mut ports: Vec = Vec::new(); + if args.len() > 2 { + let init_val = vm.reg(args[2]); + if init_val.is_object() { + let init_ptr = init_val.as_js_object_ptr(); + // SAFETY: init_val 已确认是对象值,借用即时消费。 + let init_obj = unsafe { &*init_ptr }; + + // data / origin / source 直读属性(getter 副作用与异常原值传播)。 + for (name, slot) in [("data", &mut data), ("origin", &mut origin), ("source", &mut source)] { + let si = vm.perm_intern(name); + match vm.ordinary_get(init_obj, si, init_val) { + Ok(v) => *slot = v, + Err(_) => { + if let Some(exc) = vm.take_uncaught_value() { + return NativeResult::Err(exc); + } + return NativeResult::Err(crate::error::create_type_error( + vm, + "MessageEvent init property getter failed", + )); + } + } + } + + // lastEpoch 读数值(非有限非负数归 0)。 + let si_epoch = vm.perm_intern("lastEpoch"); + match vm.ordinary_get(init_obj, si_epoch, init_val) { + Ok(v) => { + let n = oxide_runtime_api::to_number(v); + last_epoch = if n.is_finite() && n >= 0.0 { n as u32 } else { 0 }; + } + Err(_) => { + if let Some(exc) = vm.take_uncaught_value() { + return NativeResult::Err(exc); + } + return NativeResult::Err(crate::error::create_type_error( + vm, + "MessageEvent init property getter failed", + )); + } + } + + // ports 读数组属性并复制元素(非数组取空数组)。 + let si_ports = vm.perm_intern("ports"); + match vm.ordinary_get(init_obj, si_ports, init_val) { + Ok(v) => { + if v.is_object() { + let ports_ptr = v.as_js_object_ptr(); + // SAFETY: v 已确认是对象值,借用即时消费。 + let ports_obj = unsafe { &*ports_ptr }; + if ports_obj.is_array() { + if let Some(elements) = ports_obj.array_elements_vec() { + ports = elements.to_vec(); + } + } + } + } + Err(_) => { + if let Some(exc) = vm.take_uncaught_value() { + return NativeResult::Err(exc); + } + return NativeResult::Err(crate::error::create_type_error( + vm, + "MessageEvent init property getter failed", + )); + } + } + } + } + + // origin 缺省为空串。 + if origin.is_undefined() { + origin = vm.new_string(""); + } + + // 建载荷盒入 receiver 的 native_fn 槽,置 MessageEvent 标签。 + let type_js = vm.new_string(&type_str); + let inner = Box::into_raw(Box::new(MessageEventInner { + base: EventInner { + r#type: type_js, + target: JsValue::null(), + current_target: JsValue::null(), + event_phase: 0, + is_trusted: false, + bubbles: false, + cancelable: false, + default_prevented: false, + propagation_stopped: false, + immediate_stopped: false, + composed: false, + }, + data, + origin, + last_epoch, + source, + ports, + })); + // SAFETY: receiver 不可调用,native_fn 存不透明 `Box` + // 指针,与 ArrayBuffer / RegExp 的类型化存储模式一致。 + let obj = unsafe { &mut *this_val.as_js_object_ptr() }; + obj.type_tag = JsObject::OBJ_TYPE_MESSAGE_EVENT; + obj.set_native_fn(Some(unsafe { NativeFnPtr::from_raw(inner as *const ()) })); + NativeResult::Ok(this_val) +} + +/// 解析 this 为 MessageEvent 载荷盒共享引用:非 MessageEvent 对象或无载荷盒 +/// 返回 None。 +/// +/// # 注意事项 +/// 返回引用按 `'a` 声称为存活,实际存活至载荷盒被 GC 释放;调用方须在无 GC +/// 窗口的单 native 函数内消费,不得跨用户调用窗口持有。 +fn message_event_inner_of<'a>(this_val: JsValue) -> Option<&'a MessageEventInner> { + if !this_val.is_object() { + return None; + } + let obj_ptr = this_val.as_js_object_ptr(); + if obj_ptr.is_null() { + return None; + } + // SAFETY: this_val 已确认是对象值,obj_ptr 指向合法 JsObject。 + let obj = unsafe { &*obj_ptr }; + let box_ptr = message_event_payload_ptr(obj)?; + if box_ptr.is_null() { + return None; + } + // SAFETY: box_ptr 指向有效载荷盒(构造期写入、释放时置空);'a 存活由 + // 调用方「无 GC 窗口内消费」约定保证。 + Some(unsafe { &*box_ptr }) +} + +/// `MessageEvent.prototype.data`:返回消息数据。 +pub fn message_event_data_getter(vm: &mut H, args: &[u8]) -> NativeResult { + let this_val = vm.reg(if args.is_empty() { 0 } else { args[0] }); + let Some(inner) = message_event_inner_of(this_val) else { + return NativeResult::Err(crate::error::create_type_error( + vm, + "MessageEvent.prototype.data getter called on non-MessageEvent object", + )); + }; + NativeResult::Ok(inner.data) +} + +/// `MessageEvent.prototype.origin`:返回消息来源源(缺省为空串)。 +pub fn message_event_origin_getter(vm: &mut H, args: &[u8]) -> NativeResult { + let this_val = vm.reg(if args.is_empty() { 0 } else { args[0] }); + let Some(inner) = message_event_inner_of(this_val) else { + return NativeResult::Err(crate::error::create_type_error( + vm, + "MessageEvent.prototype.origin getter called on non-MessageEvent object", + )); + }; + NativeResult::Ok(inner.origin) +} + +/// `MessageEvent.prototype.lastEpoch`:返回最后纪元(引擎恒 0)。 +pub fn message_event_last_epoch_getter(vm: &mut H, args: &[u8]) -> NativeResult { + let this_val = vm.reg(if args.is_empty() { 0 } else { args[0] }); + let Some(inner) = message_event_inner_of(this_val) else { + return NativeResult::Err(crate::error::create_type_error( + vm, + "MessageEvent.prototype.lastEpoch getter called on non-MessageEvent object", + )); + }; + NativeResult::Ok(JsValue::int(inner.last_epoch as i32)) +} + +/// `MessageEvent.prototype.source`:返回消息来源对象(首版恒 null)。 +pub fn message_event_source_getter(vm: &mut H, args: &[u8]) -> NativeResult { + let this_val = vm.reg(if args.is_empty() { 0 } else { args[0] }); + let Some(inner) = message_event_inner_of(this_val) else { + return NativeResult::Err(crate::error::create_type_error( + vm, + "MessageEvent.prototype.source getter called on non-MessageEvent object", + )); + }; + NativeResult::Ok(inner.source) +} + +/// `MessageEvent.prototype.ports`:返回消息端口数组(首版恒空数组)。 +pub fn message_event_ports_getter(vm: &mut H, args: &[u8]) -> NativeResult { + let this_val = vm.reg(if args.is_empty() { 0 } else { args[0] }); + let Some(inner) = message_event_inner_of(this_val) else { + return NativeResult::Err(crate::error::create_type_error( + vm, + "MessageEvent.prototype.ports getter called on non-MessageEvent object", + )); + }; + let array_proto = JsValue::from_js_object(vm.builtin_proto(ProtoKind::ArrayProto)); + let arr = vm.alloc_object(JsObject::new_array(EMPTY_SHAPE_ID, array_proto, inner.ports.len())); + // SAFETY: arr 是本函数刚分配的对象,存活且本段无别名。 + unsafe { + for (i, value) in inner.ports.iter().enumerate() { + (*arr).set_prop_at(i, *value); + } + } + NativeResult::Ok(JsValue::from_js_object(arr)) +} diff --git a/crates/oxide_builtins/src/event_target.rs b/crates/oxide_builtins/src/event_target.rs index c2e66216..e07d23b3 100644 --- a/crates/oxide_builtins/src/event_target.rs +++ b/crates/oxide_builtins/src/event_target.rs @@ -174,7 +174,7 @@ pub fn event_target_dispatch_event(vm: &mut H, args: &[u8]) -> Native } let this_ptr = this_val.as_js_object_ptr(); - // event 品牌守卫:须为 Event 系对象(首版仅 Event 标签,派生类由后续任务接入)。 + // event 品牌守卫:须为事件系对象(Event 基类或派生类标签)。 let event_val = if args.len() > 1 { vm.reg(args[1]) } else { JsValue::undefined() }; if !event_val.is_object() { return NativeResult::Err(crate::error::create_type_error(vm, "dispatchEvent requires an Event")); @@ -185,7 +185,7 @@ pub fn event_target_dispatch_event(vm: &mut H, args: &[u8]) -> Native } // SAFETY: event_val 已确认是对象值,event_ptr 指向合法 JsObject。 let event_obj = unsafe { &*event_ptr }; - if !event_obj.is_event_obj() { + if !event::is_event_family_obj(event_obj) { return NativeResult::Err(crate::error::create_type_error(vm, "dispatchEvent requires an Event")); } diff --git a/crates/oxide_vm/src/bindings/bind_event.rs b/crates/oxide_vm/src/bindings/bind_event.rs index b3d5db8a..55070803 100644 --- a/crates/oxide_vm/src/bindings/bind_event.rs +++ b/crates/oxide_vm/src/bindings/bind_event.rs @@ -1,8 +1,10 @@ -//! Event 基类绑定:就地填充 `event_constructor` / `event_proto` 两枚 P 字段 -//! 占位(构造器本体、原型方法、原型 getter 与 Event 常量),并绑全局 `Event`。 +//! Event 体系绑定:就地填充 `event_constructor` / `event_proto` 两枚 P 字段 +//! 占位(构造器本体、原型方法、原型 getter 与 Event 常量),并绑全局 `Event`; +//! 就地填充 `message_event_constructor` / `message_event_proto` 两枚 P 字段 +//! 占位(构造器本体与原型 getter),并绑全局 `MessageEvent`。 //! -//! 九枚 P 字段中其余七枚(MessageEvent / ErrorEvent / CustomEvent 四对加 -//! EventTarget 原型)由后续子任务就地填充,本函数不读不写。 +//! 九枚 P 字段中其余四枚(ErrorEvent / CustomEvent 两对)由后续子任务就地 +//! 填充,本函数不读不写。 use std::sync::Arc; @@ -12,10 +14,11 @@ use oxide_kernel::kernel::{KernelCore, KernelSession}; use oxide_types::object::{JsObject, PropAttributes}; use oxide_types::value::JsValue; -/// 把 Event 构造器与原型方法绑定到 global。 +/// 把 Event 与 MessageEvent 构造器及原型方法绑定到 global。 /// -/// `event_constructor` / `event_proto` 两枚 P 字段是空对象占位,本函数就地 -/// 填充(不新建对象——P 字段本身就是槽位,快照/脏检查/收尾枚举已覆盖)。 +/// `event_constructor` / `event_proto` 与 `message_event_constructor` / +/// `message_event_proto` 四枚 P 字段是空对象占位,本函数就地填充(不新建 +/// 对象——P 字段本身就是槽位,快照/脏检查/收尾枚举已覆盖)。 /// /// # 幂等 /// 本函数从三条路径到达:`init_kernel_builtins`(占位是空对象)、 @@ -163,6 +166,79 @@ pub fn bind_event(core: &Arc, session: &KernelSession, global: &mut } } + // ---- 填 MessageEvent 构造器与原型占位 ---- + let me_ctor_ptr = world.message_event_constructor.as_ptr() as *mut JsObject; + let me_ctor = unsafe { &mut *me_ctor_ptr }; + let me_proto_ptr = world.message_event_proto.as_ptr() as *mut JsObject; + let me_proto = unsafe { &mut *me_proto_ptr }; + + // 构造器:function 标志、prototype 槽、name 槽、[[Prototype]] → + // Function.prototype(幂等,裸写槽)。 + me_ctor.set_function(true); + if sh.lookup_position(me_ctor.shape_id(), si_prototype).is_none() { + let shape = sh.make_shape(me_ctor.shape_id(), si_prototype); + me_ctor.set_shape_id(shape); + let pos = me_ctor.push_prop(JsValue::from_js_object(me_proto_ptr)); + me_ctor.set_data_meta(pos, PropAttributes::new(false, false, false)); + me_ctor.bump_generation(); + } + if sh.lookup_position(me_ctor.shape_id(), si_name).is_none() { + let shape = sh.make_shape(me_ctor.shape_id(), si_name); + me_ctor.set_shape_id(shape); + me_ctor + .ensure_hash_props() + .push(JsValue::perm_string(sf.string_ptr(sf.intern("MessageEvent").0))); + let pos = me_ctor.hash_props_vec().map_or(0, |v| v.len() as u32).saturating_sub(1); + me_ctor.set_data_meta(pos, PropAttributes::new(false, false, true)); + me_ctor.bump_generation(); + } + let _ = me_ctor.set_proto(fn_proto_val); + + configure_native_constructor( + me_ctor, + oxide_builtins::event::message_event_constructor:: as *const (), + 2, + ); + bind_constructor!( + core, + global, + "MessageEvent", + me_ctor_ptr, + oxide_builtins::event::message_event_constructor::, + 2, + hash: true + ); + + // 原型:constructor 槽、[[Prototype]] → Event.prototype、五个 getter + // (data / origin / lastEpoch / source / ports,每 getter 先 lookup_position + // 守卫)。 + if sh.lookup_position(me_proto.shape_id(), si_constructor).is_none() { + let shape = sh.make_shape(me_proto.shape_id(), si_constructor); + me_proto.set_shape_id(shape); + let pos = me_proto.push_prop(JsValue::from_js_object(me_ctor_ptr)); + me_proto.set_data_meta(pos, PropAttributes::new(true, false, true)); + me_proto.bump_generation(); + } + let event_proto_val = JsValue::from_js_object(world.event_proto.as_ptr() as *mut JsObject); + let _ = me_proto.set_proto(event_proto_val); + + let me_getter_fns: [(&str, *const ()); 5] = [ + ("data", oxide_builtins::event::message_event_data_getter:: as *const ()), + ("origin", oxide_builtins::event::message_event_origin_getter:: as *const ()), + ( + "lastEpoch", + oxide_builtins::event::message_event_last_epoch_getter:: as *const (), + ), + ("source", oxide_builtins::event::message_event_source_getter:: as *const ()), + ("ports", oxide_builtins::event::message_event_ports_getter:: as *const ()), + ]; + for (name, fn_ptr) in me_getter_fns { + let si = sf.intern(name).0; + if sh.lookup_position(me_proto.shape_id(), si).is_none() { + bind_accessor_getter(core, session, me_proto, name, fn_ptr); + } + } + // ---- 填 EventTarget 原型占位(无构造器,仅暴露原型与三方法)---- // 幂等:三方法经 lookup_position 守卫,[[Prototype]] 裸写槽幂等。 let et_proto_ptr = world.event_target_proto.as_ptr() as *mut JsObject; diff --git a/crates/oxide_vm/src/native_box_dispatch.rs b/crates/oxide_vm/src/native_box_dispatch.rs index 289928be..2ccb1d87 100644 --- a/crates/oxide_vm/src/native_box_dispatch.rs +++ b/crates/oxide_vm/src/native_box_dispatch.rs @@ -241,9 +241,16 @@ pub(crate) fn ops_for(family: NativeBoxFamily) -> NativeBoxOps { string_edges: None, cell_edges: None, }, - // 事件派生类(MessageEvent / ErrorEvent / CustomEvent)载荷盒由后续子 - // 任务填充;本臂当前无引用边,仅 size/drop 链消费。 - NativeBoxFamily::MessageEvent | NativeBoxFamily::ErrorEvent | NativeBoxFamily::CustomEvent => NativeBoxOps { + // MessageEvent 派生类载荷盒:基类 type / target / current_target 边加 + // data / source / ports 边。 + NativeBoxFamily::MessageEvent => NativeBoxOps { + object_edges: Some(event::message_event_native_edges), + string_edges: None, + cell_edges: None, + }, + // ErrorEvent / CustomEvent 载荷盒由后续子任务填充;本两臂当前无引用 + // 边,仅 size/drop 链消费。 + NativeBoxFamily::ErrorEvent | NativeBoxFamily::CustomEvent => NativeBoxOps { object_edges: None, string_edges: None, cell_edges: None, diff --git a/crates/oxide_vm/src/session_gc.rs b/crates/oxide_vm/src/session_gc.rs index 044745ac..3fafe4a9 100644 --- a/crates/oxide_vm/src/session_gc.rs +++ b/crates/oxide_vm/src/session_gc.rs @@ -133,6 +133,7 @@ impl SessionGc { bytes += message_channel::message_port_native_size(obj); bytes += broadcast_channel::broadcast_channel_native_size(obj); bytes += event::event_native_size(obj); + bytes += event::message_event_native_size(obj); bytes += crate::arguments_gc::arguments_native_size(obj); bytes @@ -376,9 +377,16 @@ impl SessionGc { Self::mark_string_live(live, obj.get_regexp_flags().as_string_ptr_mut()); } } - // Event 载荷盒 type 字段持有字符串边。 - if obj.is_event_obj() { - for value in event::event_native_edges(obj) { + // 事件系载荷盒(Event / MessageEvent / ErrorEvent / CustomEvent)字符串 + // 边标记:按标签取对应边函数;ErrorEvent / CustomEvent 载荷盒由后续子 + // 任务填充,当前取空边。 + if event::is_event_family_obj(obj) { + let edges = match obj.type_tag { + JsObject::OBJ_TYPE_EVENT => event::event_native_edges(obj), + JsObject::OBJ_TYPE_MESSAGE_EVENT => event::message_event_native_edges(obj), + _ => Vec::new(), + }; + for value in edges { if value.is_string() { Self::mark_string_live(live, value.as_string_ptr_mut()); } @@ -584,6 +592,7 @@ impl SessionGc { freed_bytes += message_channel::drop_message_port_native(obj); freed_bytes += broadcast_channel::drop_broadcast_channel_native(obj); freed_bytes += event::drop_event_native(obj); + freed_bytes += event::drop_message_event_native(obj); freed_bytes += crate::arguments_gc::drop_arguments_native(obj); freed_bytes diff --git a/crates/oxide_vm/src/worker/bindings.rs b/crates/oxide_vm/src/worker/bindings.rs index 608e29ea..12a1d751 100644 --- a/crates/oxide_vm/src/worker/bindings.rs +++ b/crates/oxide_vm/src/worker/bindings.rs @@ -1,6 +1,6 @@ //! Worker API 的 concrete `&mut Vm` native 函数:构造器、`postMessage`/`terminate` //! 原型方法、`self` 面(`self.postMessage`/`self.close`/`self.name`/`self.location`) -//! 与 `MessageEvent` 构造辅助。 +//! 与 `MessageEvent` / 错误事件构造辅助。 //! //! 关键约定: //! - 函数是 concrete `&mut Vm` 形态(`NativeFn`),不挂 `VmHost` trait(trait 是单 @@ -287,30 +287,59 @@ pub(crate) fn self_location(vm: &mut Vm, _args: &[u8]) -> NativeResult { NativeResult::Ok(vm.new_string("")) } -/// 事件构造辅助:建真实 Event 盒(`type` 字段)加 `data` / `message` 普通属性。 +/// 消息事件构造辅助:建真实 MessageEvent 盒(`data` + `type`,`ports` 空数组)。 /// -/// 消息事件与错误事件同型,共用本辅助:消息事件 `message` 为 undefined, -/// 错误事件 `data` 为 undefined、`message` 为错误串。 +/// 消息路径(`deliver_worker_messages` / `deliver_self_message`)经本辅助建 +/// 真实 MessageEvent,`e instanceof MessageEvent` 与 `e instanceof Event` 均 +/// 成立,`e.data` 经原型 getter 读盒字段。 /// /// # 步骤 -/// 1. 经 `create_event_box` 建真实 Event 盒(`type` 取 `type_val` 的字符串形态, -/// `[[Prototype]]` → `Event.prototype`),使 `dispatchEvent` 品牌守卫通过。 -/// 2. define `data` 属性(rehydrate 后的消息值,可枚举;GC 边)。 -/// 3. `message` 非 undefined 时 define `message` 属性(错误消息串,可枚举)。 -/// 4. 返回对象。 +/// 1. 读 `data`(`args[1]`)与 `type`(`args[2]`)。 +/// 2. 委托 `create_message_event_box` 建真实 MessageEvent 盒(`origin` 空串、 +/// `lastEpoch` 0、`source` null、`ports` 空数组,`[[Prototype]]` → +/// `MessageEvent.prototype`),使 `dispatchEvent` 品牌守卫通过。 +/// 3. 返回对象。 /// /// # 边界与前提 -/// - `data` 是 rehydrate 后的消息值(GC 边,经属性区入 mark)。 -/// - `type` 是事件类型串(`"message"`/`"error"`/`"messageerror"`)。 -/// - `message` 是错误消息串(仅错误事件定义,消息事件为 undefined)。 -/// - Event 盒无 `data` 字段(8.4 的 MessageEvent 才补),`data` 作盒对象普通属性。 +/// - `data` 是 rehydrate 后的消息值(GC 边,经盒边入 mark)。 +/// - `type` 是事件类型串(`"message"`)。 /// /// # 副作用 -/// - 新建一个 Event 盒对象(经 `alloc_object` 入对象表)。 +/// - 新建一个 MessageEvent 盒对象(经 `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() }; + + // 建真实 MessageEvent 盒(type 取 type_val 的字符串形态)。 + let type_text = vm.lookup_str(type_val).unwrap_or_default(); + let ptr = oxide_builtins::event::create_message_event_box(vm, data_val, &type_text); + NativeResult::Ok(JsValue::from_js_object(ptr)) +} + +/// 错误事件构造辅助(临时形态):建真实 Event 盒(`type` 字段)加 `data` / +/// `message` 普通属性。 +/// +/// 错误路径(`deliver_error_event`)经本辅助建错误事件:`data` 为 undefined、 +/// `message` 为错误串,供 onerror 处理器读取。 +/// +/// # 步骤 +/// 1. 读 `type`(`args[1]`)与 `message`(`args[2]`)。 +/// 2. 经 `create_event_box` 建真实 Event 盒(`type` 取 `type_val` 的字符串 +/// 形态,`[[Prototype]]` → `Event.prototype`),使 `dispatchEvent` 品牌守卫 +/// 通过。 +/// 3. define `data` 属性(undefined,可枚举)与 `message` 属性(错误消息串, +/// 可枚举)。 +/// 4. 返回对象。 +/// +/// # 边界与前提 +/// - `type` 是事件类型串(`"error"`/`"messageerror"`)。 +/// - `message` 是错误消息串。 +/// +/// # 副作用 +/// - 新建一个 Event 盒对象(经 `alloc_object` 入对象表)。 +pub(crate) fn build_error_event(vm: &mut Vm, args: &[u8]) -> NativeResult { + let type_val = if args.len() > 1 { vm.reg(args[1]) } else { JsValue::undefined() }; + let message_val = if args.len() > 2 { vm.reg(args[2]) } else { JsValue::undefined() }; // 建真实 Event 盒(type 取 type_val 的字符串形态)。 let type_text = vm.lookup_str(type_val).unwrap_or_default(); @@ -319,11 +348,9 @@ pub(crate) fn message_event_constructor(vm: &mut Vm, args: &[u8]) -> NativeResul 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); - 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); - } + let _ = vm.define_data_property(event_obj, si_data, JsValue::undefined(), PropAttributes::DEFAULT_DATA); + 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)) } diff --git a/crates/oxide_vm/src/worker/mod.rs b/crates/oxide_vm/src/worker/mod.rs index 1ab38a05..b6e6cdad 100644 --- a/crates/oxide_vm/src/worker/mod.rs +++ b/crates/oxide_vm/src/worker/mod.rs @@ -318,8 +318,8 @@ impl Vm { /// 2. 对每个 worker `poll_worker_messages` 排空通道(数据面 rehydrate 进主 realm)。 /// 3. 反查 Worker 对象(GC 根)。 /// 4. 错误面逐条交付 `onerror`;消息交付失败面逐条交付 `onmessageerror`。 - /// 5. 数据面读 `onmessage` 属性,可调用时逐条建 MessageEvent 经 - /// `execute_task` + `call_function_sync` 触发,后 `drain_microtasks`。 + /// 5. 数据面逐条建真实 MessageEvent 经 `dispatchEvent` 交付(注册表查 + /// 监听器),后 `drain_microtasks`。 /// /// # 返回值 /// 本轮是否交付了任何面(false 时调用方 1ms 轮询,避免忙等)。 @@ -368,7 +368,7 @@ impl Vm { if self.worker_object(id).is_none() { break; } - let event = self.build_message_event(data, "message", None); + let event = self.build_message_event(data, "message"); self.dispatch_event_on(worker_obj, event); self.drain_microtasks(); } @@ -407,35 +407,55 @@ impl Vm { /// # 副作用 /// - 触发监听器回调与微任务 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 event = self.build_error_event(event_type, message); self.dispatch_event_on(worker_obj, event); self.drain_microtasks(); } - /// 建事件对象(`data` + `type` + `message` 属性),委托 `message_event_constructor` - /// 构造辅助。消息事件 `message` 为 `None`(undefined),错误事件 `data` 为 - /// undefined、`message` 为错误串。 + /// 消息路径建事件对象:建真实 MessageEvent(`data` + `type`,`ports` 空 + /// 数组),委托 `message_event_constructor` 构造辅助。 /// /// # 步骤 - /// 1. `type` 与 `message` 串物化为 session 串(`message` 为 `None` 时 undefined)。 - /// 2. `data` / `type` / `message` 写入寄存器 1 / 2 / 3,委托 - /// `message_event_constructor`(按寄存器下标读取)建对象。 + /// 1. `type` 串物化为 session 串。 + /// 2. `data` / `type` 写入寄存器 1 / 2,委托 `message_event_constructor` + /// (按寄存器下标读取)建对象。 /// /// # 返回值 /// 新建的事件对象值(构造失败时返回错误对象值)。 /// /// # 副作用 - /// - 新建一个事件对象(经 `alloc_object` 入对象表)。 - fn build_message_event(&mut self, data: JsValue, type_str: &str, message: Option<&str>) -> JsValue { + /// - 新建一个 MessageEvent 盒对象(经 `alloc_object` 入对象表)。 + fn build_message_event(&mut self, data: JsValue, type_str: &str) -> JsValue { let type_val = self.new_string(type_str); - let message_val = match message { - Some(m) => self.new_string(m), - None => JsValue::undefined(), - }; self.set_reg(1, data); self.set_reg(2, type_val); - self.set_reg(3, message_val); - match bindings::message_event_constructor(self, &[0, 1, 2, 3]) { + match bindings::message_event_constructor(self, &[0, 1, 2]) { + NativeResult::Ok(v) => v, + NativeResult::Err(e) => e, + // 构造辅助只建对象,不产生尾调用(防御臂,不可达)。 + NativeResult::TailCall { .. } => JsValue::undefined(), + } + } + + /// 错误路径建事件对象(临时形态):建真实 Event 盒(`type` 字段)加 + /// `data` / `message` 普通属性,委托 `build_error_event` 构造辅助。 + /// + /// # 步骤 + /// 1. `type` 与 `message` 串物化为 session 串。 + /// 2. `type` / `message` 写入寄存器 1 / 2,委托 `build_error_event` + /// (按寄存器下标读取)建对象。 + /// + /// # 返回值 + /// 新建的事件对象值(构造失败时返回错误对象值)。 + /// + /// # 副作用 + /// - 新建一个 Event 盒对象(经 `alloc_object` 入对象表)。 + fn build_error_event(&mut self, type_str: &str, message: &str) -> JsValue { + let type_val = self.new_string(type_str); + let message_val = self.new_string(message); + self.set_reg(1, type_val); + self.set_reg(2, message_val); + match bindings::build_error_event(self, &[0, 1, 2]) { NativeResult::Ok(v) => v, NativeResult::Err(e) => e, // 构造辅助只建对象,不产生尾调用(防御臂,不可达)。 @@ -478,7 +498,7 @@ impl Vm { /// - 触发监听器回调与微任务 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); + let event = self.build_message_event(data, "message"); // 取 worker global 对象指针(Ref 守卫在语句块内消费,不跨 &mut 借用长存)。 let global_ptr = { let session = self.realm.session.borrow();