RFC-0177:父视图的焦点观察器 | |
---|---|
状态 | 已接受 |
区域 |
|
说明 | 用于让父视图了解焦点如何在其视图树中移动的 API |
问题 | |
Gerrit 更改 | |
作者 | |
审核人 | |
提交日期(年-月-日) | 2022-06-06 |
审核日期(年-月-日) | 2022-07-07 |
摘要
此 RFC 提出了一种视图焦点 API 设计,普通界面客户端可以安全地在树外使用该设计,并阐明了焦点可观察性周围的安全限制。其重点是尽可能减少信息泄露,并提供优雅的开发者体验。
设计初衷
为了通过多个组件创建用户体验(图形、输入等),界面客户端通常会通过设置视图树将内容生成委托给其他界面客户端,其中父视图管理一个或多个子视图。Ermine 系统壳就是一个很好的例子;Google 的智能显示屏也是如此。父视图的一项关键职责是监控视图焦点状态:
- 确定父视图何时可以程序化地将视图焦点移至子视图。
- 例如,如果父视图不在视图树的焦点链中,则父视图将无法将焦点移至其子视图 (https://fxbug.dev/42168713)。
- 如果视图焦点移至子视图,则用于确定当前哪个子视图具有视图焦点。
- 例如,如果用户通过触摸将焦点移至某个视图,父视图可能希望使用焦点边界装饰该子视图,并且需要知道发生的时间和子视图的身份。
焦点可能会在没有父视图参与的情况下发生变化(用户轻触、视图分离等)。必须让父视图始终了解视图焦点的移动方式,但要遵循全局视图树设置的信息限制。
此 RFC 提出了一种“焦点观察器”设计,其特点如下:(1) 允许父视图正确响应视图焦点变化,(2) 可安全地在树外使用,(3) 可最大限度地减少信息泄露,从而改善 Fuchsia View 系统的安全状况。
利益相关方
教员:
审核者:sanjayc@google.com(工作站)、quiche@google.com(人机交互)、neelsa@google.com(人机交互)、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”表示该协议提供的焦点信息的范围限定在 focus.ScopedProvider 客户端的视图树内,或者受其约束。focus.ScopedProvider 客户端的视图是此可观察视图树的根。
observation_end
时间标志着观看时段的结束,以便客户端知道返回的焦点何时准确无误。例如,如果一系列焦点变化在单个观看时段内恰好返回到之前的焦点,它允许客户端区分同一焦点值的不同返回。
focused
KOID 是视图引用 KOID,或者是特殊的标记值 ZX_KOID_INVALID
,表示视图焦点位于 focus.ScopedProvider 客户端的视图树之外。下文详细介绍了可能的值和语义。
示例视图拓扑
请考虑以下视图拓扑,其中每个圆圈代表一个 View,并且视图“U”是 focus.ScopedProvider 的客户端。
焦点可见性范围限定为视图树
focus.ScopedProvider 的客户端对全局视图树的访问权限有限(如需了解详情,请参阅安全注意事项)。它可以了解视图焦点位于其视图树内(根位于 focus.ScopedProvider 客户端的视图)或其视图树外,但会刻意省略具体信息。
当焦点位于 focus.ScopedProvider 客户端的视图树之外时,客户端只会通过 ZX_KOID_INVALID
哨兵值获知这一非常笼统的事实。客户端无法了解新视图焦点的身份。
当焦点位于 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。此字段中仅提及直接子项,即使聚焦的视图是直接子项的后代也是如此。
在此示例中,焦点移到了 U 下的 V 的子项 X。焦点观察器会报告 U 的直接子项,即 V。
摘要语义
如果在过去的观看时段内发生多次焦点变化,此 API 将仅返回最终焦点。客户端通常无法对过去的焦点变化执行操作,因此该 API 已简化为仅返回“摘要”。
通常,如果挂起的 get 客户端通过 Watch 停车回调,焦点更改将导致立即返回给客户端。不过,客户端可能会延迟停车下一个挂起的 get,因此服务器可能会看到多个焦点变化,以便在下次返回时进行汇总。服务器也可能会收到大量的焦点变化,因此,根据线程或任务调度,停车挂起的 get 可能会在多次焦点变化后得到处理。
在这些示例中,无论具体的 Watch() 调用时间如何,U 都会收到相同的通知。
状态更改语义
Watch 调用由各个客户端的状态变化驱动。
- 在连接后的首次调用时,系统会立即返回当前状态。
- 在一个 Watch 调用的返回和下一个 Watch 调用的开始之间,如果发生了多次状态更改,下一个 Watch 调用将立即返回,并包含观察到的最后一次更改。
对于等级更改,只有在收到 Watch 调用之后发生更改时,服务器才会通知客户端,并会忽略在收到 Watch 调用之前发生的更改。客户端会在此期间错过焦点变化摘要,这不适用于预期的用例。
基于状态更改进行更新会给服务器实现带来更大的负担,因为它需要跟踪每个观察器通道的上次发出的状态。不过,这会带来更直观的开发者体验,因为状态更改对客户端 Watch 调用和任何焦点更改之间的有序交换具有强大的抗干扰能力。例如,在 Watch 调用停靠在服务器上之后,在系统处理回调之前,可能会发生多次焦点变化,具体取决于服务器的线程处理方式和实现细节。
实现
视图焦点与视图生命周期和视图拓扑的维护密切相关。Scenic 是 View 管理器组件,因此此协议的实现属于 Scenic。
性能
焦点可能会频繁发生变化,但实际上是“以人为本”地进行调整。因此,FIDL 调用频率不被视为问题。FIDL 载荷也非常轻量,并且流量控制模式可避免信道填充。
工效学设计
与前代 API 相比,此 API 力求改进 DX。简化的错误处理、有损摘要语义以及没有容器数据类型,这些都应该意味着更易于采用。
进化
此 API 旨在供 OOT 仓库使用,服务器实现位于平台组件 Scenic 中。通过添加较新的挂起 get 方法,该 API 将安全地进行演变并保持向后兼容性。当使用已废弃方法的所有代码库都更新为较新的方法后,您就可以将已废弃的方法标记为已删除。
安全注意事项
此 API 会钩入 fuchsia.ui.composition.Flatland.ViewBoundProtocols 表,该表会在视图创建时将此 API 的服务器端点与与父视图关联的特定 ViewRef 紧密关联。
API 客户端无法请求其自己的视图树深处或视图树之外的更详细信息。收到的 View ref KOID 信息的范围仅限于自身及其直接子项,这有助于提高 View 的安全状况。
焦点抢夺
在更宽松的系统中,恶意视图只需请求即可从任何其他视图“窃取”焦点。Fuchsia View 系统的焦点政策通过定义焦点移动的环境和范围来降低这种可能性:只有在祖先视图授予焦点后,视图才能移动焦点,并且只能在其视图子树内移动焦点,而不能在其外部移动焦点。
此焦点观察器设计遵循此焦点政策的范围限定方法,通过将可观察性限制为观察的视图及其直接子视图。
KOID,而非 capability
另一项小改进是,焦点观察器协议会分发子视图引用的 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 对象提供支持),以允许较低协议的客户端冒充其他视图。