Files
endorphin-tessera-gui/src/runtime/event_dispatch.rs
T

672 lines
34 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//! 托管事件与订阅的传播实现。Connection 持有回调引用,Event 保存目标、阶段与停止传播状态。
//! 分发沿出现关系执行 capture → target → bubble,记录默认行为阻止状态;只有同一 VM 中的回调可以接触托管对象。
//!
//! bootstrap 调用 install 安装 Event/Subscription 控制方法;ui_bridge::calls 和 graphics/scene3d 指针适配调用 dispatch。
//! dispatch 只快照路线并生成驱动,真正回调执行由 VM 调用 Delivery,再通过 Invoke 接回源处理器。
//! vm_sequence::build 使整次传播共用同一执行预算;每次具体 Mount 是独立传播上下文,不从共享业务树猜父链。
use crate::{block, mount::Mount, ui_bridge::attach_desktop_type};
use endor_core::{
ContextNativeFunction, NativeCallable, NativeContext, NativeResult, ObjectPayload, ObjectRef,
Value, Vm, VmError, WeakObjectRef,
};
// 一次分发最多安排 10000 个处理器,另由共享 VM 驱动约束全部回调的总执行预算。
const MAX_DELIVERIES: usize = 10_000;
/// 订阅检查、事件构造与传播分支共用的参数错误构造器,源诊断位置由外层 VM 调用栈提供。
fn invalid(message: &'static str) -> VmError {
VmError::InvalidArgument(message)
}
/// ui_bridge::calls 安装 on/onClick 时调用,检查零参或单 Event 回调能力,不在此执行用户函数。
pub fn validate_handler(
store: &endor_core::ObjectStore,
function: ObjectRef,
) -> Result<(), VmError> {
if !store.object(function)?.payload().is_function() {
// 普通 Object 即使有 call 属性也不能作为这里的 Function 注册,避免实际分派才暴露类型错误。
return Err(invalid("event handler must be a Function"));
}
// 元数据必须明确存在;不能把未知 arity 当作“随便传一个 Event 总能工作”。
let arity = store
.call_arity(function)?
.ok_or(invalid("event handler requires positional metadata"))?;
// 允许无参便捷回调或单 Event 回调;实际递送时优先传 Event(若接受一参),否则零参调用。
if !arity.accepts(0) && !arity.accepts(1) {
return Err(invalid(
"event handler must accept zero arguments or one Event",
));
}
Ok(())
}
#[derive(Debug, Clone, Default)]
/// 本次传播的托管事件状态,包含目标、当前阶段和阻止默认/停止传播控制。
pub struct Event {
// 弱引用本轮 guard,检测同一 Event 是否正在分发;驱动释放后 guard 消失,事件可再次使用。
dispatch: Option<WeakObjectRef>,
// 当前传播组,一般对应路径中的一个出现/阶段;目标捕获与目标非捕获共用一组。
group: usize,
// stopPropagation 记下当前组,只阻止后续其他组;同组余下处理器仍执行。
stopped_at: Option<usize>,
// stopImmediatePropagation 全局跳过后续 Delivery,连当前组剩余处理器也停止。
immediate: bool,
// preventDefault 仅记录默认行为是否被阻止;本模块不在这里实现具体的系统默认动作。
prevented: bool,
}
impl ObjectPayload for Event {
/// 声明此 payload 的赋值遵循引用身份语义,避免把内部宿主状态当作可复制值。
fn reference_assignment_only(&self) -> bool {
true
}
/// 提供受检查的 Any 下转型入口,让宿主按具体 payload 类型读取数据。
fn as_any(&self) -> &dyn std::any::Any {
self
}
/// 返回运行时诊断/类型检查使用的稳定 payload 名称。
fn kind_name(&self) -> &'static str {
"tessera-event"
}
/// 声明此 payload 是否允许使用托管属性存储,具体字段仍由类型与调用检查约束。
fn allows_attributes(&self) -> bool {
true
}
}
#[derive(Debug, Clone)]
/// 实际拥有回调引用的托管连接,挂在目标的订阅关系中参与 GC。
pub struct Connection {
// Block 的 subscriptions 强持 Connection,Connection 再强持 handler;取消时将这一强边置空。
pub handler: Option<ObjectRef>,
// 与 Event.kind 比较的精确事件字符串,不对事件名称做前缀或真值匹配。
pub kind: String,
// 区分捕获和非捕获队列;目标阶段两种队列仍共享一个传播组。
pub capture: bool,
}
impl ObjectPayload for Connection {
/// 声明此 payload 的赋值遵循引用身份语义,避免把内部宿主状态当作可复制值。
fn reference_assignment_only(&self) -> bool {
true
}
/// 提供受检查的 Any 下转型入口,让宿主按具体 payload 类型读取数据。
fn as_any(&self) -> &dyn std::any::Any {
self
}
/// 返回运行时诊断/类型检查使用的稳定 payload 名称。
fn kind_name(&self) -> &'static str {
"tessera-event-connection"
}
/// 枚举本对象真正拥有的托管强引用,供 Aster 引用追踪和循环回收使用。
fn trace(&self, visit: &mut dyn FnMut(ObjectRef)) {
if let Some(handler) = self.handler {
// Aster GC 访问者只收到当前仍连接的处理器;None 不再保活旧回调及它捕获的业务对象。
visit(handler);
}
}
}
#[derive(Debug)]
/// 源代码可保留的订阅能力包装,用于取消对应连接。
pub struct Subscription {
// 事件 Connection 或属性 Binding 的弱引用;用户长期保留 token 不应反向保活目标和计算闭包。
pub connection: WeakObjectRef,
}
impl ObjectPayload for Subscription {
/// 声明此 payload 的赋值遵循引用身份语义,避免把内部宿主状态当作可复制值。
fn reference_assignment_only(&self) -> bool {
true
}
/// 提供受检查的 Any 下转型入口,让宿主按具体 payload 类型读取数据。
fn as_any(&self) -> &dyn std::any::Any {
self
}
/// 返回运行时诊断/类型检查使用的稳定 payload 名称。
fn kind_name(&self) -> &'static str {
"tessera-subscription"
}
}
#[derive(Debug, Clone, Copy)]
/// install 的名义方法表选用的操作标签;TypedMethod 提供类型契约,Method 只执行对应状态变化/查询。
enum Method {
Stop,
StopImmediate,
Prevent,
Prevented,
Disconnect,
Connected,
}
impl NativeCallable for Method {
/// 事件与订阅查询/控制方法无显式参数,只接受隐式 Event 或 Subscription 接收者。
fn arity(&self) -> Option<endor_core::CallArity> {
Some(endor_core::CallArity::exact(1))
}
/// TypedMethod::invoke 转交,按 receiver payload 查询/取消订阅,或更新当前事件传播控制标志。
fn invoke(&self, cx: &mut NativeContext<'_>, args: &[Value]) -> Result<NativeResult, VmError> {
// 方法接收者仍在 args[0];不像 UiBridge 的 TypeMethod,这个包装没有提前把 owner 剥出来。
let [Value::Object(owner)] = args else {
return Err(invalid("event method receiver"));
};
// 取消/查询订阅和传播控制使用不同 payload,先按方法标签分流再进行受检查的向下转型。
let result = match self {
Self::Disconnect | Self::Connected => {
// Subscription 不直接拥有 Connection,读弱身份后在当前 NativeContext 中临时升级。
let weak = cx
.store()
.payload::<Subscription>(*owner, "Subscription")?
.connection;
// 已死亡连接正常得到 None,isConnected=false/disconnect 无动作,而不是新建连接。
let connection = cx.upgrade(weak)?;
// Subscription 同时包装事件 Connection 和属性 Binding;按 payload 类型清 handler 或 compute。
let connected = if let Some(connection) = connection {
if cx
.store()
.object(connection)?
.payload()
.as_any()
.is::<crate::binding::Binding>()
{
// 属性绑定和事件订阅复用同一种取消令牌,但 Binding 的真实强边字段是 compute。
let mut data = cx
.store()
.payload::<crate::binding::Binding>(connection, "Binding")?
.clone();
// 返回查询的是操作前连接状态;disconnect 自己最后返回 Unit,不暴露该布尔。
let connected = data.compute.is_some();
// 取消切断真实强拥有边,外部仍可保留 token,但 token 弱引用本身不保活回调或目标。
if matches!(self, Self::Disconnect) {
data.compute = None;
cx.replace_payload(connection, data)?;
}
connected
} else {
// 其余合法令牌包装事件 Connection,不用 Binding 的字段布局读取它。
let mut data = cx
.store()
.payload::<Connection>(connection, "Connection")?
.clone();
// handler=None 就是已断开,保留 Connection 对象本身不代表回调仍可执行。
let connected = data.handler.is_some();
if matches!(self, Self::Disconnect) {
data.handler = None;
cx.replace_payload(connection, data)?;
}
connected
}
} else {
// 弱目标已经死亡与主动断开都属于不再连接,查询不因此报过期对象错误。
false
};
if matches!(self, Self::Disconnect) {
// API 约定取消返回 Unit,只有 isConnected 返回前面采样到的布尔。
Value::Nil
} else {
Value::Bool(connected)
}
}
_ => {
// 传播标志属于 Event payload,复制后经 replace_payload 写回以保持 store 的写屏障。
let mut event = cx.store().payload::<Event>(*owner, "Event")?.clone();
if matches!(self, Self::Prevented) {
// 只读查询不替换 payload,不制造一次虚假的事件变更。
Value::Bool(event.prevented)
} else {
match self {
Self::Stop | Self::StopImmediate => {
// Stop 保留当前组的其余回调,Immediate 额外跳过同组后续 Delivery。
event.stopped_at = Some(event.group);
event.immediate |= matches!(self, Self::StopImmediate);
}
// 只记录阻止默认,不在本模块额外调用或取消某个 OS 默认动作。
Self::Prevent => event.prevented = true,
_ => unreachable!(),
}
cx.replace_payload(*owner, event)?;
Value::Nil
}
}
};
Ok(NativeResult::Return(result))
}
}
#[derive(Debug)]
/// 安装到托管类型上的原生构造调用适配器。
pub struct Constructor {
// bootstrap 在 Event 类型/签名都建立后补入,VM 据此先检查 Type/kind/字段外壳。
pub signature: Option<endor_core::CallSignature>,
}
impl NativeCallable for Constructor {
/// 给出参数与返回值的托管类型契约;实际操作在 invoke 中执行。
fn signature(&self) -> Option<endor_core::CallSignature> {
self.signature.clone()
}
/// 至少接收隐式请求 Type 和事件 kind,额外参数留给源子类声明字段。
fn arity(&self) -> Option<endor_core::CallArity> {
Some(endor_core::CallArity::range(2, usize::MAX))
}
/// VM 执行 Event/源子类 NativeTyped 构造器时调用,验证字段前不先分配业务 Event,避免非法字段副作用。
fn invoke(&self, cx: &mut NativeContext<'_>, args: &[Value]) -> Result<NativeResult, VmError> {
// definition 是实际请求子类,kind 是唯一原生构造参数,其余后缀对应声明字段。
let [Value::Object(definition), Value::Text(kind), values @ ..] = args else {
return Err(invalid("Event requires an event kind"));
};
// 事件子类构造先验证额外字段参数数量与注解,再创建默认事件并逐字段初始化。
let fields = cx
.store()
.payload::<endor_core::EndorTypeObject>(*definition, "Type")?
.fields()
.to_vec();
if fields.len() != values.len() {
// 不用 zip 静默截断少/多实参,完整类型字段数量必须与输入后缀一致。
return Err(invalid("incorrect Event field arguments"));
}
cx.store()
.check_class_field_arguments(*definition, values)?;
// 数量和类型都检查后才创建真实子类 Event;NativeContext 会清理未输出的临时根。
let event = new_event(cx, *definition, kind)?;
for (field, value) in fields.iter().zip(values) {
// 用普通属性写入口初始化字段,保持对象引用和依赖记录受 VM 统一管理。
cx.set_attr(event, &field.name, value.clone())?;
}
Ok(NativeResult::Return(Value::Object(event)))
}
}
/// Event 构造器、click 和图形/三维指针适配调用,建立尚未分派的带名义身份事件;目标由 dispatch 填写。
pub fn new_event(
cx: &mut NativeContext<'_>,
definition: ObjectRef,
kind: &str,
) -> Result<ObjectRef, VmError> {
if kind.is_empty() || kind.len() > 128 {
return Err(invalid("event kind must contain 1..128 UTF-8 bytes"));
}
// payload 默认标志与普通属性分开:停止/默认阻止在 payload,目标/阶段信息通过托管属性公开。
let event = cx.allocate(Event::default())?;
cx.attach_type(event, definition)?;
cx.set_attr(event, "kind", Value::Text(kind.into()))?;
// 新事件尚未递送,目标、出现与 hit 都为空;kind 保留输入种类,phase 初始 none。
for name in ["target", "mount", "currentTarget", "currentMount", "hit"] {
// 初始化明确的 nil,处理器读取未分派事件时不会因字段缺失而失败。
cx.set_attr(event, name, Value::Nil)?;
}
cx.set_attr(event, "phase", Value::Text("none".into()))?;
Ok(event)
}
#[derive(Debug)]
/// install 挂到 Event/Subscription 类型上的签名包装;实例 receiver 由 VM 方法绑定机制传入。
struct TypedMethod {
// 无状态操作标签,实际事件/订阅状态都在本次传入 receiver 的托管 payload 中。
method: Method,
signature: endor_core::CallSignature,
}
impl NativeCallable for TypedMethod {
/// 给出参数与返回值的托管类型契约;实际操作在 invoke 中执行。
fn signature(&self) -> Option<endor_core::CallSignature> {
Some(self.signature.clone())
}
/// 把已声明签名的调用交给内部 Method,复用事件/订阅控制实现,不创建第二套状态。
fn invoke(&self, cx: &mut NativeContext<'_>, args: &[Value]) -> Result<NativeResult, VmError> {
self.method.invoke(cx, args)
}
}
/// ui_bridge::bootstrap 完成 Event/Subscription/Function 类型建立后调用,为两类对象安装控制与查询方法。
pub fn install(
vm: &mut Vm,
roots: &mut Vec<ObjectRef>,
event_type: ObjectRef,
token_type: ObjectRef,
function_type: ObjectRef,
) -> Result<(), VmError> {
for (definition, name, method) in [
(event_type, "stopPropagation", Method::Stop),
(
event_type,
"stopImmediatePropagation",
Method::StopImmediate,
),
(event_type, "preventDefault", Method::Prevent),
(event_type, "isDefaultPrevented", Method::Prevented),
(token_type, "disconnect", Method::Disconnect),
(token_type, "isConnected", Method::Connected),
] {
// isDefaultPrevented/isConnected 返回 Bool 且只读;控制传播和取消方法返回 Unit 并声明 Mutating。
let query = matches!(method, Method::Prevented | Method::Connected);
// callable 接一个隐式名义 receiver,查询返回 Bool/ReadOnly,修改返回 Unit/Mutating。
let function = vm.allocate(ContextNativeFunction::new(
name,
TypedMethod {
method,
signature: endor_core::CallSignature::exact(
vec![endor_core::AnnotationType::Nominal(definition)],
if query {
// isDefaultPrevented/isConnected 的返回类型是布尔,不把“无连接”写成 nil。
endor_core::AnnotationType::Bool
} else {
// 控制/取消操作没有业务返回值,VM 用 Unit 检查而非任意 Any。
endor_core::AnnotationType::Unit
},
// 同一个标签同时决定调用效果,不能让断开连接冒充只读查询。
if query {
endor_core::CallEffect::ReadOnly
} else {
endor_core::CallEffect::Mutating
},
),
},
vec![],
))?;
roots.push(function);
vm.attach_type(function, function_type)?;
vm.define_method(definition, name, function)?;
}
Ok(())
}
#[derive(Debug)]
/// 本次递送选择的对象方法或显式订阅处理器。
enum Handler {
// 捕获 Connection 对象而非函数快照,前面回调 disconnect 后本项会重新观察 handler=None。
Token(ObjectRef),
// onClick 的旧式单处理器是本轮函数快照,不具有独立 Connection 令牌。
Legacy(ObjectRef),
// onEvent 从实际类型的方法表在轮到本项时解析,以保留继承/源覆盖。
ObjectMethod,
}
#[derive(Debug)]
/// 事件传播过程中将要调用的一项处理器及其阶段/目标上下文。
struct Delivery {
// 本轮事件由 captures 显式保活;payload 中的数值句柄本身不构成 VM 强边。
event: ObjectRef,
// 当前路径项的业务对象,不一定是最终 target;祖先捕获/冒泡也用相同适配器。
owner: ObjectRef,
// 该路径项的具体出现,页面上下文无 Mount 时为 None。
mount: Option<ObjectRef>,
// stopPropagation 的边界编号;目标捕获与非捕获共享组,其他出现/阶段递增。
group: usize,
// 对外公开的 capture/target/bubble 字符串,与 group 的内部停止规则分别保存。
phase: &'static str,
// 处理器的解析策略,决定是否需要临时取连接或补 receiver。
handler: Handler,
}
impl NativeCallable for Delivery {
/// 单次递送步骤的 Event/handler/阶段已保存在适配器中,执行时不再接收参数。
fn arity(&self) -> Option<endor_core::CallArity> {
Some(endor_core::CallArity::exact(0))
}
/// vm_sequence 驱动轮到本项时调用,重查停止/连接状态,再用 Invoke 交给同预算的真实处理器。
fn invoke(&self, cx: &mut NativeContext<'_>, _: &[Value]) -> Result<NativeResult, VmError> {
// 不是快照整个传播开始时的标志,前一个回调产生的 stop 必须立刻影响当前项。
let mut event = cx.store().payload::<Event>(self.event, "Event")?.clone();
// 真正轮到处理器时重新读取传播状态,之前回调的 stop 操作立即影响后续项,无需重建序列。
if event.immediate || event.stopped_at.is_some_and(|group| group != self.group) {
// 跳过此项仍返回正常 Nil,让驱动继续推进到其他跳过项和最终 Finish 清理。
return Ok(NativeResult::Return(Value::Nil));
}
// 函数与参数前缀一起解析:对象协议需 receiver,独立处理器最初没有显式参数。
let (function, mut arguments) = match self.handler {
// 序列只捕获 Connection,不快照 handler;前面处理器取消它后,这里看到 None 并跳过。
Handler::Token(token) => {
let Some(handler) = cx
.store()
.payload::<Connection>(token, "Connection")?
.handler
else {
// 已断开连接不再调用旧 handler,即使驱动已经为它分配过 Delivery。
return Ok(NativeResult::Return(Value::Nil));
};
(handler, vec![])
}
Handler::Legacy(function) => (function, vec![]),
// 对象协议在执行到该项时解析实际类型的 onEvent;显式 receiver 先放入实参向量。
Handler::ObjectMethod => {
// 动态源子类的 onEvent 可以不同于基类默认空实现,不能在安装时固定函数。
let Some(definition) = cx.store().type_of(self.owner)? else {
return Err(invalid("untyped event target"));
};
// 按类型继承查方法,缺少协议方法是契约错误,不降级忽略对象事件能力。
let function = cx
.store()
.lookup_method(definition, "onEvent")?
.ok_or(invalid("Block has no onEvent method"))?;
(function, vec![Value::Object(self.owner)])
}
};
// 调用前更新 currentTarget/currentMount/phase,处理器观察到的是当前路径项,不会误用最终目标。
event.group = self.group;
cx.replace_payload(self.event, event)?;
cx.set_attr(self.event, "phase", Value::Text(self.phase.into()))?;
cx.set_attr(self.event, "currentTarget", Value::Object(self.owner))?;
cx.set_attr(
self.event,
"currentMount",
self.mount.map(Value::Object).unwrap_or(Value::Nil),
)?;
// 对象方法总是携带 Event;显式订阅/旧式回调按 arity 决定是否传 Event。
if matches!(self.handler, Handler::ObjectMethod)
|| cx
.store()
.call_arity(function)?
.is_some_and(|arity| arity.accepts(1))
{
// 单 Event 回调优先;仅真正不接受一参但接受零参的便捷处理器才不追加事件。
arguments.push(Value::Object(self.event));
}
Ok(NativeResult::Invoke {
function,
arguments,
})
}
}
#[derive(Debug)]
/// 正常传播结束后清理当前目标、阶段和 guard 标记,返回仍保留最终控制状态的 Event。
struct Finish(ObjectRef);
impl NativeCallable for Finish {
/// 派发结束步骤已持有 Event 引用,无需显式输入。
fn arity(&self) -> Option<endor_core::CallArity> {
Some(endor_core::CallArity::exact(0))
}
/// vm_sequence 在所有 Delivery 之后调用,清理当前阶段/守卫并返回最终 Event,供调用方读取默认阻止状态。
fn invoke(&self, cx: &mut NativeContext<'_>, _: &[Value]) -> Result<NativeResult, VmError> {
// 保留事件最终 prevented 等标志,不用 Event::default 抹掉这一轮处理结果。
let mut event = cx.store().payload::<Event>(self.0, "Event")?.clone();
// 正常序列结束清递送标记与当前上下文,保留原始 target/mount 和 prevented 供调用方读取。
event.dispatch = None;
cx.replace_payload(self.0, event)?;
cx.set_attr(self.0, "phase", Value::Text("none".into()))?;
cx.set_attr(self.0, "currentTarget", Value::Nil)?;
cx.set_attr(self.0, "currentMount", Value::Nil)?;
Ok(NativeResult::Return(Value::Object(self.0)))
}
}
/// ui_bridge::calls 的 click/dispatch 及指针适配调用,构造本轮路线/处理顺序,再返回 VM 驱动续调。
/// 无 Mount 的代码事件只访问 owner,不猜测共享对象位于哪个窗口;新订阅不加入本轮,取消可立即跳过旧项。
pub fn dispatch(
cx: &mut NativeContext<'_>,
owner: ObjectRef,
event_ref: ObjectRef,
mount: Option<ObjectRef>,
desktop: ObjectRef,
) -> Result<NativeResult, VmError> {
// 先检查真实 Event payload,复制是为了稍后重置传播标志并经写屏障写回。
let mut event = cx.store().payload::<Event>(event_ref, "Event")?.clone();
// 只有 guard 仍存活才禁止重入;此前分发失败但 guard 已释放时,不让旧弱身份永久锁住 Event。
if let Some(weak) = event.dispatch
&& cx.upgrade(weak)?.is_some()
{
// 同 Event 不可重入分发;若上轮驱动已因失败释放,弱 guard 失效则允许重新使用。
return Err(invalid("Event is already being dispatched"));
}
// kind 可以是源程序改过的普通属性,所以每次 dispatch 都重新验证实际字符串和长度。
let Value::Text(kind) = cx.store().get_attr(event_ref, "kind")? else {
return Err(invalid("Event kind must be text"));
};
if kind.is_empty() || kind.len() > 128 {
return Err(invalid("invalid event kind"));
}
// 路线由目标向外保存。无 Mount 的代码事件只访问目标对象,不猜测共享 Block 属于哪个出现。
let mut route = vec![(owner, mount)];
if let Some(mount) = mount {
// 克隆出现 payload 后沿其 parent/page 弱边构建路径,和 Block.children 的业务组成分开。
let target = cx.store().payload::<Mount>(mount, "Mount")?.clone();
// 传入 Mount 必须仍挂接且确实展示 owner;同一 Block 的另一个出现不能替代该上下文。
if !target.mounted || target.block != owner {
return Err(invalid("event context does not belong to this Block"));
}
// parent 是下一祖先弱身份,目标先放进 seen,防止父链回到自身。
let mut parent = target.parent;
// 沿弱 parent 链构建本轮路径快照,检查死亡祖先、脱离与环;不通过 Block 业务孩子关系反推祖先。
let mut seen = std::collections::HashSet::from([mount]);
while let Some(weak) = parent {
// NativeContext 跟踪升级根,驱动 captures 将保留真正需要的祖先直到本轮执行结束。
let ancestor = cx
.upgrade(weak)?
.ok_or(invalid("event route has expired"))?;
if !seen.insert(ancestor) {
// 循环父链不能让宿主构造无限驱动,遇重复身份立即拒绝。
return Err(invalid("cyclic event route"));
}
// 祖先存在还不够,必须仍挂载,不能把历史出现关系当成当前传播路径。
let data = cx.store().payload::<Mount>(ancestor, "Mount")?;
if !data.mounted {
return Err(invalid("event route is detached"));
}
route.push((data.block, Some(ancestor)));
parent = data.parent;
}
// 出现祖先之外再加入 Page 上下文,页面本身无 currentMount;弱页已失效则拒绝该旧路线。
if let Some(weak) = target.page {
// 页面属于传播最外层语义目标,但没有它自己的内容 Mount,所以 route 中配 None。
let page = cx.upgrade(weak)?.ok_or(invalid("event Page has expired"))?;
route.push((page, None));
}
}
// 所有递送和 Finish 显式捕获 guard,Event 只弱引用它;驱动完成/失败释放后 guard 自动结束本轮资格。
let guard = cx.new_object()?;
// 按最终递送顺序存临时 callable,vm_sequence::build 随后让它们成为驱动常量强边。
let mut calls = Vec::new();
// 每个出现/阶段有自己的停止边界;同业务对象多次出现也不能共用一个 group。
let mut group = 0;
// 先从最外层向目标递送捕获,再从目标向外递送非捕获;目标两次遍历都标记 phase=target。
let traversal = route
.iter()
.enumerate()
.rev()
.map(|(index, entry)| (index, entry, true))
.chain(
route
.iter()
.enumerate()
.map(|(index, entry)| (index, entry, false)),
);
for (index, &(block, current_mount), capture) in traversal {
// block 可能是共享转发别名,解析到实际 Data 后读取本轮回调/订阅列表。
let (_, data) = block::resolve(cx.store(), block)?;
// 此路径项的候选处理顺序,稍后逐项包装成独立 Delivery。
let mut handlers = Vec::new();
// 本轮先快照符合事件 kind 与 capture 的订阅顺序;回调期间新注册的连接不进入当前分发。
for token in &data.subscriptions {
// subscriptions 保存的是 Connection 而非用户持有的 Subscription 令牌。
let token_data = cx.store().payload::<Connection>(*token, "Connection")?;
if token_data.handler.is_some()
&& token_data.kind == kind
&& token_data.capture == capture
{
// 这里只快照连接身份,handler 的存活在真正执行 Delivery 时再次读取。
handlers.push(Handler::Token(*token));
}
}
if !capture {
// 非捕获阶段 onEvent 先执行,再按订阅顺序,最后兼容旧 click 回调。
handlers.insert(0, Handler::ObjectMethod);
if kind == "click"
&& let Some(handler) = data.click
{
// 旧式 onClick 仅匹配 click 且只在非捕获阶段执行,不变成所有事件通用监听器。
handlers.push(Handler::Legacy(handler));
}
}
for handler in handlers {
if calls.len() >= MAX_DELIVERIES {
// 总量包括所有祖先和阶段,不能让每个节点各用 10000 额度放大一次分发。
return Err(invalid("event delivery budget exceeded"));
}
// 每一项显式保活本轮 Event、guard、目标和可选 Mount/连接;不能仅靠 Rust ObjectRef 值当 GC 根。
let mut captures = vec![event_ref, guard, block];
if let Some(mount) = current_mount {
// captures 保留这一具体出现,不能只保留业务 Block 丢掉几何/祖先上下文。
captures.push(mount);
}
match handler {
// Connection 或旧函数必须有真实托管强边,Rust 枚举里的句柄数值不保活它们。
Handler::Token(token) | Handler::Legacy(token) => captures.push(token),
// 对象方法在轮到时查找,owner 已在 captures,不提前强持某个可能被覆盖的函数。
Handler::ObjectMethod => {}
}
// 分配一个零参步骤;真实用户函数的 receiver/Event 参数由 Delivery::invoke 再决定。
let function = cx.allocate(ContextNativeFunction::new(
"event delivery",
Delivery {
event: event_ref,
owner: block,
mount: current_mount,
group,
phase: if index == 0 {
// 路线 index=0 始终是最终目标,目标捕获/非捕获都对外显示 target。
"target"
} else if capture {
// 从外层向目标的祖先遍历显示 capture。
"capture"
} else {
// 从目标向外层的祖先遍历显示 bubble。
"bubble"
},
handler,
},
captures,
))?;
attach_desktop_type(cx, function, "Function", desktop)?;
calls.push(function);
}
// 目标捕获后不递增 group,使 stopPropagation 仍允许目标非捕获处理器;其他路径/阶段则隔离为新组。
if index != 0 || !capture {
group += 1;
}
}
// Finish 也捕获 guard,保证即使没有任何用户监听器,整次驱动仍维持活跃分发资格到结束。
let finish = cx.allocate(ContextNativeFunction::new(
"event finish",
Finish(event_ref),
vec![event_ref, guard],
))?;
// 无论传播是否被停止,Finish 都作为序列最后一项执行,它不受 Delivery 的 stopped 判定影响。
calls.push(finish);
// 驱动常量池拥有完整顺序,全部处理器共用调用者 VM 的预算,不能逐个 vm.call 重置额度。
let driver = crate::vm_sequence::build(cx, calls, "<event dispatch>", desktop)?;
event.dispatch = Some(cx.store().downgrade(guard)?);
event.group = 0;
// 再次使用 Event 时清上一轮停止和默认阻止标志;目标与 Mount 属性也被本轮参数覆盖。
event.stopped_at = None;
event.immediate = false;
event.prevented = false;
cx.replace_payload(event_ref, event)?;
cx.set_attr(event_ref, "target", Value::Object(owner))?;
cx.set_attr(
event_ref,
"mount",
mount.map(Value::Object).unwrap_or(Value::Nil),
)?;
// 仅委托驱动,不在本 Rust 调用栈递归执行用户回调;VM 后续负责返回最终 Event。
Ok(NativeResult::Invoke {
function: driver,
arguments: vec![],
})
}