You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

浏览器扩展开发:处理Lexical/ProseMirror类富文本输入框的可靠方案

问题背景

我正在开发一款浏览器扩展,帮助用户通过菜单选择快速插入常用字符串。对于标准<textarea>和<input>字段,直接设置xxx.value属性就能轻松替换内容,但遇到两类特殊场景卡壳了:

  • 使用Lexical作为输入框的网站(如kimi.com)
  • 使用ProseMirror的网站(如chatgpt.com)

针对ChatGPT,我找到临时方案:替换输入div的innerHTML为以下格式,但该方案在kimi.com完全无效:

<span>line 1</span>
<br />
<span>line 2</span>
<br />
...

我还尝试了模拟键盘事件的方法,但效果不稳定——同一网站的同一个div元素,有时能生效,有时完全没反应:

// Focus -> ArrowDown -> PageDown -> Backspace
// (ArrowDown -> PageDown -> Backspace) 用于清空div内容
// 我的场景里div总是只有一个字符,所以只按一次Backspace
const keySimu = [
  { key: 'ArrowDown', keyCode: 40 },
  { key: 'PageDown', keyCode: 34 },
  { key: 'Backspace', keyCode: 8 },
].map((ks) => new KeyboardEvent('keydown', {
  key: ks.key,
  code: ks.key,
  keyCode: ks.keyCode,
  which: ks.keyCode,
  bubbles: true,
  cancelable: true
}));
const inputEvent = new InputEvent('input', {
  inputType: 'insertText',
  data: content,
  bubbles: true
});
currentChatBoxRef.current.focus();
keySimu.forEach((ks) => {
  currentChatBoxRef.current.dispatchEvent(ks);
});
currentChatBoxRef.current.dispatchEvent(inputEvent);

已知execCommand已被废弃,想找一个可靠的通用方法处理这类基于div的富文本输入框。


可行解决方案

1. 剪贴板API模拟粘贴(最通用)

几乎所有富文本编辑器都支持粘贴操作,这是兼容性最强的方案:

async function insertTextToRichEditor(element, text) {
  // 保存原剪贴板内容,避免覆盖用户数据
  const originalClipboard = await navigator.clipboard.readText();
  
  try {
    // 将目标文本写入剪贴板
    await navigator.clipboard.writeText(text);
    // 聚焦目标输入框
    element.focus();
    // 模拟系统粘贴快捷键(适配Windows和Mac)
    const pasteEvent = new KeyboardEvent('keydown', {
      key: 'v',
      code: 'KeyV',
      ctrlKey: true,
      metaKey: true,
      bubbles: true,
      cancelable: true
    });
    element.dispatchEvent(pasteEvent);
  } finally {
    // 恢复原剪贴板内容
    await navigator.clipboard.writeText(originalClipboard);
  }
}

注意:浏览器扩展需在manifest.json中声明"clipboardWrite"和"clipboardRead"权限。

2. 针对特定编辑器的适配

如果通用粘贴方案有局限性,可以针对不同编辑器做针对性处理:

适配ProseMirror(ChatGPT)

ProseMirror维护独立内部状态,直接修改innerHTML会导致状态不一致,可尝试定位编辑器实例并调用内部API:

// 查找ProseMirror编辑器容器(不同网站结构可能不同)
function findProseMirrorInstance(element) {
  let current = element;
  while (current) {
    if (current.dataset?.prosemirrorEditor) {
      return current;
    }
    current = current.parentElement;
  }
  return null;
}

// 插入文本到ProseMirror
function insertToProseMirror(instance, text) {
  if (!instance) return;
  instance.focus();
  // 编辑器实例的属性名可能随版本变化,需自行验证
  const editor = instance._editor;
  if (editor) {
    editor.commands.insertContent(text);
  }
}

适配Lexical(Kimi.com)

Lexical对execCommand仍有较好兼容性,可利用这一点实现插入:

function insertToLexical(element, text) {
  element.focus();
  // 全选现有内容并删除
  document.execCommand('selectAll', false);
  document.execCommand('delete', false);
  // 插入目标文本
  document.execCommand('insertText', false, text);
}

虽然execCommand已被标准废弃,但多数主流富文本编辑器仍保留支持,作为临时适配方案足够可靠。

3. 优化键盘事件模拟的稳定性

如果坚持使用键盘事件方案,可通过添加延时确保事件顺序被编辑器正确处理:

async function insertTextWithKeySimu(element, content) {
  element.focus();
  // 等待聚焦完成
  await new Promise(resolve => setTimeout(resolve, 50));
  
  const keySimu = [
    { key: 'ArrowDown', keyCode: 40 },
    { key: 'PageDown', keyCode: 34 },
    { key: 'Backspace', keyCode: 8 },
  ];
  
  // 逐个触发事件,添加间隔确保编辑器处理完成
  for (const ks of keySimu) {
    const event = new KeyboardEvent('keydown', {
      key: ks.key,
      code: ks.key,
      keyCode: ks.keyCode,
      which: ks.keyCode,
      bubbles: true,
      cancelable: true
    });
    element.dispatchEvent(event);
    await new Promise(resolve => setTimeout(resolve, 20));
  }
  
  const inputEvent = new InputEvent('input', {
    inputType: 'insertText',
    data: content,
    bubbles: true
  });
  element.dispatchEvent(inputEvent);
}

通过延时避免事件堆积,能大幅提升稳定性。


内容的提问来源于stack exchange,提问作者adyingdeath

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.12 22:53:10