幹擾 document.write()

您最近曾在 Chrome 的 Play 管理中心看到如下的警告,並想知道這是什麼嗎?

(index):34 A Parser-blocking, cross-origin script,
https://paul.kinlan.me/ad-inject.js, is invoked via document.write().
This may be blocked by the browser if the device has poor network connectivity.

可組合性是網路的一大優勢,可讓我們輕鬆整合第三方建構的服務,打造出優質的新產品!組合性有一個缺點,就是它暗示了使用者體驗的共同責任。如果整合結果不盡理想,使用者體驗就會受到負面影響。

我們發現,在頁面中使用 document.write() 會導致成效不佳,尤其是用於插入指令碼的情況。雖然看起來無害,但對使用者來說,這可能會造成實際問題。

document.write('<script src="https://example.com/ad-inject.js"></script>');

瀏覽器必須先剖析 HTML 標記,才能建構 DOM 樹狀結構,進而轉譯網頁。每當剖析器遇到指令碼時,必須先停止並執行指令碼,才能繼續剖析 HTML。如果指令碼會動態插入其他指令碼,解析器就會被迫等待更長的時間下載資源,這可能會導致一或多個網路來回傳輸,並延遲頁面首次轉譯的時間

如果使用者的網路連線速度較慢 (例如使用 2G),又透過 document.write() 動態插入外部指令碼,可能會導致主要網頁內容延遲數十秒才顯示、網頁無法載入,或載入時間長到使用者在網頁顯示前就離開。根據 Chrome 的檢測,我們發現透過 document.write() 插入第三方指令碼的網頁,在 2G 上載入第三方指令碼的網頁,載入速度通常是其他網頁的兩倍。

我們在 2G 連線的 1% Chrome 穩定版使用者中進行了 28 天的現場試驗,並收集了相關資料。我們發現在 2G 上載入的所有頁面當中,有 7.6% 包含至少一個透過 document.write() 插入頂層文件的跨網站、封鎖剖析器指令碼。由於封鎖這些指令碼的載入,我們在這些載入上發現以下改善:

  • 網頁載入次數達到首次顯示內容 (向使用者提供網頁有效載入的視覺確認) 的次數增加 10%、網頁載入次數達到已完全剖析狀態的次數增加 25%,以及重新載入次數減少 10%,這表示使用者體驗有所改善。
  • 減少 21% 的平均時間 (比原先快超過 1 秒),直到首次顯示內容
  • 剖析網頁的平均時間減少了 38%,代表改善了近六秒,大幅縮短顯示對使用者重要的內容所需的時間。

考量到這項資料,Chrome 從 55 版開始,會在偵測到這個已知的錯誤模式時,代表所有使用者介入,藉此變更 Chrome 中 document.write() 的處理方式 (請參閱 Chrome 狀態)。具體來說,Chrome 會在符合下列「所有」條件時,停止執行透過 document.write() 插入的 <script> 元素:

  1. 使用者連線速度較慢,尤其是使用 2G 網路時。(日後,這項變更可能會擴及其他連線速度較慢的使用者,例如 3G 或 Wi-Fi 連線速度較慢的使用者)。
  2. document.write() 位於頂層文件中。這項介入措施不適用於 iframe 中的 document.written 指令碼,因為這些指令碼不會阻斷主頁面的轉譯作業。
  3. document.write() 中的指令碼會阻斷剖析器。含有「async」或「defer」屬性的指令碼仍會執行。
  4. 該指令碼並未託管在同一網站上。換句話說,Chrome 不會介入 eTLD+1 相符的指令碼 (例如在 www.example.org 插入的 js.example.org 託管指令碼)。
  5. 指令碼尚未儲存在瀏覽器的 HTTP 快取中。快取中的指令碼不會產生網路延遲,而且會繼續執行。
  6. 頁面要求並非重新載入。如果使用者觸發重新載入作業,Chrome 就不會介入,並會照常執行網頁。

第三方程式碼片段有時會使用 document.write() 載入指令碼。幸運的是,大多數第三方供應商都提供非同步載入替代方案,可讓第三方指令碼載入,且不會阻擋網頁上其他內容的顯示。

該如何解決這個問題?

這個簡單答案並不是使用 document.write() 插入指令碼。我們會維護一組已知的非同步載入器支援服務,建議您持續查看這項資訊。

如果您的供應商不在清單中,但支援非同步指令碼載入功能,請通知我們,我們會更新網頁,以便協助所有使用者。

如果您的供應商不支援將不同步載入的程式碼載入網頁,建議您與供應商聯絡,並告知我們和供應商,他們會受到哪些影響。

如果供應程式提供包含 document.write() 的程式碼片段,您可以將 async 屬性新增至指令碼元素,或是使用 DOM API (例如 document.appendChild()parentNode.insertBefore()) 新增指令碼元素。

如何偵測網站受到影響

系統會根據大量條件判斷是否要實施限制,因此您如何得知自己是否受到影響?

偵測使用者使用 2G 網路

要瞭解這項變更的潛在影響,您需要先瞭解有多少使用者正在使用 2G。您可以使用 Chrome 提供的 Network Information API 偵測使用者目前的網路類型和速度,然後向分析或真實使用者指標 (RUM) 系統傳送抬頭通知。

if(navigator.connection &&
    navigator.connection.type === 'cellular' &&
    navigator.connection.downlinkMax <= 0.115) {
    // Notify your service to indicate that you might be affected by this restriction.
}

在 Chrome 開發人員工具中擷取警告

自 Chrome 53 起,開發人員工具會針對有問題的 document.write() 陳述式發出警告。具體來說,如果 document.write() 要求符合第 2 到 5 項條件 (Chrome 在傳送這則警告時會忽略連線條件),警告內容會類似以下:

文件寫入警告。

在 Chrome 開發人員工具中看到警告固然很好,但如何大規模偵測這類問題?您可以檢查在介入發生時傳送至伺服器的 HTTP 標頭。

檢查指令碼資源的 HTTP 標頭

當透過 document.write 插入的指令碼遭到封鎖時,Chrome 會將下列標頭傳送至要求的資源:

Intervention: <https://shorturl/relevant/spec>;

如果系統偵測到透過 document.write 插入的指令碼,且可能在不同情況下遭到封鎖,Chrome 可能會傳送:

Intervention: <https://shorturl/relevant/spec>; level="warning"

介入標頭會隨同指令碼的 GET 要求一併傳送 (在實際介入的情況下會以非同步方式傳送)。

未來掌握了什麼?

我們最初的計畫是,在偵測到符合條件的情況下執行這項介入措施。我們在 Chrome 53 中,一開始只會在開發人員工具中顯示警告。(Beta 版於 2016 年 7 月推出。我們預計在 2016 年 9 月為所有使用者推出穩定版)。

自 Chrome 54 版起,我們將介入封鎖 2G 使用者註入的指令碼,我們預計在 2016 年 10 月中旬為所有使用者提供穩定版本。如需更多最新消息,請查看 Chrome 狀態項目

我們會在使用者連線速度緩慢 (例如 3G 或 Wi-Fi 連線速度緩慢) 時,採取適當措施。請參閱這篇 Chrome 狀態文章

想瞭解詳情嗎?

如要進一步瞭解,請參閱下列其他資源: