Chrome扩展实现WhatsApp Web消息前缀:Emoji消息失效问题求助
问题背景
开发的Chrome扩展通过content.js给WhatsApp Web发送的消息添加指定名称前缀(格式如「Thiago:\nHi」),纯文本消息正常,但含Emoji的消息无法添加前缀。
核心失效原因
keydown事件无法覆盖Emoji输入场景
代码中第一个keydown监听仅捕获单个字符的输入,但Emoji常通过输入法候选框、表情面板插入,这类操作不会触发符合event.key.length === 1的keydown事件,导致字符收集逻辑失效(虽未直接影响前缀添加,但会干扰内容跟踪)。Contenteditable元素的HTML结构与纯文本处理冲突
WhatsApp Web的编辑器会将Emoji包装在特定HTML节点(如<span>)中,而非纯文本。当前代码用editor.innerText获取内容会丢失HTML结构,再通过document.execCommand("insertHTML")插入纯文本前缀,会破坏编辑器原生格式,导致WhatsApp无法识别前缀或直接丢弃异常内容。换行符匹配逻辑不稳定
代码中用硬编码的\n匹配前缀后的换行,但contenteditable的innerText中换行可能为\r\n,或因Emoji的HTML结构导致换行位置偏移,使replace无法命中前缀,最终前缀未被正确替换或重复添加。事件监听执行时机冲突
给Enter键和发送按钮添加的监听,可能与WhatsApp原生发送逻辑的执行顺序冲突,导致前缀未完成插入就触发发送。
修复方案
方案1:适配编辑器原生HTML结构(推荐)
直接操作DOM节点,保留WhatsApp的格式逻辑,避免纯文本与HTML结构的冲突:
function addNameToMessage() { chrome.storage.local.get(["savedName"], function (result) { const savedName = result.savedName || "Nome Padrão"; const editor = document.querySelector("#main div[contenteditable='true']"); if (!editor) return; const sendMessage = () => { // 检查是否已存在前缀,避免重复添加 const prefixExists = Array.from(editor.childNodes).some(node => node.textContent?.includes(`*${savedName}:*`) ); if (!prefixExists) { // 创建符合编辑器格式的前缀节点 const prefixNode = document.createElement("span"); prefixNode.textContent = `*${savedName}:*\n`; editor.insertBefore(prefixNode, editor.firstChild); } // 触发input事件,让WhatsApp识别内容更新 const inputEvent = new Event("input", { bubbles: true, cancelable: true }); editor.dispatchEvent(inputEvent); // 执行发送 const sendButton = document.querySelector("span[data-icon='send']"); sendButton?.click(); }; // 监听Enter键(排除组合键场景) editor.addEventListener("keydown", (event) => { if (event.key === "Enter" && !event.ctrlKey && !event.altKey && !event.metaKey) { event.preventDefault(); event.stopImmediatePropagation(); sendMessage(); } }, true); // 监听发送按钮点击,优先执行自定义逻辑 const sendButton = document.querySelector("span[data-icon='send']"); sendButton?.addEventListener("click", (event) => { event.preventDefault(); event.stopImmediatePropagation(); sendMessage(); }, true); }); } function waitForMainElement() { const mainElement = document.querySelector("#main"); if (mainElement) { const editor = document.querySelector("#main div[contenteditable='true']"); if (editor) { addNameToMessage(); return; // 找到元素后停止轮询 } } setTimeout(waitForMainElement, 1000); } waitForMainElement();
方案2:优化纯文本处理逻辑
若坚持用纯文本处理,需修复换行匹配并使用insertText避免HTML冲突:
// 修改sendMessage中的核心逻辑 const sendMessage = () => { const prefixRegex = new RegExp(`\\*${savedName}:\\*\\s*`, "g"); const userMessage = editor.innerText.replace(prefixRegex, "").trim(); const newMessage = `*${savedName}:*\n${userMessage}`; editor.focus(); const range = document.createRange(); range.selectNodeContents(editor); const selection = window.getSelection(); selection.removeAllRanges(); selection.addRange(range); // 用insertText代替insertHTML,自动适配编辑器格式 document.execCommand("insertText", false, newMessage); const inputEvent = new Event("input", { bubbles: true }); editor.dispatchEvent(inputEvent); const sendButton = document.querySelector("span[data-icon='send']"); sendButton?.click(); };
关键修复说明
- 使用
insertText替代insertHTML:纯文本插入方式自动适配contenteditable原生格式,不会破坏Emoji的结构; - 正则匹配前缀:用
\\s*匹配前缀后的任意空白字符(含各种换行符),确保替换逻辑稳定; - 前缀重复检查:发送前验证是否已添加前缀,避免重复插入;
- 事件优先级控制:通过捕获阶段监听(第三个参数
true)优先执行自定义逻辑,阻止原生事件冲突。
内容的提问来源于stack exchange,提问作者Thiago Machado

