A2UI 用户手势受限函数机制解析:用 Action Context 阻断渲染期函数自动执行

发布时间:2026/9/15 6:27:12
A2UI 用户手势受限函数机制解析:用 Action Context 阻断渲染期函数自动执行 A2UI 用户手势受限函数机制解析用 Action Context 阻断渲染期函数自动执行【免费下载链接】a2ui项目地址: https://gitcode.com/GitHub_Trending/a2/a2uiA2UI 是一套声明式 JSON UI 协议渲染器Renderer的数据上下文引擎会在求值包含函数调用的表达式时自动执行函数。本提案specification/proposals/user_initiated_functions.md针对openUrl等具有副作用导航、剪贴板、修改系统状态的客户端目录函数设计了一套Action Context Enforcement with Event ClassificationAction 执行作用域 激活事件分类约束机制通过目录函数元数据requiresUserActivation与渲染器运行时的isExecutingAction/actionIntent状态保证此类函数只在用户真实意图的激活事件点击、触摸、提交作用域内执行从根源上阻断布局渲染、字符串插值、响应式状态更新与被动事件中的自动调用。读完本文你将理解该提案的问题模型、完整协议设计、四个平台Web/Flutter/Android/iOS的实现方案以及它在当前仓库 v1.0 规范中的实际落地状态。1. 背景A2UI 声明式协议下的函数自动调用问题A2UI 客户端目录函数如openUrl是在渲染器上执行动作的。当前机制下只要表达式引擎在求值一个包含函数调用的表达式该函数就会被执行。与具备内联代码执行能力的 JavaScript 方案不同A2UI 是纯声明式 JSON 协议LLM 或服务端生成的 Payload 无法直接执行任意脚本、也无法派发合成.click()DOM 事件——但这并不妨碍函数在非用户主动触发的场景下被自动执行提案归纳出三类主要向量1.1 初始 Surface 渲染自动触发服务端或 LLM 的 Payload 可以在组件属性中携带函数调用而该属性在初始布局构建阶段即被求值。一旦 Surface 渲染完成openUrl就会在没有任何用户交互的情况下立即执行。1.2 动态属性插值formatString滥用表达式引擎在组件渲染或模板列表迭代时求值字符串插值表达式。如果 Payload 包含如下文本{ component: Text, text: ${openUrl(https://example.com/phish)} }渲染器会将该openUrl()作为字符串格式化的一部分进行求值从而在渲染过程中未经用户同意自动打开外部浏览器窗口。1.3 响应式数据模型重求值当后台数据同步或状态更新修改了DataContext时响应式依赖会触发绑定表达式的重新求值任何绑定到状态更新的函数调用都会自动执行。1.4 安全与用户体验影响钓鱼与非法跳转用户未点击任何链接或按钮就可能被导航到外部网站或被深度链接进原生 App。弹窗拦截冲突浏览器会拦截非用户手势发起的window.open导致运行时错误或静默失败。用户失控带副作用的行为导航、复制数据、修改系统状态未被明确发起违反了副作用动作必须由用户意图显式发起的核心要求。2. 方案权衡Pros Cons2.1 优势自动调用防护阻止布局渲染、动态属性插值、后台状态更新以及被动输入事件onBlurAction、onChangeAction期间触发窗口/标签页导航或副作用。目录兼容性不需要改动目录定义中的组件元数据无需向组件 Schema 添加userInteractionLevel交互注解。标准组件绑定自定义组件作者继续编写标准事件处理器Lit 中click${props.action}、React 中onClick{props.action}、Flutter 中onPressed: props.action由 Binder 自动包裹回调以设置 Action Execution Scope。协议统一处理避免按protocolVersion 1.0与 1.0分流的渲染器版本判断跨所有协议版本统一生效。跨平台一致性在 Web、Flutter、Jetpack Compose、SwiftUI 上复用各平台原生事件原语。2.2 代价与权衡可信事件检查要求渲染器必须在 action binder 内检查原生事件类型click/touchend对比blur/input并验证event.isTrusted否则脚本通过.dispatchEvent()或未受控定时器可以程序化地打开激活 Action 上下文。3. 核心方案Action 上下文强制与事件分类方案由两部分组成在目录函数定义上增加requiresUserActivation: boolean如openUrl为true框架在渲染器层强制实施Action Execution ScopeAction 执行作用域与 Activation Event Classification激活事件分类。当函数定义设置requiresUserActivation: true时A2UI 框架保证该函数只在某个有意的用户激活 Action如 click、tap、submit的执行作用域内被派发时执行。布局渲染、动态属性插值、响应式模型更新、被动输入处理器onBlurAction、onChangeAction中的非发起式调用都会被渲染器引擎拦截并拒绝。3.1 目录函数元数据requiresUserActivation目录函数定义中requiresUserActivation声明函数在调用时是否需要用户激活上下文{ functions: { openUrl: { type: object, description: Opens the specified URL in a browser or handler. Requires user activation., returnType: void, requiresUserActivation: true, properties: { call: {const: openUrl}, args: { type: object, properties: { url: {type: string, format: uri} }, required: [url] } } } } }3.2 运行时 Action 执行作用域与事件分类与给目录组件定义加元数据或在 JSON Payload 中引入新 Action 类型不同渲染器引擎在运行时对底层交互事件的意图进行分类激活意图actionIntent activation由物理的、有意的用户激活事件触发Web可信 DOM 事件click、auxclick、touchend、submit、聚焦交互节点上的Enter/Space按键移动端主手势回调Flutter 的onPressed/onTap、Compose 的onClick、SwiftUI 的Button(action:)。允许执行标记了requiresUserActivation: true的函数。被动意图actionIntent passive由连续输入、焦点变化或被动事件触发WebDOM 事件blur、focus、input、change、pointermove、mouseenter移动端输入/焦点回调onChangeAction、onBlurAction、onFocusAction。阻止标记了requiresUserActivation: true的函数同时允许标准状态模型更新或数据校验。非 Action 作用域isExecutingAction false在 Surface 布局构建、动态字符串插值${openUrl(...)}、模板迭代、响应式状态更新或直接 Agent 函数调用消息期间生效。阻止标记了requiresUserActivation: true的函数。Agent 调用拒绝声明为callableFrom: rendererOrAgent或rendererOnly且设置了requiresUserActivation: true的函数如果被服务端/Agent 消息直接调用必须返回运行时安全错误。因为后端 Agent 的直接调用没有物理客户端用户激活上下文isExecutingAction false禁止自动执行用户激活受限函数。3.3 异步执行边界用户发起 Action 后执行链可能包含异步处理回合异步状态更新、数据转换、awaitpromise/future。await回合间的作用域保持Action 执行作用域isExecutingAction true且actionIntent activation会跨异步任务边界await、Promises、Futures、Coroutines、Tasks持续存在只要该执行流由用户激活事件发起。规则只要初始执行由有意的用户激活 Action 触发该 Action 处理器流水线内后续的异步操作与函数调用就仍处于激活的 Action Context 中允许执行requiresUserActivation函数。4. 渲染器 Action 执行模型渲染器通过Action Execution ScopesisExecutingAction与actionIntent强制执行用户手势约束完整决策流程如下5. 开发者体验与组件编写自定义组件作者无需修改组件实现即可支持该特性。5.1 自动 Action 包装当自定义组件通过 propsprops.action接收 action 回调时渲染器框架 Binder如web_core中的GenericBinder负责生成这些回调。Binder 自动捕获原生触摸/点击事件并将回调执行包裹在dataContext.executeInActionScope(intent, callback)中。运行时启停Runtime Enablement组件作者在标准框架代码中处理运行时启停disabled、enabled或自定义状态 propsLitbutton ?disabled${props.disabled} click${e !props.disabled props.action(e)}Click Me/buttonReactbutton disabled{props.disabled} onClick{e !props.disabled props.action(e)}Click Me/buttonFlutterElevatedButton(onPressed: props.disabled ? null : () props.action(), child: Text(Click Me))Jetpack ComposeButton(onClick props.action, enabled !props.disabled) { Text(Click Me) }如果组件实例在运行时处于禁用状态props.action()会被跳过不打开 Action Context、不执行任何函数。6. 四平台实现设计6.1 Web 引擎web_core设计在web_coreLit、React、Angular、Vue 渲染器共享中Action Context 处理集中在GenericBinder与DataContext内实现。web_coreAction Binder 实现提案目标设计// web_core: src/v0_9/data/data-context.ts export type ActionIntent activation | passive; export class DataContext { private _isExecutingAction false; private _actionIntent: ActionIntent passive; public get isExecutingAction(): boolean { return this._isExecutingAction; } public get actionIntent(): ActionIntent { return this._actionIntent; } public executeInActionScopeT(intent: ActionIntent, callback: () T): T { const prevAction this._isExecutingAction; const prevIntent this._actionIntent; this._isExecutingAction true; this._actionIntent intent; try { const result callback(); if (result instanceof Promise) { return result.finally(() { this._isExecutingAction prevAction; this._actionIntent prevIntent; }) as unknown as T; } this._isExecutingAction prevAction; this._actionIntent prevIntent; return result; } catch (error) { this._isExecutingAction prevAction; this._actionIntent prevIntent; throw error; } } } // web_core: src/v0_9/rendering/generic-binder.ts const ACTIVATION_EVENTS new Set([click, auxclick, touchend, submit]); export function bindAction(dataContext: DataContext, actionCall: ActionDefinition) { return (eventOrOptions?: Event | {event?: Event}) { const domEvent eventOrOptions instanceof Event ? eventOrOptions : ((eventOrOptions as any)?.nativeEvent ?? (eventOrOptions as any)?.event); // Classify event intent based on DOM event type const isActivationEvent domEvent domEvent.isTrusted (ACTIVATION_EVENTS.has(domEvent.type) || (domEvent instanceof KeyboardEvent (domEvent.key Enter || domEvent.key ))); const intent: ActionIntent isActivationEvent ? activation : passive; return dataContext.executeInActionScope(intent, () { return dataContext.invokeFunction(actionCall.call, actionCall.args); }); }; }Catalog.invoker校验提案目标设计// web_core: src/v0_9/catalog/types.ts this.invoker (name, rawArgs, ctx) { const fn this.functions.get(name); if (!fn) throw new A2uiExpressionError(Function not found: ${name}, name); if (fn.requiresUserActivation) { const isValidScope ctx.isExecutingAction ctx.actionIntent activation; if (!isValidScope) { throw new A2uiSecurityError( Execution blocked: Function ${name} requires a user activation Action context (e.g. click, tap, submit). It cannot be executed during layout rendering, interpolation, passive events (blur/change), or reactive updates., name, ); } } const safeArgs fn.schema.parse(rawArgs); return fn.execute(safeArgs, ctx); };与当前源码的对照在仓库现有的 renderers/web_core/src/v0_9/catalog/types.ts 中Catalog.invoker已具备函数不存在抛A2uiExpressionError、fn.schema.parse(rawArgs)校验并剥离非法参数、再执行fn.execute的核心骨架DataContext的实际实现在 renderers/web_core/src/v0_9/rendering/data-context.tsGenericBinder.bindAction的现有实现通过dispatchAction(resolveDeepSync(value))分发 action见 generic-binder.ts错误体系则集中在 v0_9/errors.tsA2uiError基类携带机器可读code派生出A2uiExpressionError、A2uiStateError等。上文中的executeInActionScope、bindAction的isTrusted分类逻辑属于提案的设计草图与当前 v0_9 源码存在差异——这正是本提案待落地部分。6.2 FlutterDart设计Flutter/Dart 中Action Context 作用域通过DataContext.runInActionScope传播enum ActionIntent { activation, passive } class DataContext { bool _isExecutingAction false; ActionIntent _actionIntent ActionIntent.passive; bool get isExecutingAction _isExecutingAction; ActionIntent get actionIntent _actionIntent; R runInActionScopeR(ActionIntent intent, R Function() block) { final prevAction _isExecutingAction; final prevIntent _actionIntent; _isExecutingAction true; _actionIntent intent; try { final result block(); if (result is Future) { return (result.whenComplete(() { _isExecutingAction prevAction; _actionIntent prevIntent; })) as R; } _isExecutingAction prevAction; _actionIntent prevIntent; return result; } catch (error) { _isExecutingAction prevAction; _actionIntent prevIntent; rethrow; } } } // Button Widget Binder: ElevatedButton( onPressed: props.disabled ? null : () { context.runInActionScope(ActionIntent.activation, () { actionDispatcher.dispatch(props.action, context: context); }); }, child: Text(props.label), ); // Function Invocation Guard: void verifyFunctionExecution(FunctionDefinition fn, DataContext context) { if (fn.requiresUserActivation) { if (!context.isExecutingAction || context.actionIntent ! ActionIntent.activation) { throw SecurityException( Function ${fn.name} requires execution within an active intentional user activation Action context., ); } } }6.3 AndroidKotlin / Jetpack Compose设计Android 上Jetpack Compose 的点击处理器在携带ActionContextElement的协程中执行enum class ActionIntent { ACTIVATION, PASSIVE } class ActionContextElement( val isExecutingAction: Boolean true, val intent: ActionIntent ActionIntent.ACTIVATION ) : CoroutineContext.Element { companion object Key : CoroutineContext.KeyActionContextElement override val key: CoroutineContext.Key* Key } // Jetpack Compose Button Component Composable fun A2UIButton(componentId: String, label: String, enabled: Boolean true, onAction: suspend () - Unit) { val coroutineScope rememberCoroutineScope() Button( onClick { if (!enabled) returnButton coroutineScope.launch(ActionContextElement(isExecutingAction true, intent ActionIntent.ACTIVATION)) { onAction() } }, enabled enabled ) { Text(text label) } }ActionContextElement实现了CoroutineContext.Element从而在协程切换suspend/launch之间传递激活上下文对应提案 3.3 节的跨异步边界保持作用域。6.4 iOSSwift / SwiftUI设计SwiftUI 中TaskLocal值A2UIActionScope将 Action Context 作用域绑定到async/await任务上public enum ActionIntent { case activation case passive } public enum A2UIActionScope { TaskLocal public static var isExecutingAction: Bool false TaskLocal public static var actionIntent: ActionIntent .passive } // SwiftUI Button Component struct A2UIButton: View { let actionCall: ActionDefinition let context: DataContext var body: some View { Button(action: { Task { await A2UIActionScope.$isExecutingAction.withValue(true) { await A2UIActionScope.$actionIntent.withValue(.activation) { await context.invokeFunction(actionCall.call, args: actionCall.args) } } } }) { Text(actionCall.label) } } }利用 Swift 的TaskLocal特性withValue块内的所有await任务天然继承 Action 作用域天然满足异步边界要求。7. 安全有效性与威胁矩阵Threat / ScenarioRisk LevelProtection MechanismInitial Render Auto-TriggerModel payload 加载时调用openUrlHighBlocked初始布局渲染处于isExecutingAction false。Interpolation Abuse在文本节点注入${openUrl(...)}HighBlocked表达式引擎以isExecutingAction false运行。Reactive Model Update状态同步触发函数执行MediumBlocked响应式状态更新在 Action Context 之外运行。Passive Event ExploitationopenUrl绑定到onBlurAction/onChangeActionMediumBlockedblur/change 事件以actionIntent passive运行。Synthetic Event Attack脚本调用.click()MediumBlockedBinder 要求domEvent.isTrusted true。8. 提案落地状态与仓库证据本提案当前状态为Draft作者 Greg Spencer创建于 2026-08-05但其核心元数据已在 v1.0 规范与基础目录中正式落地Schema 定义specification/v1_0/json/catalog_definition.json 在FunctionDefinition中新增requiresUserActivationboolean默认falseSpecifies whether this function requires a user activation context to execute并通过if/then条件约束一旦requiresUserActivation: trueallowedCallers只能为rendererOnly——这与提案第 3.2 节Agent 调用拒绝直接呼应保证受限函数绝不可能被 Agent 消息直接自动执行。目录落地specification/v1_0/catalogs/basic/catalog.json 中openUrl已设置requiresUserActivation: true、returnType: void其url参数除string (format: uri)外还支持DataBinding与FunctionCall引用见common_types.json说明即使 URL 来自数据绑定或嵌套函数调用执行同样受用户激活约束。版本演进specification/v1_0/docs/evolution_guide.md 记录 v1.0 起为FunctionDefinition增加requiresUserActivation默认false并更新openUrl定义为requiresUserActivation: true同时明确 wire 层FunctionCallPayload 不携带allowedCallers/returnType这类静态元数据只放在目录函数定义中。也就是说元数据层Catalog 声明已随 v1.0 落地运行时强制层Action Execution Scope、isTrusted校验、安全异常则处于本提案的设计阶段等待各渲染器按第 4、6 节模型实现。对于协议规范读者可结合 specification/v1_0/docs/a2ui_protocol.md 理解函数调用在协议中的位置对于渲染器开发者可从 renderers/web_core/src/v0_9/rendering/data-context.ts、generic-binder.ts 与 catalog/types.ts 出发将提案中的executeInActionScope、bindAction分类逻辑接入现有dispatchAction调用链。【免费下载链接】a2ui项目地址: https://gitcode.com/GitHub_Trending/a2/a2ui创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询