為避免惡意指令碼濫用敏感 API (例如彈出式視窗、全螢幕等),瀏覽器會透過使用者啟用來控管這些 API 的存取權。使用者啟用是瀏覽工作階段的狀態,會因使用者動作而產生:「活躍」狀態通常表示使用者目前正在與網頁互動,或是在網頁載入後已完成互動。使用者手勢是相同概念的熱門但誤導性字詞。舉例來說,使用者的滑動或閃爍手勢不會啟動網頁,因此從指令碼的角度來看,也並非由使用者啟動。
現今的主要瀏覽器顯示,使用者啟動控制項如何控制啟用管制的 API 方面的行為差異相當大。在 Chrome 中,該實作是以權杖式模型為基礎,結果過於複雜,無法為所有啟用的 API 定義一致的行為。例如,Chrome 一直無法透過 postMessage()
和 setTimeout()
呼叫存取啟用管制的 API;使用者啟動作業也不支援 Promise、XHR、遊戲手把互動等。請注意,其中有些是長期熱門錯誤。
在第 72 版中,Chrome 提供使用者啟用第 2 版,因此所有啟用的 API 的使用者啟用功能皆已完成。這個方法可解決上述的不一致問題 (還有一些,例如 MessageChannels),因為我們相信這可以簡化使用者啟動程序的網頁開發作業。此外,新的實作方式為建議的新規格提供參考實作,有助於長期將所有瀏覽器整合在一起。
使用者啟用 v2 的運作方式為何?
新 API 會在頁框階層的每個 window
物件中保留兩個位元的使用者啟用狀態:用於記錄使用者啟用狀態的固定位元 (如果畫面曾有使用者啟動),另一個則是目前狀態的暫時性位元 (如果影格在使用者啟動約一秒後啟動)。在影格設定後,固定式位元絕不會重設。每次使用者互動時都會設定暫時位元,並會在到期時間結束 (約一秒) 後或透過啟用消耗的 API (例如 window.open()
) 時重設。
請注意,不同的啟用管制 API 仰賴使用者啟用的方式不同;新的 API 不會變更上述任何 API 專屬行為。舉例來說,由於 window.open()
會照常利用使用者啟用行為,因此每次使用者啟動只能有一個彈出式視窗,但如果頁框 (或其任何子頁框) 曾看到使用者操作,Navigator.prototype.vibrate()
仍可持續有效,以此類推。
異動內容
- 使用者啟用 v2 針對跨影格邊界的使用者啟用瀏覽權限進行正式化的概念:與特定影格的互動現在無論來源為何,都會啟用所有包含影格 (且僅限這些影格)。(在 Chrome 72 版中,我們提供暫時性的解決方法,可讓您將所有相同來源的影格的瀏覽權限擴大至適用範圍。一旦有辦法明確將使用者啟用作業明確傳遞至子頁框,我們就會移除這個解決方法)。
- 從已啟用的影格呼叫啟用管制的 API 時,如果從事件處理常式程式碼外部呼叫,只要使用者的啟用狀態為「有效」(例如未使用或未使用),就會運作。在使用者啟用第 2 版之前,它會無條件失敗。
- 在到期時間間隔內,如有多筆未使用的使用者互動,會整合到與最後一個互動對應的單次啟動中。
啟用管制 API 的一致性範例
以下兩個彈出式視窗 (使用 window.open()
開啟) 的範例,說明瞭使用者啟用 v2 如何使啟用管制的 API 行為保持一致。
鏈結 setTimeout()
呼叫
這個範例來自 我們的 setTimeout()
示範。如果 click
處理常式嘗試在一秒內嘗試開啟彈出式視窗,則無論程式碼如何「組合」延遲,都應該會成功。使用者啟用 v2 符合此預期,因此下列每個事件處理常式都會在 click
開啟彈出式視窗 (延遲時間為 100 毫秒):
function popupAfter100ms() {
setTimeout(callWindowOpen, 100);
}
function asyncPopupAfter100ms() {
setTimeout(popupAfter100ms, 0);
}
someButton.addEventListener('click', popupAfter100ms);
someButton.addEventListener('click', asyncPopupAfter100ms);
如果沒有使用者啟用第 2 版,在我們測試的所有瀏覽器中,第二個事件處理常式就會失敗。(即使在某些情況下的第一個錯誤會失敗)。
跨網域 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 版,上層頁框在收到第二則訊息時,無法開啟彈出式視窗。即使第一則訊息「鏈結」到另一個跨來源影格 (也就是第一個接收端將訊息轉發給其他訊息),則即使第一則訊息失敗。
此做法適用於使用者啟用 2 版,無論是原始形式或鏈結,皆適用。