在 2024 年 Web Almanac 中,移动端页面 JavaScript 的中位数里,168 KB 来自第一方,375 KB 来自第三方。1按重量计算,一个普通页面上一半以上的脚本是由网站所有者以外的人编写的。Almanac 关于第三方的章节发现,92% 的页面上存在第三方资源,而脚本是最常见的第三方请求类型。2广告网络的脚本,即发布商为承载广告而添加的脚本,就是这些请求之一。该脚本在加载时给页面带来的代价,与其说取决于它做什么,不如说取决于加载它的元素是如何编写的。
对于没有任何属性的 script 元素,浏览器会停下来
根据 HTML Living Standard,浏览器处理外部脚本有三种方式,它们的区别仅在于元素上的一个属性。3第一种是两个属性都没有的默认方式:脚本“会被立即获取并执行,在两者都完成之前阻止解析”。第二种是 defer,它在解析的同时并行获取文件,并在“页面完成解析时”执行。在第三种即 async 方式下,文件同样被并行获取,但会“在可用时立即执行(可能在解析完成之前)”。只有第一种方式会在文件通过网络传输期间让解析器停下来,在文件到达之前,该元素之后的任何内容都不会被绘制。
Google 的 Lighthouse 审核将这一区别变成了发布商可以在自己页面上运行的测试。它会标记出文档 head 中既没有 defer 也没有 async 的 script 元素。4报告“列出所有阻塞页面首次绘制的网址”。该定义不涉及大小,也不涉及用途。分析脚本、广告脚本和字体加载器,只要位于 head 中且没有属性,都会被同等对待。
加载指南正是为第三方脚本而写的
web.dev 上的指南对来自网站外部的脚本说得很直接。它说,同步脚本会“延迟 DOM 的构建和渲染”,DOM 即浏览器对文档的工作模型。5由此得出的规则是:“除非脚本必须在页面渲染之前运行,否则始终异步加载第三方脚本。”async 适用于必须在加载过程早期运行的脚本;defer 适用于“不太关键的资源”,其例子是首屏以下的视频播放器。按照这一标准,在文字显示到屏幕上之后才工作的广告脚本,属于不太关键的一类。
这种做法已经普及,但并未覆盖所有页面。2024 年 Web Almanac 发现,87% 的页面使用了 async,高于 2022 年的 76%;47% 的页面使用了 defer,高于 42%。1这些数字描述的是页面整体。一个自身脚本都已延迟加载的页面,仍然可能带有一个由第三方提供的阻塞元素。安装代码片段除非经过发布商编辑,否则会按照网络编写的方式加载。
等待 DOM 就绪的脚本,没有首次绘制可以阻塞
浏览器用一个事件来标记解析器完成构建 DOM 的时刻。根据 MDN,DOMContentLoaded“在 HTML 文档被完全解析,并且所有延迟脚本”都已下载并执行之后“触发”。6该事件不会等待图片、子框架或异步脚本。因此,在这一时刻开始工作的脚本是在文字已经存在之后才开始的,而无需等待最后一张图片。
AdBubbles 嵌入脚本 adbubbles.js 在页面 DOM 就绪后加载。它是一个静态文件,通过 Cloudflare 的缓存提供,用纯 JavaScript 编写,没有任何依赖。它读取的各网站数据也是一个静态文件,在广告活动发生变化时重新生成,并在边缘节点缓存。因此,一次页面浏览不会给广告服务器带来任何负担,唯一的动态请求是一个很小的信标。嵌入脚本只扫描发布商配置的范围,并跳过标题、链接、代码、预格式化文本、表单控件、导航、页眉、页脚,以及任何标记为 data-noads 的内容。安装说明介绍了范围设置。
开设发布商账户免费,将嵌入脚本添加到网站也免费。添加网站的发布商可以申请抓取,以显示其哪些词语目前可以出售。范围、每页最多气泡数以及被排除的广告主类别,都在同一个账户中选择。
读者离开时发送的信标是代价的另一半
读者离开的那一刻,是脚本拖慢读者的另一个机会。在卸载时进行报告的分析代码,过去依赖“阻塞式同步 XMLHttpRequest 调用”之类的技术。7MDN 记载,所有这些方法都“会阻塞文档的卸载,从而减慢导航到下一个页面的速度”。sendBeacon 方法取代了它们。它以异步方式发送一个小型 HTTP POST 请求。浏览器会“在用户代理有机会时”传输数据,“而不会延迟卸载或下一次导航”。
嵌入脚本使用 sendBeacon 进行报告,并发送三种信标。每次页面加载发送一次浏览信标。气泡可见满一秒时发送一次打开信标,读者点击气泡中的链接时发送一次点击信标。除此之外,没有任何信息离开页面。
AdBubbles 不设置 Cookie,不存储标识符,也不收集读者的任何个人数据。读者所在的国家/地区从 Cloudflare 的请求头中读取,而不是从存储的 IP 地址中获取。将发布商页面上的词语与购买这些词语的广告主相匹配,就是 AdBubbles 所做的全部事情。AdBubbles 无权决定哪些人看到气泡,从不在页面或网站之间跟踪读者,也不承诺任何效果。隐私立场已完整公布。
浏览器现在会统计广告的代价,并根据总量采取行动
Chrome 的重度广告干预显示了一家浏览器厂商认为怎样才算过多。只有“在用户没有与之互动(例如没有点按或点击它)的情况下”,广告才会被视为重度广告。8除此之外,满足三个阈值中的任何一个就足够了。第一个是网络带宽超过 4 MB。第二个是在任意 30 秒窗口内主线程时间超过 15 秒,第三个是总计超过 60 秒。
干预触发时,广告框架的内容会“被替换为‘广告已移除’消息”,该机制作用于被标记为广告的 iframe。8衡量标准是字节数和主线程时间;广告的内容不起任何作用。气泡在发布商自身文档内的 Shadow DOM 中渲染,而不是在 iframe 中,因此该机制针对的是另一种广告。嵌入脚本给某个页面增加了多少字节和主线程时间,要由该页面自己的测量结果来显示。
发布商可以在自己的页面上进行的检查,除了浏览器之外不需要任何其他东西。页面源代码列出了 head 中的每个 script 元素,每一个要么带有 async 或 defer,要么两者都没有。Lighthouse 的“优化建议”部分会将两者都没有的元素列为阻塞首次绘制的网址。4出现在该列表上的广告网络代码片段,按照标准的定义就是在阻塞页面,无论该网络的文档对其重量是怎么说的。广告脚本是否会出现在该列表上,取决于其元素上的属性,而这个属性在任何发布商粘贴之前就已经写入代码片段了。