生成模型:Claude Opus 4.6 (anthropic/claude-opus-4-6) Token 消耗:输入 ~170k tokens,输出 ~5k tokens(本节)
上一节我们了解了浏览器控制的整体架构。本节深入 CDP(Chrome DevTools Protocol)层——OpenClaw 如何通过 WebSocket 与 Chrome 通信,执行低级浏览器操作。
16.2.1 CDP 连接管理
WebSocket URL 规范化
Chrome 的 CDP 接口通过 /json/version 端点暴露 WebSocket URL。但这个 URL 可能需要规范化处理:
// src/browser/cdp.ts — normalizeCdpWsUrl
export function normalizeCdpWsUrl(wsUrl: string, cdpUrl: string): string {
const ws = new URL(wsUrl);
const cdp = new URL(cdpUrl);
// 如果 ws 是 loopback 但 cdp 不是 → 替换主机名
// (远程 Chrome 可能返回 127.0.0.1 作为 ws 地址)
if (isLoopbackHost(ws.hostname) && !isLoopbackHost(cdp.hostname)) {
ws.hostname = cdp.hostname;
ws.port = cdp.port;
ws.protocol = cdp.protocol === "https:" ? "wss:" : "ws:";
}
// 协议升级:如果 cdp 是 https,ws 也应该是 wss
if (cdp.protocol === "https:" && ws.protocol === "ws:") {
ws.protocol = "wss:";
}
// 传递认证信息
if (!ws.username && !ws.password && (cdp.username || cdp.password)) {
ws.username = cdp.username;
ws.password = cdp.password;
}
// 传递查询参数
for (const [key, value] of cdp.searchParams.entries()) {
if (!ws.searchParams.has(key)) {
ws.searchParams.append(key, value);
}
}
return ws.toString();
}
这个函数处理了远程 CDP 连接的常见问题:Chrome 返回的 webSocketDebuggerUrl 通常是 ws://127.0.0.1:9222/...(因为 Chrome 只知道自己在本地),但如果 OpenClaw 通过反向代理或 SSH 隧道访问远程 Chrome,就需要将 URL 替换为实际的远程地址。
withCdpSocket() 是 CDP 通信的核心原语:
createCdpSender() 实现了 CDP 的 JSON-RPC 协议:
衍生解释:CDP 使用 JSON-RPC over WebSocket 协议。每个请求有一个递增的 id,服务端回复时带上相同的 id,客户端通过 id 将请求和响应配对。这是一个经典的请求-响应多路复用模式——一个 WebSocket 连接可以同时承载多个并发的请求/响应对。pending Map 存储了每个未完成请求的 Promise 回调。
16.2.2 CDP 辅助函数
全页截图的关键:先通过 Page.getLayoutMetrics 获取页面的完整内容尺寸(可能远大于视口),然后将该尺寸作为 clip 参数传入截图命令。captureBeyondViewport: true 确保能截取超出当前视口的内容。
userGesture: true 很重要——某些浏览器 API(如 navigator.clipboard.writeText()、Notification.requestPermission())要求在"用户手势"上下文中调用。通过此参数,CDP 模拟了用户交互环境。
注意这里使用的是浏览器级别的 WebSocket(通过 /json/version 获取),而非标签页级别的 WebSocket。Target.createTarget 是一个浏览器级别的 CDP 命令。
16.2.3 Target ID 管理与标签页操作
在 CDP 中,每个浏览器标签页(以及 Service Worker、iframe 等)都是一个 Target,由一个唯一的 targetId 标识。OpenClaw 使用 targetId 来定位和操作具体的标签页。
返回类型是一个判别联合(Discriminated Union):
衍生解释:Target ID 是 Chrome 内部生成的 UUID 格式字符串(如 8D3E4F7A-2B1C-4D5E-9F0A-1B2C3D4E5F6A)。在 Agent 与用户的对话中,传递完整的 UUID 不太方便。前缀匹配允许 Agent 只用前几个字符就能唯一定位一个标签页——类似 Git 的短 commit hash。ambiguous 结果意味着前缀匹配到多个标签页,需要更长的前缀。
标签页信息通过 CDP 的 /json/list 端点获取:
本地 vs 远程的处理差异:
本地:直接 HTTP 调用 /json/list,效率更高
远程:通过 Playwright 的持久连接列出页面,避免每次都建立新的短暂连接
CDP 通信中的认证通过 getHeadersWithAuth() 统一处理:
这支持三种认证方式:
Chrome Extension Relay Token——通过自定义 header x-openclaw-relay-token 传递
URL Basic Auth——http://user:pass@host:port/ 格式
CDP 通信基于 JSON-RPC over WebSocket——每个请求/响应通过递增 id 配对,支持并发多路复用
WebSocket URL 规范化处理远程连接的地址替换、协议升级、认证传递
withCdpSocket() 是一次性连接模式——打开 WebSocket、执行操作、关闭,适合截图、JS 执行等短期操作
截图支持全页模式——通过 Page.getLayoutMetrics 获取完整内容尺寸,captureBeyondViewport 截取视口外内容
JS 执行使用 userGesture: true——模拟用户交互上下文,解锁受限的浏览器 API
Target ID 支持前缀模糊匹配——Agent 不需要记忆完整的 UUID,类似 Git 短 hash
三种认证方式:Extension Relay Token、URL Basic Auth、无认证