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 View 系统的安全状况,同时最大限度地减少信息泄露。

利益相关方

教员

审核者: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 时间标记了观察期的结束,以便客户端知道返回的焦点何时准确。例如,当发生了一系列焦点更改而在单个观看时间段内返回到上一个焦点时,客户端可以区分同一焦点值的不同返回。

focused KOID 可以是视图引用 KOID,也可以是特殊标记值 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 在标记为“视图树的其余部分”的较大三角形中有未指定的父项。

将焦点可见性范围限定为视图树

焦点.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 的视图树时再次收到通知。

L1 标题:聚焦观察器用法。
  L2 参与者 U。
  L3 参与者 ScopedProvider(作为 S)。
  L4 U -> S:手表。
  L5 备注 U 右侧:焦点已移动到 U。
  L6 S -> U:response(U)。
  L7 U -> S:手表。
  L8 备注 U 右侧:焦点已移出 U 范围。
  L9 S -> U:响应(“无效”)。
  L10 U -> S:手表。
  L11 U 右侧备注:正在等待焦点更改。

将焦点值报告给客户

focused 是三个值类之一,其中包含 ZX_KOID_INVALID 标记值。如果 focused 有效(即,不是标记),视图将能够在自身及其子视图之间任意移动焦点。

具体而言:

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

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

L1 标题:聚焦观察器用法。
  L2 参与者 U。
  L3 参与者 ScopedProvider(作为 S)。
  L4 U -> S:手表。
  L5 U 右侧备注:焦点已移动到 X。
  L6 S -> U: response(V)。
  L7 U -> S:手表。
  L8 U 右侧备注:等待焦点更改。

摘要语义

如果在过去的观看时间段内发生了多次焦点更改,此 API 将仅返回最终焦点。客户端通常无法对过去的焦点更改执行操作,因此将 API 简化为仅返回“摘要”。

通常,如果挂起获取客户端通过监视运行回调,焦点更改将立即返回到客户端。不过,客户端有可能在下次挂起时延迟停车,因此服务器可能会看到多项焦点更改,以在下次返回时汇总。服务器也可能会收到大量焦点更改,因此根据线程或任务调度,系统可能会在多次焦点更改后得到处理的挂起挂起函数。

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

L1 标题:聚焦观察器用法。
  L2 参与者 U。
  L3 参与者 ScopedProvider(作为 S)。
  L4 U -> S:手表。
  L5 U 右侧备注:焦点已移动到 X。
  L6 U 右侧备注:焦点移动到 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 调用停靠在服务器上后,在处理回调之前,可能会临时发生多次焦点变化,具体取决于服务器的线程性和实现细节。

实现

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

性能

焦点变更可能会频繁发生,但实际会按照“人类的比例”移动。因此,FIDL 调用频率不会被视为问题。FIDL 载荷也非常轻量,且流控制模式可以避免通道填充。

工效学设计

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

进化

此 API 旨在在 OOT 代码库中使用,而服务器实现位于平台组件“Sense”中。通过添加较新的挂起获取方法,该 API 将安全地改进并保持向后兼容性。当所有使用已弃用方法的代码库都更新为较新的方法时,可将已弃用的方法标记为已删除。

安全注意事项

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

L1 标题:聚焦观察器连接。
  L2 参与者界面客户端作为 U。
  L3 参与者 Flatland 作为 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 U 右侧备注:等待焦点更改。

API 客户端无法在自己的视图树中或其视图树之外请求获取更详细的信息。接收到的视图引用 KOID 信息的范围限定为其自身及其直接子项,从而改善视图的安全状况。

偷焦点

在更为宽松的系统中,恶意视图可以通过请求获得任何其他视图的焦点。Fuchsia View 系统的焦点政策通过定义焦点移动的情况和范围来降低这种可能性:视图仅在由祖先视图授予焦点时才能移动焦点,并且只能在其视图子树内移动焦点,而不能在视图子树之外移动焦点。

此焦点观察器设计遵循此焦点政策的作用域方法,将可观察性限制为观察到的视图及其直接子项。

KOID 不是功能

另一个小小的改进是,焦点观察器协议会提供子视图引用的 KOID,而不是视图引用本身的副本。某些界面协议作用于视图引用,因此返回 KOID 可以降低滥用的可能性。例如,如果 Ermine 的 focus.ScopedProvider 通道端点委托给 KeyboardListener 协议,这是安全的委托,因为“C”无法模拟 Ermine 或 Ermine 的任何子视图。

典型用途是确定哪个视图已获得焦点,而视图的视图引用 KOID 足以满足此需求。请注意,请求焦点需要实时视图引用,而不仅仅是视图引用的 KOID。客户端应维护自己的子视图引用列表(即通过 Flatland 协议获取),并且这些视图引用可用于请求焦点。

隐私注意事项

FocusChainListener 协议提供了视图树的完整可见性,可直达根视图。此焦点观察器协议会有意限制可见性范围,其中可见视图树的根位于客户端视图本身。

ViewRefFocused 的作用域已经限定为客户端的视图。此焦点观察器协议仅将客户端的可见性扩展到客户端视图的直接子视图。

通过这些缓解措施,我们预计隐私保护措施带来的影响微乎其微。

测试

该实现将包含单元测试和平台端集成测试。此外,与任何其他 SDK 可见的 FIDL 一样,该 FIDL 也会进行 CTF 测试。

文档

fuchsia.dev 中提供了使用文档指南。

缺点、替代方案和问题

此 API 并不适合用于观察焦点的所有已知用法。不过,之前的社交化工作强化了为不同需求创建单独的 API 的需求。后续 RFC 将会处理其他“焦点观察者”API。

现有艺术和参考资料

旧版 FocusChainListener 的问题

目前,用于观察视图焦点在视图树之间的移动的唯一选项是 fuchsia.ui.focus.FocusChainListener 协议。由于以下问题,它已废弃:

  • 它使客户端能够全局可见性,使客户端能够了解视图焦点的移动位置,而这会泄露平台实现细节。例如,根场景中的所有视图都会在此焦点链中公开,这样客户端就可以对根场景的结构进行断言,从而防止平台内部的实现更改。
  • 它会发出 fuchsia.ui.views.ViewRef 令牌(由 Zircon 事件对对象提供支持),从而允许协议较弱的客户端伪装成另一个视图。