LinkedIn 静默迁移了 ProseMirror 到 Quill — 并且破坏了所有触及 Composer 的浏览器自动化工具

发布: (2026年5月3日 GMT+8 15:34)
6 分钟阅读
原文: Dev.to

Source: Dev.to

崩溃

症状很具体。我的 MCP 服务器的 safari_fill 工具——它通过遍历 React Fiber 并调用 editor.commands.setContent(html) 来忠实地填充 ProseMirror——现在在触及 contenteditable 的瞬间就会崩溃辅助守护进程并关闭 composer 对话框。

相同的 composer URL。乍一看相同的 DOM 树。相同的选择器。底层编辑器不同。

DOM 说出了真相

我打开浏览器控制台,运行了常用的探测代码:

const el = document.querySelector('[contenteditable="true"]');
el.editor // -> undefined
el.closest('.ProseMirror') // -> null
el.closest('.ql-editor') // -> 

就在这里。.ql-editor 是 Quill 的标准类名。LinkedIn 在 2026 年初的某个时候将帖子编辑器从 ProseMirror 换成了 Quill,但我找不到任何公告。

为什么会崩溃

Quill 和 ProseMirror 一样,不允许你“随意”把文本塞进 contenteditable。两个编辑器都持有内部模型——Quill 称之为 Delta ——而 DOM 是该模型的下游。

如果绕过模型直接写入 DOM,会出现两件事:

  • 模型与 DOM 不一致。
  • 下一次用户触发的事件(键击、保存)会触发一次重新渲染,由于差异不一致而抛出异常。

这正是导致编辑器崩溃的原因。我的填充操作写入了 innerText,Delta 状态认为编辑器仍然是空的,React 树尝试调和,结果对话框消失。Swift 守护进程捕获了连锁异常并自行崩溃,以确保彻底结束。

修复方法:按 Quill 期望的方式驱动它

Quill 提供了一个编程 API。你只需要获取实例的引用。以下是我找到的查找顺序:

  1. 向上遍历,寻找具有 .ql-container 类的祖先元素。
  2. 尝试访问 .__quill —— Quill 2.x 会直接在此属性上挂载实例。
  3. 回退到 React Fiber:沿着 fiber 链向上遍历,查找 memoizedProps.quillstateNode.quill(LinkedIn 将 Quill 包装在一个 React 组件中,实例保存在 props 中)。
  4. 如果仍未找到,则回退到真实的 CGEvent Cmd+V 粘贴——Quill 会在 isTrusted: true 的剪贴板事件中响应。

获取实例后,实际的填充只需一行代码:

quill.setContents([{ insert: text + '\n' }], 'api');

'api' 源标记是关键所在。它告诉 Quill “这来自你的 API,更新模型并同步更新 DOM”。文本被提交,Delta 保持一致,React 父组件也不会因为模型损坏而尝试重新调和。

这件事让我对平台自动化的认识

编辑器并不是一个稳定的接口。 ProseMirror 和 Quill 有不同的 API、不同的状态模型,以及不同的“什么算作真实编辑”的规则。针对其中一个只能在平台仍然支持时有效。LinkedIn 在没有任何变更日志的情况下就换了编辑器。我唯一知道的就是我的代码坏了。

DOM 是最低的公共基准;编辑器模型才是实际的。 每个在 contenteditable 上合成事件的自动化工具都在真相之下的一个层次上工作。有时这样可以(因为编辑器会进行调和),有时不行(因为编辑器崩溃或悄悄丢弃输入)。更可靠的做法始终是找到编辑器实例并调用其 API。

第三个更令人不安的教训:我无法在 LinkedIn 上完全验证我的修复,因为 LinkedIn 在无头环境下的模态弹窗行为目前独立出现了故障。撰写按钮可以接受点击,弹窗的 DOM 会生成,但它从未在视觉上打开。因此 Quill 检测已经就位——并在测试页面上验证——但 LinkedIn 特定的实时路径仍受制于我尚未解决的另一个模态问题。

要点

如果你正在构建任何向第三方富文本编辑器(如 Slack、LinkedIn、Discord、Medium、Notion)输入内容的功能——编辑器的身份是你与平台之间合同的一部分,而平台并不保证其稳定性。请在运行时检测编辑器类型。为未知情况准备回退方案(理想情况下使用真实的剪贴板事件)。记录你检测到的结果,这样当编辑器发生变化时,你可以通过自己的遥测发现,而不是在晚上 11 点收到 Slack 消息。

在操作之前,先读取 contenteditable 的 class 列表。ProseMirror 和 Quill 有不同的类签名,DOM 会告诉你正在处理的是什么——前提是你去查询。

修复已在 safari-mcp@2.10.2 中发布。源码在 GitHub

0 浏览
Back to Blog

相关文章

阅读更多 »

Claude 运行快速。Codex 发布。

摘要:我给 Claude 和 Codex 两个大型编码任务。- Claude 大约在一小时内完成。- Codex 大约用了八小时。乍一看,这看起来像是……