672 lines
34 KiB
Rust
672 lines
34 KiB
Rust
//! 托管事件与订阅的传播实现。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![],
|
||
})
|
||
}
|