干预 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.

可组合性是 Web 的强大功能之一,可让我们轻松集成第三方构建的服务,从而打造出出色的新产品!可组合的缺点之一是,它暗示着对用户体验共同承担责任。如果集成不够理想,则会对用户体验产生负面影响。

导致性能不佳的一个已知原因是在页面内使用 document.write(),尤其是那些注入脚本的用途。如下所示,它虽然无害,但可能会给用户带来真正的问题。

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

浏览器必须先通过解析 HTML 标记来构建 DOM 树,然后才能渲染页面。 每当解析器遇到脚本时,都必须先停止并执行脚本,然后才能继续解析 HTML。如果脚本动态注入另一个脚本,解析器将被迫等待更长时间才能下载资源,这可能会导致一次或多次网络往返,并延迟页面的首次渲染时间

对于网速较慢(例如使用 2G 网络)的用户,通过 document.write() 动态注入的外部脚本可能会使主页面内容的显示时间延迟几十秒,或者导致网页无法成功加载,或者导致用户因网页加载用时过长而放弃。根据 Chrome 中的插桩,我们了解到,在 2G 网络上,如果网页包含通过 document.write() 插入的第三方脚本,则其加载用时通常是其他网页的 2 倍。

我们对 1% 的 Chrome 稳定版用户进行了为期 28 天的现场试用,仅限使用 2G 连接的用户。我们发现,在 2G 网络上,7.6% 的网页加载包含至少 1 个通过 document.write() 在顶级文档中插入的跨网站解析器阻塞脚本。由于阻止加载这些脚本,我们在这些加载方面看到了以下改进:

  • 10% 的网页加载达到了首次内容渲染(向用户直观确认网页正在有效加载),25% 的网页加载达到了完全解析状态,并且重新加载次数减少了 10%,这表明用户的挫败感有所降低。
  • 首次内容渲染的平均时间缩短了 21%(缩短了超过 1 秒)
  • 将解析网页所需的平均时间缩短了 38%,这相当于将解析时间缩短了近 6 秒,大大缩短了向用户显示重要内容所需的时间。

考虑到这些数据,从版本 55 开始,Chrome 会在检测到这种已知不良模式时代表所有用户进行干预,具体做法是更改 document.write() 在 Chrome 中的处理方式(请参阅 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 的 Developer Console 中只显示了一条警告。 (Beta 版于 2016 年 7 月发布。我们预计将于 2016 年 9 月向所有用户推出稳定版。)

我们将从 Chrome 54 开始,暂时阻止向 2G 用户注入的脚本。Chrome 54 预计将于 2016 年 10 月中旬为所有用户提供稳定版本。如需了解更多动态,请参阅 Chrome 状态条目

随着时间的推移,我们希望在任何用户的连接速度缓慢(例如 3G 或 Wi-Fi 速度缓慢)时进行干预。请按照此Chrome 状态条目中的说明操作。

希望了解更多信息?

如需了解详情,请参阅下面列出的其他资源: