dootask/electron/lib/page-input.js
kuaifan a3d5bdf93c feat(ai-assistant): 重构页面操作为统一执行器(Web DOM floor + Electron CDP 双后端)
页面操作(doo page → /ws → 浮窗 operation-module → action-executor)原用手写
iView-only 选择器 + 朴素 value= 赋值,在 React+Radix 同源 iframe 业务表单上几乎
填不进。重构为「复用 Playwright 注入脚本的描述层 + 按环境选后端的执行层」:

描述层(共用):
- 新增 aria/aria-bundle.js,vendoring Playwright ariaSnapshot/roleUtils/domUtils
  子集预构建为 ESM(Apache-2.0 NOTICE + 固定上游 commit),产出 YAML 快照与活的
  ref→Element Map。
- page-context-collector 改用 buildSnapshot 采集,operation-module 经 setRefMap
  以 Map 直接持有元素取代 selector 反查。

执行层(按环境选后端,失败回退):
- web-backend:原生 prototype value setter + beforeinput/input/change/blur 序列
  + 回读校验(不符报 value_not_applied,绝不假报成功);自定义下拉/日期 open→click。
- electron-backend + electron/lib/page-input.js:经 webContents.debugger 走 CDP
  Input 域产生 isTrusted=true 可信输入(insertText/dispatchMouseEvent/KeyEvent),
  任一步失败回退 Web 后端。
- 复杂控件(动态明细表/成员选择器/文件上传)如实返回 unsupported_widget。

协议与链路(doo page、active-context 失效守卫、导航类 action)保持不变。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 02:03:59 +00:00

58 lines
2.3 KiB
JavaScript
Vendored
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// AI 助手页面操作 · CDP 可信输入(主进程)
//
// 渲染端经 window.electron.sendAsync('pageInput', {op, ...}) 调用preload 已有的
// invoke 通道);这里用 webContents.debugger 走 CDP Input 域,产生 isTrusted=true 的
// 可信输入,等价 Playwright 真正可靠的那一半,但第一方、在用户会话内,且同源 iframe 可达。
//
// 目标 webContents = event.sender发起的渲染进程同源 iframe 在其内,视口坐标可达)。
// 任一步骤失败抛错 → 渲染端 electron-backend 捕获后回退 Web 后端。
const { ipcMain } = require('electron')
const attached = new WeakSet()
function ensureAttached(wc) {
if (attached.has(wc)) return
if (!wc.debugger.isAttached()) {
wc.debugger.attach('1.3') // 抛错则上抛,渲染端回退
}
attached.add(wc)
wc.once('destroyed', () => attached.delete(wc))
}
async function dispatchClick(wc, x, y) {
const base = { x, y, button: 'left', buttons: 1, clickCount: 1 }
await wc.debugger.sendCommand('Input.dispatchMouseEvent', { type: 'mouseMoved', x, y })
await wc.debugger.sendCommand('Input.dispatchMouseEvent', Object.assign({ type: 'mousePressed' }, base))
await wc.debugger.sendCommand('Input.dispatchMouseEvent', Object.assign({ type: 'mouseReleased' }, base))
}
async function handle(event, args) {
const wc = event.sender
const op = args && args.op
ensureAttached(wc)
switch (op) {
case 'insertText':
// 覆盖当前选区(渲染端已 selectAll
await wc.debugger.sendCommand('Input.insertText', { text: String(args.text == null ? '' : args.text) })
return { ok: true }
case 'click':
await dispatchClick(wc, Math.round(args.x), Math.round(args.y))
return { ok: true }
case 'key': {
const key = String(args.key || '')
await wc.debugger.sendCommand('Input.dispatchKeyEvent', { type: 'keyDown', key })
await wc.debugger.sendCommand('Input.dispatchKeyEvent', { type: 'keyUp', key })
return { ok: true }
}
default:
throw new Error('unknown_op: ' + op)
}
}
function registerPageInput() {
ipcMain.handle('pageInput', handle)
}
module.exports = { registerPageInput }