Repository navigation
[Bug] 输入法上屏后粘贴多行文本只剩第一行:insertText 走 CONTROLLED_TEXT_INSERTION_COMMAND 被 Lexical 吞段(附复现脚本与零改动修复) #8823
Replies: 1 comment
你指的路径与我们代码里的粘贴路径不是同一条——这个区分对你有利1. 我们的粘贴路径是可核的
editor.registerCommand(PASTE_COMMAND, (event) => { … handlers.pasteText(text) … }) // :183以及 而你现在报的是 这个区分很重要:它说明你遇到的不是"我们的粘贴处理写错了",而是"上屏插入这条路径我们没有接管"。建议在正文里明确写出这一点——否则容易被读成"粘贴功能坏了",从而找错方向。 2. 一条我此前已核实的相关事实(对你有用)在多个版本上我都检索过:发布出去的客户端里没有 因此修法只有两个方向(建议在报告里写明你主张哪一个):
你写的是"零改动修复"——如果它是配置/开关层面的(例如改用另一条 insertion 命令、或给编辑器加一个 option),请把那个开关具名写出;若它其实是"补一小段 3. 这是第四个中文输入法/composer 报告了
⇒ 建议在正文里点出这个群(你这台是"真机 Android WebView + 用户实测复现",价值很高)。四条合起来足以说明"组字/上屏期间的多段文本插入"是一类系统性问题,而不是四个孤例——这比单独立案更容易被排期。 4. 版本口径你写的是 一条边界我确认的是我们的粘贴路径是 |
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
[Bug] 中文输入法上屏后粘贴多行文本只保留第一行:
beforeinput insertText走CONTROLLED_TEXT_INSERTION_COMMAND,Lexical 只写入首段(附复现脚本与零改动修复)环境
@deepseek-ai/dsh0.2.1-alpha.1(cordis4.0.5-alpha.1)playwright-core驱动,移动 UA / 412×915 /isMobile/hasTouch)@deepseek-ai/dsh-client-ui-conversation的 composer(Lexical 编辑器)…/dsh-client-ui-conversation/lib/client.js(877842 B / 23304 行)一、症状
在输入框里先用中文输入法打字上屏几个字,然后粘贴一段多行长文本,粘贴内容只剩第一行,其余全部丢失。
反过来:直接粘贴(没有先打字上屏)同样一段长文本,完全正常,一字不少。
用户原话(DSHA App 内 Web 界面):粘贴长内容被截断;先打几个字再粘贴才出问题。
二、复现(最小步骤)
abc)—— 这一步必须走 IME composition(中文/日文/韩文输入法、或移动端软键盘候选词上屏);<<HEAD>>\n第二行);abc<<HEAD>>,第二行及其后所有行消失。对照(均不触发):不先打字直接粘贴;先打字后粘贴单行文本;用
Ctrl+V/ 右键粘贴菜单(走paste事件)。三、边界矩阵(实测,6 用例)
E系列全部在同一个 composer 会话内、按序执行。payload 为多行等长行,L(n) = n*60 + 17。失败用例终态完全一致:
DOM len=11、paras=1、文本"abc<<HEAD>>"—— 即「composition 上屏的 3 字 + 粘贴内容的第一行」,第一行之后全部丢失。时间维度排除:粘贴后按 250ms 步长采样 8 秒,长度全程稳定在 11,不是延迟补全,是同步截断。等待 3 秒、重新聚焦均不能解除 ⇒ 状态残留,非时序问题。
原生对照(关键):同样序列打在原生
<div contenteditable>上 →len=29519且尾标记在,原生不受影响;Lexical 上「无 composition 前置」的纯insertText20k →len=30017无损 ⇒ 与浏览器 / WebView 无关,锅在 composer 自身。四、机制(事件序列取证)
探针监听
beforeinput / input / compositionstart / compositionend / paste,F1为「上屏3字 → insertText 200 字符(4 行)」:F2(无 composition 对照)只有一条beforeinput:insertText data=200 comp=false prevented=false dom=0→DOM len=200 paras=1,全文正常。F3(同会话连做两次):第二次仍重复同样的碎片序列、终态仍len=11⇒ 同一会话内永久性状态污染,无法自愈。三例
localStorage中均无草稿 key(draft keys: [])⇒ 与草稿持久化无关。五、根因(源码定位)
@deepseek-ai/dsh-client-ui-conversation/lib/client.js,dsh0.2.1-alpha.1:1)
beforeinput主分发(:10306)je$2=CONTROLLED_TEXT_INSERTION_COMMAND(:10049)。注意判断是精确相等"\n" === c,所以含换行的整段文本不命中前两个分支,整段(含\n)被交给je$2。对比:真正的粘贴事件走另一条路(
:10335)⇒
insertText通道(CONTROLLED_TEXT_INSERTION_COMMAND)会吞段,paste通道(PASTE_COMMAND)不会。 这正是「直接粘贴正常、输入法上屏后粘贴异常」的分叉点:Android WebView 的粘贴菜单与输入法commitText都走InputConnection.commitText→ 等价insertText,而不是paste事件。2)不一致判定器
Bn(t, e, n, o, r, i)(:10155)返回
true表示「DOM 与 model 不一致、需要走 Lexical 自己的命令」。返回表达式中与 composition 强相关的两个子句(:10164):a.isDirty()—— composition 上屏必然把节点标脏;_ = n.length—— 多行文本长度远大于 1。两者同时为真 ⇒
Bn返回true⇒ 走je$2,随后 Lexical 的insertText实现按\n拆段时只把第一段写进 model,其余段被吞。DOM 侧随后出现的那串data=60 / data=0交替的input事件都发生在dom=11不变的状态下,是 model 已被截断之后的空转。3)为什么「无 composition 前置」不触发:节点未
isDirty(),Bn走不到那条子句,insertText正常按段写入。六、修复方案
方案 A(零宿主改动,插件侧兜底,已验证)
在 composer 的
beforeinput上装一个 capture 期守卫:命中「insertText+data含换行 + 落在 Lexical 编辑器中」时,preventDefault()+stopPropagation(),再用DataTransfer造一个ClipboardEvent('paste')派发回去,把同一段文本改投paste通道(即上文Je$2那条不吞段的路径)。核心逻辑(约 30 行,可直接放进任意客户端插件):
实测(装上守卫后,10 用例全过)
len=7只剩首行;守卫后len=32全文保留G9 是本 bug 的对照实证:同一页面、同一序列,装守卫前
{"len":7,"txt":"abc你好世界"}(只剩第一行),装守卫后{"len":32,"txt":"abc你好世界\n第二行\n第三行\n第四行\n第五行\n<<TAIL>>"}。不回归:普通打字、Shift+Enter 软换行、真
Ctrl+V(paste事件路径)、无 composition 的纯insertText路径全部行为不变。方案 B(改宿主,一行级)
在
:10306的insertText分支里,把「含换行的多行文本」也送进paste通道,而不是CONTROLLED_TEXT_INSERTION_COMMAND:更保守的等价修法:在
Bn(:10164)里把a.isDirty() && _ > 1收窄成a.isDirty() && _ > 1 && !a.isComposing()之外再补「且文本不含换行」,让多行文本不走 Lexical 自己的 insertText 命令。七、验证方法
复现脚本(
playwright-core,Chromium,移动 UA;需一个带 token 的 dsh 实例):判据:
len === payload.length且tail === true。修复前len=11 tail=false;修复后(方案 A)len=620 tail=true。八、已知不确定项
Input.insertText可稳定复现;真机上由 Android WebViewInputConnection.commitText触发,两条路径在本 bug 上等价(均为insertText而非paste),但真机软键盘厂商差异(搜狗 / Gboard / 系统输入法)未逐一覆盖。Bn返回true只是必要条件,insertText命令内部「按\n拆段只写首段」的确切代码路径未逐行读出(je$2的 handler 在压缩后的 bundle 内,未展开定位);本文的机制结论以事件序列 + 分支命中为证据。e.dataTransfer构造在不同浏览器下DataTransfer可用性不同,故方案 A(页面内守卫)作为主推。0.1.x/0.2.0-rc.*)上是否同样存在 —— 本报告基于0.2.1-alpha.1实测。All reactions