富文本编辑器写入失败:insertText 落到标题框的三层原因

举报
yd_228692151 发表于 2026/11/03 06:45:11 2026/11/03
【摘要】 富文本编辑器写入失败:insertText 落到标题框的三层原因 现象用 CDP 驱动一个博客/文档编辑器页面,流程看起来完全正常:点开编辑区、调用 Input.insertText 写入正文、再点提交。实际结果是标题变成了一整篇文章的第一句话,正文区域仍然是空的。$ node cdp.js typefile article.mdinserted 4821 chars$ node cdp....

富文本编辑器写入失败:insertText 落到标题框的三层原因

现象

用 CDP 驱动一个博客/文档编辑器页面,流程看起来完全正常:点开编辑区、调用 Input.insertText 写入正文、再点提交。实际结果是标题变成了一整篇文章的第一句话,正文区域仍然是空的。

$ node cdp.js typefile article.md
inserted 4821 chars

$ node cdp.js eval "document.querySelector('#blogTitle').value.length"
318
$ node cdp.js eval "document.querySelector('.editor-body').innerText.length"
0

Input.insertText 的语义是「把文本送进当前获得焦点的可编辑元素」。它不认选择器,只认焦点。所以「写错地方」这件事本身已经说明:以为点中的编辑区,根本没有拿到焦点。

下面三层原因,任一层没排掉,都会停在同一个症状上。

第一层:编辑模式不对,光标在富文本里

很多平台的编辑器默认是富文本(contenteditable + execCommand 风格),Markdown 只是可切换的第二种模式。在富文本模式下灌入 Markdown 源码,得到的是「字面文本」而不是结构:

  • # 标题 变成一段含有井号的普通段落
  • 三反引号围起来的代码块被合并成一行,缩进全丢
  • 列表符号 - 原样保留,但不成为列表

判定方式很直接——回读编辑器的 HTML 而不是纯文本,看有没有结构标签:

const ed = document.querySelector('[contenteditable="true"]');
({ html: ed.innerHTML.slice(0, 200), hasPre: !!ed.querySelector('pre') })

如果 hasPre 为 false 而源文件里明明有代码块,就说明模式不对。切模式一般是页面上一个 span.markdown 之类的 tab,用 el.click() 触发即可。

第二层:编辑区在跨进程 iframe 里,父页面根本没有焦点可给

切到 Markdown 之后,常见的新症状是:编辑区在视觉上存在,但父页面里查不到任何可编辑元素。

document.querySelectorAll('[contenteditable="true"], textarea').length
// 0

页面明明有输入光标闪动。这时要怀疑 iframe。两种 iframe 差别很大:

类型 表现 能否用父页面 document 访问
同源 iframe iframe.contentDocument 有值 能
跨域同进程 iframe contentDocument 为 null,但 iframe.contentWindow 可读取部分属性 不能
跨进程 iframe(OOPIF) /json/list 里作为独立 type: iframe 的 target 出现 完全不能

判定:

curl -s http://127.0.0.1:9222/json/list | jq -r '.[] | "\(.type)\t\(.url)"'

输出里如果有一行 iframe https://res-xxx.cdn.cn/EditorPortal/...,且它的 origin 与主文档不同,那就是 OOPIF。此时父页面里:

  • iframe.contentDocument 返回 null
  • 对编辑区坐标派发真实鼠标事件不会把焦点送进去
  • Input.insertText 仍然发给父文档里最后一个获得焦点的元素——在博客编辑器里,那正好是标题输入框

这就是「正文全进标题」的完整链路。typefile 本身没有 bug,它忠实地把字符送给了当前焦点。

第三层:直连 iframe target,用编辑器自己的 API

OOPIF 是独立 target,可以直接对它求值,不需要经过父页面:

WS=$(curl -s http://127.0.0.1:9222/json/list \
  | jq -r '.[] | select(.type=="iframe" and (.url|test("EditorPortal"))) | .webSocketDebuggerUrl')

连上这个 WS 后,先在它的上下文里找编辑器实例。这类编辑器通常把实例挂在 window 上,或者暴露一对读写方法:

Object.keys(window).filter(k => /editor|Editor|cm|quill|tinymce/i.test(k))
// ["editor", "setEditorContent", "getEditorContent"]

有专有 API 时优先用它,绕开焦点问题:

window.setEditorContent(markdownText)
window.getEditorContent().length   // 回读校验

没有专有 API 的(例如 CodeMirror 6)退回到「先聚焦再插入」:

const cm = document.querySelector('.cm-content');
cm.focus();
// 然后对这个 target 发 Input.insertText

注意 cm.focus() 必须在该 iframe 自己的执行上下文里调用。在父页面调用 iframe.focus() 只是聚焦 frame 元素,不会把 caret 放进 contenteditable,insertText 依旧落空。

写入后的三个回读校验

插入动作返回成功不代表内容正确。提交前回读三项,成本很低,能挡掉绝大多数「发出去才发现是空文」:

const s = window.getEditorContent();
({
  len: s.length,                         // 与源文件字符数同量级
  fences: (s.match(/^```/gm) || []).length,  // 代码块围栏成对出现
  newlines: (s.match(/\n/g) || []).length
})

判据:

  • len 与源文件长度差异超过 10%,说明有内容被吞(常见于 < > 被当 HTML 转义)
  • fences 应为偶数;为 0 说明代码块没被识别,模式还停在富文本
  • newlines 为 0 说明整篇被压成一行,是典型的富文本粘贴结果

提交前还要看按钮状态

正文 OK 之后,提交按钮往往仍是 disabled,因为条款没勾。这一条不属于编辑器问题,但症状很像「点了没反应」,容易误判回编辑器:

const b = document.querySelector('#submit-btn');
({ disabled: b.disabled, cls: b.className })

条款复选框常是 span 而不是 input,checked 属性不存在,得看 className 里有没有 checked 标记。勾完再回读一次按钮状态,disabled === false 才点。

小结式的判据表

症状 判定命令 结论
正文全进标题框 回读各输入框 value 长度 焦点在标题,编辑区没拿到焦点
结构丢失、代码块一行 回读 innerHTML 查 pre 模式是富文本,未切 Markdown
查不到 contenteditable querySelectorAll 返回 0 编辑区在 iframe 内
/json/list 有 iframe target 看 target 的 origin OOPIF,需直连求值
写入成功但内容为空 回读编辑器 getter 焦点未进入子 frame
提交按钮仍灰 回读 disabled 条款未勾,不是编辑器问题

整条链路的要点只有一个:Input.insertText 只认焦点,不认选择器。任何「写进去了但位置不对」的问题,都应当从「焦点此刻在哪个文档的哪个元素上」开始查,而不是继续调整点击坐标。

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

评论(0)

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

全部回复

上滑加载中

设置昵称

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

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

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