| RFC-0177:家长视图的焦点观察器 | |
|---|---|
| 状态 | 已接受 |
| 区域 |
|
| 说明 | 供父视图了解焦点在其视图树中如何移动的 API |
| 问题 | |
| Gerrit 更改 | |
| 作者 | |
| 审核人 | |
| 提交日期(年-月-日) | 2022-06-06 |
| 审核日期(年-月-日) | 2022-07-07 |
摘要
此 RFC 提出了一种 API 设计,用于实现 view focus,该 API 可供普通的 UI 客户端在树外安全使用,并阐明了围绕焦点可观测性的安全限制。重点在于尽可能减少信息暴露,并提供出色的开发者体验。
设计初衷
为了通过多个组件创建用户体验(图形、输入等),界面客户端通常会通过设置视图树将内容制作委托给其他界面客户端,其中父视图管理一个或多个子视图。Ermine 系统 shell 就是一个这样的示例;Google 的智能显示屏是另一个示例。父视图的一项主要责任是监控视图焦点状态:
- 用于确定父视图何时可能以编程方式将视图焦点移动到子视图。
- 例如,如果父视图不在视图树的焦点链中 (https://fxbug.dev/42168713),则父视图将焦点移至其子视图的请求会失败。
- 如果视图焦点移至子视图,则用于标识哪个子视图当前具有视图焦点。
- 例如,如果用户通过触摸将焦点移至某个视图,父视图可能希望使用焦点边界来装饰该子视图,并且需要知道焦点何时移至该子视图以及该子视图的身份。
焦点可能会在没有父视图参与的情况下发生变化(用户触摸、视图分离等)。父视图必须及时了解视图焦点如何移动,但方式要符合全局视图树设置的信息限制。
此 RFC 提出了一种“焦点观察器”设计,该设计 (1) 允许父视图正确响应视图焦点变化,(2) 可安全地在树外使用,并且 (3) 以最小的信息暴露来提高 Fuchsia 视图系统的安全态势。
利益相关方
辅导员:
审核者:sanjayc@google.com(工作站)、quiche@google.com(HCI)、neelsa@google.com(HCI)、akbiggs@google.com(Flutter)
咨询对象:shiveshganju@google.com、fmil@google.com、emircan@google.com、jsankey@google.com
共同化:
此 RFC 已与受影响的团队的负责人进行过社交化。
要求
- 尽可能减少了对重点可观测性的信息暴露
- 用于获取观测渠道的明确界定的安全机制
- 将 SDK 纳入“合作伙伴”级别或更高级别
- 提升开发者体验
设计
此焦点观测器的核心提案是以下 FIDL 协议。
library fuchsia.ui.observation.focus;
using zx;
protocol ScopedProvider {
Watch() -> (ScopedResponse);
};
type ScopedResponse = table {
1: observation_end zx.time;
2: focused zx.koid;
};
名称中的“Scoped”表示该协议提供聚焦信息,这些信息限定在 ScopedProvider 客户端的视图树中的焦点内。focus.ScopedProvider 客户端的视图是此可观测视图树的根。
observation_end 时间标志着观看期的结束,以便客户端知道返回的焦点何时是准确的。例如,如果一系列焦点更改恰好在单个 Watch 周期内返回到之前的焦点,则它允许客户端区分同一焦点值的不同返回。
focused KOID 是视图引用 KOID,或者表示视图焦点位于 focus.ScopedProvider 客户端的视图树之外的特殊标记值 ZX_KOID_INVALID。下文将详细讨论可能的值和语义。
查看拓扑示例
请考虑以下视图拓扑,其中每个圆圈代表一个视图,而视图“U”是 ScopedProvider 的客户端。
焦点可见性范围限定为视图树
聚焦客户端。ScopedProvider 对全局视图树的可见性有限(详见“安全注意事项”)。它可以了解视图焦点是在其视图树(以 focus.ScopedProvider 客户端的视图为根)中还是在其视图树之外,但具体细节有意省略。
当焦点位于 focus.ScopedProvider 客户端视图树之外时,系统只会通过 ZX_KOID_INVALID sentinel 值告知客户端这一非常笼统的事实。客户端不会了解新视图焦点的身份。
当焦点位于 focus.ScopedProvider 客户端的视图树中时,系统只会向客户端告知以下信息:
- 如果焦点位于 focus.ScopedProvider 客户端的视图本身上,则为客户端视图的 KOID。
- 如果焦点位于客户端视图的直接子视图上,则为该直接子视图的 KOID。
- 如果焦点位于客户端视图的间接子视图上,则为作为该间接子视图的祖先的直接子视图的 KOID。
父视图需要知道它何时有权在子视图之间移动焦点。当视图焦点位于其视图树中时,它具有此功能。否则,对 fuchsia.ui.views.Focuser.RequestFocus() 的调用将始终失败。
值得注意的是,focus.ScopedProvider 的信息是通过渠道传播的快照,因此更改焦点的请求可能会与下一次快照更新发生竞态。例如,某个快照可能表明焦点位于 focus.ScopedProvider 客户端的视图树中,如果某个祖先视图成功请求将焦点更改到此视图树之外,则将焦点更改到直接子级的请求可能会被拒绝。
在此序列图中,当焦点移至 U 时,U 会收到通知;当焦点完全移出 U 的视图树时,U 会再次收到通知。
报告给客户端的焦点值
focused 是三个值类之一,其中包括 ZX_KOID_INVALID 标记值。如果 focused 有效(即不是标记值),则视图有权在自身及其子视图之间任意移动焦点。
具体而言:
- 如果
focused为ZX_KOID_INVALID,则焦点已离开此视图树。这种情况可能是由多种原因造成的。例如,U 处的视图树可能已连接到全局视图树,但某个祖先视图可能已将焦点移出,移至 U 的同级视图。或者,U 可能已与全局视图树断开连接,这意味着 U 不再有资格持有焦点。 或者,U 的祖先本身可能已断开连接,在这种情况下,该祖先的所有后代都无法保持焦点。请参阅焦点政策。 - 如果它是父视图的视图引用 KOID,则父视图本身具有焦点。 此用法与 fuchsia.ui.views.ViewRefFocused 完全相同,因此我们可以弃用该协议。
- 如果它是有效或父级的 KOID,则它是直接子级的视图引用 KOID。此字段中仅提及直接子项,即使聚焦的视图是直接子项的后代也是如此。
在此示例中,焦点移到了 X(U 下的 V 的子级)。焦点观察器报告 U 的直接子级,即 V。
摘要语义
如果在过去的观看时段内多次更改焦点,此 API 将仅返回最终焦点。客户端通常无法对过去的焦点变化做出反应,因此简化了 API,使其仅返回“摘要”。
通常,如果挂起获取客户端通过 Watch 停放回调,焦点更改将导致立即返回到客户端。不过,客户端可能会在下一次挂起获取时延迟停车,因此服务器可能会看到多次焦点更改,以便在下一次返回时进行总结。服务器也可能会收到大量焦点变化,因此根据线程或任务调度,停放的挂起获取可能会在多次焦点变化后得到服务。
在这些示例中,无论具体的 Watch() 调用时间如何,U 都会收到相同的通知。
状态更改语义
手表通话是根据每个客户端的状态变化来驱动的。
- 在连接后的第一次调用时,系统会立即返回当前状态。
- 在一次 Watch 调用返回结果和下一次 Watch 调用开始之间,如果发生了多次状态更改,下一次 Watch 调用将立即返回观察到的最后一次更改。
对于级别更改,服务器仅在收到 Watch 调用后发生更改时通知客户端,并忽略收到 Watch 调用之前的更改。客户端会在此期间错过焦点更改摘要,这不符合预期用例。
如果基于状态变化进行更新,则会给服务器实现带来更大的负担,因为它需要跟踪每个观察者渠道的上次发布状态。不过,由于状态变化对客户端的 Watch 调用与任何焦点变化之间的顺序交换具有鲁棒性,因此它能带来更直观的开发者体验。例如,在手表通话停留在服务器上之后,到回调处理之前,可能会发生多次焦点变化,具体取决于服务器的线程处理方式和实现细节。
实现
视图焦点与视图生命周期和视图拓扑维护密切相关。 Scenic 是视图管理器组件,因此此协议的实现属于 Scenic。
性能
焦点变化可能很频繁,但实际上是以“人类尺度”移动的。因此,FIDL 调用频率不会被视为问题。FIDL 载荷也非常轻,并且流量控制模式可避免通道填充。
工效学设计
此 API 旨在改进其前身产品的开发者体验。简化的错误处理、有损摘要语义和缺少容器数据类型应该意味着更容易采用。
进化
此 API 旨在供 OOT 代码库使用,服务器实现位于平台组件 Scenic 中。通过添加新的挂起获取方法,API 将安全地发展并保持向后兼容性。当使用已弃用方法的所有代码库都更新为较新的方法后,即可将该已弃用方法标记为已删除。
安全注意事项
此 API 会挂钩到 fuchsia.ui.composition.Flatland.ViewBoundProtocols 表,从而在创建视图时将此 API 的服务器端点与父视图关联的特定 ViewRef 紧密关联。
API 客户端无法在其视图树深处或视图树之外请求更详细的信息。接收到的视图 ref KOID 信息的作用域限定为自身及其直接子级,从而提高了视图的安全性。
焦点窃取
在权限更宽松的系统中,恶意视图只需请求焦点,即可从任何其他视图中“窃取”焦点。Fuchsia 视图系统的焦点政策通过定义焦点移动的环境和范围来缓解这种可能性:只有在视图被祖先视图授予焦点时,它才能移动焦点;并且它只能在自己的视图子树内移动焦点,而不能在子树外移动焦点。
此焦点观测器设计遵循了相应焦点政策的范围限定方法,将可观测性限制为观测到的视图及其直接子级。
KOID 不是功能
另一项小改进是,焦点观测器协议会传递子视图引用 (ref) 的 KOID,而不是视图引用本身的副本。某些界面协议会针对视图引用执行操作,因此返回 KOID 可降低误用的可能性。例如,如果将 Ermine 的 focus.ScopedProvider 渠道端点委托给另一个组件“C”,则这是一个安全的委托,因为“C”无法模拟 Ermine 或 Ermine 的任何子视图来访问 KeyboardListener 协议。
典型用法是识别哪个视图已获得焦点,为此,视图的视图引用 KOID 就足够了。请注意,请求焦点需要实时视图引用,而不仅仅是视图引用的 KOID。客户端应维护自己的子视图引用列表(即通过 Flatland 协议获取),这些视图引用可用于请求焦点。
隐私注意事项
FocusChainListener 协议可让您全面了解视图树,直至根视图。此焦点观测器协议有意限制了可见性范围,其中可见视图树以客户端视图本身为根。
ViewRefFocused 已限定为客户端的视图。此焦点观测器协议仅将客户端的可见性扩展到客户端视图的直接子视图。
由于采取了这些缓解措施,我们预计对隐私的影响将微乎其微。
测试
该实现将包含单元测试和平台端集成测试。 此外,与任何其他 SDK 可见的 FIDL 一样,它将具有 CTF 测试。
文档
fuchsia.dev 中将提供使用情况文档指南。
缺点、替代方案和未知因素
此 API 并不适合所有已知的焦点观测使用情形。不过,之前的一次宣传活动强调了需要针对不同需求创建不同的 API。后续 RFC 将处理其他“焦点观察器”API。
在先技术和参考资料
旧版 FocusChainListener 存在问题
目前,在整个视图树中观察视图焦点移动的唯一方法是使用 fuchsia.ui.focus.FocusChainListener 协议。由于存在以下问题,该方法已被弃用:
- 它使客户端能够全局了解视图焦点已移至何处,从而泄露平台实现细节。例如,根场景中的所有视图都会在此焦点链中公开,这允许客户端断言根场景的结构,从而防止平台内部实现发生更改。
- 它会发出 fuchsia.ui.views.ViewRef 令牌(由 Zircon eventpair 对象提供支持),使较弱协议的客户端能够冒充其他视图。