一次 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 所在进程

三个要点:

  1. flatten: true 让子会话复用父连接,不用为每个 target 单开 WebSocket。
  2. 坐标是 iframe 局部坐标系,不是主页面视口坐标。这个坑很隐蔽——用视口坐标会偏。
  3. 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 会话。)

posted @ 2026-10-23 00:38  随风如影  阅读(10)  评论(0)    收藏  举报