16.2 CDP 层实现

生成模型: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 替换为实际的远程地址。

CDP Socket 封装

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 确保能截取超出当前视口的内容。

JavaScript 执行

userGesture: true 很重要——某些浏览器 API(如 navigator.clipboard.writeText()Notification.requestPermission())要求在"用户手势"上下文中调用。通过此参数,CDP 模拟了用户交互环境。

标签页创建

注意这里使用的是浏览器级别的 WebSocket(通过 /json/version 获取),而非标签页级别的 WebSocket。Target.createTarget 是一个浏览器级别的 CDP 命令。


16.2.3 Target ID 管理与标签页操作

什么是 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 的持久连接列出页面,避免每次都建立新的短暂连接


16.2.4 认证与安全

CDP 通信中的认证通过 getHeadersWithAuth() 统一处理:

这支持三种认证方式:

  1. Chrome Extension Relay Token——通过自定义 header x-openclaw-relay-token 传递

  2. URL Basic Auth——http://user:pass@host:port/ 格式

  3. 无认证——本地 loopback 连接通常不需要


本节小结

  1. CDP 通信基于 JSON-RPC over WebSocket——每个请求/响应通过递增 id 配对,支持并发多路复用

  2. WebSocket URL 规范化处理远程连接的地址替换、协议升级、认证传递

  3. withCdpSocket() 是一次性连接模式——打开 WebSocket、执行操作、关闭,适合截图、JS 执行等短期操作

  4. 截图支持全页模式——通过 Page.getLayoutMetrics 获取完整内容尺寸,captureBeyondViewport 截取视口外内容

  5. JS 执行使用 userGesture: true——模拟用户交互上下文,解锁受限的浏览器 API

  6. Target ID 支持前缀模糊匹配——Agent 不需要记忆完整的 UUID,类似 Git 短 hash

  7. 三种认证方式:Extension Relay Token、URL Basic Auth、无认证

Last updated