Introducing Kitesurf: 一款在 Cloudflare Workers 上运行的代理优先浏览器
我们应该自己构建一个浏览器吗?这是一个每隔几个月就会在 Cloudflare 内部冒出来的问题。不出所料,这类问题会引发长长的讨论,包含各种理由和说明,阐述为什么我们应该去做。浏览器显然是我们每天在计算机上使用的最重要软件,它可以说是互联网的操作系统。我们是一家致力于帮助 [构建一个更好的互联网](https://www.cloudflare.com/about-overview/) 的公司——谁不想迎接构建新浏览器的挑战呢?
但我们始终找不到技术难度与通过构建浏览器解决独特问题之间的平衡。因此,这个想法一再被搁置。直到现在。
一些神奇的事情发生了:我们达到了一个临界点,一系列在 [开发者平台](https://developers.cloudflare.com/) 的强大技术进步成为现实,同时人工智能代理的兴起和对新型浏览器的需求也变得至关重要。
在 Workers 中运行 [WebAssembly (Wasm)](https://developers.cloudflare.com/workers/runtime-apis/webassembly/) 现在已经非常成熟。像 [动态工作者](https://developers.cloudflare.com/dynamic-workers/)、[基于 SQLite 的可持久对象](https://developers.cloudflare.com/durable-objects/api/sqlite-storage-api/)、[工作者之间的 RPC](https://developers.cloudflare.com/workers/runtime-apis/rpc/) 和 [服务绑定](https://developers.cloudflare.com/workers/runtime-apis/bindings/service-bindings/) 等原语,以及更高的 [NodeJS 兼容性](https://developers.cloudflare.com/workers/runtime-apis/nodejs/) 和更高的 [限制](https://developers.cloudflare.com/workers/platform/limits/) 为实现更雄心勃勃和复杂的应用打开了大门,这在之前是完全不可能的。
我们的无头浏览器自动化 API 产品 [Browser Run](https://developers.cloudflare.com/browser-run/) 随着 AI 的崛起也经历了巨大的增长。代理需要浏览器来执行许多任务,许多情况下没有浏览器就无法成功。
但问题是——像 Chromium 这样的浏览器引擎是为人类构建的,而不是代理,它们的开销是 AI 模型根本不需要的。它们消耗了大量内存和计算资源,为每个代理提供自己的实例是极其昂贵的,这使得许多网络的广泛使用仅限于最复杂和昂贵的 AI 模型以及高参数知识,而禁止了许多其他代理应用的使用。
我们应该为所有代理提供一个在 AI 模型重要性方面表现突出的浏览器,即使这意味着对人类只是提供一些有用的特性。例如:
- AI 不在乎选项卡、主题、浏览器扩展或设备间同步。它关注的是令牌数量、上下文窗口、可扩展性、性能和成本。
- 结构化的、机器可读的内容很重要,但视觉完美和流畅的 60fps 滚动并不重要。如果 CSS 解析稍有偏差或者渲染不是精准的,代理也完全可以应对。
- AI 使用浏览器的威胁模型是不同的,新的问题如提示注入和工具安全是重中之重。
面对这些认知,12 周前我们再次问了这个问题:我们应该自己构建一个浏览器吗?这次答案是一致的:**是的!**
今天,我们宣布 **Kitesurf**,这是一款完全建立在 Workers 之上的新浏览器,专为代理构建,当前处于测试阶段,免费提供 [Browser Run](https://developers.cloudflare.com/browser-run/)。
Kitesurf 在 CPU 和内存消耗方面比 Chromium 更高效,特别是常见的代理任务,例如截图和 HTML 提取。接下来是我们构建它的故事。准备好,它会变得技术性十足——但我们保证会让它有趣。
## 开始的地方
Kitesurf 的起源和许多其他伟大想法一样,来自 Cloudflare。有人发现了有趣的事情,接下来他们就以一个看似不可能但非常吸引人的想法让团队陷入纠缠。
我们最初的灵感来自 [obscura](https://github.com/h4ckf0r0day/obscura),这是一个用 Rust 编写的无头引擎,用于 AI 自动化,没有 "Chrome、没有 Node.js、没有依赖"。
然后,在 AI 代理的帮助下,我们尝试将其移植到 Workers 中,起初并不太成功。但当我们为 AI 提供一个清晰的计划以及成功的详细定义,让代理能够在需要时不断循环并提出问题后,它就成功了。
被这种(几乎)可行的概念证明震惊后,我们决定让团队继续努力。
### 设计决策
我们在开始之前做出了一些设计决策。
**测试、测试、再测试**
我们知道,从原型转变为真正可以在生产环境中有效执行任务的全功能浏览器需要大量工作和迭代。我们不会隐瞒,利用 AI 加速这一过程是关键。但如何在如此复杂的项目中使用 AI,同时保持代码和结果的质量不下降且不失去速度呢?答案是提供尽可能多的测试。
引入 [Web Platform Tests](https://github.com/web-platform-tests/wpt) (WPT),这是理想的设置:一套广泛的成功标准让 AI 代理有明确的目标。我们策划了分配给代理的功能的选择和顺序,使得人类能够集中精力进行架构工作并审核代理的做法。
然而,WPT 测试也仅能到此为止:它们只衡量是否符合 [W3C](https://www.w3.org/) 标准,而不是浏览器是否能渲染和交互现实网站。为填补这一空白,我们实现了一种集成测试和视觉回归测试的组合——它在真实网站上运行多步骤的 [Puppeteer](https://pptr.dev/) 测试,对比 Chromium 和 Kitesurf 的断言,并在每一步渲染输出时高亮任何不想要的差异。
**尽量使用 Rust**
Cloudflare 一直在努力为 [WebAssembly (Wasm)](https://developers.cloudflare.com/workers/runtime-apis/webassembly/) 提供极好的支持。这很好,因为我们可以使用高性能的 C、C++ 和 Rust 包并将其编译为 Wasm。如果我们使用 Emscripten(例如)及其众多模拟的依赖关系,编译的二进制文件会变得臃肿缓慢。
相反,我们选择尽可能使用原生 Rust,并直接使用 [wasm-bindgen](https://developers.cloudflare.com/workers/languages/rust/#javascript-plumbing-wasm-bindgen) 编译,从而避免不必要的模拟层并尽可能接近底层, [可靠运行](https://blog.cloudflare.com/making-rust-workers-reliable/)。
**异常处理**
浏览器必须能够渲染整个不可靠甚至有时是敌对的网络,而不掉落任何正在处理的页面,因此异常处理不仅仅是规范——这是应用存活的方式,能够在遇到错误输入时存活而不崩溃。
因此,我们在一开始就承诺了一条规则:任何失败都降级为空白框架或个别缺失,绝不允许会话崩溃。在每个边界捕获错误,默认回退到安全和空的状态,并记录足够的信息来进行故障排除。
**隔离**
与在笔记本电脑上运行浏览器(你访问的是信任的网站,可以接受资源共享)不同,代理面临的任务要求是任意的输入:来自任意源的代码。
因此,我们在假设每个页面载入都是不受信任的输入,且每个会话都是全新的基础上构建了这个浏览器。每个组件都是隔离的,仅能访问其功能所需的资源。
这一点与 Cloudflare Workers 的 [安全模型](https://developers.cloudflare.com/workers/reference/security-model/#isolation) 完美吻合,Workers 的设计本身就围绕隔离而构建。但是,该平台只为隔离提供边界。我们仍然需要在应用级别执行相同的原则,决定每个组件被允许接触哪些资源,并确保没有信息泄漏。
**尽可能保持无状态**
状态是导致失败变得昂贵的原因——如果没有任何东西可以重建,从崩溃中恢复只需重新发起一个请求。无状态组件是一次性和并行的:遇到停滞时立即杀死它,同时运行成千上万个,并根据请求量调整其大小,而非保持状态温暖。对于瞬时加载的自动化而言这恰恰合适,因为负载呈波动状态,最便宜的解决方案就是激活仅在使用后消失的工作。总之,**无状态组件应该尽可能保持无状态**。
## 我们如何构建它
我们有了良好的计划、广泛的测试和良好的工具环境,可以开始超越最初的概念验证。Kitesurf 请求的生命周期至今仍然保持着高水平:
让我们深入探讨 Kitesurf 的三个主要组成部分:引擎、PageScript 和 PageRenderer。
### 从来源获取
为了渲染一个不受信任的网页,浏览器必须从互联网获取任意资产——图像、字体、CSS、JavaScript 和 Wasm 文件。这是浏览器可能进行的危险操作之一。
Kitesurf 通过一个单一的组件——SandboxOutbound Worker,来完成这一切,其他任何组件都无法直接接触网络,这是由动态 Workers 强制实施的。引擎使用它来引导页面,获取主文档及其脚本,PageScript 获取其他所有内容:样式表、图像、字体,以及页面自己的 fetch() 调用。
我们使用 SandboxOutbound 强制执行 [CORS](https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/CORS),插入浏览器形状的头部,过滤响应,并保持每个页面的 cookie 在各自的 jar 中。任何未通过我们策略的请求都将返回 403——每个组件仅获得所需的网络而无其他。
### 引擎
引擎是 Kitesurf 唯一面向公众的组件。它处理 Chrome DevTools 协议 (CDP) 的 WebSocket 和 HTTP REST API,提供一个用于内部测试的着陆页,最重要的是,存储每个会话的状态。其他所有组件都是无状态的。
使用 CDP 的好处是客户端兼容性:[Puppeteer](https://developers.cloudflare.com/browser-run/puppeteer/)、Playwright、chrome-remote-interface 以及实际的 Chrome DevTools 前端。指向 Kitesurf 时它们都能正常工作。这也是 Browser Run [如何运作](https://developers.cloudflare.com/browser-run/cdp/)(更详细的内容稍后再谈)。
有趣的是,引擎实际上是 Kitesurf 组件中最简单的部分,接下来的部分才有趣。
### PageScript
PageScript 很好地展示了我们新 Workers 功能的强大:在这种情况下,是 [动态工作者](https://developers.cloudflare.com/dynamic-workers/)。 Kitesurf 在没有此功能时根本无法实现。
以下是 PageScript 内部工作原理的简化示意图。
每个新页面或进程外 iframe ([OOPIF](https://www.chromium.org/developers/design-documents/oop-iframes/)) 都使用动态工作者启动一个长生存的 PageScript isolate,处理由干净的 [globalThis](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/globalThis) 和 [DOM](https://developer.mozilla.org/en-US/docs/Web/API/Document_Object_Model) 文档对象组成的页面会话。
DOM 对象随即填充解析 HTML 文档的结果,并运行所有 JavaScript 脚本。用于解析 HTML 和 CSS 的部分来自于 [Blitz](https://github.com/DioxusLabs/blitz)(一个模块化渲染引擎)和 Firefox 的 [Stylo](https://github.com/servo/stylo)(高性能 CSS 解析器),它们都用 Rust 编写。
对于每个找到的 <script> 标签或 .wasm 文件,我们都在同一 isolate 中运行 JavaScript 和 WebAssembly 代码。
**但是,关于评估**
你问起 eval,确实比较棘手,因为出于 [安全原因](https://developers.cloudflare.com/workers/runtime-apis/web-standards/#javascript-standards) 我们仍然不支持 Workers 中的 eval。如果我们再开启另一个 isolate 来处理它们,它将无法访问 globalThis。
我们的解决方案是使用 [Boa JS](https://boajs.dev/),这是一个用 Rust 编写的 ECMAScript 引擎,编译并在 Workers 上运行。我们实际上是在一个运行时的基础上执行另一个运行时,这看起来并不理想,确实不是,但它足够好来处理我们代码中偶尔出现的 eval。未来,当 Workers 中本地支持 eval 到位时,我们将从 Boa 切换出去。
### PageRenderer
这个组件主要负责从计算出的页面对象生成实际的像素。它的工作原理如下:
PageRenderer 在一个与引擎 Worker 的循环中工作。每当引擎需要一帧时,PageRenderer 会从 PageScript 获取页面对象(也称为场景),提取内部字体和图像,来自 [静态资产](https://developers.cloudflare.com/workers/static-assets/) 的资源,将其栅格化为图像缓冲区,随后返回给引擎,以客户端可以显示的格式,如 JPEG/PNG 或 PDF。
这里的魔力很大一部分由另一个 Blitz 模块 [blitz-paint](https://github.com/DioxusLabs/blitz/tree/main/packages/blitz-paint) 处理,它使用 Parley 来形成字符、选择字体并将文本分成行。
**Workers 内置 RPC 系统:相同的应用程序,多重 isolates**
Cloudflare Workers 具有 [内置的远程过程调用 (RPC) 系统](https://blog.cloudflare.com/javascript-native-rpc/),支持调用其他 Workers 上的方法,传递对象以及调用这些对象的方法。你无需担心 API 模式、类型或认证,只需调用 remoteFunction(...params),它便能工作。
Kitesurf 利用这个 RPC 系统:引擎 Worker 通过 RPC 调用 PageRenderer Worker 的 renderFrame() 方法,使用一次性调用获取 PNG 作为结果。由于渲染器不持有页面状态(仅持有一次性缓存),引擎可以安全地终止并重新启动任何失败或卡住的 RPC 调用——使每个渲染请求都成为自包含、可重试且其 isolate 成本便宜、可随时抛弃。
## Kitesurf 通过 215,000+ WPT 测试并在于不断发展
Kitesurf 运行良好。目前已经通过了大约 215,000+ [WPT 测试](https://github.com/web-platform-tests/wpt),并且我们每周都会增加数百个通过的测试。从我们项目开始到现在,下面是随时间的演变:
值得注意的是,浏览器中对代理重要的部分(如 CSS、DOM、HTML、选择、SVG 和 XHR)已经有了良好的覆盖。甚至在代理上下文中可能并不重要的流,现在也得到了合理支持。
性能方面,Kitesurf 表现良好。下面是对比 Chromium 和 Kitesurf 在 [Browser Run](https://developers.cloudflare.com/browser-run/quick-actions/) 中执行五次快速行为的中位数结果,在一个 [14-URL corpus](https://kitesurf.cloudflare.app/corpus.txt) 中进行比较。
虽然 Chromium 在计时上稍占优势,因为 [JIT](https://en.wikipedia.org/wiki/Just-in-time_compilation) 首次遇到此页面时总会比冷软件渲染器快——目前速度约为 1.7 倍。大部分的差距来自于栅格化和 JPEG/PNG 编码,而我们会持续优化这些部分。
但是,Kitesurf 在内存和 CPU 的使用上取得了 3-7 倍的优势,真正推动你的账单。更少的内存意味着我们可以运行更多会话,实现更好的扩展性,并从根本上降低我们的成本和你的成本。
### 最重要的测试:Kitesurf 运行 Doom
我们强调了测试在设计决策中的重要性,但我们都知道,无论有多少测试,项目在 Doom 运行之前都不算真正完成。下面是 Kitesurf 运行 [Doom 的实验](https://blog.cloudflare.com/doom-multiplayer-workers/) 为我们的 [silentspacemarine.com](https://silentspacemarine.com/) 展示的情景。
### 今天在 Browser Run 尝试
你可以通过 Browser Run 今天尝试 Kitesurf,**当前在测试阶段免费提供**,在……
---
原文链接:[点击查看](https://blog.cloudflare.com/kitesurf/)
但我们始终找不到技术难度与通过构建浏览器解决独特问题之间的平衡。因此,这个想法一再被搁置。直到现在。
一些神奇的事情发生了:我们达到了一个临界点,一系列在 [开发者平台](https://developers.cloudflare.com/) 的强大技术进步成为现实,同时人工智能代理的兴起和对新型浏览器的需求也变得至关重要。
在 Workers 中运行 [WebAssembly (Wasm)](https://developers.cloudflare.com/workers/runtime-apis/webassembly/) 现在已经非常成熟。像 [动态工作者](https://developers.cloudflare.com/dynamic-workers/)、[基于 SQLite 的可持久对象](https://developers.cloudflare.com/durable-objects/api/sqlite-storage-api/)、[工作者之间的 RPC](https://developers.cloudflare.com/workers/runtime-apis/rpc/) 和 [服务绑定](https://developers.cloudflare.com/workers/runtime-apis/bindings/service-bindings/) 等原语,以及更高的 [NodeJS 兼容性](https://developers.cloudflare.com/workers/runtime-apis/nodejs/) 和更高的 [限制](https://developers.cloudflare.com/workers/platform/limits/) 为实现更雄心勃勃和复杂的应用打开了大门,这在之前是完全不可能的。
我们的无头浏览器自动化 API 产品 [Browser Run](https://developers.cloudflare.com/browser-run/) 随着 AI 的崛起也经历了巨大的增长。代理需要浏览器来执行许多任务,许多情况下没有浏览器就无法成功。
但问题是——像 Chromium 这样的浏览器引擎是为人类构建的,而不是代理,它们的开销是 AI 模型根本不需要的。它们消耗了大量内存和计算资源,为每个代理提供自己的实例是极其昂贵的,这使得许多网络的广泛使用仅限于最复杂和昂贵的 AI 模型以及高参数知识,而禁止了许多其他代理应用的使用。
我们应该为所有代理提供一个在 AI 模型重要性方面表现突出的浏览器,即使这意味着对人类只是提供一些有用的特性。例如:
- AI 不在乎选项卡、主题、浏览器扩展或设备间同步。它关注的是令牌数量、上下文窗口、可扩展性、性能和成本。
- 结构化的、机器可读的内容很重要,但视觉完美和流畅的 60fps 滚动并不重要。如果 CSS 解析稍有偏差或者渲染不是精准的,代理也完全可以应对。
- AI 使用浏览器的威胁模型是不同的,新的问题如提示注入和工具安全是重中之重。
面对这些认知,12 周前我们再次问了这个问题:我们应该自己构建一个浏览器吗?这次答案是一致的:**是的!**
今天,我们宣布 **Kitesurf**,这是一款完全建立在 Workers 之上的新浏览器,专为代理构建,当前处于测试阶段,免费提供 [Browser Run](https://developers.cloudflare.com/browser-run/)。
Kitesurf 在 CPU 和内存消耗方面比 Chromium 更高效,特别是常见的代理任务,例如截图和 HTML 提取。接下来是我们构建它的故事。准备好,它会变得技术性十足——但我们保证会让它有趣。
## 开始的地方
Kitesurf 的起源和许多其他伟大想法一样,来自 Cloudflare。有人发现了有趣的事情,接下来他们就以一个看似不可能但非常吸引人的想法让团队陷入纠缠。
我们最初的灵感来自 [obscura](https://github.com/h4ckf0r0day/obscura),这是一个用 Rust 编写的无头引擎,用于 AI 自动化,没有 "Chrome、没有 Node.js、没有依赖"。
然后,在 AI 代理的帮助下,我们尝试将其移植到 Workers 中,起初并不太成功。但当我们为 AI 提供一个清晰的计划以及成功的详细定义,让代理能够在需要时不断循环并提出问题后,它就成功了。
被这种(几乎)可行的概念证明震惊后,我们决定让团队继续努力。
### 设计决策
我们在开始之前做出了一些设计决策。
**测试、测试、再测试**
我们知道,从原型转变为真正可以在生产环境中有效执行任务的全功能浏览器需要大量工作和迭代。我们不会隐瞒,利用 AI 加速这一过程是关键。但如何在如此复杂的项目中使用 AI,同时保持代码和结果的质量不下降且不失去速度呢?答案是提供尽可能多的测试。
引入 [Web Platform Tests](https://github.com/web-platform-tests/wpt) (WPT),这是理想的设置:一套广泛的成功标准让 AI 代理有明确的目标。我们策划了分配给代理的功能的选择和顺序,使得人类能够集中精力进行架构工作并审核代理的做法。
然而,WPT 测试也仅能到此为止:它们只衡量是否符合 [W3C](https://www.w3.org/) 标准,而不是浏览器是否能渲染和交互现实网站。为填补这一空白,我们实现了一种集成测试和视觉回归测试的组合——它在真实网站上运行多步骤的 [Puppeteer](https://pptr.dev/) 测试,对比 Chromium 和 Kitesurf 的断言,并在每一步渲染输出时高亮任何不想要的差异。
**尽量使用 Rust**
Cloudflare 一直在努力为 [WebAssembly (Wasm)](https://developers.cloudflare.com/workers/runtime-apis/webassembly/) 提供极好的支持。这很好,因为我们可以使用高性能的 C、C++ 和 Rust 包并将其编译为 Wasm。如果我们使用 Emscripten(例如)及其众多模拟的依赖关系,编译的二进制文件会变得臃肿缓慢。
相反,我们选择尽可能使用原生 Rust,并直接使用 [wasm-bindgen](https://developers.cloudflare.com/workers/languages/rust/#javascript-plumbing-wasm-bindgen) 编译,从而避免不必要的模拟层并尽可能接近底层, [可靠运行](https://blog.cloudflare.com/making-rust-workers-reliable/)。
**异常处理**
浏览器必须能够渲染整个不可靠甚至有时是敌对的网络,而不掉落任何正在处理的页面,因此异常处理不仅仅是规范——这是应用存活的方式,能够在遇到错误输入时存活而不崩溃。
因此,我们在一开始就承诺了一条规则:任何失败都降级为空白框架或个别缺失,绝不允许会话崩溃。在每个边界捕获错误,默认回退到安全和空的状态,并记录足够的信息来进行故障排除。
**隔离**
与在笔记本电脑上运行浏览器(你访问的是信任的网站,可以接受资源共享)不同,代理面临的任务要求是任意的输入:来自任意源的代码。
因此,我们在假设每个页面载入都是不受信任的输入,且每个会话都是全新的基础上构建了这个浏览器。每个组件都是隔离的,仅能访问其功能所需的资源。
这一点与 Cloudflare Workers 的 [安全模型](https://developers.cloudflare.com/workers/reference/security-model/#isolation) 完美吻合,Workers 的设计本身就围绕隔离而构建。但是,该平台只为隔离提供边界。我们仍然需要在应用级别执行相同的原则,决定每个组件被允许接触哪些资源,并确保没有信息泄漏。
**尽可能保持无状态**
状态是导致失败变得昂贵的原因——如果没有任何东西可以重建,从崩溃中恢复只需重新发起一个请求。无状态组件是一次性和并行的:遇到停滞时立即杀死它,同时运行成千上万个,并根据请求量调整其大小,而非保持状态温暖。对于瞬时加载的自动化而言这恰恰合适,因为负载呈波动状态,最便宜的解决方案就是激活仅在使用后消失的工作。总之,**无状态组件应该尽可能保持无状态**。
## 我们如何构建它
我们有了良好的计划、广泛的测试和良好的工具环境,可以开始超越最初的概念验证。Kitesurf 请求的生命周期至今仍然保持着高水平:
让我们深入探讨 Kitesurf 的三个主要组成部分:引擎、PageScript 和 PageRenderer。
### 从来源获取
为了渲染一个不受信任的网页,浏览器必须从互联网获取任意资产——图像、字体、CSS、JavaScript 和 Wasm 文件。这是浏览器可能进行的危险操作之一。
Kitesurf 通过一个单一的组件——SandboxOutbound Worker,来完成这一切,其他任何组件都无法直接接触网络,这是由动态 Workers 强制实施的。引擎使用它来引导页面,获取主文档及其脚本,PageScript 获取其他所有内容:样式表、图像、字体,以及页面自己的 fetch() 调用。
我们使用 SandboxOutbound 强制执行 [CORS](https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/CORS),插入浏览器形状的头部,过滤响应,并保持每个页面的 cookie 在各自的 jar 中。任何未通过我们策略的请求都将返回 403——每个组件仅获得所需的网络而无其他。
### 引擎
引擎是 Kitesurf 唯一面向公众的组件。它处理 Chrome DevTools 协议 (CDP) 的 WebSocket 和 HTTP REST API,提供一个用于内部测试的着陆页,最重要的是,存储每个会话的状态。其他所有组件都是无状态的。
使用 CDP 的好处是客户端兼容性:[Puppeteer](https://developers.cloudflare.com/browser-run/puppeteer/)、Playwright、chrome-remote-interface 以及实际的 Chrome DevTools 前端。指向 Kitesurf 时它们都能正常工作。这也是 Browser Run [如何运作](https://developers.cloudflare.com/browser-run/cdp/)(更详细的内容稍后再谈)。
有趣的是,引擎实际上是 Kitesurf 组件中最简单的部分,接下来的部分才有趣。
### PageScript
PageScript 很好地展示了我们新 Workers 功能的强大:在这种情况下,是 [动态工作者](https://developers.cloudflare.com/dynamic-workers/)。 Kitesurf 在没有此功能时根本无法实现。
以下是 PageScript 内部工作原理的简化示意图。
每个新页面或进程外 iframe ([OOPIF](https://www.chromium.org/developers/design-documents/oop-iframes/)) 都使用动态工作者启动一个长生存的 PageScript isolate,处理由干净的 [globalThis](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/globalThis) 和 [DOM](https://developer.mozilla.org/en-US/docs/Web/API/Document_Object_Model) 文档对象组成的页面会话。
DOM 对象随即填充解析 HTML 文档的结果,并运行所有 JavaScript 脚本。用于解析 HTML 和 CSS 的部分来自于 [Blitz](https://github.com/DioxusLabs/blitz)(一个模块化渲染引擎)和 Firefox 的 [Stylo](https://github.com/servo/stylo)(高性能 CSS 解析器),它们都用 Rust 编写。
对于每个找到的 <script> 标签或 .wasm 文件,我们都在同一 isolate 中运行 JavaScript 和 WebAssembly 代码。
**但是,关于评估**
你问起 eval,确实比较棘手,因为出于 [安全原因](https://developers.cloudflare.com/workers/runtime-apis/web-standards/#javascript-standards) 我们仍然不支持 Workers 中的 eval。如果我们再开启另一个 isolate 来处理它们,它将无法访问 globalThis。
我们的解决方案是使用 [Boa JS](https://boajs.dev/),这是一个用 Rust 编写的 ECMAScript 引擎,编译并在 Workers 上运行。我们实际上是在一个运行时的基础上执行另一个运行时,这看起来并不理想,确实不是,但它足够好来处理我们代码中偶尔出现的 eval。未来,当 Workers 中本地支持 eval 到位时,我们将从 Boa 切换出去。
### PageRenderer
这个组件主要负责从计算出的页面对象生成实际的像素。它的工作原理如下:
PageRenderer 在一个与引擎 Worker 的循环中工作。每当引擎需要一帧时,PageRenderer 会从 PageScript 获取页面对象(也称为场景),提取内部字体和图像,来自 [静态资产](https://developers.cloudflare.com/workers/static-assets/) 的资源,将其栅格化为图像缓冲区,随后返回给引擎,以客户端可以显示的格式,如 JPEG/PNG 或 PDF。
这里的魔力很大一部分由另一个 Blitz 模块 [blitz-paint](https://github.com/DioxusLabs/blitz/tree/main/packages/blitz-paint) 处理,它使用 Parley 来形成字符、选择字体并将文本分成行。
**Workers 内置 RPC 系统:相同的应用程序,多重 isolates**
Cloudflare Workers 具有 [内置的远程过程调用 (RPC) 系统](https://blog.cloudflare.com/javascript-native-rpc/),支持调用其他 Workers 上的方法,传递对象以及调用这些对象的方法。你无需担心 API 模式、类型或认证,只需调用 remoteFunction(...params),它便能工作。
Kitesurf 利用这个 RPC 系统:引擎 Worker 通过 RPC 调用 PageRenderer Worker 的 renderFrame() 方法,使用一次性调用获取 PNG 作为结果。由于渲染器不持有页面状态(仅持有一次性缓存),引擎可以安全地终止并重新启动任何失败或卡住的 RPC 调用——使每个渲染请求都成为自包含、可重试且其 isolate 成本便宜、可随时抛弃。
## Kitesurf 通过 215,000+ WPT 测试并在于不断发展
Kitesurf 运行良好。目前已经通过了大约 215,000+ [WPT 测试](https://github.com/web-platform-tests/wpt),并且我们每周都会增加数百个通过的测试。从我们项目开始到现在,下面是随时间的演变:
值得注意的是,浏览器中对代理重要的部分(如 CSS、DOM、HTML、选择、SVG 和 XHR)已经有了良好的覆盖。甚至在代理上下文中可能并不重要的流,现在也得到了合理支持。
性能方面,Kitesurf 表现良好。下面是对比 Chromium 和 Kitesurf 在 [Browser Run](https://developers.cloudflare.com/browser-run/quick-actions/) 中执行五次快速行为的中位数结果,在一个 [14-URL corpus](https://kitesurf.cloudflare.app/corpus.txt) 中进行比较。
虽然 Chromium 在计时上稍占优势,因为 [JIT](https://en.wikipedia.org/wiki/Just-in-time_compilation) 首次遇到此页面时总会比冷软件渲染器快——目前速度约为 1.7 倍。大部分的差距来自于栅格化和 JPEG/PNG 编码,而我们会持续优化这些部分。
但是,Kitesurf 在内存和 CPU 的使用上取得了 3-7 倍的优势,真正推动你的账单。更少的内存意味着我们可以运行更多会话,实现更好的扩展性,并从根本上降低我们的成本和你的成本。
### 最重要的测试:Kitesurf 运行 Doom
我们强调了测试在设计决策中的重要性,但我们都知道,无论有多少测试,项目在 Doom 运行之前都不算真正完成。下面是 Kitesurf 运行 [Doom 的实验](https://blog.cloudflare.com/doom-multiplayer-workers/) 为我们的 [silentspacemarine.com](https://silentspacemarine.com/) 展示的情景。
### 今天在 Browser Run 尝试
你可以通过 Browser Run 今天尝试 Kitesurf,**当前在测试阶段免费提供**,在……
---
原文链接:[点击查看](https://blog.cloudflare.com/kitesurf/)
评论
暂无评论。