LinkedIn 静默迁移了 ProseMirror 到 Quill — 并且破坏了所有触及 Composer 的浏览器自动化工具
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。你只需要获取实例的引用。以下是我找到的查找顺序:
- 向上遍历,寻找具有
.ql-container类的祖先元素。 - 尝试访问
.__quill—— Quill 2.x 会直接在此属性上挂载实例。 - 回退到 React Fiber:沿着 fiber 链向上遍历,查找
memoizedProps.quill或stateNode.quill(LinkedIn 将 Quill 包装在一个 React 组件中,实例保存在 props 中)。 - 如果仍未找到,则回退到真实的 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。