RFC-0177:适用于父视图的 Focus Observer

RFC-0177:父视图的焦点观察器
状态已接受
区域
  • 视图系统
  • HCI
说明

用于让父视图了解焦点在其视图树中如何移动的 API

问题
Gerrit 更改
作者
审核人
提交日期(年-月-日)2022-06-06
审核日期(年-月-日)2022-07-07

摘要

此 RFC 提出了一种视图焦点 API 设计,该设计可在树外安全使用,适用于普通界面客户端,并阐明了围绕焦点可观测性的安全限制。重点在于最大限度地减少信息泄露,并提供优雅的开发者体验。

设计初衷

为了从多个 组件创建用户体验(图形、输入等),界面客户端通常会通过设置视图树将内容生成委托给其他界面客户端,其中 父视图管理一个或多个子视图。Ermine 系统 shell 就是这样一个示例;Google 的智能显示屏是另一个示例。父视图的一项关键 职责是监控视图焦点状态:

  1. 确定父视图何时可以通过编程方式将视图焦点移动到子视图。
  2. 如果视图焦点已移动到子视图,则确定哪个子视图目前具有视图焦点。
    • 例如,如果用户通过触摸将焦点移动到某个视图,则父视图可能希望使用焦点边界来装饰该子视图,并且需要知道发生的时间和子视图的身份。

焦点可能会在没有父视图参与的情况下发生变化(用户触摸、视图分离等)。必须让父视图了解视图焦点如何移动,但 方式要尊重信息限制设置的 全局视图树。

此 RFC 提出了一种“焦点观察器”设计,该设计 (1) 允许父视图正确响应视图焦点变化,(2) 可在树外安全使用,并且 (3) 提高了 Fuchsia 视图系统的安全状况,同时最大限度地减少了信息泄露。

利益相关方

辅导员:

审核人:sanjayc@google.com (Workstation)、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”表示该协议提供限定于或约束在 focus.ScopedProvider 客户端的视图树中的焦点信息。focus.ScopedProvider 客户端的视图是此可观察视图树的根。

observation_end 时间标志着 Watch 周期的结束,以便客户端知道返回的焦点何时准确。例如,如果一系列焦点变化恰好在一个 Watch 周期内返回到之前的焦点,则它允许客户端区分同一焦点值的不同返回。

focused KOID 是视图引用 KOID,或者是 特殊 Sentinel 值 ZX_KOID_INVALID,表示视图 焦点位于 focus.ScopedProvider 客户端的视图树之外。下面将更详细地讨论可能的值和语义。

示例视图拓扑

考虑以下视图拓扑,其中每个圆圈代表一个视图,视图“U”是 focus.ScopedProvider 的客户端。

L1 示例视图拓扑。
  L2 节点 U、V、W、X、Y。
  V 和 W 的 L3 U 父级。
  X 和 Y 的 L4 V 父级。
  L5 U 在一个标有“其余视图树”的较大三角形中具有未指定的父级。

焦点可见性限定于视图树

focus.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 会再次收到通知。

L1 标题:重点介绍观测器使用情况。
  L2 参与者 U。
  L3 参与者 ScopedProvider 为 S。
  L4 U -> S: Watch.
  L5 U 右侧的 Note:焦点移至 U。
  L6 S -> U: response(U).
  L7 U -> S:观看。
  L8 注释(位于 U 的右侧):焦点已移到 U 的外部。
  L9 S -> U: response("invalid")。
  L10 U -> S:观看。
  L11 注释 U 的右侧:等待焦点更改。

向客户端报告的焦点值

focused 是三个值类之一,其中包括 ZX_KOID_INVALID Sentinel 值。如果 focused 有效(即不是 Sentinel),则该视图有权在自身及其子视图之间任意移动焦点。

具体而言:

  • 如果 focusedZX_KOID_INVALID,则焦点已离开此视图树。这种情况可能是由多种原因造成的。例如,U 的视图树可能已连接到全局视图树,但祖先视图可能已将焦点移出到 U 的同级视图。或者,U 可能已与全局视图树断开连接,这意味着 U 不再有资格持有焦点。 或者,U 的祖先本身可能已断开连接,在这种情况下,该祖先的所有后代都无法持有焦点。请参阅 焦点政策
  • 如果是父级的视图引用 KOID,则父视图本身具有焦点。此用法与 fuchsia.ui.views.ViewRefFocused 相同,这 使我们可以弃用该协议。
  • 如果它是无效或父级的 KOID,则它是直接子级的视图引用 KOID。此字段中仅提及直接子级,即使聚焦的视图是直接子级的后代也是如此。

在此示例中,焦点移动到 X,X 是 U 下的 V 的子级。焦点观察器报告 U 的直接子级,即 V。

L1 标题:重点介绍观测器使用情况。
  L2 参与者 U。
  L3 参与者 ScopedProvider 为 S。
  L4 U -> S: Watch.
  L5 U 右侧的备注:焦点已移至 X。
  L6 S -> U: response(V).
  L7 U -> S:观看。
  L8 注释(U 的右侧):等待焦点变化。

摘要语义

如果在过去的 Watch 周期内发生了多次焦点变化,此 API 将仅返回最终焦点。客户端通常无法对过去的焦点变化执行操作,因此 API 经过简化,仅返回“摘要”。

通常,如果挂起获取客户端通过 Watch 停放回调,则焦点变化将导致立即返回到客户端。但是,客户端可能会延迟停放下一个挂起获取,因此服务器可能会看到多个焦点变化,以便在下一次返回时进行总结。服务器也可能会收到大量焦点变化,因此,根据线程或任务调度,停放的挂起获取可能会在多次焦点变化后得到服务。

在这些示例中,无论具体的 Watch() 调用时间如何,U 都会收到相同的通知。

L1 标题:重点介绍观测器使用情况。
  L2 参与者 U。
  L3 参与者 ScopedProvider 为 S。
  L4 U -> S: Watch.
  L5 U 右侧的备注:焦点已移至 X。
  L6 U 右侧的 Note:焦点移至 Y。
  L7 注意 U 右侧:焦点移至 W。
  L8 S -> U: response(W).
  L9 U -> S:观看。
  L10 注释(位于 U 的右侧):等待焦点变化。

L1 标题:重点介绍观测器使用情况。
  L2 参与者 U。
  L3 参与者 ScopedProvider 为 S。
  L4 U 右侧的注释:焦点已移至 X。
  L5 注意 U 右侧:焦点移至 Y。
  L6 U 右侧的备注:焦点已移至 W。
  L7 U -> S:观看。
  L8 S -> U: response(W).
  L9 U -> S:观看。
  L10 注释(位于 U 的右侧):等待焦点变化。

状态变化语义

Watch 调用由每个客户端的状态变化驱动。

  • 在连接后的第一次调用时,系统会立即返回当前状态。
  • 在一次 Watch 调用的返回与下一次 Watch 调用的开始之间,如果发生了多个状态变化,则下一次 Watch 调用将立即返回观察到的最后一次变化。

对于级别变化,服务器仅在收到 Watch 调用后发生变化时通知客户端,并忽略收到 Watch 调用之前的变化。 客户端会错过该期间的焦点变化摘要,这不适合预期用例。

基于状态变化会给服务器实现带来更大的负担,因为它需要跟踪每个观察器通道的上次发布的状态。但是,这会带来更直观的开发者体验,因为状态变化对于客户端的 Watch 调用与任何焦点变化之间的排序交换具有鲁棒性。例如,在 Watch 调用停放在服务器上后,在处理回调之前,可能会发生多次焦点变化,具体取决于服务器的线程性和实现细节。

实现

视图焦点与视图生命周期和视图拓扑的维护密切相关。 Scenic 是视图管理器组件,因此此协议的实现属于 Scenic。

性能

焦点变化可能很频繁,但实际上是以“人类规模”移动的。因此,FIDL 调用频率不会被视为问题。FIDL 有效负载也非常轻,并且流控制模式避免了通道填充。

工效学设计

此 API 致力于改进 DX,优于其前身。简化的错误处理、有损摘要语义以及缺少容器数据类型应该意味着更容易采用。

进化

此 API 旨在在 OOT 仓库中使用,服务器实现位于平台组件 Scenic 中。通过添加新的挂起获取方法,API 将安全地发展并保留向后兼容性。 当使用已废弃方法的所有仓库都更新为较新的方法时,可以将已废弃的方法标记为已删除。

安全注意事项

此 API 会挂钩到 fuchsia.ui.composition.Flatland.ViewBoundProtocols 表,该表在视图创建时将此 API 的服务器端点与父视图关联的特定 ViewRef 紧密关联。

L1 Title: Focus observer hookup。
  L2 参与者界面客户端,表示为 U。
  L3 参与者 Flatland as S.
  L4 参与者焦点提供程序,表示为 F。L5 U 右侧的注释:“U”:视图 U 的视图引用。
  L6 U -> S: Flatland.CreateView2(U, server_end:f.u.o.focus.ScopedProvider).
  L7 U -> S: Flatland.Present().
  L8 U -> F: F: f.u.o.focus.ScopedProvider.Watch().
  L9 Note right of U: waiting for focus change.

API 客户端无法请求其自身视图树深处或视图树之外的更详细信息。收到的视图引用 KOID 信息限定于自身及其直接子级,这提高了视图的安全性。

焦点窃取

在更宽松的系统中,恶意视图只需请求即可从任何其他视图“窃取”焦点。Fuchsia 视图系统的 焦点政策通过定义焦点移动的 情况和范围来缓解这种可能性:视图只能在祖 101}先视图授予焦点的情况下移动焦点,并且只能在其视图 子树内移动焦点,而不能在其外部移动焦点。

此焦点观察器设计遵循此焦点政策的限定范围方法,将可观察性限制为观察到的视图及其直接子级。

KOID 而非功能

另一个小改进是,焦点观察器协议会分发子视图引用的 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 对象提供支持),这允许较弱协议的客户端冒充 另一个视图。