From 8a8c3e9b9c903dea70d8ba6fff4846e1efaa16ef Mon Sep 17 00:00:00 2001 From: pjh456 <147148383@qq.com> Date: Sat, 10 Oct 2026 08:24:13 +0000 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=96=B0=E5=A2=9E=20Event=20=E5=9F=BA?= =?UTF-8?q?=E7=B1=BB=E8=BD=BD=E8=8D=B7=E7=9B=92=E3=80=81GC=20=E6=8E=A5?= =?UTF-8?q?=E7=BA=BF=E3=80=81=E6=9E=84=E9=80=A0=E5=99=A8=E4=B8=8E=E5=8E=9F?= =?UTF-8?q?=E5=9E=8B=E7=BB=91=E5=AE=9A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.lock | 38 +- Cargo.toml | 2 +- crates/oxide_builtins/src/event.rs | 405 +++++++++++++++++++++ crates/oxide_builtins/src/lib.rs | 2 + crates/oxide_types/src/object.rs | 13 + crates/oxide_vm/src/bindings/bind_event.rs | 175 ++++++++- crates/oxide_vm/src/native_box_dispatch.rs | 41 ++- crates/oxide_vm/src/session_gc.rs | 12 +- 8 files changed, 642 insertions(+), 46 deletions(-) create mode 100644 crates/oxide_builtins/src/event.rs diff --git a/Cargo.lock b/Cargo.lock index d9b150b3..4404904f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1160,7 +1160,7 @@ dependencies = [ [[package]] name = "oxide_builtins" -version = "0.14.7" +version = "0.14.8" dependencies = [ "chrono", "fast-float", @@ -1185,7 +1185,7 @@ dependencies = [ [[package]] name = "oxide_bytecode" -version = "0.14.7" +version = "0.14.8" dependencies = [ "num-bigint", "oxide_log", @@ -1193,7 +1193,7 @@ dependencies = [ [[package]] name = "oxide_cfg" -version = "0.14.7" +version = "0.14.8" dependencies = [ "oxide_bytecode", "oxide_emit", @@ -1204,7 +1204,7 @@ dependencies = [ [[package]] name = "oxide_cli" -version = "0.14.7" +version = "0.14.8" dependencies = [ "ansi_term", "bumpalo", @@ -1226,7 +1226,7 @@ dependencies = [ [[package]] name = "oxide_code_cache" -version = "0.14.7" +version = "0.14.8" dependencies = [ "lru", "oxide_bytecode", @@ -1235,7 +1235,7 @@ dependencies = [ [[package]] name = "oxide_compiler" -version = "0.14.7" +version = "0.14.8" dependencies = [ "oxide_bytecode", "oxide_cfg", @@ -1255,7 +1255,7 @@ dependencies = [ [[package]] name = "oxide_dce" -version = "0.14.7" +version = "0.14.8" dependencies = [ "oxide_bytecode", "oxide_cfg", @@ -1269,7 +1269,7 @@ dependencies = [ [[package]] name = "oxide_emit" -version = "0.14.7" +version = "0.14.8" dependencies = [ "num-bigint", "oxc_allocator", @@ -1284,7 +1284,7 @@ dependencies = [ [[package]] name = "oxide_engine" -version = "0.14.7" +version = "0.14.8" dependencies = [ "oxide_bytecode", "oxide_compiler", @@ -1296,7 +1296,7 @@ dependencies = [ [[package]] name = "oxide_ir" -version = "0.14.7" +version = "0.14.8" dependencies = [ "oxide_bytecode", "oxide_log", @@ -1305,7 +1305,7 @@ dependencies = [ [[package]] name = "oxide_kernel" -version = "0.14.7" +version = "0.14.8" dependencies = [ "bumpalo", "dashmap", @@ -1319,7 +1319,7 @@ dependencies = [ [[package]] name = "oxide_liveness" -version = "0.14.7" +version = "0.14.8" dependencies = [ "oxide_bytecode", "oxide_cfg", @@ -1331,7 +1331,7 @@ dependencies = [ [[package]] name = "oxide_log" -version = "0.14.7" +version = "0.14.8" dependencies = [ "tracing", "tracing-appender", @@ -1340,7 +1340,7 @@ dependencies = [ [[package]] name = "oxide_parser" -version = "0.14.7" +version = "0.14.8" dependencies = [ "oxc_allocator", "oxc_ast", @@ -1355,7 +1355,7 @@ dependencies = [ [[package]] name = "oxide_regalloc" -version = "0.14.7" +version = "0.14.8" dependencies = [ "oxide_bytecode", "oxide_cfg", @@ -1371,7 +1371,7 @@ dependencies = [ [[package]] name = "oxide_runtime_api" -version = "0.14.7" +version = "0.14.8" dependencies = [ "num-bigint", "num-traits", @@ -1384,7 +1384,7 @@ dependencies = [ [[package]] name = "oxide_test262" -version = "0.14.7" +version = "0.14.8" dependencies = [ "oxide_bytecode", "oxide_compiler", @@ -1402,7 +1402,7 @@ dependencies = [ [[package]] name = "oxide_types" -version = "0.14.7" +version = "0.14.8" dependencies = [ "bumpalo", "dashmap", @@ -1415,7 +1415,7 @@ dependencies = [ [[package]] name = "oxide_vm" -version = "0.14.7" +version = "0.14.8" dependencies = [ "bumpalo", "chrono", diff --git a/Cargo.toml b/Cargo.toml index a5dca1b3..f88d069c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,7 +23,7 @@ members = [ ] [workspace.package] -version = "0.14.7" +version = "0.14.8" edition = "2021" license = "MIT" rust-version = "1.80" diff --git a/crates/oxide_builtins/src/event.rs b/crates/oxide_builtins/src/event.rs new file mode 100644 index 00000000..cb49bbc4 --- /dev/null +++ b/crates/oxide_builtins/src/event.rs @@ -0,0 +1,405 @@ +//! Event 基类行为面:`Event` 构造器(`new Event(type, init)`)、原型 getter +//! (type / target / currentTarget / eventPhase / isTrusted / bubbles / +//! cancelable / defaultPrevented / composed)与原型方法(preventDefault / +//! stopPropagation / stopImmediatePropagation / composedPath)。 +//! +//! 事件载荷盒(type 串与九项状态位)存于对象 `native_fn` 槽,向 GC 家族表 +//! 暴露 edges/size/drop 三自由函数。 + +use std::mem; + +use oxide_kernel::shape_forge::EMPTY_SHAPE_ID; +use oxide_types::object::{JsObject, NativeFnPtr}; +use oxide_types::value::JsValue; + +use oxide_runtime_api::{NativeResult, ProtoKind, VmHost}; + +/// Event 载荷盒:存于对象 `native_fn` 槽(`Box::into_raw`,realm 局部单线程)。 +/// `r#type` 是事件类型串(GC 字符串边),`target` / `current_target` 是目标 +/// 对象(GC 对象边,初始 null),其余为简单状态位。 +struct EventInner { + r#type: JsValue, + target: JsValue, + current_target: JsValue, + event_phase: u32, + is_trusted: bool, + bubbles: bool, + cancelable: bool, + default_prevented: bool, + propagation_stopped: bool, + immediate_stopped: bool, + composed: bool, +} + +/// 取载荷盒指针:仅 Event 标签对象返回 `Some`,其余标签返回 `None`。 +fn event_payload_ptr(obj: &JsObject) -> Option<*mut EventInner> { + if !obj.is_event_obj() { + return None; + } + obj.native_fn().map(|ptr| ptr.as_ptr() as *mut EventInner) +} + +/// 收集 Event 的引用边(type 串边与 target / current_target 对象边)。 +/// +/// `target` / `current_target` 空窗口(构造期回填之前)为 null,边函数自守, +/// 语义自含。 +pub fn event_native_edges(obj: &JsObject) -> Vec { + let Some(ptr) = event_payload_ptr(obj) else { + return Vec::new(); + }; + if ptr.is_null() { + return Vec::new(); + } + // SAFETY: ptr 指向有效载荷盒(槽位仅构造期写入、释放时置空),只读字段。 + let inner = unsafe { &*ptr }; + vec![inner.r#type, inner.target, inner.current_target] +} + +/// 只读核算 Event 载荷盒字节(不释放)。 +pub fn event_native_size(obj: &JsObject) -> u64 { + let Some(ptr) = event_payload_ptr(obj) else { + return 0; + }; + if ptr.is_null() { + return 0; + } + mem::size_of::() as u64 +} + +/// 释放 Event 载荷盒(`Box`),返回释放字节数。 +/// +/// 盒 drop 时 type / target / current_target 的 JsValue 边随盒释放(GC 已 +/// 判定死对象);槽置空保证重复调用零释放(幂等)。 +pub fn drop_event_native(obj: &mut JsObject) -> u64 { + let bytes = event_native_size(obj); + if bytes == 0 { + return 0; + } + let Some(ptr) = event_payload_ptr(obj) else { + return 0; + }; + // SAFETY: ptr 非空(event_native_size 已验证),Box::from_raw 恰好释放一次。 + unsafe { + drop(Box::from_raw(ptr)); + } + obj.set_native_fn(None); + bytes +} + +/// 解析 this 为 Event 载荷盒共享引用:非 Event 对象或无载荷盒返回 None。 +/// +/// # 注意事项 +/// 返回引用按 `'a` 声称为存活,实际存活至载荷盒被 GC 释放;调用方须在无 GC +/// 窗口的单 native 函数内消费,不得跨用户调用窗口持有。 +fn event_inner_of<'a>(this_val: JsValue) -> Option<&'a EventInner> { + 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 }; + if !obj.is_event_obj() { + return None; + } + let box_ptr = event_payload_ptr(obj)?; + if box_ptr.is_null() { + return None; + } + // SAFETY: box_ptr 指向有效载荷盒(构造期写入、释放时置空);'a 存活由 + // 调用方「无 GC 窗口内消费」约定保证。 + Some(unsafe { &*box_ptr }) +} + +/// 解析 this 为 Event 载荷盒可变引用:非 Event 对象或无载荷盒返回 None。 +/// +/// # 注意事项 +/// 返回引用按 `'a` 声称为存活,实际存活至载荷盒被 GC 释放;调用方须在无 GC +/// 窗口的单 native 函数内消费,不得跨用户调用窗口持有。 +fn event_inner_of_mut<'a>(this_val: JsValue) -> Option<&'a mut EventInner> { + 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 { &mut *obj_ptr }; + if !obj.is_event_obj() { + return None; + } + let box_ptr = event_payload_ptr(obj)?; + if box_ptr.is_null() { + return None; + } + // SAFETY: box_ptr 指向有效载荷盒(构造期写入、释放时置空);'a 存活由 + // 调用方「无 GC 窗口内消费」约定保证。 + Some(unsafe { &mut *box_ptr }) +} + +/// Event 构造器:`new Event(type, init)`。 +/// +/// # 步骤 +/// 1. 非构造形态(`!constructing_native`)抛 TypeError(HTML 规范只有 +/// [[Construct]],无 [[Call]]) +/// 2. type 经完整 ToString 转串;空串抛 SyntaxError +/// 3. init 字典读 bubbles / cancelable / composed(缺省 false) +/// 4. 建载荷盒入 receiver 的 `native_fn` 槽,置 type_tag +/// 5. 返回 receiver +/// +/// # 边界与前提 +/// - receiver(`args[0]`)是 VM 按构造器 prototype 新分配的对象,proto 已 +/// 指向 %Event.prototype%; +/// - init 缺省(undefined)或非对象时对应标志取 false。 +/// +/// # 副作用 +/// - receiver 的 `native_fn` 槽写入载荷盒、`type_tag` 置 Event 标签。 +pub fn event_constructor(vm: &mut H, args: &[u8]) -> NativeResult { + if !vm.constructing_native() { + return NativeResult::Err(crate::error::create_type_error(vm, "Event constructor requires new")); + } + + let this_val = vm.reg(args[0]); + if !this_val.is_object() { + return NativeResult::Err(crate::error::create_type_error(vm, "Event 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 字典读 bubbles / cancelable / composed(缺省或非对象时取 false)。 + let mut bubbles = false; + let mut cancelable = false; + let mut composed = false; + 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 }; + for (name, slot) in + [("bubbles", &mut bubbles), ("cancelable", &mut cancelable), ("composed", &mut composed)] + { + let si = vm.perm_intern(name); + match vm.ordinary_get(init_obj, si, init_val) { + Ok(v) => *slot = oxide_runtime_api::to_boolean(v), + Err(_) => { + if let Some(exc) = vm.take_uncaught_value() { + return NativeResult::Err(exc); + } + return NativeResult::Err(crate::error::create_type_error( + vm, + "Event init property getter failed", + )); + } + } + } + } + } + + // 建载荷盒入 receiver 的 native_fn 槽,置 Event 标签。 + let type_js = vm.new_string(&type_str); + let inner = Box::into_raw(Box::new(EventInner { + r#type: type_js, + target: JsValue::null(), + current_target: JsValue::null(), + event_phase: 0, + is_trusted: false, + bubbles, + cancelable, + default_prevented: false, + propagation_stopped: false, + immediate_stopped: false, + composed, + })); + // SAFETY: receiver 不可调用,native_fn 存不透明 `Box` 指针, + // 与 ArrayBuffer / RegExp 的类型化存储模式一致。 + let obj = unsafe { &mut *this_val.as_js_object_ptr() }; + obj.type_tag = JsObject::OBJ_TYPE_EVENT; + obj.set_native_fn(Some(unsafe { NativeFnPtr::from_raw(inner as *const ()) })); + NativeResult::Ok(this_val) +} + +/// `Event.prototype.type`:返回事件类型串。 +pub fn event_type_getter(vm: &mut H, args: &[u8]) -> NativeResult { + let this_val = vm.reg(if args.is_empty() { 0 } else { args[0] }); + let Some(inner) = event_inner_of(this_val) else { + return NativeResult::Err(crate::error::create_type_error( + vm, + "Event.prototype.type getter called on non-Event object", + )); + }; + NativeResult::Ok(inner.r#type) +} + +/// `Event.prototype.target`:返回事件目标对象(未派发时 null)。 +pub fn event_target_getter(vm: &mut H, args: &[u8]) -> NativeResult { + let this_val = vm.reg(if args.is_empty() { 0 } else { args[0] }); + let Some(inner) = event_inner_of(this_val) else { + return NativeResult::Err(crate::error::create_type_error( + vm, + "Event.prototype.target getter called on non-Event object", + )); + }; + NativeResult::Ok(inner.target) +} + +/// `Event.prototype.currentTarget`:返回当前派发目标(未派发时 null)。 +pub fn event_current_target_getter(vm: &mut H, args: &[u8]) -> NativeResult { + let this_val = vm.reg(if args.is_empty() { 0 } else { args[0] }); + let Some(inner) = event_inner_of(this_val) else { + return NativeResult::Err(crate::error::create_type_error( + vm, + "Event.prototype.currentTarget getter called on non-Event object", + )); + }; + NativeResult::Ok(inner.current_target) +} + +/// `Event.prototype.eventPhase`:返回派发阶段(0 未派发 / 1 捕获 / 2 目标 / 3 冒泡)。 +pub fn event_phase_getter(vm: &mut H, args: &[u8]) -> NativeResult { + let this_val = vm.reg(if args.is_empty() { 0 } else { args[0] }); + let Some(inner) = event_inner_of(this_val) else { + return NativeResult::Err(crate::error::create_type_error( + vm, + "Event.prototype.eventPhase getter called on non-Event object", + )); + }; + NativeResult::Ok(JsValue::int(inner.event_phase as i32)) +} + +/// `Event.prototype.isTrusted`:返回是否由用户操作产生(引擎恒 false)。 +pub fn event_is_trusted_getter(vm: &mut H, args: &[u8]) -> NativeResult { + let this_val = vm.reg(if args.is_empty() { 0 } else { args[0] }); + let Some(inner) = event_inner_of(this_val) else { + return NativeResult::Err(crate::error::create_type_error( + vm, + "Event.prototype.isTrusted getter called on non-Event object", + )); + }; + NativeResult::Ok(JsValue::bool(inner.is_trusted)) +} + +/// `Event.prototype.bubbles`:返回是否冒泡。 +pub fn event_bubbles_getter(vm: &mut H, args: &[u8]) -> NativeResult { + let this_val = vm.reg(if args.is_empty() { 0 } else { args[0] }); + let Some(inner) = event_inner_of(this_val) else { + return NativeResult::Err(crate::error::create_type_error( + vm, + "Event.prototype.bubbles getter called on non-Event object", + )); + }; + NativeResult::Ok(JsValue::bool(inner.bubbles)) +} + +/// `Event.prototype.cancelable`:返回是否可取消。 +pub fn event_cancelable_getter(vm: &mut H, args: &[u8]) -> NativeResult { + let this_val = vm.reg(if args.is_empty() { 0 } else { args[0] }); + let Some(inner) = event_inner_of(this_val) else { + return NativeResult::Err(crate::error::create_type_error( + vm, + "Event.prototype.cancelable getter called on non-Event object", + )); + }; + NativeResult::Ok(JsValue::bool(inner.cancelable)) +} + +/// `Event.prototype.defaultPrevented`:返回默认行为是否已被阻止。 +pub fn event_default_prevented_getter(vm: &mut H, args: &[u8]) -> NativeResult { + let this_val = vm.reg(if args.is_empty() { 0 } else { args[0] }); + let Some(inner) = event_inner_of(this_val) else { + return NativeResult::Err(crate::error::create_type_error( + vm, + "Event.prototype.defaultPrevented getter called on non-Event object", + )); + }; + NativeResult::Ok(JsValue::bool(inner.default_prevented)) +} + +/// `Event.prototype.composed`:返回是否跨 Shadow DOM 边界。 +pub fn event_composed_getter(vm: &mut H, args: &[u8]) -> NativeResult { + let this_val = vm.reg(if args.is_empty() { 0 } else { args[0] }); + let Some(inner) = event_inner_of(this_val) else { + return NativeResult::Err(crate::error::create_type_error( + vm, + "Event.prototype.composed getter called on non-Event object", + )); + }; + NativeResult::Ok(JsValue::bool(inner.composed)) +} + +/// `Event.prototype.preventDefault`:cancelable 时置 defaultPrevented。 +pub fn event_prevent_default(vm: &mut H, args: &[u8]) -> NativeResult { + let this_val = vm.reg(if args.is_empty() { 0 } else { args[0] }); + let Some(inner) = event_inner_of_mut(this_val) else { + return NativeResult::Err(crate::error::create_type_error( + vm, + "Event.prototype.preventDefault called on non-Event object", + )); + }; + if inner.cancelable { + inner.default_prevented = true; + } + NativeResult::Ok(JsValue::undefined()) +} + +/// `Event.prototype.stopPropagation`:置传播停止标志。 +pub fn event_stop_propagation(vm: &mut H, args: &[u8]) -> NativeResult { + let this_val = vm.reg(if args.is_empty() { 0 } else { args[0] }); + let Some(inner) = event_inner_of_mut(this_val) else { + return NativeResult::Err(crate::error::create_type_error( + vm, + "Event.prototype.stopPropagation called on non-Event object", + )); + }; + inner.propagation_stopped = true; + NativeResult::Ok(JsValue::undefined()) +} + +/// `Event.prototype.stopImmediatePropagation`:置传播停止与立即停止双标志。 +pub fn event_stop_immediate_propagation(vm: &mut H, args: &[u8]) -> NativeResult { + let this_val = vm.reg(if args.is_empty() { 0 } else { args[0] }); + let Some(inner) = event_inner_of_mut(this_val) else { + return NativeResult::Err(crate::error::create_type_error( + vm, + "Event.prototype.stopImmediatePropagation called on non-Event object", + )); + }; + inner.propagation_stopped = true; + inner.immediate_stopped = true; + NativeResult::Ok(JsValue::undefined()) +} + +/// `Event.prototype.composedPath`:返回事件传播路径(首版仅含 this 自身)。 +pub fn event_composed_path(vm: &mut H, args: &[u8]) -> NativeResult { + let this_val = vm.reg(if args.is_empty() { 0 } else { args[0] }); + let Some(_) = event_inner_of(this_val) else { + return NativeResult::Err(crate::error::create_type_error( + vm, + "Event.prototype.composedPath called on non-Event object", + )); + }; + // 首版:路径仅含 this 自身(完整传播路径由事件派发任务补全)。 + 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, 1)); + // SAFETY: arr 是本函数刚分配的对象,存活且本段无别名。 + unsafe { + (*arr).set_prop_at(0, this_val); + } + NativeResult::Ok(JsValue::from_js_object(arr)) +} diff --git a/crates/oxide_builtins/src/lib.rs b/crates/oxide_builtins/src/lib.rs index 94d09652..f6a5349f 100644 --- a/crates/oxide_builtins/src/lib.rs +++ b/crates/oxide_builtins/src/lib.rs @@ -34,6 +34,8 @@ pub mod disposable_stack; pub mod error; /// eval 全局函数实现(动态编译执行脚本/表达式)。 pub mod eval; +/// Event 基类实现(构造器、原型 getter 与传播方法,载荷盒与 GC 三自由函数族)。 +pub mod event; /// Function 内置对象实现(call/apply/bind/toString)。 pub mod function; /// 全局函数模块(URI 编解码与 Annex B 的 escape/unescape)。 diff --git a/crates/oxide_types/src/object.rs b/crates/oxide_types/src/object.rs index 85475d29..f7c4f7fc 100644 --- a/crates/oxide_types/src/object.rs +++ b/crates/oxide_types/src/object.rs @@ -232,6 +232,14 @@ impl JsObject { /// 载荷盒(`BroadcastChannelInner`),无对象边(mpsc 非 GC 边、通道名为 /// Rust `String`)。 pub const OBJ_TYPE_BROADCAST_CHANNEL: u8 = 34; + /// Event 对象:事件基类载荷盒(type 串与九项状态位)存于 `native_fn` 槽。 + pub const OBJ_TYPE_EVENT: u8 = 35; + /// MessageEvent 对象:事件派生类载荷盒(data 与 ports 边)存于 `native_fn` 槽。 + pub const OBJ_TYPE_MESSAGE_EVENT: u8 = 36; + /// ErrorEvent 对象:事件派生类载荷盒(message 与 error 边)存于 `native_fn` 槽。 + pub const OBJ_TYPE_ERROR_EVENT: u8 = 37; + /// CustomEvent 对象:事件派生类载荷盒(detail 边)存于 `native_fn` 槽。 + pub const OBJ_TYPE_CUSTOM_EVENT: u8 = 38; /// `is_session_epoch` 字段中的 session 标记位。 pub const SESSION_EPOCH_BIT: u8 = 0x01; /// `is_session_epoch` 字段中的 GC 标记位。 @@ -319,6 +327,11 @@ impl JsObject { pub fn is_broadcast_channel(&self) -> bool { self.type_tag == Self::OBJ_TYPE_BROADCAST_CHANNEL } + /// 是否 Event 对象(`native_fn` 槽存事件基类载荷盒)。 + #[inline] + pub fn is_event_obj(&self) -> bool { + self.type_tag == Self::OBJ_TYPE_EVENT + } /// 是否 DataView 对象。 #[inline] pub fn is_data_view_obj(&self) -> bool { diff --git a/crates/oxide_vm/src/bindings/bind_event.rs b/crates/oxide_vm/src/bindings/bind_event.rs index 22391974..f0d76917 100644 --- a/crates/oxide_vm/src/bindings/bind_event.rs +++ b/crates/oxide_vm/src/bindings/bind_event.rs @@ -1,28 +1,165 @@ -//! Event 体系绑定:本文件是内核接线占位(零行为变化)。九枚 P 字段 -//! (Event / MessageEvent / ErrorEvent / CustomEvent 四枚原型与构造器对加 -//! EventTarget 原型)当前是空对象,本函数为它们的绑定入口预留,真实构造器 / -//! 原型方法 / 原型链连线由后续任务就地填充(不新建对象——P 字段本身就是槽位, -//! 快照 / 脏检查 / 收尾枚举已覆盖)。 +//! Event 基类绑定:就地填充 `event_constructor` / `event_proto` 两枚 P 字段 +//! 占位(构造器本体、原型方法、原型 getter 与 Event 常量),并绑全局 `Event`。 +//! +//! 九枚 P 字段中其余七枚(MessageEvent / ErrorEvent / CustomEvent 四对加 +//! EventTarget 原型)由后续子任务就地填充,本函数不读不写。 use std::sync::Arc; +use crate::bind_constructor; +use crate::bindings::{apply_binding_table, bind_accessor_getter, configure_native_constructor}; use oxide_kernel::kernel::{KernelCore, KernelSession}; -use oxide_types::object::JsObject; +use oxide_types::object::{JsObject, PropAttributes}; +use oxide_types::value::JsValue; -/// Event 体系绑定入口(当前为空占位)。 +/// 把 Event 构造器与原型方法绑定到 global。 +/// +/// `event_constructor` / `event_proto` 两枚 P 字段是空对象占位,本函数就地 +/// 填充(不新建对象——P 字段本身就是槽位,快照/脏检查/收尾枚举已覆盖)。 /// /// # 幂等 /// 本函数从三条路径到达:`init_kernel_builtins`(占位是空对象)、 -/// `rebind_dirty_builtins` 的 `event` 分支(占位是重建后的空对象)、 -/// global-dirty 路径(占位可能已填满)。本函数当前不写任何槽位,空操作天然 -/// 幂等;后续任务就地填充时,各槽写须沿用 `lookup_position` 前置守卫 -/// (有槽即跳过),保持与 `bind_broadcast_channel` 同型的幂等契约。 +/// `rebind_dirty_builtins` 的 `event` 分支(占位是重建后的空对象)、global-dirty +/// 路径(占位可能已填满)。全部槽写经 `lookup_position` 前置守卫,有槽即跳过; +/// `bind_constructor!` 的全局槽臂自带既有槽原位更新,天然幂等。 /// -/// # 边界 -/// 九枚 P 字段是空对象占位,本函数不读不写,行为与未调用一致; -/// EventTarget 原型不占 ProtoKind(builtins 不读它),绑定层后续直接访问 -/// `world.event_target_proto` 槽。 -/// -/// `core` / `session` / `global` / `realm_id` 当前未使用,保留签名与其他 -/// 绑定函数一致,供后续任务就地填充时直接取用。 -pub fn bind_event(_core: &Arc, _session: &KernelSession, _global: &mut JsObject, _realm_id: u32) {} +/// `realm_id` 未使用(Event 无 well-known 符号键),保留签名与其他绑定函数一致。 +pub fn bind_event(core: &Arc, session: &KernelSession, global: &mut JsObject, _realm_id: u32) { + let world = session.builtin_world(); + let ctor_ptr = world.event_constructor.as_ptr() as *mut JsObject; + let ctor = unsafe { &mut *ctor_ptr }; + let proto_ptr = world.event_proto.as_ptr() as *mut JsObject; + let proto = unsafe { &mut *proto_ptr }; + + let sf = core.perm_interner().as_ref(); + let sh = core.shape_forge().as_ref(); + + // ---- 填构造器占位 ---- + ctor.set_function(true); + + // prototype 槽 → 原型对象(描述符 {f,f,f})。 + let si_prototype = sf.intern("prototype").0; + if sh.lookup_position(ctor.shape_id(), si_prototype).is_none() { + let shape = sh.make_shape(ctor.shape_id(), si_prototype); + ctor.set_shape_id(shape); + let pos = ctor.push_prop(JsValue::from_js_object(proto_ptr)); + ctor.set_data_meta(pos, PropAttributes::new(false, false, false)); + ctor.bump_generation(); + } + + // name 槽 → "Event"(描述符 {f,f,t})。 + let si_name = sf.intern("name").0; + if sh.lookup_position(ctor.shape_id(), si_name).is_none() { + let shape = sh.make_shape(ctor.shape_id(), si_name); + ctor.set_shape_id(shape); + ctor.ensure_hash_props() + .push(JsValue::perm_string(sf.string_ptr(sf.intern("Event").0))); + let pos = ctor.hash_props_vec().map_or(0, |v| v.len() as u32).saturating_sub(1); + ctor.set_data_meta(pos, PropAttributes::new(false, false, true)); + ctor.bump_generation(); + } + + // [[Prototype]] → Function.prototype(幂等,裸写槽)。 + let fn_proto_val = JsValue::from_js_object(world.function_proto.as_ptr() as *mut JsObject); + let _ = ctor.set_proto(fn_proto_val); + + configure_native_constructor(ctor, oxide_builtins::event::event_constructor:: as *const (), 2); + bind_constructor!( + core, + global, + "Event", + ctor_ptr, + oxide_builtins::event::event_constructor::, + 2, + hash: true + ); + + // Event 常量:CAPTURING_PHASE / AT_TARGET / BUBBLING_PHASE(绑构造器, + // 描述符 {t,f,t})。 + let constants: [(&str, i32); 3] = [("CAPTURING_PHASE", 1), ("AT_TARGET", 2), ("BUBBLING_PHASE", 3)]; + for (name, value) in constants { + let si = sf.intern(name).0; + if sh.lookup_position(ctor.shape_id(), si).is_none() { + let shape = sh.make_shape(ctor.shape_id(), si); + ctor.set_shape_id(shape); + let pos = ctor.push_prop(JsValue::int(value)); + ctor.set_data_meta(pos, PropAttributes::new(true, false, true)); + ctor.bump_generation(); + } + } + + // ---- 填原型占位 ---- + // constructor 槽 → 构造器对象(描述符 {t,f,t})。 + let si_constructor = sf.intern("constructor").0; + if sh.lookup_position(proto.shape_id(), si_constructor).is_none() { + let shape = sh.make_shape(proto.shape_id(), si_constructor); + proto.set_shape_id(shape); + let pos = proto.push_prop(JsValue::from_js_object(ctor_ptr)); + proto.set_data_meta(pos, PropAttributes::new(true, false, true)); + 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); + + // 原型方法:preventDefault / stopPropagation / stopImmediatePropagation / + // composedPath(每方法先 lookup_position 守卫)。 + let si_prevent_default = sf.intern("preventDefault").0; + if sh.lookup_position(proto.shape_id(), si_prevent_default).is_none() { + apply_binding_table( + world, + proto, + core, + &[ + ( + "preventDefault", + oxide_builtins::event::event_prevent_default:: as *const (), + 0, + ), + ( + "stopPropagation", + oxide_builtins::event::event_stop_propagation:: as *const (), + 0, + ), + ( + "stopImmediatePropagation", + oxide_builtins::event::event_stop_immediate_propagation:: as *const (), + 0, + ), + ( + "composedPath", + oxide_builtins::event::event_composed_path:: as *const (), + 0, + ), + ], + ); + } + + // 原型 getter:type / target / currentTarget / eventPhase / isTrusted / + // bubbles / cancelable / defaultPrevented / composed(每 getter 先 + // lookup_position 守卫)。 + let getter_fns: [(&str, *const ()); 9] = [ + ("type", oxide_builtins::event::event_type_getter:: as *const ()), + ("target", oxide_builtins::event::event_target_getter:: as *const ()), + ( + "currentTarget", + oxide_builtins::event::event_current_target_getter:: as *const (), + ), + ("eventPhase", oxide_builtins::event::event_phase_getter:: as *const ()), + ("isTrusted", oxide_builtins::event::event_is_trusted_getter:: as *const ()), + ("bubbles", oxide_builtins::event::event_bubbles_getter:: as *const ()), + ("cancelable", oxide_builtins::event::event_cancelable_getter:: as *const ()), + ( + "defaultPrevented", + oxide_builtins::event::event_default_prevented_getter:: as *const (), + ), + ("composed", oxide_builtins::event::event_composed_getter:: as *const ()), + ]; + for (name, fn_ptr) in getter_fns { + let si = sf.intern(name).0; + if sh.lookup_position(proto.shape_id(), si).is_none() { + bind_accessor_getter(core, session, proto, name, fn_ptr); + } + } +} diff --git a/crates/oxide_vm/src/native_box_dispatch.rs b/crates/oxide_vm/src/native_box_dispatch.rs index 5610ae7c..289928be 100644 --- a/crates/oxide_vm/src/native_box_dispatch.rs +++ b/crates/oxide_vm/src/native_box_dispatch.rs @@ -1,7 +1,7 @@ //! native 载荷家族的单点分类与每家族边函数引用(家族表)。 //! //! 分类是复合谓词:header 位(map/set/module_ns)优先于 `type_tag`, -//! 两维互斥、一个对象至多归入一个家族。tag 维显式枚举全部 0..=34, +//! 两维互斥、一个对象至多归入一个家族。tag 维显式枚举全部 0..=38, //! 未登记 tag panic。 //! //! 关键约定: @@ -10,13 +10,13 @@ //! - 注册面集中在 `ops_for` 的家族表,mark/size/drop/clone/rewrite 各链 //! 由同一张表驱动,新增家族只在此登记。 -use oxide_builtins::{data_view, disposable_stack, map, message_channel, module, set, typed_array, weak_map}; +use oxide_builtins::{data_view, disposable_stack, event, map, message_channel, module, set, typed_array, weak_map}; use oxide_types::object::{Cell, JsObject, JsString}; use oxide_types::value::JsValue; /// native 载荷家族。 /// -/// 15 个 native 家族 + 空家族;空家族是无 native 盒类型的显式归类结果。 +/// 21 个 native 家族 + 空家族;空家族是无 native 盒类型的显式归类结果。 #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub(crate) enum NativeBoxFamily { /// 无 native 载荷盒。 @@ -53,6 +53,14 @@ pub(crate) enum NativeBoxFamily { MessagePort, /// BroadcastChannel 通道对象(tag 34,mpsc 发送/接收对与通道名,无对象边)。 BroadcastChannel, + /// Event 事件基类对象(tag 35,type 串与九项状态位载荷盒)。 + Event, + /// MessageEvent 事件派生类对象(tag 36,data 与 ports 边)。 + MessageEvent, + /// ErrorEvent 事件派生类对象(tag 37,message 与 error 边)。 + ErrorEvent, + /// CustomEvent 事件派生类对象(tag 38,detail 边)。 + CustomEvent, /// mapped arguments 对象(tag 20,同步状态盒存 `native_data`,无引用边)。 Arguments, } @@ -87,7 +95,7 @@ pub(crate) fn classify(obj: &JsObject) -> NativeBoxFamily { /// 按类型 tag 分类 native 载荷家族。 /// /// # 边界与前提 -/// - 0..=34 全 tag 显式枚举;未登记 tag(35 及以上)panic。 +/// - 0..=38 全 tag 显式枚举;未登记 tag(39 及以上)panic。 pub(crate) fn classify_by_tag(tag: u8) -> NativeBoxFamily { match tag { JsObject::OBJ_TYPE_ARRAY_BUFFER => NativeBoxFamily::ArrayBuffer, @@ -126,6 +134,10 @@ pub(crate) fn classify_by_tag(tag: u8) -> NativeBoxFamily { JsObject::OBJ_TYPE_RAW_JSON => NativeBoxFamily::None, JsObject::OBJ_TYPE_MESSAGE_PORT => NativeBoxFamily::MessagePort, JsObject::OBJ_TYPE_BROADCAST_CHANNEL => NativeBoxFamily::BroadcastChannel, + JsObject::OBJ_TYPE_EVENT => NativeBoxFamily::Event, + JsObject::OBJ_TYPE_MESSAGE_EVENT => NativeBoxFamily::MessageEvent, + JsObject::OBJ_TYPE_ERROR_EVENT => NativeBoxFamily::ErrorEvent, + JsObject::OBJ_TYPE_CUSTOM_EVENT => NativeBoxFamily::CustomEvent, _ => panic!("unregistered type tag: {tag}"), } } @@ -223,6 +235,19 @@ pub(crate) fn ops_for(family: NativeBoxFamily) -> NativeBoxOps { string_edges: None, cell_edges: None, }, + // Event 基类载荷盒:type 串边与 target / current_target 对象边。 + NativeBoxFamily::Event => NativeBoxOps { + object_edges: Some(event::event_native_edges), + string_edges: None, + cell_edges: None, + }, + // 事件派生类(MessageEvent / ErrorEvent / CustomEvent)载荷盒由后续子 + // 任务填充;本臂当前无引用边,仅 size/drop 链消费。 + NativeBoxFamily::MessageEvent | NativeBoxFamily::ErrorEvent | NativeBoxFamily::CustomEvent => NativeBoxOps { + object_edges: None, + string_edges: None, + cell_edges: None, + }, // mapped arguments 同步状态盒:无引用边(位图与帧身份均为原始值), // 三边函数全空,仅 size/drop 链消费。 NativeBoxFamily::Arguments => NativeBoxOps { @@ -261,6 +286,10 @@ mod tests { JsObject::OBJ_TYPE_REGEXP | JsObject::OBJ_TYPE_REGEX_STUB => NativeBoxFamily::RegExp, JsObject::OBJ_TYPE_MESSAGE_PORT => NativeBoxFamily::MessagePort, JsObject::OBJ_TYPE_BROADCAST_CHANNEL => NativeBoxFamily::BroadcastChannel, + JsObject::OBJ_TYPE_EVENT => NativeBoxFamily::Event, + JsObject::OBJ_TYPE_MESSAGE_EVENT => NativeBoxFamily::MessageEvent, + JsObject::OBJ_TYPE_ERROR_EVENT => NativeBoxFamily::ErrorEvent, + JsObject::OBJ_TYPE_CUSTOM_EVENT => NativeBoxFamily::CustomEvent, JsObject::OBJ_TYPE_ARGUMENTS => NativeBoxFamily::Arguments, _ => NativeBoxFamily::None, } @@ -268,7 +297,7 @@ mod tests { #[test] fn tag_dimension_every_tag_hits_exactly_one_family() { - for tag in 0..=34u8 { + for tag in 0..=38u8 { let obj = obj_with_tag(tag); let family = classify(&obj); assert_eq!(family, expected_family_for_tag(tag), "tag {tag} 家族不一致"); @@ -302,7 +331,7 @@ mod tests { #[test] #[should_panic(expected = "unregistered type tag")] fn unregistered_tag_panics() { - let obj = obj_with_tag(35); + let obj = obj_with_tag(39); classify(&obj); } } diff --git a/crates/oxide_vm/src/session_gc.rs b/crates/oxide_vm/src/session_gc.rs index d0b87d81..f99bcdaa 100644 --- a/crates/oxide_vm/src/session_gc.rs +++ b/crates/oxide_vm/src/session_gc.rs @@ -11,7 +11,7 @@ use crate::native_box_dispatch; use crate::vm::RootGroup; use crate::vm::Vm; use oxide_builtins::{ - array_buffer, broadcast_channel, data_view, disposable_stack, map, message_channel, module, regexp, set, + array_buffer, broadcast_channel, data_view, disposable_stack, event, map, message_channel, module, regexp, set, typed_array, weak_map, }; @@ -132,6 +132,7 @@ impl SessionGc { bytes += weak_map::weak_map_native_size(obj); bytes += message_channel::message_port_native_size(obj); bytes += broadcast_channel::broadcast_channel_native_size(obj); + bytes += event::event_native_size(obj); bytes += crate::arguments_gc::arguments_native_size(obj); bytes @@ -375,6 +376,14 @@ 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) { + if value.is_string() { + Self::mark_string_live(live, value.as_string_ptr_mut()); + } + } + } } /// 从 VM roots 标记存活 session 对象、字符串与 BigInt,供 `sweep` 判定。 @@ -574,6 +583,7 @@ impl SessionGc { freed_bytes += weak_map::drop_weak_map_native(obj); 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 += crate::arguments_gc::drop_arguments_native(obj); freed_bytes