RFC-0177:上層檢視的焦點觀察器

RFC-0177:父項檢視區塊的焦點觀察器
狀態已接受
區域
  • 查看系統
  • HCI
說明

API,供父項檢視區塊瞭解焦點在其檢視區塊樹狀結構中的移動方式

問題
Gerrit 變更
作者
審查人員
提交日期 (年-月-日)2022-06-06
審查日期 (年-月-日)2022-07-07

摘要

本 RFC 建議採用 檢視區塊焦點的 API 設計,確保一般 UI 用戶端可安全地在樹狀結構外使用,並釐清焦點可觀測性的安全性限制。重點在於盡量減少資訊曝光,並提供優雅的開發人員體驗。

提振精神

如要從多個元件建立使用者體驗 (圖像、輸入等),UI 用戶端通常會設定檢視區塊樹狀結構,將內容製作作業委派給其他 UI 用戶端,其中父項檢視區塊會管理一或多個子項檢視區塊。Ermine 系統殼層就是其中一例,Google 的智慧螢幕也是。家長檢視畫面的一項主要責任是監控檢視畫面焦點狀態:

  1. 判斷父項檢視區塊何時可能會以程式輔助方式,將檢視區塊焦點移至子項檢視區塊。
    • 舉例來說,如果父項檢視畫面不在檢視畫面樹狀結構的焦點鏈中 (https://fxbug.dev/42168713),父項檢視畫面將焦點移至子項檢視畫面的要求就會失敗。
  2. 如果檢視區塊焦點移至子項檢視區塊,請找出目前具有檢視區塊焦點的子項
    • 舉例來說,如果使用者透過觸控將焦點移至檢視區塊,父項檢視區塊可能會想以焦點邊界裝飾該子項檢視區塊,因此需要知道發生這種情況的時間和子項檢視區塊的身分。

焦點可能會在沒有父項檢視區塊參與的情況下變更 (使用者觸控、檢視區塊分離等)。父項檢視區塊必須隨時掌握檢視區塊焦點的移動方式,但要遵守全域檢視區塊樹狀結構設定的資訊限制

這份 RFC 建議採用「焦點觀察器」設計,可 (1) 讓父項檢視區塊正確回應檢視區塊焦點變化、(2) 安全地在樹狀結構外使用,以及 (3) 提升 Fuchsia View 系統的安全防護機制,並盡量減少資訊曝光。

利害關係人

協助人員:

審查人員: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,或是特殊信號值 ZX_KOID_INVALID,表示檢視畫面焦點位於 Focus.ScopedProvider 用戶端檢視畫面樹狀結構之外。下文將詳細說明可能的值和語意。

查看拓撲的範例

請參考下列檢視區塊拓撲,其中每個圓圈代表一個檢視區塊,而檢視區塊「U」是 ScopedProvider 的用戶端。

L1 範例檢視拓撲。
  L2 節點 U、V、W、X、Y。
  V 和 W 的 L3 U 父項。
  L4 V 是 X 和 Y 的父項。
  在標示為「其餘檢視畫面樹狀結構」的較大三角形中,L5 U 的父項未指定。

以檢視樹狀結構為範圍,著重曝光率

ScopedProvider 的用戶端只能有限度地查看全域檢視區塊樹狀結構 (詳情請參閱「安全考量」)。它可以瞭解檢視區塊焦點是位於檢視區塊樹狀結構中 (以 focus.ScopedProvider 用戶端的檢視區塊為根),還是位於檢視區塊樹狀結構外,但會刻意省略詳細資料。

當焦點位於 focus.ScopedProvider 用戶端檢視樹狀結構之外時,系統只會通知用戶端這項非常普遍的事實,並提供 ZX_KOID_INVALID 標記值。用戶端不會得知新焦點的 ID。

當焦點保留在 focus.ScopedProvider 用戶端的檢視樹狀結構中時,系統只會通知用戶端以下資訊:

  • 如果焦點是 focus.ScopedProvider 用戶端的檢視畫面本身,則為用戶端檢視畫面的 KOID。
  • 如果焦點位於用戶端檢視區塊的直接子項檢視區塊,則為該直接子項檢視區塊的 KOID。
  • 如果焦點位於用戶端檢視區塊的間接子項檢視區塊,則為該間接子項檢視區塊的祖先,也就是直接子項檢視區塊的 KOID。

父項檢視區塊需要知道何時有權在子項之間移動焦點。當檢視區塊焦點位於檢視區塊樹狀結構中時,這項功能就會發揮作用。否則,對 fuchsia.ui.views.Focuser.RequestFocus() 的呼叫一律會失敗。

請注意,focus.ScopedProvider 的資訊是透過管道傳播的快照,因此變更焦點的要求可能會與下一個快照更新發生競爭。舉例來說,某個快照可能指出焦點位於 focus.ScopedProvider 用戶端的檢視區塊樹狀結構中,如果祖先檢視區塊成功要求將焦點變更至這個檢視區塊樹狀結構外部,則將焦點變更至直接子項的要求可能會遭到拒絕。

在這個序列圖中,當焦點移至 U 時,系統會通知 U,當焦點完全移出 U 的檢視區塊樹狀結構時,系統也會通知 U。

L1 Title: Focus observer usage.
  L2 參與者 U.
  L3 參與者 ScopedProvider (如 S)。
  L4 U -> S: Watch.
  L5 Note right of U: focus moved to U.
  L6 S -> U: response(U).
  L7 U -> S: Watch.
  L8 Note right of U: focus moved outside of U.
  L9 S -> U: response("invalid")。
  L10 U -> S:觀看。
  L11 Note right of U: waiting for focus change.

向客戶回報的重點價值

focused 是三種值類別之一,包括 ZX_KOID_INVALID 前哨值。如果 focused 有效 (即不是前哨),檢視畫面就能在自身和子項檢視畫面之間任意移動焦點。

具體情形如下:

  • 如果 focusedZX_KOID_INVALID,表示焦點已離開這個檢視區塊樹狀結構。發生這種情況的原因有很多,舉例來說,U 的檢視區塊樹狀結構可能已連線至全域檢視區塊樹狀結構,但祖先檢視區塊可能已將焦點移出,移至 U 的同層級檢視區塊。或者,U 可能已與全域檢視區塊樹狀結構中斷連線,表示 U 不再符合保留焦點的資格。或者,U 的祖先本身可能已中斷連線,在這種情況下,該祖先的所有後代都無法保留焦點。請參閱專注模式政策
  • 如果是父項檢視區塊的參照 KOID,則父項檢視區塊本身會成為焦點。 這與 fuchsia.ui.views.ViewRefFocused 的用法相同,因此我們可以淘汰該通訊協定。
  • 如果 KOID 並非無效或父項,則為直接子項的檢視參照 KOID。這個欄位只會提及直接子項,即使聚焦的檢視區塊是直接子項的後代也一樣。

在本例中,焦點已移至 X,也就是 U 底下 V 的子項。焦點觀察器會回報 U 的直接子項,也就是 V。

L1 Title: Focus observer usage.
  L2 參與者 U.
  L3 參與者 ScopedProvider (如 S)。
  L4 U -> S: Watch.
  L5 Note right of U: focus moved to X.
  L6 S -> U: response(V).
  L7 U -> S: Watch.
  L8 Note right of U: waiting for focus change.

摘要語意

如果過去觀看期間內發生多次焦點變更,這項 API 只會傳回最終焦點。用戶端通常無法對過去的焦點變更採取行動,因此 API 經過簡化,只會傳回「摘要」。

一般來說,如果暫止的 GET 用戶端透過 Watch 停駐回呼,焦點變更會導致立即返回用戶端。不過,用戶端可能會在下一個暫停取得時延遲停車,因此伺服器可能會看到多個焦點變更,以便在下一個傳回時進行摘要。伺服器也可能收到大量焦點變更,因此視執行緒或工作排程而定,擱置的暫停擷取作業可能會在多次焦點變更後獲得服務。

在這些範例中,無論特定 Watch() 呼叫時間為何,U 都會收到相同的通知。

L1 Title: Focus observer usage.
  L2 參與者 U.
  L3 參與者 ScopedProvider (如 S)。
  L4 U -> S: Watch.
  L5 Note right of U: focus moved to X.
  L6 Note right of U: focus moved to Y.
  L7 Note 右側的 U:焦點已移至 W。
  L8 S -> U: response(W)。
  L9 U -> S: Watch.
  L10 Note right of U: waiting for focus change.

L1 Title: Focus observer usage.
  L2 參與者 U.
  L3 參與者 ScopedProvider (如 S)。
  L4 Note right of U: focus moved to X.
  L5 Note right of U:焦點已移至 Y。
  L6 附註 U 右側:焦點已移至 W。
  L7 U -> S: Watch.
  L8 S -> U: response(W)。
  L9 U -> S: Watch.
  L10 Note right of U: waiting for focus change.

狀態變更語意

Watch 呼叫會根據每個用戶端的狀態變化而觸發。

  • 連線後第一次呼叫時,系統會立即傳回目前狀態。
  • 在一次 Watch 呼叫傳回結果和下一次 Watch 呼叫開始之間,如果發生多項狀態變更,下一次 Watch 呼叫會立即傳回結果,其中包含觀察到的最後一項變更。

如果層級有變更,伺服器只會在收到 Watch 呼叫通知用戶端,並忽略收到 Watch 呼叫前的變更。在這段期間,用戶端會錯過焦點變更摘要,這不符合預期的使用案例。

如果根據狀態變更建立,伺服器實作的負擔會更大,因為需要追蹤每個觀察者管道的最後發布狀態。不過,這可帶來更直覺的開發人員體驗,因為狀態變更可有效防止用戶端 Watch 呼叫與任何焦點變更之間發生順序交換。舉例來說,Watch 呼叫在伺服器上暫停後,回呼處理前可能會發生幾次焦點變更,具體次數取決於伺服器的執行緒和實作詳細資料。

實作

檢視區塊焦點與檢視區塊生命週期和檢視區塊拓撲維護作業密切相關。 Scenic 是檢視畫面管理工具元件,因此這個通訊協定的實作屬於 Scenic。

效能

焦點可能會頻繁變更,但實際上是以「人體尺度」移動。因此 FIDL 呼叫頻率不會被視為問題。FIDL 酬載資料量也很小,且流程控制模式可避免管道塞滿資料。

人體工學

這個 API 的目標是改善 DX,超越前代 API。簡化錯誤處理、有損摘要語意,以及缺少容器資料型別,應該表示採用會更容易。

演化

這項 API 適用於 OOT 存放區,伺服器實作項目則位於平台元件 Scenic 中。API 會新增較新的懸掛式 GET 方法,以安全的方式演進,並保留回溯相容性。當所有使用已淘汰方法的存放區都更新為較新的方法時,已淘汰的方法即可標示為已刪除。

安全性考量

這個 API 會連結至 fuchsia.ui.composition.Flatland.ViewBoundProtocols 表格,在建立檢視區塊時,將這個 API 的伺服器端點與父項檢視區塊相關聯的特定 ViewRef 緊密連結。

L1 Title: Focus observer hookup.
  L2 參與者 UI 用戶端為 U。
  L3 參與者 Flatland as S.
  L4 參與者焦點供應商為 F。
  L5 Note right of U: 'U': view ref for view 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 View 系統的焦點政策會定義焦點移動的情況和範圍,藉此降低這種可能性:只有在祖先檢視區塊授予焦點時,檢視區塊才能移動焦點,且只能在檢視區塊子樹狀結構內移動焦點,無法移至外部。

這個焦點觀察器設計會遵循這項焦點政策的範圍方法,將可觀察性限制在觀察到的檢視區塊及其直接子項。

KOID 不是功能

另一項小幅改良是焦點觀察器通訊協定會傳遞子項檢視區塊參照的 KOID,而不是檢視區塊參照本身的副本。部分 UI 通訊協定會對檢視畫面參照採取行動,因此傳回 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 物件支援),讓較弱通訊協定的用戶端可冒充其他檢視區塊。