跨域 iframe 里的自动化:用 CDP 绕过同源限制

举报
yd_230653297 发表于 2026/11/04 01:10:07 2026/11/04
【摘要】 父页面碰不到 iframe 的 DOM,埋点还要靠 iframe 回传。记录用 CDP 的 frame 树与隔离世界进入跨域 iframe、以及为什么必须从父页面上下文操作,附坐标路由与真实事件的坑。

问题是怎么冒出来的

最近在做一套云端开发环境的自动化。主控页面在 devstation.example.com,真正干活的应用跑在另一个域名的 iframe 里(5173-<id>.workspace.example.com)。

按老习惯,直接 document.querySelector 找输入框、.click() 点按钮。结果一行都跑不通:

// 在父页面执行
document.querySelector('iframe').contentDocument   // null
document.querySelector('iframe').contentWindow.document  // 直接抛 SecurityError

跨域隔离。父页面拿不到 iframe 里的 DOM,postMessage 又要对方配合监听,而我们面对的是一套打包好的第三方应用,改不了它的代码。

更麻烦的是:任务埋点在 iframe 里,它把事件 postMessage 给父页面,父页面再上报。也就是说,光在独立标签里把应用跑通是不够的——没有父页面,埋点就断了。实测确实如此:应用里对话正常、响应完整,服务端的任务状态依然是未完成。

CDP 的做法:从浏览器层面进 frame

浏览器调试协议里,frame 是一等公民。不需要同源,因为它本来就在内核层。

三步:

# 1. 拿整棵 frame 树
tree = cdp('Page.getFrameTree')
# 遍历 childFrames,按 url 找到目标 iframe

# 2. 给这个 frame 建一个隔离世界(拿到独立的 executionContextId)
ctx = cdp('Page.createIsolatedWorld', {
    'frameId': frame_id,
    'worldName': 'hermesIsolated',
    'grantUniveralAccess': True,
})

# 3. 带 contextId 执行 JS,DOM 就在你手里了
cdp('Runtime.evaluate', {
    'expression': 'document.body.innerText',
    'contextId': ctx['executionContextId'],
    'returnByValue': True,
    'awaitPromise': True,
})

createIsolatedWorld 是关键。它给你一个独立的 JS world,和页面自己的 world 共享 DOM,但不共享变量。好处有两个:

  • 不污染页面的全局变量,React/Vue 不会因为多了一份全局而犯迷糊;
  • `grantUniveralAccess: True` 让这个 world 能访问跨域 frame 的内容——正是我们需要的。

拿到 contextId 之后,读 DOM、改 value、点按钮、派发事件,和在普通页面里没有任何区别。

输入事件要在谁身上发

这里有个坑:Runtime.evaluate 能操作 iframe 的 DOM,但 Input.dispatchKeyEvent / Input.dispatchMouseEvent 是页面级的,发出去之后由浏览器按坐标和焦点路由。

所以真实键盘/鼠标操作要这样做:

  1. 在 iframe 的上下文里量出目标元素的坐标(`getBoundingClientRect`);
  2. 用父页面的 CDP 会话发 `Input.dispatchMouseEvent`,坐标直接用那个值。

前提是 iframe 从 (0,0) 铺满视口——这时 iframe 内坐标和页面坐标一致。如果 iframe 有偏移,就加上 iframe.getBoundingClientRect() 的 x/y。

我们这次正好是铺满的,省了一步。点进去之后焦点落在 iframe 里的 contenteditable 上,接着 Input.insertText 直接生效,回车就能发送。

为什么非得用真实事件

很多富文本 / 聊天输入框是这么长起来的:

  • 输入框是 `contenteditable`,不是 `textarea`;
  • 值由框架的响应式状态托管,直接改 `innerText` 不会触发状态更新;
  • 发送按钮绑在 `keydown` 上,判断 `Enter` 且非 `Shift`。

这时候 .click() 和直接赋值都是白搭,只有真实事件序列(keyDown → char → keyUp)才会走完整链路。判断方法很简单:操作完读一次内部状态(字数统计、DOM 文本长度、按钮状态),没变就是事件没到位。

顺手记几个坑

现象 原因 处理
`contentDocument` 返回 null 跨域 iframe 走 CDP frame,别想着注入
frame 树里找不到 iframe 加载还没完成 轮询 frame url,等出现再操作
坐标点了没反应 iframe 有偏移 加上 iframe 的 rect.x/y
在 iframe 里操作了但埋点没上报 没有父页面 必须从父页面的 frame 上下文里操作
隔离世界读不到页面变量 设计如此 只操作 DOM,别读 `window.xxx`

小结

自动化卡住的时候,先分清是「权限不够」还是「层次不对」。同一个浏览器里,跨域不是权限问题,是你站在了错误的执行层。

CDP 把执行层暴露出来之后,跨域 iframe 就不再是墙:Page.getFrameTree 找到它,createIsolatedWorld 拿到入口,剩下的和在自家页面上写脚本没有区别。

真正要留意的反而是另一半——业务埋点靠什么链路触发。把功能跑通只是及格,跑通的位置不对,数据照样不会记在你头上。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。