跳到主要内容

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弹窗/对话框
CaptchaWatchdogCAPTCHA 等待/处理
DownloadsWatchdog下载跟踪
CrashWatchdog / LocalBrowserWatchdog崩溃检测 / 本地浏览器生命周期
SecurityWatchdog / PermissionsWatchdog / StorageStateWatchdog域名安全、权限、登录态

它们在 BrowserSession.attach_all_watchdogs(browser/session.py:1608)统一挂载。start()(browser/session.py:719)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:2377,优先查 _cached_selector_map)。这正是 §02 序列化时建的那张表。
  • 查不到就兜底:_click_by_indexnode is None,返回「Element index 14 not available — page may have changed」(tools/service.py:712),不抛崩溃——这就是为什么页面变了点到旧编号时 agent 能继续。
  • <select> 有特判:如果点到下拉框,watchdog 返回 validation_error,_click_by_index 自动改调 dropdown_options 把选项列出来当快捷帮助(tools/service.py:736)。
  • 新标签页探测:点击前后比对标签页集合,检测是否开了新 tab(tools/service.py:720749)。
  • 高亮:点击前非阻塞地高亮该元素(highlight_interaction_element,tools/service.py:723),方便人观察。

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:1563)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:1038),等浏览器自动解完 CAPTCHA,并把结果作为一条 ActionResult 注入上下文让 LLM 知情。CAPTCHA 处理对 LLM 是透明的(prompt 里也明说「别自己解 CAPTCHA」)。

4.8 代码地图

主题文件符号
浏览器总线browser/session.pyBrowserSessionevent_busResilientEventBus
index→node 查表browser/session.pyget_element_by_index_cached_selector_map
发起点击事件tools/service.py_click_by_index(dispatch ClickElementEvent)
点击落 CDPbrowser/watchdogs/default_action_watchdog.pyon_ClickElementEventInput.dispatchMouseEvent
输入/滚动browser/watchdogs/default_action_watchdog.pyon_TypeTextEventon_ScrollEvent
挂载看门狗browser/session.pyattach_all_watchdogs