Browser Use Chrome 扩展逆向分析

OpenAI Codex vs Anthropic Claude — 2026-06-03

源码位置:~/daily/codex-chrome-ext/ · ~/daily/codex-chrome-bridge/ · ~/daily/claude-chrome-ext/

核心架构对比

维度 OpenAI Codex Anthropic Claude
扩展数量2 个(Codex 主扩展 + Chrome Bridge)1 个
通信架构Native Messaging + 本地 HTTP 轮询 (127.0.0.1:17171)Native Messaging + 云端 WebSocket (wss://bridge.claudeusercontent.com)
远程控制不支持(必须同机)支持(云端 relay,SSH 远程服务器可控制本地浏览器)
页面理解模糊元素匹配(text / aria-label / label / CSS selector)Accessibility Tree(结构化文本 + ref_id 映射)
截图chrome.tabs.captureVisibleTabCDP Page.captureScreenshot + 智能缩放(pxPerToken=28, maxTarget=1568)
可信点击CDP Input.dispatchMouseEvent(isTrusted=true)CDP Input.dispatchMouseEvent(isTrusted=true)
文字输入注入脚本设置 value + dispatch input/change 事件CDP Input.insertText
UI 反馈贝塞尔路径动画光标 + Favicon 徽章 + 弹簧物理引擎橙色光标 + 边框发光脉冲 + Stop 按钮 + 静态指示条
Side Panel无(依赖 Codex 桌面 App)内置完整 React 对话 UI
GIF 录制内置(offscreen document + gif.js)
Tab 管理Work Window 概念(pin 一个窗口)Tab Group 概念(chrome.tabGroups API)
SW 保活chrome.alarms(每 30s)Offscreen Document keepalive(每 20s)
定时任务chrome.alarms 定时执行预设 prompt

OpenAI Codex 架构

┌─────────────────────┐ │ Codex Cloud Agent │ (OpenAI 服务端 AI) └─────────┬───────────┘ │ HTTP API ┌─────────▼───────────┐ │ Codex Desktop App │ (本地桌面应用) │ + Bridge Server │ (监听 127.0.0.1:17171) │ + Native Messaging │ └──┬──────────┬───────┘ │ │ │ nativeMsg│ HTTP poll (1.2s) │ │ ┌──▼───┐ ┌──▼──────────────┐ │Codex │ │Codex Chrome │ │扩展 │ │Bridge 扩展 │ │(动画)│ │(实际控制) │ └──────┘ └──┬──────────────┘ │ chrome.scripting.executeScript │ chrome.debugger (CDP 1.3) ┌──▼──────────────┐ │ Target Page │ └─────────────────┘

扩展 1:Codex(主扩展)— 光标动画

ID: hehggadaopoacecdllhhajmbjkdcmajg

Manifest 权限
// manifest.json 核心字段 { "permissions": [ "alarms", "bookmarks", "debugger", "downloads", "downloads.ui", "favicon", "history", "nativeMessaging", "notifications", "readingList", "scripting", "sessions", "storage", "tabGroups", "tabs", "topSites" ], "host_permissions": ["<all_urls>"], "content_security_policy": { "extension_pages": "... connect-src 'self' http://127.0.0.1:* ws://127.0.0.1:* ..." } }
光标动画引擎(content-scripts/codex.js)

路径规划:生成多条贝塞尔候选曲线,通过评分函数选最优路径:

  • 路径长度、角度变化能量、最大角度变化、是否越界
  • 直线短距离(<196px)走 scoot 模式,长距离走贝塞尔
  • candidateCount=20 条候选路径

弹簧物理:所有动画属性(位置、旋转、缩放、透明度)用临界阻尼弹簧驱动:

// 弹簧参数结构 { dampingFraction: 0.9, response: 0.19 } // 位置弹簧 { dampingFraction: 0.9, response: 0.12 } // 旋转弹簧 { dampingFraction: 0.85, response: 0.2 } // 拉伸弹簧 { dampingFraction: 0.86, response: 0.42 } // 可见性弹簧 // 弹簧更新(固定时间步长 1/240s) function springStep(spring, omega, damping) { const halfDt = W / 2; // W = 1/240 const v = spring.velocity + spring.force * halfDt; spring.value += v * W; spring.force = v * -damping + (spring.target - spring.value) * omega; spring.velocity = v + spring.force * halfDt; }

"思考"动画:光标到达后做正弦波摇摆(振幅 12.5°,频率约 0.88Hz)

Favicon 徽章:动态生成 SVG 叠加到页面 favicon 上,三种状态:active(光标叠加)、deliverable(绿点)、handoff(黄点)

通信协议

主扩展通过 nativeMessaging 与 Codex 桌面 App 直连,仅做光标动画同步:

// App → 扩展:更新光标状态 { "type": "AGENT_CURSOR_STATE", "state": { "cursor": { "x": 500, "y": 300, "visible": true, "moveSequence": 42 }, "sessionId": "sess_abc", "turnId": "turn_123", "isVisible": true } } // 扩展 → App:光标到达目标 { "type": "AGENT_CURSOR_ARRIVED", "moveSequence": 42, "sessionId": "sess_abc", "turnId": "turn_123" }

扩展 2:Codex Chrome Bridge — 浏览器控制器

ID: bpdlgcaopbfacedplnkopcilkmodnmcc

轮询机制(background.js)
const DEFAULT_BRIDGE_URL = "http://127.0.0.1:17171"; const POLL_DELAY_MS = 1200; const RETRY_DELAY_MS = 3000; const HEARTBEAT_INTERVAL_MS = 5000; // 主循环 async function pollLoop() { while (true) { await sendHeartbeat({ status: "idle" }); const response = await fetch(`${bridgeUrl}/api/next?clientId=${clientId}`); const payload = await response.json(); if (!payload.command) { await sleep(POLL_DELAY_MS); continue; } const result = await runCommand(payload.command); await postResult(bridgeUrl, { id: payload.command.id, clientId, ok: true, result }); } } // API 端点: // GET /api/next?clientId=xxx — 获取下一条命令 // POST /api/result — 返回执行结果 // POST /api/client/heartbeat — 心跳上报 // GET /health — 健康检查
全部命令清单 + 实现方式
命令实现说明
clickCDP + 注入先注入找元素坐标 → CDP dispatchMouseEvent 可信点击 → fallback 注入 click()
doubleClickCDPclickCount=2
traceClickCDP点击 + 同时录制 Network/Console/Exception/Dialog
type注入脚本setNativeValue + dispatchEvent(input/change),支持 contentEditable
pressKey注入脚本KeyboardEvent keydown/keyup,支持修饰键
selectOption注入脚本操作 <select>,按 value/label/index 匹配
setChecked注入脚本设置 checkbox/radio 的 checked 属性
scroll / scrollIntoView注入脚本window.scrollTo / element.scrollIntoView
navigateChrome APIchrome.tabs.update({ url })
newTab / closeTabChrome APIchrome.tabs.create / remove
reloadChrome API支持 bypassCache,可等待加载完成
goBack / goForward注入脚本history.back() / forward()
listTabs / activateTabChrome API标签页管理
captureVisibleTabChrome API截图(png/jpeg)
uploadFilesCDPDOM.setFileInputFiles,真实文件上传
queryElements / listInputs注入脚本元素查询,返回结构化元数据
extractText / extractHtml注入脚本提取页面内容
waitForSelector / waitForText注入脚本轮询等待(200ms 间隔,默认 5s 超时)
runScript注入脚本new Function("args", expression) — 执行任意 JS
pinWorkWindow / createWorkWindowChrome API指定/创建工作窗口
元素查找机制(pageActionExecutor)

所有元素查找都走 resolveElements(matcher, options),支持多维度匹配:

// matcher 支持的字段 { selector: "button.submit", // CSS 选择器 candidateSelector: "...", // 候选元素范围 text: "提交", // innerText 包含 exactText: "提交订单", // innerText 精确匹配 label: "用户名", // ariaLabel/title/placeholder/label 任一包含 ariaLabel: "...", // aria-label 包含 title: "...", // title 属性包含 dataTestId: "login-btn", // data-test-id 精确匹配 iconName: "close", // Material icon 名称 role: "button", // ARIA role tagName: "input", // HTML 标签名 visible: true, // 可见性过滤 index: 0, // 多个匹配时取第几个 } // 关联查找(通过 label 找到对应 input) // 1. label[for="id"] → document.getElementById(id) // 2. label 内部的 input/textarea/select // 3. [aria-labelledby~="id"] 反向查找

可信点击流程

// 1. 注入脚本找元素 → 返回中心坐标 const target = await executeInTab(tabId, { action: "describeClickTarget", params }); // 2. CDP 发送可信鼠标事件(isTrusted=true) await chrome.debugger.sendCommand(target, "Input.dispatchMouseEvent", { type: "mouseMoved", x, y, button: "none" }); await chrome.debugger.sendCommand(target, "Input.dispatchMouseEvent", { type: "mousePressed", x, y, button: "left", clickCount: 1 }); await chrome.debugger.sendCommand(target, "Input.dispatchMouseEvent", { type: "mouseReleased", x, y, button: "left", clickCount: 1 }); // 3. 如果 CDP 失败,fallback 到注入脚本 element.click()
网络追踪(traceDebuggerActivity)
// 开启 CDP 监听 → 执行操作 → 等待 → 收集结果 async function traceDebuggerActivity(tabId, actionHandler, options) { return withDebugger(tabId, async (target) => { await chrome.debugger.sendCommand(target, "Network.enable"); await chrome.debugger.sendCommand(target, "Runtime.enable"); await chrome.debugger.sendCommand(target, "Page.enable"); chrome.debugger.onEvent.addListener(onEvent); await actionHandler(target); // 执行操作(如点击) await sleep(waitAfterMs); // 等待网络活动 // 收集 response body(仅 json/text/js/xml/html) return { network, console, exceptions, dialogs }; }); } // 监听的事件: // Network.requestWillBeSent / responseReceived / loadingFinished / loadingFailed // Runtime.consoleAPICalled / exceptionThrown // Page.javascriptDialogOpening

Anthropic Claude 架构

┌──────────────────────────┐ │ claude.ai Web App │ (对话 UI) │ wss://api.anthropic.com │ (AI 推理 + 流式对话) └──────────┬───────────────┘ │ externally_connectable(OAuth + ping) ┌──────────▼───────────────────────────────────┐ │ Claude Chrome Extension (单个扩展) │ │ ┌─────────────────────┐ │ │ │ Side Panel UI │ React app │ │ │ (完整对话界面) │ sidepanel.html │ │ ├─────────────────────┤ │ │ │ Service Worker │ 核心调度 │ │ │ ┌─────────────────┐ │ │ │ │ │ Native Messaging│ │ ← 本地直连 │ │ │ │ host 1: claude_browser_extension │ │ ← Claude Desktop │ │ │ host 2: claude_code_browser_extension │ │ ← Claude Code │ │ ├─────────────────┤ │ │ │ │ │ WebSocket Bridge│ │ ← 云端中继 │ │ │ │ wss://bridge.claudeusercontent.com │ │ │ │ └─────────────────┘ │ │ │ ├─────────────────────┤ │ │ │ Content Scripts (3) │ │ │ │ ├ accessibility-tree│ → 所有页面 │ │ │ ├ agent-visual-indicator│ → 所有页面 │ │ │ └ content-script │ → claude.ai │ │ ├─────────────────────┤ │ │ │ Offscreen Document │ 音频 + GIF + 保活 │ │ └─────────────────────┘ │ └──────────────────────────────────────────────┘

ID: fcoeoabgfenejglbffodgkkbkcdhcgfn,版本 1.0.74

WebSocket Bridge 协议
// 连接 const ws = new WebSocket(`wss://bridge.claudeusercontent.com/chrome/${device_id}`); // 握手 ws.send(JSON.stringify({ type: "connect", client_type: "chrome-extension", device_id: "xxx", os_platform: "macOS", extension_version: "1.0.74", oauth_token: "...", // 或 display_name })); // 收到的消息类型 "paired" // 已与桌面端配对 "waiting" // 等待配对 "ping" / "pong" // 心跳 "peer_connected" // 桌面端上线 → 设置 MCP_CONNECTED=true "peer_disconnected" // 桌面端下线 "tool_call" // 执行工具 "pairing_request" // 配对请求 // tool_call 消息结构 { type: "tool_call", tool_use_id: "toolu_xxx", tool: "click", args: { /* ... */ }, target_device_id: "...", // 可选,多设备时定向 client_type: "desktop", // 调用方类型 permission_mode: "...", allowed_domains: ["..."], session_scope: { /* ... */ }, } // 返回结果 ws.send(JSON.stringify({ type: "tool_result", tool_use_id: "toolu_xxx", result: { content: [/* ... */] }, // 或 error: { content: [...] } }));
Native Messaging 协议
// 尝试连接两个 native host(按优先级) const hosts = [ { name: "com.anthropic.claude_browser_extension", label: "Desktop" }, { name: "com.anthropic.claude_code_browser_extension", label: "Claude Code" }, ]; // 连接验证:发 ping 等 pong(10s 超时) port.postMessage({ type: "ping" }); // 成功后发 get_status 获取初始状态 port.postMessage({ type: "get_status" }); // 收到的消息类型: "tool_request" // { method: "execute_tool", params: { tool, args, client_id, session_scope } } "status_response" // 状态回报 "mcp_connected" // MCP 已连接 "mcp_disconnected" // MCP 已断开 // 返回结果 port.postMessage({ type: "tool_response", result: { content: "..." }, // 或 error: { content: "..." } }); // 权限拒绝时附加特殊提示: "IMPORTANT: The user has explicitly declined this action. Do not attempt to use" "other tools or workarounds. Instead, acknowledge the denial and ask the user..."
Accessibility Tree 生成(accessibility-tree.js)

注入到所有页面的 document_start,暴露全局函数:

window.__generateAccessibilityTree(filter, depth, maxChars, refId) // 参数: // filter: "all" | "interactive" // depth: 最大深度,默认 15 // maxChars: 输出最大字符数 // refId: 聚焦某个子树(如 "ref_42") // 输出示例: navigation [ref_1] link "首页" [ref_2] href="/" link "产品" [ref_3] href="/products" main [ref_4] heading "欢迎" [ref_5] textbox [ref_6] placeholder="搜索..." button "搜索" [ref_7] combobox [ref_8] option "选项A" (selected) value="a" option "选项B" value="b" // 元素引用映射(WeakRef 避免内存泄漏) window.__claudeElementMap = { "ref_1": WeakRef(element), ... } window.__claudeElementReverseMap = WeakMap { element → "ref_1" }

角色推断逻辑

// 优先使用 role 属性,否则从标签名推断 const roleMap = { a: "link", button: "button", nav: "navigation", input: /* 根据 type: submit→button, checkbox→checkbox, radio→radio, file→button, 其它→textbox */, select: "combobox", textarea: "textbox", h1-h6: "heading", img: "image", main: "main", header: "banner", footer: "contentinfo", section: "region", article: "article", aside: "complementary", table: "table", ul/ol: "list", li: "listitem", label: "label", };

敏感数据脱敏:password、cc-number、cc-csc 等自动替换为 [value redacted]

限制:最多 10000 个元素,超出后提示 truncated

全部工具 + CDP 命令
工具CDP 命令说明
screenshotPage.captureScreenshot智能缩放 + 移除其他扩展注入的元素
read_pageexecuteScript调用 __generateAccessibilityTree,返回结构化文本
clickInput.dispatchMouseEventmouseMove → mousePressed → mouseReleased
left_click_dragInput.dispatchMouseEvent拖拽(mousePressed → mouseMoved... → mouseReleased)
typeInput.insertText直接通过 CDP 插入文本
keyInput.dispatchKeyEventkeyDown + keyUp
scrollexecuteScriptwindow.scrollBy / element.scrollIntoView
navigatechrome.tabs.update导航到 URL
waitsetTimeout等待指定毫秒
tabs_contextChrome API获取 tab group 中所有 tab 信息
browser_batch组合批量执行多个 action
// 截图智能缩放算法 const config = { pxPerToken: 28, // 每个 token 对应的像素数 maxTargetPx: 1568, // 单边最大像素 maxTargetTokens: 1568, // 最大 token 消耗 }; // 二分搜索找到满足约束的最大分辨率 function computeOptimalSize(width, height, config) { const ratio = width / height; let lo = 1, hi = width; while (lo + 1 < hi) { const mid = Math.floor((lo + hi) / 2); const h = Math.round(mid / ratio); if (mid <= maxTargetPx && tokenCount(mid, h) <= maxTargetTokens) lo = mid; else hi = mid; } return [lo, Math.round(lo / ratio)]; }
视觉反馈(agent-visual-indicator.js)

虚拟光标:橙色 SVG 箭头,两层(白色描边 + 橙色填充),带发光 filter

// 光标样式 filter: drop-shadow(0 0 4px rgba(217,119,87,0.9)) drop-shadow(0 0 10px rgba(217,119,87,0.45)); // 移动动画:CSS transition(比 Codex 的弹簧物理简单很多) transition: transform 180ms cubic-bezier(0.2, 0, 0, 1);

边框发光

box-shadow: inset 0 0 15px rgba(217, 119, 87, 0.7), inset 0 0 25px rgba(217, 119, 87, 0.5), inset 0 0 35px rgba(217, 119, 87, 0.2); // 脉冲动画(2s ease-in-out infinite) @keyframes claude-pulse { 0%, 100% { opacity: 0.6; } 50% { opacity: 1; } }

消息驱动

"SHOW_AGENT_INDICATORS" // 显示光标 + 边框 + Stop 按钮 "HIDE_AGENT_INDICATORS" // 隐藏全部 "UPDATE_PHANTOM_CURSOR" // 更新光标位置 { x, y } "HIDE_FOR_TOOL_USE" // 执行工具前隐藏(避免截图中出现) "SHOW_AFTER_TOOL_USE" // 工具执行完恢复 "SHOW_STATIC_INDICATOR" // 显示"Claude is active in this tab group"指示条
Offscreen Document 功能

三个职责

  • Service Worker 保活:每 20s 发 SW_KEEPALIVE 消息,防止 MV3 的 30s 空闲杀死
  • 音频播放:Web Audio API 播放通知音
  • GIF 生成:用 gif.js 将截图帧合成 GIF,叠加点击指示器、拖拽路径、进度条、Claude 水印

复刻要点

最小可行版本(MVP)

预估工作量:2-3 天

  1. Chrome Extension(MV3)
    • permissions: debugger, scripting, tabs, activeTab, storage, nativeMessaging
    • host_permissions: <all_urls>
    • background service worker
  2. 本地 Bridge Server(Node.js / Python)
    • HTTP 或 WebSocket 均可
    • 命令队列 + 结果回传
    • 参考 Codex 的 /api/next + /api/result 模式最简单
  3. 核心工具
    • screenshot: CDP Page.captureScreenshot
    • click: CDP Input.dispatchMouseEvent
    • type: CDP Input.insertText
    • navigate: chrome.tabs.update
    • read_page: 注入 accessibility tree 生成函数

关键技术决策

页面理解:Accessibility Tree vs 元素匹配?

Claude 的 A11y Tree 更优:让 AI 直接读页面结构,通过 ref_id 操作,不需要 AI 构造 CSS 选择器。复刻时直接搬 accessibility-tree.js(仅 7KB,未混淆)。

截图:captureVisibleTab vs CDP?

CDP Page.captureScreenshot 更灵活(支持 clip 区域、format 参数、不需要 tab 在前台),推荐用 Claude 的方案。

通信:HTTP 轮询 vs WebSocket vs Native Messaging?

HTTP 轮询最稳(不怕 SW 被杀),WebSocket 延迟更低但需要重连逻辑。如果只做本地用,HTTP 轮询 + 1s 间隔够了。

SW 保活

必须处理。用 Offscreen Document(Claude 方案)或 chrome.alarms(Codex 方案)。

可直接复用的代码

Claude accessibility-tree.js(7KB,未混淆)→ ~/daily/claude-chrome-ext/unpacked/assets/accessibility-tree.js-CCweLwU2.js

Codex Bridge background.js(2270 行,未混淆)→ ~/daily/codex-chrome-bridge/unpacked/background.js,包含完整的命令执行器

Codex pageActionExecutor(Bridge background.js 内)→ 元素查找 + 事件派发逻辑,可直接搬

Claude agent-visual-indicator.js(17KB,未混淆)→ 光标 + 边框发光 + Stop 按钮,可直接复用

manifest.json 模板

{ "manifest_version": 3, "name": "Browser Use Agent", "version": "0.1.0", "description": "AI Agent Browser Controller", "permissions": [ "debugger", // CDP:可信点击、截图、文件上传 "scripting", // 注入脚本到页面 "tabs", // 标签页管理 "activeTab", // 当前标签页 "storage", // 持久化配置 "alarms", // SW 保活 + 定时任务 "offscreen" // 可选:音频、GIF、更好的保活 ], "host_permissions": ["<all_urls>"], "background": { "service_worker": "background.js", "type": "module" }, "content_scripts": [ { "js": ["accessibility-tree.js"], "matches": ["<all_urls>"], "run_at": "document_start", "all_frames": true }, { "js": ["visual-indicator.js"], "matches": ["<all_urls>"], "run_at": "document_idle" } ], "content_security_policy": { "extension_pages": "script-src 'self'; object-src 'none'; connect-src 'self' http://127.0.0.1:* ws://127.0.0.1:*;" } }

CDP 关键命令速查

// ===== 连接 ===== await chrome.debugger.attach({ tabId }, "1.3"); // ===== 截图 ===== const { data } = await chrome.debugger.sendCommand( { tabId }, "Page.captureScreenshot", { format: "png", quality: 80, clip: { x, y, width, height, scale: 1 } } ); // data 是 base64 编码的图片 // ===== 可信鼠标点击 ===== await chrome.debugger.sendCommand({ tabId }, "Input.dispatchMouseEvent", { type: "mouseMoved", x: 500, y: 300, button: "none", pointerType: "mouse" }); await chrome.debugger.sendCommand({ tabId }, "Input.dispatchMouseEvent", { type: "mousePressed", x: 500, y: 300, button: "left", buttons: 1, clickCount: 1, pointerType: "mouse" }); await chrome.debugger.sendCommand({ tabId }, "Input.dispatchMouseEvent", { type: "mouseReleased", x: 500, y: 300, button: "left", buttons: 1, clickCount: 1, pointerType: "mouse" }); // ===== 文本输入 ===== await chrome.debugger.sendCommand({ tabId }, "Input.insertText", { text: "Hello World" }); // ===== 按键 ===== await chrome.debugger.sendCommand({ tabId }, "Input.dispatchKeyEvent", { type: "keyDown", key: "Enter", code: "Enter", windowsVirtualKeyCode: 13 }); await chrome.debugger.sendCommand({ tabId }, "Input.dispatchKeyEvent", { type: "keyUp", key: "Enter", code: "Enter", windowsVirtualKeyCode: 13 }); // ===== 文件上传 ===== await chrome.debugger.sendCommand({ tabId }, "DOM.enable"); const { root } = await chrome.debugger.sendCommand({ tabId }, "DOM.getDocument", { depth: -1 }); const { nodeId } = await chrome.debugger.sendCommand({ tabId }, "DOM.querySelector", { nodeId: root.nodeId, selector: 'input[type="file"]' }); await chrome.debugger.sendCommand({ tabId }, "DOM.setFileInputFiles", { nodeId, files: ["/path/to/file.pdf"] }); // ===== JS 对话框处理 ===== chrome.debugger.onEvent.addListener((source, method, params) => { if (method === "Page.javascriptDialogOpening") { chrome.debugger.sendCommand(source, "Page.handleJavaScriptDialog", { accept: true, promptText: "response text" }); } }); // ===== 断开 ===== await chrome.debugger.detach({ tabId });