数据截至 (上游 commit 898f23f0b672)
04 · 浏览器层:事件总线、看门狗与 CDP
本章讲最底层:动作并不直接戳浏览器,而是往一条事件总线上 dispatch 事件,由一组「看门狗」订阅处理,最终用 Chrome DevTools Protocol(CDP)把鼠标/键盘事件发给真实浏览器。我们把一次「点 14 号」从头追到尾。
4.1 它要解决的小问题
操作浏览器有一堆横切关注点:点击、输入、截图、处理弹窗、处理下载、检测崩溃、解 CAPTCHA、存登录态……如果全塞进一个大类会乱成一团。Browser Use 用事件驱动 + 看门狗解耦:每件事是一个事件,每个看门狗只管自己那摊。
4.2 直觉:浏览器是一条总线 + 一群专员
- 总线:
BrowserSession持有一个EventBus(browser/session.py:549,用bubus库的EventBus)。 - 专员(watchdog):每个 watchdog 订阅它关心的事件类型,用
on_<EventName>命名约定挂处理函数。
看门狗清单(browser/watchdogs/):
| watchdog | 管什么 |
|---|---|
DefaultActionWatchdog | 点击/输入/滚动等核心动作,直接发 CDP |
DOMWatchdog | 响应 BrowserStateRequestEvent,跑 DOM 序列化(§02) |
ScreenshotWatchdog | 截图 |
PopupsWatchdog | 弹窗/对话框 |
CaptchaWatchdog | CAPTCHA 等待/处理 |
DownloadsWatchdog | 下载跟踪 |
CrashWatchdog / LocalBrowserWatchdog | 崩溃检测 / 本地浏览器生命周期 |
SecurityWatchdog / PermissionsWatchdog / StorageStateWatchdog | 域名安全、权限、登录态 |
它们在 BrowserSession.attach_all_watchdogs(browser/session.py:1680)统一挂载。start()(browser/session.py:721)dispatch BrowserStartEvent,各 watchdog 的 on_BrowserStartEvent 响应启动浏览器/建 CDP 连接。
4.3 一次「点 14 号」端到端
LLM 输出 click(index=14)
│
① Tools._click_by_index(params, browser_session) tools/service.py:700
│ node = await browser_session.get_element_by_index(14)
│ ↑ 从 §02 缓存的 selector_map 查到真实 EnhancedDOMTreeNode
│
② browser_session.event_bus.dispatch(ClickElementEvent(node=node)) tools/service.py:727
│ await event (等处理完)
│
③ DefaultActionWatchdog.on_ClickElementEvent(event) watchdogs/default_action_watchdog.py:337
│ 解析 node 的几何/backend_node_id
│
④ cdp_session.cdp_client.send.Input.dispatchMouseEvent(...) default_action_watchdog.py:906
│ 真实浏览器收到一次鼠标按下/抬起
▼
返回 click_metadata(可能含坐标/新标签页信息)
几个细节:
- index→node 的查表在
get_element_by_index(browser/session.py:2449,优先查_cached_selector_map)。这正是 §02 序列化时建的那张表。 - 查不到就兜底:
_click_by_index若node is None,返回「Element index 14 not available — page may have changed」(tools/service.py:716),不抛崩溃——这就是为什么页面变了点到旧编号时 agent 能继续。 - 点
<select>有特判:如果点到下拉框,watchdog 返回validation_error,_click_by_index自动改调dropdown_options把选项列出来当快捷帮助(tools/service.py:740)。 - 新标签页探测:点击前后比对标签页集合,检测是否开了新 tab(
tools/service.py:724、749)。 - 高亮:点击前非阻塞地高亮该元素(
highlight_interaction_element,tools/service.py:727),方便人观察。
4.4 输入文字、滚动同理
同一个 watchdog 还处理 on_TypeTextEvent(default_action_watchdog.py:451)、on_ScrollEvent(default_action_watchdog.py:513)、on_ClickCoordinateEvent(default_action_watchdog.py:389,坐标点击),底层都是 CDP 的 Input.dispatchMouseEvent / 键盘事件。所以「动作」这一抽象在浏览器层统一落到 CDP。
4.5 感知也是事件
§02 提过:get_browser_state_summary(browser/session.py:1587)dispatch BrowserStateRequestEvent,由 DOMWatchdog 接住跑序列化。所以感知和行动都走同一条事件总线——这是理解整套架构的关键:Agent 几乎不直接碰浏览器,它只跟 BrowserSession 这条总线打交道。
4.6 韧性设计
ResilientEventBus(browser/session.py:106)重写了 EventBus,让在已拆除的总线上 step()/wait_until_idle() 变成 no-op 而非断言崩溃——注释解释这是为了 warm-Lambda 恢复场景(worker 可能在 dispatch 重启总线前就 step 了)。这是把框架往无服务器/长驻环境硬化的痕迹。
4.7 CAPTCHA 钩在主循环最前
值得一提:每个 step 的 Phase 0 会先 wait_if_captcha_solving()(agent/service.py:1040),等浏览器自动解完 CAPTCHA,并把结果作为一条 ActionResult 注入上下文让 LLM 知情。CAPTCHA 处理对 LLM 是透明的(prompt 里也明说「别自己解 CAPTCHA」)。