確保所有 API 的使用者啟用作業一致

Mustaq Ahmed
Joe Medley
Joe Medley

為避免惡意指令碼濫用彈出式視窗等敏感 API, 全螢幕等等,瀏覽器會透過使用者來控管這些 API 的存取權 啟用。使用者啟用是指瀏覽工作階段中的相應狀態 轉換為「有效」「狀態」通常表示使用者 目前正在與網頁互動,或是在之後完成互動 載入。使用者手勢是相同概念的熱門但誤導性字詞。適用對象 舉例來說,使用者的滑動或閃爍手勢不會啟動網頁,因此 並非如此

現今的主要瀏覽器顯示使用者啟動方式截然不同的行為 控制啟用限制的 API。Chrome 的實作是以 Chrome 為基礎 採用符記式模型,因故難以定義 所有啟用管制 API 的行為舉例來說,Chrome 已 透過以下方式,以完整的方式存取啟用管制的 API postMessage()setTimeout() 呼叫;以及使用者啟用作業的 支援 Promise XHR遊戲手把互動等。請注意,其中部分項目 也可能是長期存在的熱門錯誤

在第 72 版中,Chrome 提供「使用者啟用功能 2.0 版」, 所有啟動限制的 API 啟用期限都已完成。這可以解決問題 這些不一致的情況 (另外還有一些, MessageChannels)。 引導使用者啟用應用程式此外,新的實作方式 將提議的參考實作 新規格

使用者啟用 v2 的運作方式為何?

新 API 在每個 window 物件上都維持 2 位元使用者啟用狀態 區隔:適合歷來使用者啟用狀態的一個固定點 (如果 影格速率是指使用者啟動的影格數),而目前狀態的短暫位元 (使用者大約一秒後啟動了使用者啟動的影格)。便利性 設定影格後,影格生命週期不會重設暫時的位元 會在每次使用者互動時設定,並在到期後重設 或藉由呼叫啟用採用的 API (例如 window.open())。

請注意,不同的啟用管制 API 仰賴使用者啟用作業的不同 管理方式;新的 API 不會變更上述任何 API 專屬行為。例如: 每次使用者啟用時,只允許一個彈出式視窗,因為「window.open()」會使用 如以往的使用者啟動作業,Navigator.prototype.vibrate() 繼續 瀏覽過任何影格 (或其任何子頁框) 的使用者操作、 依此類推

異動內容

  • 「使用者啟用 v2」針對使用者啟用可視性的概念正式闡明 跨影格邊界:使用者與特定影格互動時 啟用所有包含影格 (且僅限這些影格),無論其 來源。(在 Chrome 72 中,我們提供了暫時替代方案,將 可在所有相同來源影格的顯示設定我們會在執行初期 可讓您 將使用者啟用作業明確傳遞至子頁框)。
  • 從已啟用的影格呼叫啟用管制的 API 時 事件處理常式程式碼之外,只要使用者啟用這個 API,這個 API 就能持續運作 狀態為「已啟用」例如未過期也未消耗。使用者之前 啟用第 2 版時,會無條件失敗。
  • 在到期日間隔內多次未使用的使用者互動融合 對應至與最終互動對應的單次啟用中。

啟用管制 API 的一致性範例

以下是兩個彈出式視窗 (使用 window.open() 開啟) 的範例, 展示「使用者啟用第 2 版」如何產生啟用管制 API 的行為 保持一致

鏈結 setTimeout() 呼叫

這個範例來自 我們的 setTimeout() 示範。 如果 click 處理常式嘗試在一秒內嘗試開啟彈出式視窗,則預期會 就算程式碼的「撰寫」方式使用者啟用第 2 版符合 因此,下列每個事件處理常式都會在 click (延遲 100 毫秒):

function popupAfter100ms() {
  setTimeout(callWindowOpen, 100);
}

function asyncPopupAfter100ms() {
  setTimeout(popupAfter100ms, 0);
}

someButton.addEventListener('click', popupAfter100ms);
someButton.addEventListener('click', asyncPopupAfter100ms);

在沒有使用者啟用第 2 版的情況下,第二個事件處理常式在所有的瀏覽器中都會失敗 目標使用者、使用的資料 以及模型訓練與測試方式(即使第 1 個 VM 無法使用 在某些情況下)。

跨網域 postMessage() 呼叫

我們來看一下 我們的 postMessage() 示範。 假設跨來源子頁框中的 click 處理常式會直接傳送兩則訊息 對應至上層頁框上層頁框應該可以開啟彈出式視窗 收到以下任一訊息 (但兩者只能擇一):

// Parent frame code
window.addEventListener('message', e => {
  if (e.data === 'open_popup' && e.origin === child_origin)
    window.open('about:blank');
});

// Child frame code:
someButton.addEventListener('click', () => {
  parent.postMessage('hi_there', parent_origin);
  parent.postMessage('open_popup', parent_origin);
});

如未啟用「使用者啟用 2.0 版」,上層頁框在收到回應時無法開啟彈出式視窗 傳送第二則訊息即便第一則訊息「鏈結」也是如此到另一個 跨來源頁框 (換言之,如果第一個接收端將訊息轉寄至其他來源) 其他)。

這項做法適用於「使用者啟用 2.0 版」,可用於原始形式和 鏈結。