mirror of
https://github.com/pjh456/OxideJS.git
synced 2026-10-26 19:51:11 +08:00
feat: 事件目标弱引用注册表加三方法加原型链
This commit is contained in:
@@ -86,6 +86,56 @@ pub fn drop_event_native(obj: &mut JsObject) -> u64 {
|
||||
bytes
|
||||
}
|
||||
|
||||
/// 读事件类型串(GC 字符串边)。非 Event 对象或无载荷盒返回 None。
|
||||
///
|
||||
/// 供派发路径(`dispatchEvent`)取事件类型串、intern 后查监听器。
|
||||
pub fn event_type_value(obj: &JsObject) -> Option<JsValue> {
|
||||
let ptr = event_payload_ptr(obj)?;
|
||||
if ptr.is_null() {
|
||||
return None;
|
||||
}
|
||||
// SAFETY: ptr 指向有效载荷盒(构造期写入、释放时置空),只读字段。
|
||||
Some(unsafe { &*ptr }.r#type)
|
||||
}
|
||||
|
||||
/// 设派发目标与阶段:`target` / `current_target` 置 `target`、`event_phase` 置
|
||||
/// `phase`。非 Event 对象或无载荷盒时 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 {
|
||||
return;
|
||||
};
|
||||
if ptr.is_null() {
|
||||
return;
|
||||
}
|
||||
// SAFETY: ptr 指向有效载荷盒,写入派发字段(无 GC 窗口)。
|
||||
let inner = unsafe { &mut *ptr };
|
||||
inner.target = target;
|
||||
inner.current_target = target;
|
||||
inner.event_phase = phase;
|
||||
}
|
||||
|
||||
/// 读 `defaultPrevented` 标志。非 Event 对象或无载荷盒返回 None。
|
||||
pub fn event_default_prevented(obj: &JsObject) -> Option<bool> {
|
||||
let ptr = event_payload_ptr(obj)?;
|
||||
if ptr.is_null() {
|
||||
return None;
|
||||
}
|
||||
// SAFETY: ptr 指向有效载荷盒,只读字段。
|
||||
Some(unsafe { &*ptr }.default_prevented)
|
||||
}
|
||||
|
||||
/// 读 `stopImmediatePropagation` 标志。非 Event 对象或无载荷盒返回 None。
|
||||
pub fn event_immediate_stopped(obj: &JsObject) -> Option<bool> {
|
||||
let ptr = event_payload_ptr(obj)?;
|
||||
if ptr.is_null() {
|
||||
return None;
|
||||
}
|
||||
// SAFETY: ptr 指向有效载荷盒,只读字段。
|
||||
Some(unsafe { &*ptr }.immediate_stopped)
|
||||
}
|
||||
|
||||
/// 解析 this 为 Event 载荷盒共享引用:非 Event 对象或无载荷盒返回 None。
|
||||
///
|
||||
/// # 注意事项
|
||||
@@ -235,6 +285,40 @@ pub fn event_constructor<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
NativeResult::Ok(this_val)
|
||||
}
|
||||
|
||||
/// 建一个 Event 载荷盒对象(type 为 `type_str`),返回对象指针。
|
||||
///
|
||||
/// 供交付路径(消息事件 / 错误事件)建真实 Event 盒,使 `dispatchEvent` 品牌守卫
|
||||
/// 通过。盒的 `target` / `currentTarget` 初始 null、`eventPhase` 初始 0(派发时设);
|
||||
/// `[[Prototype]]` 指向 `Event.prototype`(`e instanceof Event` 成立)。
|
||||
///
|
||||
/// # 副作用
|
||||
/// - 新建一个 Event 盒对象(经 `alloc_object` 入对象表)。
|
||||
pub fn create_event_box<H: VmHost>(vm: &mut H, type_str: &str) -> *mut JsObject {
|
||||
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: false,
|
||||
cancelable: false,
|
||||
default_prevented: false,
|
||||
propagation_stopped: false,
|
||||
immediate_stopped: false,
|
||||
composed: false,
|
||||
}));
|
||||
let event_proto = JsValue::from_js_object(vm.builtin_proto(ProtoKind::EventProto));
|
||||
let obj = JsObject::new_empty(EMPTY_SHAPE_ID, event_proto);
|
||||
let ptr = vm.alloc_object(obj);
|
||||
// SAFETY: ptr 是本函数刚 alloc_object 的对象,存活且本段无别名;native_fn 存
|
||||
// 不透明 `Box<EventInner>` 指针,与 ArrayBuffer / RegExp 的类型化存储模式一致。
|
||||
let event_obj = unsafe { &mut *ptr };
|
||||
event_obj.type_tag = JsObject::OBJ_TYPE_EVENT;
|
||||
event_obj.set_native_fn(Some(unsafe { NativeFnPtr::from_raw(inner as *const ()) }));
|
||||
ptr
|
||||
}
|
||||
|
||||
/// `Event.prototype.type`:返回事件类型串。
|
||||
pub fn event_type_getter<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
let this_val = vm.reg(if args.is_empty() { 0 } else { args[0] });
|
||||
|
||||
@@ -0,0 +1,255 @@
|
||||
//! EventTarget 行为面:`addEventListener` / `removeEventListener` / `dispatchEvent`
|
||||
//! 三原型方法。
|
||||
//!
|
||||
//! 监听器注册表是 per-realm 弱引用结构(目标对象裸指针为键、不保活),由 `VmHost`
|
||||
//! 的 `et_register` / `et_unregister` / `et_lookup` 三方法操作,GC sweep 按 mark 位
|
||||
//! 剪枝、full_reset 清表。注册表值(`EventTargetState`)持监听器回调的 GC 对象边,
|
||||
//! mark 期经根枚举标活(存活目标的回调随目标存活)。`ListenerEntry` /
|
||||
//! `EventTargetState` 类型落 `oxide_runtime_api`(trait 所在层,本 crate 依赖之)。
|
||||
|
||||
use oxide_runtime_api::{ListenerEntry, NativeResult, VmHost};
|
||||
use oxide_types::value::JsValue;
|
||||
|
||||
use crate::event;
|
||||
|
||||
/// 解析 `addEventListener` / `removeEventListener` 的 options 参数:布尔(capture)
|
||||
/// 或对象(once / capture,passive / signal 忽略)。返回 (capture, once)。
|
||||
///
|
||||
/// # 边界与前提
|
||||
/// - options 对象属性经 `ordinary_get` 读(getter 副作用与异常原值传播)。
|
||||
/// - 缺失或非对象时取缺省(capture=false, once=false)。
|
||||
fn parse_event_options<H: VmHost>(vm: &mut H, args: &[u8]) -> Result<(bool, bool), JsValue> {
|
||||
let opt_val = if args.len() > 3 { vm.reg(args[3]) } else { JsValue::undefined() };
|
||||
if opt_val.is_bool() {
|
||||
return Ok((oxide_runtime_api::to_boolean(opt_val), false));
|
||||
}
|
||||
if !opt_val.is_object() {
|
||||
return Ok((false, false));
|
||||
}
|
||||
let opt_ptr = opt_val.as_js_object_ptr();
|
||||
// SAFETY: opt_val 已确认是对象值,借用即时消费。
|
||||
let opt_obj = unsafe { &*opt_ptr };
|
||||
let mut capture = false;
|
||||
let mut once = false;
|
||||
for (name, slot) in [("capture", &mut capture), ("once", &mut once)] {
|
||||
let si = vm.perm_intern(name);
|
||||
match vm.ordinary_get(opt_obj, si, opt_val) {
|
||||
Ok(v) => *slot = oxide_runtime_api::to_boolean(v),
|
||||
Err(_) => {
|
||||
if let Some(exc) = vm.take_uncaught_value() {
|
||||
return Err(exc);
|
||||
}
|
||||
return Err(crate::error::create_type_error(vm, "Event options property getter failed"));
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok((capture, once))
|
||||
}
|
||||
|
||||
/// `EventTarget.prototype.addEventListener(type, callback, options)`。
|
||||
///
|
||||
/// # 步骤
|
||||
/// 1. this 解析为目标对象(非对象抛 TypeError)。
|
||||
/// 2. type 经完整 ToString 转串、intern 得 si;空串抛 SyntaxError。
|
||||
/// 3. callback 须为函数(首版不支持对象形 handleEvent,非函数抛 TypeError)。
|
||||
/// 4. options 是布尔(capture)或对象(once / capture,passive / signal 忽略)。
|
||||
/// 5. 重复登记(同 type + callback + capture)为 no-op;否则追加进注册表。
|
||||
///
|
||||
/// # 副作用
|
||||
/// - 写注册表(`et_register`)。
|
||||
pub fn event_target_add_event_listener<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
let this_val = vm.reg(if args.is_empty() { 0 } else { args[0] });
|
||||
if !this_val.is_object() {
|
||||
return NativeResult::Err(crate::error::create_type_error(vm, "addEventListener called on non-object"));
|
||||
}
|
||||
let this_ptr = this_val.as_js_object_ptr();
|
||||
|
||||
// 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 event type to a string",
|
||||
));
|
||||
}
|
||||
};
|
||||
if type_str.is_empty() {
|
||||
return NativeResult::Err(crate::error::create_syntax_error(vm, "The provided type is empty"));
|
||||
}
|
||||
let type_si = vm.perm_intern(&type_str);
|
||||
|
||||
// callback 须为函数(首版不支持对象形 handleEvent)。
|
||||
let callback = if args.len() > 2 { vm.reg(args[2]) } else { JsValue::undefined() };
|
||||
if !crate::iterator::is_callable(callback) {
|
||||
return NativeResult::Err(crate::error::create_type_error(
|
||||
vm,
|
||||
"Event listener callback must be a function",
|
||||
));
|
||||
}
|
||||
|
||||
// options:布尔(capture)或对象(once / capture)。
|
||||
let (capture, once) = match parse_event_options(vm, args) {
|
||||
Ok(co) => co,
|
||||
Err(e) => return NativeResult::Err(e),
|
||||
};
|
||||
|
||||
// 重复登记(同 type + callback + capture)为 no-op;否则追加。
|
||||
let entry = ListenerEntry { type_si, callback, capture, once, is_attribute: false };
|
||||
let existing = vm.et_lookup(this_ptr);
|
||||
if existing.iter().any(|e| e.type_si == type_si && e.capture == capture && e.callback == callback) {
|
||||
return NativeResult::Ok(JsValue::undefined());
|
||||
}
|
||||
vm.et_register(this_ptr, entry);
|
||||
NativeResult::Ok(JsValue::undefined())
|
||||
}
|
||||
|
||||
/// `EventTarget.prototype.removeEventListener(type, callback, options)`。
|
||||
///
|
||||
/// # 步骤
|
||||
/// 1. this 解析为目标对象(非对象抛 TypeError)。
|
||||
/// 2. type 经完整 ToString 转串、intern 得 si。
|
||||
/// 3. 按同 type + callback + capture 查条目移除(不存在为 no-op)。
|
||||
///
|
||||
/// # 副作用
|
||||
/// - 写注册表(`et_unregister`)。
|
||||
pub fn event_target_remove_event_listener<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
let this_val = vm.reg(if args.is_empty() { 0 } else { args[0] });
|
||||
if !this_val.is_object() {
|
||||
return NativeResult::Err(crate::error::create_type_error(vm, "removeEventListener called on non-object"));
|
||||
}
|
||||
let this_ptr = this_val.as_js_object_ptr();
|
||||
|
||||
// type 经完整 ToString 转串(与 addEventListener 同一 intern 口径)。
|
||||
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 event type to a string",
|
||||
));
|
||||
}
|
||||
};
|
||||
let type_si = vm.perm_intern(&type_str);
|
||||
|
||||
let callback = if args.len() > 2 { vm.reg(args[2]) } else { JsValue::undefined() };
|
||||
let (capture, _) = match parse_event_options(vm, args) {
|
||||
Ok(co) => co,
|
||||
Err(e) => return NativeResult::Err(e),
|
||||
};
|
||||
vm.et_unregister(this_ptr, type_si, callback, capture);
|
||||
NativeResult::Ok(JsValue::undefined())
|
||||
}
|
||||
|
||||
/// `EventTarget.prototype.dispatchEvent(event)`:同步派发事件,返回
|
||||
/// `!event.defaultPrevented`。
|
||||
///
|
||||
/// # 步骤
|
||||
/// 1. this 解析为目标对象(非对象抛 TypeError)。
|
||||
/// 2. event 品牌守卫(须为 Event 系对象),设 target / currentTarget 为 this、
|
||||
/// eventPhase 为 AT_TARGET(2)。
|
||||
/// 3. 取事件类型串、intern 得 si,查注册表取本对象该类型的监听器快照。
|
||||
/// 4. 逐个监听器调用(GC 根纪律:调用前钉事件与回调进寄存器,调用后重读载荷指针);
|
||||
/// `once` 条目调用后移除;`stopImmediatePropagation` 截断循环。
|
||||
/// 5. 返回 `!event.defaultPrevented`。
|
||||
///
|
||||
/// # 边界与前提
|
||||
/// - 首版派发只在目标(无冒泡、无捕获阶段),监听器按登记序调用。
|
||||
/// - 监听器回调的异常原值上抛(不吞)。
|
||||
///
|
||||
/// # 副作用
|
||||
/// - 写事件载荷盒(target / currentTarget / eventPhase);移除 `once` 条目。
|
||||
pub fn event_target_dispatch_event<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
let this_val = vm.reg(if args.is_empty() { 0 } else { args[0] });
|
||||
if !this_val.is_object() {
|
||||
return NativeResult::Err(crate::error::create_type_error(vm, "dispatchEvent called on non-object"));
|
||||
}
|
||||
let this_ptr = this_val.as_js_object_ptr();
|
||||
|
||||
// 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"));
|
||||
}
|
||||
let event_ptr = event_val.as_js_object_ptr();
|
||||
if event_ptr.is_null() {
|
||||
return NativeResult::Err(crate::error::create_type_error(vm, "dispatchEvent requires an Event"));
|
||||
}
|
||||
// SAFETY: event_val 已确认是对象值,event_ptr 指向合法 JsObject。
|
||||
let event_obj = unsafe { &*event_ptr };
|
||||
if !event_obj.is_event_obj() {
|
||||
return NativeResult::Err(crate::error::create_type_error(vm, "dispatchEvent requires an Event"));
|
||||
}
|
||||
|
||||
// 设派发目标与阶段(AT_TARGET = 2)。
|
||||
// SAFETY: event_obj 存活,无 GC 窗口。
|
||||
unsafe {
|
||||
event::event_set_dispatch(&mut *event_ptr, this_val, 2);
|
||||
}
|
||||
|
||||
// 取事件类型串、intern 得 si,查注册表取监听器快照。
|
||||
let type_val = match event::event_type_value(event_obj) {
|
||||
Some(v) => v,
|
||||
None => return NativeResult::Ok(JsValue::bool(true)),
|
||||
};
|
||||
let type_text = match vm.lookup_str(type_val) {
|
||||
Some(t) => t,
|
||||
None => return NativeResult::Ok(JsValue::bool(true)),
|
||||
};
|
||||
let type_si = vm.perm_intern(&type_text);
|
||||
// 注册表按目标对象存全部监听器,此处按事件类型过滤(同型才派发)。
|
||||
let listeners: Vec<ListenerEntry> = vm
|
||||
.et_lookup(this_ptr)
|
||||
.into_iter()
|
||||
.filter(|e| e.type_si == type_si)
|
||||
.collect();
|
||||
if listeners.is_empty() {
|
||||
return NativeResult::Ok(JsValue::bool(true));
|
||||
}
|
||||
|
||||
// 逐个监听器调用(GC 根纪律:调用前钉事件与回调进寄存器,调用后重读载荷指针)。
|
||||
for entry in &listeners {
|
||||
// 钉事件对象与回调进返回寄存器(GC 根,防调用窗口内被回收)。
|
||||
vm.set_reg(0, event_val);
|
||||
vm.set_reg(1, entry.callback);
|
||||
// 调用监听器(this = 目标,实参 = 事件)。异常原值上抛。
|
||||
match vm.call_function_sync(entry.callback, this_val, &[event_val]) {
|
||||
Ok(_) => {}
|
||||
Err(_) => {
|
||||
if let Some(exc) = vm.take_uncaught_value() {
|
||||
return NativeResult::Err(exc);
|
||||
}
|
||||
return NativeResult::Err(crate::error::create_type_error(vm, "Event listener threw"));
|
||||
}
|
||||
}
|
||||
// 调用窗口后重读事件对象(窗口内可发生 GC,原地不搬移但重读守纪律)。
|
||||
let event_val = vm.reg(0);
|
||||
let event_ptr = event_val.as_js_object_ptr();
|
||||
// SAFETY: event_val 是 Event 对象(品牌守卫已过),存活。
|
||||
let event_obj = unsafe { &*event_ptr };
|
||||
// once 条目调用后移除。
|
||||
if entry.once {
|
||||
vm.et_unregister(this_ptr, entry.type_si, entry.callback, entry.capture);
|
||||
}
|
||||
// stopImmediatePropagation 截断循环。
|
||||
if event::event_immediate_stopped(event_obj) == Some(true) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// 返回 !event.defaultPrevented(末次调用窗口后重读载荷指针)。
|
||||
let event_val = vm.reg(0);
|
||||
let event_ptr = event_val.as_js_object_ptr();
|
||||
// SAFETY: event_val 是 Event 对象(品牌守卫已过),存活。
|
||||
let event_obj = unsafe { &*event_ptr };
|
||||
let default_prevented = event::event_default_prevented(event_obj).unwrap_or(false);
|
||||
NativeResult::Ok(JsValue::bool(!default_prevented))
|
||||
}
|
||||
@@ -36,6 +36,9 @@ pub mod error;
|
||||
pub mod eval;
|
||||
/// Event 基类实现(构造器、原型 getter 与传播方法,载荷盒与 GC 三自由函数族)。
|
||||
pub mod event;
|
||||
/// EventTarget 实现(addEventListener / removeEventListener / dispatchEvent 三原型
|
||||
/// 方法与 per-realm 弱引用监听器注册表)。
|
||||
pub mod event_target;
|
||||
/// Function 内置对象实现(call/apply/bind/toString)。
|
||||
pub mod function;
|
||||
/// 全局函数模块(URI 编解码与 Annex B 的 escape/unescape)。
|
||||
|
||||
Reference in New Issue
Block a user