為避免惡意指令碼濫用彈出式視窗、全螢幕等敏感 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
處理常式嘗試在 1 秒內開啟彈出式視窗,則無論程式碼如何「組合」延遲時間,都應成功。使用者啟用功能 v2 符合這項預期,因此下列每個事件處理常式都會在 click
上開啟彈出式視窗 (延遲 100 毫秒):
function popupAfter100ms() {
setTimeout(callWindowOpen, 100);
}
function asyncPopupAfter100ms() {
setTimeout(popupAfter100ms, 0);
}
someButton.addEventListener('click', popupAfter100ms);
someButton.addEventListener('click', asyncPopupAfter100ms);
在沒有使用者啟用功能 v2 的情況下,第二個事件處理常式在我們測試的所有瀏覽器中都會失敗。(在某些情況下,第一個嘗試也會失敗)。
跨網域 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);
});
如果沒有使用者啟用功能 v2,父項框架在收到第二個訊息時,就無法開啟彈出式視窗。即使第一則訊息「連結」至另一個跨來源框架 (也就是第一個接收器將訊息轉寄給其他接收器),也會失敗。
這項功能適用於 User Activation v2,無論是原始表單還是鏈結都適用。