HTML 規格修正:字串版 setTimeout 的 Trusted Types 檢查改為同步丟例外
WHATWG HTML Standard commit 6992cb519a02 · 2026-09-26
當 setTimeout 或 setInterval 收到字串、而 Trusted Types 判定違規時,HTML 規格原本把檢查放在計時器到期後的 task 裡,現在改為在呼叫當下就同步丟出例外。規格文字因此追上了瀏覽器的實際行為,而不是反過來要求瀏覽器改。
這是 WHATWG HTML 的 commit 6992cb519a02db34f653ddd54c50fad2565285f5,作者 Shannon Booth,日期為 2026-09-26,只動到 source 一個檔案(21 行新增、21 行刪除)。
原本的問題
Trusted Types 要求危險的字串 sink(例如把字串當成程式碼執行)必須收到 TrustedScript,否則視違規處理。字串版的 setTimeout("doSomething()", 1000) 就是這種 sink,因為字串最終會被當成腳本執行。
舊版規格的 timer initialization steps 把「取得符合 Trusted Types 的字串」這一步,放在計時器 task 內部執行。commit message 說得很直接:該步驟在 task 裡取得字串,同時又「可能丟出例外」。照規格字面讀,違規會在延遲之後、於 task 中才浮現,呼叫端的 try/catch 接不到。
這和實作不一致。commit message 指出,所有實作看起來都已經收斂到同步丟例外,規格才是落後的一方。
核心改動
改動是把一段步驟從 task 內搬到演算法最前面。轉換發生在 task 建立之前,所以例外會直接從 setTimeout 呼叫處冒出來。搬移後的條件是:handler 不是 Function,而且沒有給 previousId。
// 規格行為對照(概念示意,非規格原文)
// 舊:違規在 task 內才被檢查
setTimeout(untrustedString, 0); // 呼叫端看不到例外
// 新:違規在呼叫當下同步丟出
try {
setTimeout(untrustedString, 0);
} catch (e) {
// 這裡接得到 Trusted Types 違規
}被搬動的步驟內容沒有變:用 global 是不是 Window 決定 globalName(Window 或 WorkerGlobalScope),用 repeat 決定 methodName(setInterval 或 setTimeout),兩者以空格串成 sink,再以 TrustedScript 與 "script" 呼叫 get trusted type compliant string。也就是 sink 名稱仍是 Window setTimeout 這類字串,違規報告看起來一樣。
另外,task 內原本那段已被刪掉,只剩下 Assert:此時 handler 必為字串。因為轉換已提前完成,task 裡不再需要處理失敗。
前後對照
| 項目 | 舊規格 | 新規格 |
|---|---|---|
| 檢查時機 | 計時器 task 內 | task 建立之前 |
| 違規的呈現 | 延遲後在 task 中出現 | setTimeout 呼叫處同步丟出 |
try/catch 能否接到 | 規格字面上不能 | 可以 |
| 與瀏覽器實作 | 不一致 | 一致(依 commit message) |
影響範圍
commit message 說明實作已經是同步丟例外,所以對現有瀏覽器的行為多半沒有新的變化,主要影響的是規格讀者與測試。以下是需要留意的對象:
- 瀏覽器引擎與 web-platform-tests 維護者:規格現在明確要求同步行為,與既有實作對齊,之前依舊規格寫的測試或註解需要對照。
- 啟用 Trusted Types 的網站:若有程式用
try/catch包住字串版setTimeout來攔截違規,這是規格明文支持的寫法;若原本以為違規只會出現在延遲之後的全域錯誤,應檢查錯誤處理是否真的走到同步路徑。 - 仍在用字串版計時器的舊程式:最穩妥的做法仍是改成傳
Function,這種情況根本不會經過 Trusted Types 檢查,因為條件只針對非Function的 handler。
另外,previousId 的條件值得注意:setInterval 重複觸發時會帶 previousId,所以只有第一次排程才做轉換,之後的重複排程不會再檢查一次。
commit 沒有附帶新的 IDL 或 API 變更,也沒有說明對應的 issue 編號或測試異動,這些資訊在 commit 內文中未提供。