一次 CDP 调试协议的实战:为什么你的鼠标事件进不了跨域 iframe
一次 CDP 调试协议的实战:为什么你的鼠标事件进不了跨域 iframe
记录一个浏览器架构层面的问题。表面看是"自动化点击失效",实际是 Chrome 进程模型和调试协议 target 体系的一次正面碰撞。

现象
用 CDP 的 Input.dispatchMouseEvent 在页面上按坐标派发鼠标事件,目标是一个滑块验证码。坐标经过截图核对,准确无误。
事件发出去了,Input.dispatchMouseEvent 返回成功。但页面上的滑块位置没有任何变化。
排除法做了几轮:
- 坐标错误?截图核对两次,排除。
- 元素被遮挡?
elementFromPoint返回的正是该元素,排除。 - 事件被
preventDefault?页面在捕获阶段没有拦截监听器,排除。 - 需要
isTrusted?CDP 的 Input 域事件本身就是 trusted 的,排除。
四次尝试,全部静默失败。
定位:target 体系
翻 Target.getTargets 的返回,能看到完整的调试目标清单。关键发现是这一条:
{
"targetId": "...",
"type": "iframe",
"url": "https://<跨根域>/static/template/drag_ele.html",
"attached": false
}
真实在跑的 UI,在一个独立的 iframe target 里。
这里要澄清一个常见误解:Page.getFrameTree 和 Target.getTargets 看到的 iframe 集合不是同一个。
Page.getFrameTree 返回的是同进程的 frame 树。跨根域的 iframe 走出进程(Site Isolation),它不会出现在 frame 树里,但会以独立 target 的形式出现在 Target.getTargets 的平铺列表里。
这两个 API 的差异,就是问题所在。
根因:事件路由跟着 session 走
CDP 的事件派发是会话(session)绑定的。
主页面是一个 session。跨域 iframe 是另一个独立 target,需要单独 Target.attachToTarget 才能拿到它自己的 session。
在主页面的 session 上调用 Input.dispatchMouseEvent,事件进入的是主页面所属的渲染进程的输入队列。而那个 iframe 在另一个进程——它的输入队列里什么都没收到。
所以是四次全静默,连失败判定都没有:事件根本没到。
正确路径
Target.getTargets
→ 过滤 type === 'iframe' 且 url 匹配的目标
Target.attachToTarget({ targetId, flatten: true })
→ 得到该 iframe 专属的 sessionId
Runtime.evaluate (sessionId=<iframe 的>)
→ 读写 iframe 内 DOM
Input.dispatchMouseEvent (sessionId=<iframe 的>)
→ 事件直达该 iframe 所在进程
三个要点:
flatten: true让子会话复用父连接,不用为每个 target 单开 WebSocket。- 坐标是 iframe 局部坐标系,不是主页面视口坐标。这个坑很隐蔽——用视口坐标会偏。
Runtime.evaluate在 iframe session 上执行时,window是该 iframe 的 window,主页面变量不可见。这是特性不是 bug。
一个更隐蔽的坑:坐标输入的两套路由
Site Isolation 不是简单地"跨域就走独立进程",它的判定单位是 eTLD+1(可注册域),不是完整域名。
举例说明差异:
| 主页面 | iframe | 同注册域? | 通道 |
|---|---|---|---|
| a.qq.com | b.qq.com | 是(都是 qq.com) | 同进程,主 session + 祖先视口偏移 |
| a.qq.com | c.com | 否 | 独立进程,需要独立 session |
后一种必须走独立 session。前一种如果也去 attach 独立 target,会 attach 失败——因为它压根没有独立 target。
所以实现上要做双通道 fallback:先试独立 target,找不到就回退到主 session 加祖先偏移。
顺带修掉的一个底层 bug
拖动过程中,Input.dispatchMouseEvent 的 mouseMoved 事件必须携带 buttons 位掩码。
位掩码的定义(W3C UI Events):
- 主键按下 =
1 - 右键按下 =
2 - 中键按下 =
4
拖动期间每一条 mouseMoved 都要带 buttons: 1。缺这个字段,页面侧读到的 e.buttons === 0,逻辑判断直接认为"左键没按住",整个拖动判定失败。
这个字段在多数自动化库里是自动补的,手写 CDP 派发时容易漏。
收尾
整件事的技术骨架就这么多:CDP session 隔离 + Site Isolation 进程模型 + 位掩码。
它跟反爬检测无关,跟"轨迹像不像人"无关。是个纯粹的架构匹配问题——你得知道事件该往哪个 session 发。
记录完毕。
(本文记录的问题与解法,已在开源工具 dyyz1993/xbrowser 中沉淀为可复用能力,支持在 xbrowser CLI 中直接通过 --frame 调度跨域 iframe 会话。)

浙公网安备 33010602011771号